php-src/ext/mysqlnd/mysqlnd.c
2007-11-08 17:29:06 +00:00

2079 lines
58 KiB
C

/*
+----------------------------------------------------------------------+
| PHP Version 6 |
+----------------------------------------------------------------------+
| Copyright (c) 2006-2007 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Georg Richter <georg@mysql.com> |
| Andrey Hristov <andrey@mysql.com> |
| Ulf Wendel <uwendel@mysql.com> |
+----------------------------------------------------------------------+
*/
/* $Id$ */
#include "php.h"
#include "mysqlnd.h"
#include "mysqlnd_wireprotocol.h"
#include "mysqlnd_priv.h"
#include "mysqlnd_result.h"
#include "mysqlnd_statistics.h"
#include "mysqlnd_charset.h"
#include "mysqlnd_debug.h"
#include "php_ini.h"
#include "ext/standard/basic_functions.h"
#include "ext/standard/php_lcg.h"
#include "ext/standard/info.h"
/* the server doesn't support 4byte utf8, but let's make it forward compatible */
#define MYSQLND_MAX_ALLOWED_USER_LEN 256 /* 64 char * 4byte */
#define MYSQLND_MAX_ALLOWED_DB_LEN 256 /* 64 char * 4byte */
/*
TODO :
- Don't bind so tightly the metadata with the result set. This means
that the metadata reading should not expect a MYSQLND_RES pointer, it
does not need it, but return a pointer to the metadata (MYSQLND_FIELD *).
For normal statements we will then just assign it to a member of
MYSQLND_RES. For PS statements, it will stay as part of the statement
(MYSQLND_STMT) between prepare and execute. At execute the new metadata
will be sent by the server, so we will discard the old one and then
finally attach it to the result set. This will make the code more clean,
as a prepared statement won't have anymore stmt->result != NULL, as it
is now, just to have where to store the metadata.
- Change mysqlnd_simple_command to accept a heap dynamic array of MYSQLND_STRING
terminated by a string with ptr being NULL. Thus, multi-part messages can be
sent to the network like writev() and this can save at least for
mysqlnd_stmt_send_long_data() new malloc. This change will probably make the
code in few other places cleaner.
*/
extern MYSQLND_CHARSET *mysqlnd_charsets;
const char * mysqlnd_server_gone = "MySQL server has gone away";
const char * mysqlnd_out_of_sync = "Commands out of sync; you can't run this command now";
MYSQLND_STATS *mysqlnd_global_stats = NULL;
static zend_bool mysqlnd_library_initted = FALSE;
enum_func_status mysqlnd_send_close(MYSQLND * conn TSRMLS_DC);
/* {{{ mysqlnd_library_init */
static
void mysqlnd_library_init()
{
if (mysqlnd_library_initted == FALSE) {
mysqlnd_library_initted = TRUE;
_mysqlnd_init_ps_subsystem();
/* Should be calloc, as mnd_calloc will reference LOCK_access*/
mysqlnd_global_stats = calloc(1, sizeof(MYSQLND_STATS));
#ifdef ZTS
mysqlnd_global_stats->LOCK_access = tsrm_mutex_alloc();
#endif
}
}
/* }}} */
/* {{{ mysqlnd_library_end */
static
void mysqlnd_library_end()
{
if (mysqlnd_library_initted == TRUE) {
#ifdef ZTS
tsrm_mutex_free(mysqlnd_global_stats->LOCK_access);
#endif
/* mnd_free will reference LOCK_access and crash...*/
free(mysqlnd_global_stats);
mysqlnd_global_stats = NULL;
mysqlnd_library_initted = FALSE;
}
}
/* }}} */
/* {{{ mysqlnd_conn::free_contents */
static void
MYSQLND_METHOD(mysqlnd_conn, free_contents)(MYSQLND *conn TSRMLS_DC)
{
zend_bool pers = conn->persistent;
DBG_ENTER("mysqlnd_conn::free_contents");
mysqlnd_local_infile_default(conn);
if (conn->current_result) {
conn->current_result->m.free_result_contents(conn->current_result TSRMLS_CC);
mnd_efree(conn->current_result);
conn->current_result = NULL;
}
if (conn->net.stream) {
DBG_INF_FMT("Freeing stream. abstract=%p", conn->net.stream->abstract);
if (pers) {
php_stream_free(conn->net.stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR);
} else {
php_stream_free(conn->net.stream, PHP_STREAM_FREE_CLOSE);
}
conn->net.stream = NULL;
}
DBG_INF("Freeing memory of members");
if (conn->host) {
DBG_INF("Freeing host");
mnd_pefree(conn->host, pers);
conn->host = NULL;
}
if (conn->user) {
DBG_INF("Freeing user");
mnd_pefree(conn->user, pers);
conn->user = NULL;
}
if (conn->passwd) {
DBG_INF("Freeing passwd");
mnd_pefree(conn->passwd, pers);
conn->passwd = NULL;
}
if (conn->unix_socket) {
DBG_INF("Freeing unix_socket");
mnd_pefree(conn->unix_socket, pers);
conn->unix_socket = NULL;
}
if (conn->scheme) {
DBG_INF("Freeing scheme");
mnd_pefree(conn->scheme, pers);
conn->scheme = NULL;
}
if (conn->server_version) {
DBG_INF("Freeing server_version");
mnd_pefree(conn->server_version, pers);
conn->server_version = NULL;
}
if (conn->host_info) {
DBG_INF("Freeing host_info");
mnd_pefree(conn->host_info, pers);
conn->host_info = NULL;
}
if (conn->scramble) {
DBG_INF("Freeing scramble");
mnd_pefree(conn->scramble, pers);
conn->scramble = NULL;
}
if (conn->last_message) {
mnd_pefree(conn->last_message, pers);
conn->last_message = NULL;
}
if (conn->options.charset_name) {
mnd_pefree(conn->options.charset_name, pers);
conn->options.charset_name = NULL;
}
if (conn->options.num_commands) {
unsigned int i;
for (i=0; i < conn->options.num_commands; i++) {
mnd_pefree(conn->options.init_commands[i], pers);
}
mnd_pefree(conn->options.init_commands, pers);
conn->options.init_commands = NULL;
}
if (conn->options.cfg_file) {
mnd_pefree(conn->options.cfg_file, pers);
conn->options.cfg_file = NULL;
}
if (conn->options.cfg_section) {
mnd_pefree(conn->options.cfg_section, pers);
conn->options.cfg_section = NULL;
}
if (conn->options.ssl_key) {
mnd_pefree(conn->options.ssl_key, pers);
conn->options.ssl_key = NULL;
}
if (conn->options.ssl_cert) {
mnd_pefree(conn->options.ssl_cert, pers);
conn->options.ssl_cert = NULL;
}
if (conn->options.ssl_ca) {
mnd_pefree(conn->options.ssl_ca, pers);
conn->options.ssl_ca = NULL;
}
if (conn->options.ssl_capath) {
mnd_pefree(conn->options.ssl_capath, pers);
conn->options.ssl_capath = NULL;
}
if (conn->options.ssl_cipher) {
mnd_pefree(conn->options.ssl_cipher, pers);
conn->options.ssl_cipher = NULL;
}
if (conn->zval_cache) {
DBG_INF("Freeing zval cache reference");
mysqlnd_palloc_free_thd_cache_reference(&conn->zval_cache);
conn->zval_cache = NULL;
}
if (conn->qcache) {
DBG_INF("Freeing qcache reference");
mysqlnd_qcache_free_cache_reference(&conn->qcache);
conn->qcache = NULL;
}
if (conn->net.cmd_buffer.buffer) {
DBG_INF("Freeing cmd buffer");
mnd_pefree(conn->net.cmd_buffer.buffer, pers);
conn->net.cmd_buffer.buffer = NULL;
}
conn->charset = NULL;
conn->greet_charset = NULL;
DBG_VOID_RETURN;
}
/* }}} */
/* {{{ mysqlnd_conn::dtor */
static void
MYSQLND_METHOD_PRIVATE(mysqlnd_conn, dtor)(MYSQLND *conn TSRMLS_DC)
{
DBG_ENTER("mysqlnd_conn::dtor");
DBG_INF_FMT("conn=%llu", conn->thread_id);
conn->m->free_contents(conn TSRMLS_CC);
mnd_pefree(conn, conn->persistent);
DBG_VOID_RETURN;
}
/* }}} */
/* {{{ mysqlnd_simple_command_handle_response */
enum_func_status
mysqlnd_simple_command_handle_response(MYSQLND *conn, enum php_mysql_packet_type ok_packet,
zend_bool silent, enum php_mysqlnd_server_command command
TSRMLS_DC)
{
enum_func_status ret;
DBG_ENTER("mysqlnd_simple_command_handle_response");
DBG_INF_FMT("silent=%d packet=%d command=%s", silent, ok_packet, mysqlnd_command_to_text[command]);
switch (ok_packet) {
case PROT_OK_PACKET:{
php_mysql_packet_ok ok_response;
PACKET_INIT_ALLOCA(ok_response, PROT_OK_PACKET);
if (FAIL == (ret = PACKET_READ_ALLOCA(ok_response, conn))) {
if (!silent) {
DBG_ERR_FMT("Error while reading %s's OK packet", mysqlnd_command_to_text[command]);
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading %s's OK packet. PID=%d",
mysqlnd_command_to_text[command], getpid());
}
} else {
DBG_INF_FMT("OK from server");
if (0xFF == ok_response.field_count) {
/* The server signalled error. Set the error */
SET_CLIENT_ERROR(conn->error_info, ok_response.error_no,
ok_response.sqlstate, ok_response.error);
ret = FAIL;
/*
Cover a protocol design error: error packet does not
contain the server status. Therefore, the client has no way
to find out whether there are more result sets of
a multiple-result-set statement pending. Luckily, in 5.0 an
error always aborts execution of a statement, wherever it is
a multi-statement or a stored procedure, so it should be
safe to unconditionally turn off the flag here.
*/
conn->upsert_status.server_status &= ~SERVER_MORE_RESULTS_EXISTS;
SET_ERROR_AFF_ROWS(conn);
} else {
SET_NEW_MESSAGE(conn->last_message, conn->last_message_len,
ok_response.message, ok_response.message_len,
conn->persistent);
conn->upsert_status.warning_count = ok_response.warning_count;
conn->upsert_status.server_status = ok_response.server_status;
conn->upsert_status.affected_rows = ok_response.affected_rows;
conn->upsert_status.last_insert_id = ok_response.last_insert_id;
}
}
PACKET_FREE_ALLOCA(ok_response);
break;
}
case PROT_EOF_PACKET:{
php_mysql_packet_eof ok_response;
PACKET_INIT_ALLOCA(ok_response, PROT_EOF_PACKET);
if (FAIL == (ret = PACKET_READ_ALLOCA(ok_response, conn))) {
SET_CLIENT_ERROR(conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE,
"Malformed packet");
if (!silent) {
DBG_ERR_FMT("Error while reading %s's EOF packet", mysqlnd_command_to_text[command]);
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading %s's EOF packet. PID=%d",
mysqlnd_command_to_text[command], getpid());
}
} else if (0xFF == ok_response.field_count) {
/* The server signalled error. Set the error */
SET_CLIENT_ERROR(conn->error_info, ok_response.error_no,
ok_response.sqlstate, ok_response.error);
SET_ERROR_AFF_ROWS(conn);
} else if (0xFE != ok_response.field_count) {
SET_CLIENT_ERROR(conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE,
"Malformed packet");
if (!silent) {
DBG_ERR_FMT("EOF packet expected, field count wasn't 0xFE but 0x%2X", ok_response.field_count);
php_error_docref(NULL TSRMLS_CC, E_WARNING,
"EOF packet expected, field count wasn't 0xFE but 0x%2X",
ok_response.field_count);
}
} else {
DBG_INF_FMT("OK from server");
}
PACKET_FREE_ALLOCA(ok_response);
break;
}
default:
ret = FAIL;
SET_CLIENT_ERROR(conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE,
"Malformed packet");
php_error_docref(NULL TSRMLS_CC, E_ERROR, "Wrong response packet %d passed to the function",
ok_packet);
break;
}
DBG_INF(ret == PASS ? "PASS":"FAIL");
DBG_RETURN(ret);
}
/* }}} */
/* {{{ mysqlnd_simple_command */
enum_func_status
mysqlnd_simple_command(MYSQLND *conn, enum php_mysqlnd_server_command command,
const char * const arg, size_t arg_len,
enum php_mysql_packet_type ok_packet, zend_bool silent TSRMLS_DC)
{
enum_func_status ret = PASS;
php_mysql_packet_command cmd_packet;
DBG_ENTER("mysqlnd_simple_command");
DBG_INF_FMT("command=%s ok_packet=%d silent=%d", mysqlnd_command_to_text[command], ok_packet, silent);
switch (conn->state) {
case CONN_READY:
break;
case CONN_QUIT_SENT:
SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
DBG_ERR("Server is gone");
DBG_RETURN(FAIL);
default:
SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
mysqlnd_out_of_sync);
DBG_ERR("Command out of sync");
DBG_RETURN(FAIL);
}
/* clean UPSERT info */
memset(&conn->upsert_status, 0, sizeof(conn->upsert_status));
SET_ERROR_AFF_ROWS(conn);
SET_EMPTY_ERROR(conn->error_info);
PACKET_INIT_ALLOCA(cmd_packet, PROT_CMD_PACKET);
cmd_packet.command = command;
if (arg && arg_len) {
cmd_packet.argument = arg;
cmd_packet.arg_len = arg_len;
}
if (! PACKET_WRITE_ALLOCA(cmd_packet, conn)) {
if (!silent) {
DBG_ERR_FMT("Error while sending %s packet", mysqlnd_command_to_text[command]);
php_error(E_WARNING, "Error while sending %s packet. PID=%d", mysqlnd_command_to_text[command], getpid());
}
DBG_ERR("Server is gone");
ret = FAIL;
} else if (ok_packet != PROT_LAST) {
ret = mysqlnd_simple_command_handle_response(conn, ok_packet, silent, command TSRMLS_CC);
}
/*
There is no need to call FREE_ALLOCA on cmd_packet as the
only allocated string is cmd_packet.argument and it was passed
to us. We should not free it.
*/
DBG_INF(ret == PASS ? "PASS":"FAIL");
DBG_RETURN(ret);
}
/* }}} */
/* {{{ mysqlnd_conn::set_server_option */
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, set_server_option)(MYSQLND * const conn,
enum_mysqlnd_server_option option TSRMLS_DC)
{
enum_func_status ret;
char buffer[2];
DBG_ENTER("mysqlnd_conn::set_server_option");
int2store(buffer, (uint) option);
ret = mysqlnd_simple_command(conn, COM_SET_OPTION, buffer, sizeof(buffer),
PROT_EOF_PACKET, FALSE TSRMLS_CC);
DBG_RETURN(ret);
}
/* }}} */
/* {{{ _mysqlnd_restart_psession */
PHPAPI void _mysqlnd_restart_psession(MYSQLND *conn TSRMLS_DC)
{
DBG_ENTER("_mysqlnd_restart_psession");
MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_CONNECT_REUSED);
/* Free here what should not be seen by the next script */
if (conn->last_message) {
mnd_pefree(conn->last_message, conn->persistent);
conn->last_message = NULL;
}
DBG_VOID_RETURN;
}
/* }}} */
/* {{{ mysqlnd_end_psession */
PHPAPI void mysqlnd_end_psession(MYSQLND *conn)
{
}
/* }}} */
/* {{{ mysqlnd_connect */
PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn,
char *host, char *user,
char *passwd, unsigned int passwd_len,
char *db, unsigned int db_len,
unsigned int port,
char *socket,
unsigned int mysql_flags,
MYSQLND_THD_ZVAL_PCACHE *zval_cache
TSRMLS_DC)
{
char *transport = NULL, *errstr = NULL;
char *hashed_details = NULL;
int transport_len, hashed_details_len, errcode = 0;
unsigned int streams_options = ENFORCE_SAFE_MODE;
unsigned int streams_flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT;
zend_bool self_alloced = FALSE;
struct timeval tv;
zend_bool unix_socket = FALSE;
const MYSQLND_CHARSET * charset;
zend_bool reconnect = FALSE;
php_mysql_packet_greet greet_packet;
php_mysql_packet_auth *auth_packet;
php_mysql_packet_ok ok_packet;
DBG_ENTER("mysqlnd_connect");
DBG_INF_FMT("host=%s user=%s db=%s port=%d flags=%d persistent=%d state=%d",
host?host:"", user?user:"", db?db:"", port, mysql_flags,
conn? conn->persistent:0, conn? conn->state:-1);
DBG_INF_FMT("state=%d", conn->state);
if (conn && conn->state > CONN_ALLOCED && conn->state ) {
DBG_INF("Connecting on a connected handle.");
if (conn->state < CONN_QUIT_SENT) {
MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_CLOSE_IMPLICIT);
reconnect = TRUE;
mysqlnd_send_close(conn TSRMLS_CC);
}
conn->m->free_contents(conn TSRMLS_CC);
MYSQLND_DEC_CONN_STATISTIC(&conn->stats, STAT_OPENED_CONNECTIONS);
if (conn->persistent) {
MYSQLND_DEC_CONN_STATISTIC(&conn->stats, STAT_OPENED_PERSISTENT_CONNECTIONS);
}
/* Now reconnect using the same handle */
}
if (!host || !host[0]) {
host = "localhost";
}
if (!user || !user[0]) {
user = php_get_current_user();
}
if (!passwd) {
passwd = "";
passwd_len = 0;
}
if (!db) {
db = "";
db_len = 0;
}
if (!port && !socket) {
port = 3306;
}
#ifndef PHP_WIN32
if (!strncasecmp(host, "localhost", sizeof("localhost") - 1)) {
if (!socket) {
socket = "/tmp/mysql.sock";
}
transport_len = spprintf(&transport, 0, "unix://%s", socket);
unix_socket = TRUE;
} else
#endif
{
transport_len = spprintf(&transport, 0, "tcp://%s:%d", host, port);
}
DBG_INF_FMT("transport=%p", transport);
if (conn->persistent) {
struct timeval tv;
gettimeofday(&tv, NULL);
/* We should generate something unique */
hashed_details_len = spprintf(&hashed_details, 0, "%s@%s@%s@%ld@%ld@%0.8F",
transport, user, db, tv.tv_sec, (long int)tv.tv_usec,
php_combined_lcg(TSRMLS_C) * 10);
DBG_INF_FMT("hashed_details=%s", hashed_details);
}
PACKET_INIT_ALLOCA(greet_packet, PROT_GREET_PACKET);
PACKET_INIT(auth_packet, PROT_AUTH_PACKET, php_mysql_packet_auth *);
PACKET_INIT_ALLOCA(ok_packet, PROT_OK_PACKET);
if (!conn) {
conn = mysqlnd_init(FALSE);
self_alloced = TRUE;
}
conn->state = CONN_ALLOCED;
conn->net.packet_no = 0;
if (conn->options.timeout_connect) {
tv.tv_sec = conn->options.timeout_connect;
tv.tv_usec = 0;
}
if (conn->persistent) {
conn->scheme = pestrndup(transport, transport_len, 1);
mnd_efree(transport);
} else {
conn->scheme = transport;
}
DBG_INF(conn->scheme);
conn->net.stream = php_stream_xport_create(conn->scheme, transport_len, streams_options, streams_flags,
hashed_details,
(conn->options.timeout_connect) ? &tv : NULL,
NULL /*ctx*/, &errstr, &errcode);
DBG_INF_FMT("stream=%p", conn->net.stream);
if (errstr || !conn->net.stream) {
goto err;
}
if (hashed_details) {
/*
If persistent, the streams register it in EG(persistent_list).
This is unwanted. ext/mysql or ext/mysqli are responsible to clean,
whatever they have to.
*/
zend_rsrc_list_entry *le;
if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_len + 1,
(void*) &le) == SUCCESS) {
/*
in_free will let streams code skip destructing - big HACK,
but STREAMS suck big time regarding persistent streams.
Just not compatible for extensions that need persistency.
*/
conn->net.stream->in_free = 1;
zend_hash_del(&EG(persistent_list), hashed_details, hashed_details_len + 1);
conn->net.stream->in_free = 0;
}
#if ZEND_DEBUG
/* Shut-up the streams, they don't know what they are doing */
conn->net.stream->__exposed = 1;
#endif
mnd_efree(hashed_details);
}
if (conn->options.timeout_read)
{
tv.tv_sec = conn->options.timeout_read;
tv.tv_usec = 0;
php_stream_set_option(conn->net.stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv);
}
if (!unix_socket) {
/* Set TCP_NODELAY */
mysqlnd_set_sock_no_delay(conn->net.stream);
}
if (FAIL == PACKET_READ_ALLOCA(greet_packet, conn)) {
DBG_ERR("Error while reading greeting packet");
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading greeting packet. PID=%d", getpid());
goto err;
} else if (greet_packet.error_no) {
DBG_ERR_FMT("errorno=%d error=%s", greet_packet.error_no, greet_packet.error);
SET_CLIENT_ERROR(conn->error_info, greet_packet.error_no,
greet_packet.sqlstate, greet_packet.error);
goto err;
} else if (greet_packet.pre41) {
DBG_ERR_FMT("Connecting to 3.22, 3.23 & 4.0 is not supported. Server is %-.32s",
greet_packet.server_version);
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Connecting to 3.22, 3.23 & 4.0 "
" is not supported. Server is %-.32s", greet_packet.server_version);
SET_CLIENT_ERROR(conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE,
"Connecting to 3.22, 3.23 & 4.0 servers is not supported");
goto err;
}
conn->thread_id = greet_packet.thread_id;
conn->protocol_version = greet_packet.protocol_version;
conn->server_version = greet_packet.server_version;
greet_packet.server_version = NULL; /* The string will be freed otherwise */
conn->greet_charset = mysqlnd_find_charset_nr(greet_packet.charset_no);
/* we allow load data local infile by default */
mysql_flags |= CLIENT_LOCAL_FILES;
auth_packet->user = user;
auth_packet->password = passwd;
if (conn->options.charset_name &&
(charset = mysqlnd_find_charset_name(conn->options.charset_name)))
{
auth_packet->charset_no = charset->nr;
#if PHP_MAJOR_VERSION >= 6
} else if (UG(unicode)) {
auth_packet->charset_no = 200;/* utf8 - swedish collation, check mysqlnd_charset.c */
#endif
} else {
auth_packet->charset_no = greet_packet.charset_no;
}
auth_packet->db = db;
auth_packet->db_len = db_len;
auth_packet->max_packet_size= 3UL*1024UL*1024UL*1024UL;
auth_packet->client_flags= mysql_flags;
conn->scramble = auth_packet->server_scramble_buf = mnd_pemalloc(SCRAMBLE_LENGTH, conn->persistent);
memcpy(auth_packet->server_scramble_buf, greet_packet.scramble_buf, SCRAMBLE_LENGTH);
if (!PACKET_WRITE(auth_packet, conn)) {
conn->state = CONN_QUIT_SENT;
SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);
goto err;
}
if (FAIL == PACKET_READ_ALLOCA(ok_packet, conn) || ok_packet.field_count >= 0xFE) {
if (ok_packet.field_count == 0xFE) {
/* old authentication with new server !*/
DBG_ERR("mysqlnd cannot connect to MySQL 4.1+ using old authentication");
php_error_docref(NULL TSRMLS_CC, E_WARNING, "mysqlnd cannot connect to MySQL 4.1+ using old authentication");
} else if (ok_packet.field_count == 0xFF) {
if (ok_packet.sqlstate[0]) {
if (!self_alloced) {
strncpy(conn->error_info.sqlstate, ok_packet.sqlstate, sizeof(conn->error_info.sqlstate));
}
DBG_ERR_FMT("ERROR:%d [SQLSTATE:%s] %s",
ok_packet.error_no, ok_packet.sqlstate, ok_packet.error);
}
if (!self_alloced) {
conn->error_info.error_no = ok_packet.error_no;
strncpy(conn->error_info.error, ok_packet.error, sizeof(conn->error_info.error));
}
}
} else {
conn->state = CONN_READY;
conn->user = pestrdup(user, conn->persistent);
conn->passwd = pestrndup(passwd, passwd_len, conn->persistent);
conn->port = port;
if (host && !socket) {
char *p;
conn->host = pestrdup(host, conn->persistent);
spprintf(&p, 0, "MySQL host info: %s via TCP/IP", conn->host);
if (conn->persistent) {
conn->host_info = pestrdup(p, 1);
mnd_efree(p);
} else {
conn->host_info = p;
}
} else {
conn->unix_socket = pestrdup(socket, conn->persistent);
conn->host_info = pestrdup("MySQL host info: Localhost via UNIX socket", conn->persistent);
}
conn->client_flag = auth_packet->client_flags;
conn->max_packet_size = auth_packet->max_packet_size;
/* todo: check if charset is available */
conn->charset = mysqlnd_find_charset_nr(auth_packet->charset_no);
conn->server_capabilities = greet_packet.server_capabilities;
conn->upsert_status.warning_count = 0;
conn->upsert_status.server_status = greet_packet.server_status;
conn->upsert_status.affected_rows = 0;
SET_NEW_MESSAGE(conn->last_message, conn->last_message_len,
ok_packet.message, ok_packet.message_len,
conn->persistent);
SET_EMPTY_ERROR(conn->error_info);
PACKET_FREE_ALLOCA(greet_packet);
PACKET_FREE(auth_packet);
PACKET_FREE_ALLOCA(ok_packet);
conn->zval_cache = mysqlnd_palloc_get_thd_cache_reference(zval_cache);
conn->net.cmd_buffer.length = 128L*1024L;
conn->net.cmd_buffer.buffer = mnd_pemalloc(conn->net.cmd_buffer.length, conn->persistent);
mysqlnd_local_infile_default(conn);
{
uint buf_size;
buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to uint*/
conn->m->set_client_option(conn, MYSQLND_OPT_NET_READ_BUFFER_SIZE,
(char *)&buf_size TSRMLS_CC);
buf_size = MYSQLND_G(net_cmd_buffer_size); /* this is long, cast to uint*/
conn->m->set_client_option(conn, MYSQLND_OPT_NET_CMD_BUFFER_SIZE,
(char *)&buf_size TSRMLS_CC);
}
MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_CONNECT_SUCCESS);
if (reconnect) {
MYSQLND_INC_GLOBAL_STATISTIC(STAT_RECONNECT);
}
MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_OPENED_CONNECTIONS);
if (conn->persistent) {
MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_OPENED_PERSISTENT_CONNECTIONS);
}
DBG_INF_FMT("connection_id=%llu", conn->thread_id);
#if PHP_MAJOR_VERSION >= 6
{
uint as_unicode = 1;
conn->m->set_client_option(conn, MYSQLND_OPT_NUMERIC_AND_DATETIME_AS_UNICODE,
(char *)&as_unicode TSRMLS_CC);
DBG_INF("unicode set");
}
#endif
DBG_RETURN(conn);
}
err:
PACKET_FREE_ALLOCA(greet_packet);
PACKET_FREE(auth_packet);
PACKET_FREE_ALLOCA(ok_packet);
if (errstr) {
DBG_ERR_FMT("[%d] %.64s (trying to connect via %s)", errcode, errstr, conn->scheme);
SET_CLIENT_ERROR(conn->error_info, errcode, UNKNOWN_SQLSTATE, errstr);
php_error_docref(NULL TSRMLS_CC, E_WARNING, "[%d] %.64s (trying to connect via %s)", errcode, errstr, conn->scheme);
mnd_efree(errstr);
}
if (conn->scheme) {
mnd_pefree(conn->scheme, conn->persistent);
conn->scheme = NULL;
}
/* This will also close conn->net.stream if it has been opened */
conn->m->free_contents(conn TSRMLS_CC);
if (self_alloced) {
/*
We have alloced, thus there are no references to this
object - we are free to kill it!
*/
conn->m->dtor(conn TSRMLS_CC);
} else {
MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_CONNECT_FAILURE);
}
DBG_RETURN(NULL);
}
/* }}} */
/* {{{ mysqlnd_conn::query */
/*
If conn->error_info.error_no is not zero, then we had an error.
Still the result from the query is PASS
*/
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, query)(MYSQLND *conn, const char *query, unsigned int query_len TSRMLS_DC)
{
enum_func_status ret;
DBG_ENTER("mysqlnd_conn::query");
DBG_INF_FMT("conn=%llu query=%s", conn->thread_id, query);
if (PASS != mysqlnd_simple_command(conn, COM_QUERY, query, query_len,
PROT_LAST /* we will handle the OK packet*/,
FALSE TSRMLS_CC)) {
DBG_RETURN(FAIL);
}
/*
Here read the result set. We don't do it in simple_command because it need
information from the ok packet. We will fetch it ourselves.
*/
ret = mysqlnd_query_read_result_set_header(conn, NULL TSRMLS_CC);
DBG_RETURN(ret);
}
/* }}} */
/*
COM_FIELD_LIST is special, different from a SHOW FIELDS FROM :
- There is no result set header - status from the command, which
impacts us to allocate big chunk of memory for reading the metadata.
- The EOF packet is consumed by the metadata packet reader.
*/
/* {{{ mysqlnd_conn::list_fields */
MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_conn, list_fields)(MYSQLND *conn, const char *table, const char *achtung_wild TSRMLS_DC)
{
/* db + \0 + wild + \0 (for wild) */
char buff[MYSQLND_MAX_ALLOWED_DB_LEN * 4 * 2 + 1 + 1], *p;
size_t table_len, wild_len;
MYSQLND_RES *result = NULL;
DBG_ENTER("mysqlnd_conn::list_fields");
DBG_INF_FMT("conn=%llu table=%s wild=%s", conn->thread_id, table? table:"",achtung_wild? achtung_wild:"");
p = buff;
if (table && (table_len = strlen(table))) {
memcpy(p, table, MIN(table_len, MYSQLND_MAX_ALLOWED_DB_LEN * 4));
p += table_len;
*p++ = '\0';
}
if (achtung_wild && (wild_len = strlen(achtung_wild))) {
memcpy(p, achtung_wild, MIN(wild_len, MYSQLND_MAX_ALLOWED_DB_LEN * 4));
p += wild_len;
*p++ = '\0';
}
if (PASS != mysqlnd_simple_command(conn, COM_FIELD_LIST, buff, p - buff,
PROT_LAST /* we will handle the OK packet*/,
FALSE TSRMLS_CC)) {
DBG_RETURN(NULL);
}
/*
Prepare for the worst case.
MyISAM goes to 2500 BIT columns, double it for safety.
*/
result = mysqlnd_result_init(5000, mysqlnd_palloc_get_thd_cache_reference(conn->zval_cache) TSRMLS_CC);
if (FAIL == result->m.read_result_metadata(result, conn TSRMLS_CC)) {
DBG_ERR("Error ocurred while reading metadata");
result->m.free_result(result, TRUE TSRMLS_CC);
DBG_RETURN(NULL);
}
result->type = MYSQLND_RES_NORMAL;
result->m.fetch_row = result->m.fetch_row_normal_unbuffered;
result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED));
result->unbuf->eof_reached = TRUE;
DBG_RETURN(result);
}
/* }}} */
/* {{{ mysqlnd_conn::list_method */
MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_conn, list_method)(MYSQLND *conn, const char *query,
const char *achtung_wild, char *par1 TSRMLS_DC)
{
char *show_query = NULL;
size_t show_query_len;
MYSQLND_RES *result = NULL;
DBG_ENTER("mysqlnd_conn::list_method");
DBG_INF_FMT("conn=%llu query=%s wild=%d", conn->thread_id, query, achtung_wild);
if (par1) {
if (achtung_wild) {
show_query_len = spprintf(&show_query, 0, query, par1, achtung_wild);
} else {
show_query_len = spprintf(&show_query, 0, query, par1);
}
} else {
if (achtung_wild) {
show_query_len = spprintf(&show_query, 0, query, achtung_wild);
} else {
show_query_len = strlen(show_query = (char *)query);
}
}
if (PASS == conn->m->query(conn, show_query, show_query_len TSRMLS_CC)) {
result = conn->m->store_result(conn TSRMLS_CC);
}
if (show_query != query) {
mnd_efree(show_query);
}
DBG_RETURN(result);
}
/* }}} */
/* {{{ mysqlnd_conn::errno */
static unsigned int
MYSQLND_METHOD(mysqlnd_conn, errno)(const MYSQLND * const conn)
{
return conn->error_info.error_no;
}
/* }}} */
/* {{{ mysqlnd_conn::error */
static const char *
MYSQLND_METHOD(mysqlnd_conn, error)(const MYSQLND * const conn)
{
return conn->error_info.error;
}
/* }}} */
/* {{{ mysqlnd_conn::sqlstate */
static const char *
MYSQLND_METHOD(mysqlnd_conn, sqlstate)(const MYSQLND * const conn)
{
return conn->error_info.sqlstate[0] ? conn->error_info.sqlstate:MYSQLND_SQLSTATE_NULL;
}
/* }}} */
/* {{{ mysqlnd_old_escape_string */
PHPAPI ulong mysqlnd_old_escape_string(char *newstr, const char *escapestr, int escapestr_len TSRMLS_DC)
{
DBG_ENTER("mysqlnd_old_escape_string");
DBG_RETURN(mysqlnd_cset_escape_slashes(mysqlnd_find_charset_name("latin1"),
newstr, escapestr, escapestr_len TSRMLS_CC));
}
/* }}} */
/* {{{ mysqlnd_conn::escape_string */
static ulong
MYSQLND_METHOD(mysqlnd_conn, escape_string)(const MYSQLND * const conn, char *newstr,
const char *escapestr, int escapestr_len TSRMLS_DC)
{
DBG_ENTER("mysqlnd_conn::escape_string");
DBG_INF_FMT("conn=%llu", conn->thread_id);
if (conn->upsert_status.server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES) {
DBG_RETURN(mysqlnd_cset_escape_quotes(conn->charset, newstr, escapestr, escapestr_len TSRMLS_CC));
}
DBG_RETURN(mysqlnd_cset_escape_slashes(conn->charset, newstr, escapestr, escapestr_len TSRMLS_CC));
}
/* }}} */
/* {{{ mysqlnd_conn::dump_debug_info */
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, dump_debug_info)(MYSQLND * const conn TSRMLS_DC)
{
DBG_ENTER("mysqlnd_conn::dump_debug_info");
DBG_INF_FMT("conn=%llu", conn->thread_id);
DBG_RETURN(mysqlnd_simple_command(conn, COM_DEBUG, NULL, 0, PROT_EOF_PACKET, FALSE TSRMLS_CC));
}
/* }}} */
/* {{{ mysqlnd_conn::select_db */
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, select_db)(MYSQLND * const conn,
const char * const db,
unsigned int db_len TSRMLS_DC)
{
enum_func_status ret;
DBG_ENTER("mysqlnd_conn::select_db");
DBG_INF_FMT("conn=%llu db=%s", conn->thread_id, db);
ret = mysqlnd_simple_command(conn, COM_INIT_DB, db, db_len, PROT_OK_PACKET, FALSE TSRMLS_CC);
/*
The server sends 0 but libmysql doesn't read it and has established
a protocol of giving back -1. Thus we have to follow it :(
*/
SET_ERROR_AFF_ROWS(conn);
DBG_RETURN(ret);
}
/* }}} */
/* {{{ mysqlnd_conn::ping */
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, ping)(MYSQLND * const conn TSRMLS_DC)
{
enum_func_status ret;
DBG_ENTER("mysqlnd_conn::ping");
DBG_INF_FMT("conn=%llu", conn->thread_id);
ret = mysqlnd_simple_command(conn, COM_PING, NULL, 0, PROT_OK_PACKET, TRUE TSRMLS_CC);
/*
The server sends 0 but libmysql doesn't read it and has established
a protocol of giving back -1. Thus we have to follow it :(
*/
SET_ERROR_AFF_ROWS(conn);
DBG_INF_FMT("ret=%d", ret);
DBG_RETURN(ret);
}
/* }}} */
/* {{{ mysqlnd_conn::stat */
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, stat)(MYSQLND *conn, char **message, unsigned int * message_len TSRMLS_DC)
{
enum_func_status ret;
php_mysql_packet_stats stats_header;
DBG_ENTER("mysqlnd_conn::stat");
DBG_INF_FMT("conn=%llu", conn->thread_id);
ret = mysqlnd_simple_command(conn, COM_STATISTICS, NULL, 0, PROT_LAST, FALSE TSRMLS_CC);
if (FAIL == ret) {
DBG_RETURN(FAIL);
}
PACKET_INIT_ALLOCA(stats_header, PROT_STATS_PACKET);
if (FAIL == (ret = PACKET_READ_ALLOCA(stats_header, conn))) {
DBG_RETURN(FAIL);
}
*message = stats_header.message;
*message_len = stats_header.message_len;
/* Ownership transfer */
stats_header.message = NULL;
PACKET_FREE_ALLOCA(stats_header);
DBG_INF(*message);
DBG_RETURN(PASS);
}
/* }}} */
/* {{{ mysqlnd_conn::kill */
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, kill)(MYSQLND *conn, unsigned int pid TSRMLS_DC)
{
enum_func_status ret;
char buff[4];
DBG_ENTER("mysqlnd_conn::kill");
DBG_INF_FMT("conn=%llu pid=%lu", conn->thread_id, pid);
int4store(buff, pid);
/* If we kill ourselves don't expect OK packet, PROT_LAST will skip it */
if (pid != conn->thread_id) {
ret = mysqlnd_simple_command(conn, COM_PROCESS_KILL, buff, 4, PROT_OK_PACKET, FALSE TSRMLS_CC);
/*
The server sends 0 but libmysql doesn't read it and has established
a protocol of giving back -1. Thus we have to follow it :(
*/
SET_ERROR_AFF_ROWS(conn);
} else if (PASS == (ret = mysqlnd_simple_command(conn, COM_PROCESS_KILL, buff,
4, PROT_LAST, FALSE TSRMLS_CC))) {
conn->state = CONN_QUIT_SENT;
}
DBG_RETURN(ret);
}
/* }}} */
/* {{{ _mysqlnd_conn::set_charset */
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, set_charset)(MYSQLND * const conn, const char * const csname TSRMLS_DC)
{
enum_func_status ret = PASS;
char *query;
size_t query_len;
const MYSQLND_CHARSET * const charset = mysqlnd_find_charset_name(csname);
DBG_ENTER("mysqlnd_conn::set_charset");
DBG_INF_FMT("conn=%llu cs=%s", conn->thread_id, csname);
if (!charset) {
SET_CLIENT_ERROR(conn->error_info, CR_CANT_FIND_CHARSET, UNKNOWN_SQLSTATE,
"Invalid characterset or character set not supported");
DBG_RETURN(FAIL);
}
query_len = spprintf(&query, 0, "SET NAMES %s", csname);
if (FAIL == conn->m->query(conn, query, query_len TSRMLS_CC)) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error executing query");
} else if (conn->error_info.error_no) {
ret = FAIL;
} else {
conn->charset = charset;
}
mnd_efree(query);
DBG_INF(ret == PASS? "PASS":"FAIL");
DBG_RETURN(ret);
}
/* }}} */
/* {{{ mysqlnd_conn::refresh */
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, refresh)(MYSQLND * const conn, unsigned long options TSRMLS_DC)
{
zend_uchar bits[1];
DBG_ENTER("mysqlnd_conn::refresh");
DBG_INF_FMT("conn=%llu options=%lu", conn->thread_id, options);
int1store(bits, options);
DBG_RETURN(mysqlnd_simple_command(conn, COM_REFRESH, (char *)bits, 1, PROT_OK_PACKET, FALSE TSRMLS_CC));
}
/* }}} */
/* {{{ mysqlnd_conn::shutdown */
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, shutdown)(MYSQLND * const conn, unsigned long level TSRMLS_DC)
{
zend_uchar bits[1];
DBG_ENTER("mysqlnd_conn::shutdown");
DBG_INF_FMT("conn=%llu level=%lu", conn->thread_id, level);
int1store(bits, level);
DBG_RETURN(mysqlnd_simple_command(conn, COM_SHUTDOWN, (char *)bits, 1, PROT_OK_PACKET, FALSE TSRMLS_CC));
}
/* }}} */
/* {{{ mysqlnd_send_close */
enum_func_status
mysqlnd_send_close(MYSQLND * conn TSRMLS_DC)
{
enum_func_status ret = PASS;
DBG_ENTER("mysqlnd_send_close");
DBG_INF_FMT("conn=%llu conn->net.stream->abstract=%p",
conn->thread_id, conn->net.stream? conn->net.stream->abstract:NULL);
switch (conn->state) {
case CONN_READY:
DBG_INF("Connection clean, sending COM_QUIT");
ret = mysqlnd_simple_command(conn, COM_QUIT, NULL, 0, PROT_LAST,
TRUE TSRMLS_CC);
/* Do nothing */
break;
case CONN_SENDING_LOAD_DATA:
/*
Don't send COM_QUIT if we are in a middle of a LOAD DATA or we
will crash (assert) a debug server.
*/
case CONN_NEXT_RESULT_PENDING:
case CONN_QUERY_SENT:
case CONN_FETCHING_DATA:
MYSQLND_INC_GLOBAL_STATISTIC(STAT_CLOSE_IN_MIDDLE);
DBG_ERR_FMT("Brutally closing connection [%p][%s]", conn, conn->scheme);
/*
Do nothing, the connection will be brutally closed
and the server will catch it and free close from its side.
*/
case CONN_ALLOCED:
/*
Allocated but not connected or there was failure when trying
to connect with pre-allocated connect.
Fall-through
*/
case CONN_QUIT_SENT:
/* The user has killed its own connection */
break;
}
/*
We hold one reference, and every other object which needs the
connection does increase it by 1.
*/
conn->state = CONN_QUIT_SENT;
DBG_RETURN(ret);
}
/* }}} */
/* {{{ mysqlnd_conn::close */
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, close)(MYSQLND * conn, enum_connection_close_type close_type TSRMLS_DC)
{
enum_func_status ret = PASS;
static enum_mysqlnd_collected_stats
close_type_to_stat_map[MYSQLND_CLOSE_LAST] = {
STAT_CLOSE_EXPLICIT,
STAT_CLOSE_IMPLICIT,
STAT_CLOSE_DISCONNECT
};
enum_mysqlnd_collected_stats stat = close_type_to_stat_map[close_type];
DBG_ENTER("mysqlnd_conn::close");
DBG_INF_FMT("conn=%llu", conn->thread_id);
MYSQLND_INC_CONN_STATISTIC(&conn->stats, stat);
MYSQLND_DEC_CONN_STATISTIC(&conn->stats, STAT_OPENED_CONNECTIONS);
if (conn->persistent) {
MYSQLND_DEC_CONN_STATISTIC(&conn->stats, STAT_OPENED_PERSISTENT_CONNECTIONS);
}
/*
Close now, free_reference will try,
if we are last, but that's not a problem.
*/
ret = mysqlnd_send_close(conn TSRMLS_CC);
ret = conn->m->free_reference(conn TSRMLS_CC);
DBG_RETURN(ret);
}
/* }}} */
/* {{{ mysqlnd_conn::get_reference */
static MYSQLND *
MYSQLND_METHOD_PRIVATE(mysqlnd_conn, get_reference)(MYSQLND * const conn)
{
++conn->refcount;
return conn;
}
/* }}} */
/* {{{ mysqlnd_conn::free_reference */
static enum_func_status
MYSQLND_METHOD_PRIVATE(mysqlnd_conn, free_reference)(MYSQLND * const conn TSRMLS_DC)
{
enum_func_status ret = PASS;
DBG_ENTER("mysqlnd_conn::free_reference");
DBG_INF_FMT("conn=%llu conn->refcount=%u", conn->thread_id, conn->refcount);
if (!(--conn->refcount)) {
/*
No multithreading issues as we don't share the connection :)
This will free the object too, of course because references has
reached zero.
*/
ret = mysqlnd_send_close(conn TSRMLS_CC);
conn->m->dtor(conn TSRMLS_CC);
}
DBG_RETURN(ret);
}
/* }}} */
/* {{{ mysqlnd_conn::field_count */
static unsigned int
MYSQLND_METHOD(mysqlnd_conn, field_count)(const MYSQLND * const conn)
{
return conn->field_count;
}
/* }}} */
/* {{{ mysqlnd_conn::insert_id */
static mynd_ulonglong
MYSQLND_METHOD(mysqlnd_conn, insert_id)(const MYSQLND * const conn)
{
return conn->upsert_status.last_insert_id;
}
/* }}} */
/* {{{ mysqlnd_conn::affected_rows */
static mynd_ulonglong
MYSQLND_METHOD(mysqlnd_conn, affected_rows)(const MYSQLND * const conn)
{
return conn->upsert_status.affected_rows;
}
/* }}} */
/* {{{ mysqlnd_conn::warning_count */
static unsigned int
MYSQLND_METHOD(mysqlnd_conn, warning_count)(const MYSQLND * const conn)
{
return conn->upsert_status.warning_count;
}
/* }}} */
/* {{{ mysqlnd_conn::info */
static const char *
MYSQLND_METHOD(mysqlnd_conn, info)(const MYSQLND * const conn)
{
return conn->last_message;
}
/* }}} */
/* {{{ mysqlnd_get_client_info */
PHPAPI const char * mysqlnd_get_client_info()
{
return MYSQLND_VERSION;
}
/* }}} */
/* {{{ mysqlnd_get_client_version */
PHPAPI unsigned int mysqlnd_get_client_version()
{
return MYSQLND_VERSION_ID;
}
/* }}} */
/* {{{ mysqlnd_conn::get_server_info */
static const char *
MYSQLND_METHOD(mysqlnd_conn, get_server_info)(const MYSQLND * const conn)
{
return conn->server_version;
}
/* }}} */
/* {{{ mysqlnd_conn::get_host_info */
static const char *
MYSQLND_METHOD(mysqlnd_conn, get_host_info)(const MYSQLND * const conn)
{
return conn->host_info;
}
/* }}} */
/* {{{ mysqlnd_conn::get_proto_info */
static unsigned int
MYSQLND_METHOD(mysqlnd_conn, get_proto_info)(const MYSQLND *const conn)
{
return conn->protocol_version;
}
/* }}} */
/* {{{ mysqlnd_conn::charset_name */
static const char *
MYSQLND_METHOD(mysqlnd_conn, charset_name)(const MYSQLND * const conn)
{
return conn->charset->name;
}
/* }}} */
/* {{{ mysqlnd_conn::thread_id */
static mynd_ulonglong
MYSQLND_METHOD(mysqlnd_conn, thread_id)(const MYSQLND * const conn)
{
return conn->thread_id;
}
/* }}} */
/* {{{ mysqlnd_conn::get_server_version */
static unsigned long
MYSQLND_METHOD(mysqlnd_conn, get_server_version)(const MYSQLND * const conn)
{
long major, minor, patch;
char *p;
if (!(p = conn->server_version)) {
return 0;
}
major = strtol(p, &p, 10);
p += 1; /* consume the dot */
minor = strtol(p, &p, 10);
p += 1; /* consume the dot */
patch = strtol(p, &p, 10);
return (unsigned long)(major * 10000L + (unsigned long)(minor * 100L + patch));
}
/* }}} */
/* {{{ mysqlnd_conn::more_results */
static zend_bool
MYSQLND_METHOD(mysqlnd_conn,more_results)(const MYSQLND * const conn)
{
/* (conn->state == CONN_NEXT_RESULT_PENDING) too */
return conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS? TRUE:FALSE;
}
/* }}} */
/* {{{ mysqlnd_conn::next_result */
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, next_result)(MYSQLND * const conn TSRMLS_DC)
{
enum_func_status ret;
DBG_ENTER("mysqlnd_conn::next_result");
DBG_INF_FMT("conn=%llu", conn->thread_id);
if (conn->state != CONN_NEXT_RESULT_PENDING) {
DBG_RETURN(FAIL);
}
SET_EMPTY_ERROR(conn->error_info);
conn->upsert_status.affected_rows= ~(mynd_ulonglong) 0;
/*
We are sure that there is a result set, since conn->state is set accordingly
in mysqlnd_store_result() or mysqlnd_fetch_row_unbuffered()
*/
if (FAIL == (ret = mysqlnd_query_read_result_set_header(conn, NULL TSRMLS_CC))) {
DBG_ERR_FMT("Serious error. %s::%d", __FILE__, __LINE__);
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Serious error. PID=%d", getpid());
conn->state = CONN_QUIT_SENT;
}
DBG_RETURN(ret);
}
/* }}} */
/* {{{ mysqlnd_field_type_name */
PHPAPI const char *mysqlnd_field_type_name(enum mysqlnd_field_types field_type)
{
switch(field_type) {
case FIELD_TYPE_STRING:
case FIELD_TYPE_VAR_STRING:
return "string";
case FIELD_TYPE_TINY:
case FIELD_TYPE_SHORT:
case FIELD_TYPE_LONG:
case FIELD_TYPE_LONGLONG:
case FIELD_TYPE_INT24:
return "int";
case FIELD_TYPE_FLOAT:
case FIELD_TYPE_DOUBLE:
case FIELD_TYPE_DECIMAL:
case FIELD_TYPE_NEWDECIMAL:
return "real";
case FIELD_TYPE_TIMESTAMP:
return "timestamp";
case FIELD_TYPE_YEAR:
return "year";
case FIELD_TYPE_DATE:
case FIELD_TYPE_NEWDATE:
return "date";
case FIELD_TYPE_TIME:
return "time";
case FIELD_TYPE_SET:
return "set";
case FIELD_TYPE_ENUM:
return "enum";
case FIELD_TYPE_GEOMETRY:
return "geometry";
case FIELD_TYPE_DATETIME:
return "datetime";
case FIELD_TYPE_TINY_BLOB:
case FIELD_TYPE_MEDIUM_BLOB:
case FIELD_TYPE_LONG_BLOB:
case FIELD_TYPE_BLOB:
return "blob";
case FIELD_TYPE_NULL:
return "null";
case FIELD_TYPE_BIT:
return "bit";
default:
return "unknown";
}
}
/* }}} */
/* {{{ mysqlnd_conn::change_user */
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, change_user)(MYSQLND * const conn,
const char *user,
const char *passwd,
const char *db TSRMLS_DC)
{
/*
User could be max 16 * 3 (utf8), pass is 20 usually, db is up to 64*3
Stack space is not that expensive, so use a bit more to be protected against
stack overrungs.
*/
size_t user_len;
enum_func_status ret;
php_mysql_packet_chg_user_resp chg_user_resp;
char buffer[MYSQLND_MAX_ALLOWED_USER_LEN + 1 + SCRAMBLE_LENGTH + MYSQLND_MAX_ALLOWED_DB_LEN + 1];
char *p = buffer;
DBG_ENTER("mysqlnd_conn::change_user");
DBG_INF_FMT("conn=%llu user=%s passwd=%s db=%s",
conn->thread_id, user?user:"", passwd?"***":"null", db?db:"");
if (!user) {
user = "";
}
if (!passwd) {
passwd = "";
}
if (!db) {
db = "";
}
/* 1. user ASCIIZ */
user_len = MIN(strlen(user), MYSQLND_MAX_ALLOWED_DB_LEN);
memcpy(p, user, user_len);
p += user_len;
*p++ = '\0';
/* 2. password SCRAMBLE_LENGTH followed by the scramble or \0 */
if (passwd[0]) {
*p++ = SCRAMBLE_LENGTH;
php_mysqlnd_scramble((unsigned char *)p, conn->scramble, (unsigned char *)passwd);
p += SCRAMBLE_LENGTH;
} else {
*p++ = '\0';
}
/* 3. db ASCIIZ */
if (db[0]) {
size_t db_len = strlen(db);
memcpy(p, db, MIN(db_len, MYSQLND_MAX_ALLOWED_DB_LEN));
p += db_len;
}
*p++ = '\0';
if (PASS != mysqlnd_simple_command(conn, COM_CHANGE_USER, buffer, p - buffer,
PROT_LAST /* we will handle the OK packet*/,
FALSE TSRMLS_CC)) {
DBG_RETURN(FAIL);
}
PACKET_INIT_ALLOCA(chg_user_resp, PROT_CHG_USER_PACKET);
ret = PACKET_READ_ALLOCA(chg_user_resp, conn);
conn->error_info = chg_user_resp.error_info;
PACKET_FREE_ALLOCA(chg_user_resp);
if (conn->error_info.error_no) {
ret = FAIL;
/*
COM_CHANGE_USER is broken in 5.1. At least in 5.1.15 and 5.1.14, 5.1.11 is immune.
bug#25371 mysql_change_user() triggers "packets out of sync"
When it gets fixed, there should be one more check here
*/
if (mysqlnd_get_server_version(conn) > 50113L &&
mysqlnd_get_server_version(conn) < 50118L)
{
php_mysql_packet_ok redundant_error_packet;
PACKET_INIT_ALLOCA(redundant_error_packet, PROT_OK_PACKET);
PACKET_READ_ALLOCA(redundant_error_packet, conn);
PACKET_FREE_ALLOCA(redundant_error_packet);
DBG_INF_FMT("Server is %d, buggy, sends two ERR messages", mysqlnd_get_server_version(conn));
}
}
if (ret == PASS) {
mnd_pefree(conn->user, conn->persistent);
conn->user = pestrndup(user, user_len, conn->persistent);
mnd_pefree(conn->passwd, conn->persistent);
conn->passwd = pestrdup(passwd, conn->persistent);
if (conn->last_message) {
mnd_pefree(conn->last_message, conn->persistent);
conn->last_message = NULL;
}
conn->charset = conn->greet_charset;
memset(&conn->upsert_status, 0, sizeof(conn->upsert_status));
}
SET_ERROR_AFF_ROWS(conn);
/*
Here we should close all statements. Unbuffered queries should not be a
problem as we won't allow sending COM_CHANGE_USER.
*/
DBG_INF(ret == PASS? "PASS":"FAIL");
DBG_RETURN(ret);
}
/* }}} */
/* {{{ mysqlnd_conn::set_client_option */
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn, set_client_option)(MYSQLND * const conn,
enum mysqlnd_option option,
const char * const value
TSRMLS_DC)
{
DBG_ENTER("mysqlnd_conn::set_client_option");
DBG_INF_FMT("conn=%llu option=%d", conn->thread_id, option);
switch (option) {
#if PHP_MAJOR_VERSION >= 6
case MYSQLND_OPT_NUMERIC_AND_DATETIME_AS_UNICODE:
conn->options.numeric_and_datetime_as_unicode = *(uint*) value;
break;
#endif
case MYSQLND_OPT_NET_CMD_BUFFER_SIZE:
conn->net.cmd_buffer.length = *(uint*) value;
if (!conn->net.cmd_buffer.buffer) {
conn->net.cmd_buffer.buffer = mnd_pemalloc(conn->net.cmd_buffer.length, conn->persistent);
} else {
conn->net.cmd_buffer.buffer = mnd_perealloc(conn->net.cmd_buffer.buffer,
conn->net.cmd_buffer.length,
conn->persistent);
}
break;
case MYSQLND_OPT_NET_READ_BUFFER_SIZE:
conn->options.net_read_buffer_size = *(uint*) value;
break;
#ifdef MYSQLND_STRING_TO_INT_CONVERSION
case MYSQLND_OPT_INT_AND_YEAR_AS_INT:
conn->options.int_and_year_as_int = *(uint*) value;
break;
#endif
case MYSQL_OPT_CONNECT_TIMEOUT:
conn->options.timeout_connect = *(uint*) value;
break;
#ifdef WHEN_SUPPORTED_BY_MYSQLI
case MYSQL_OPT_READ_TIMEOUT:
conn->options.timeout_read = *(uint*) value;
break;
case MYSQL_OPT_WRITE_TIMEOUT:
conn->options.timeout_write = *(uint*) value;
break;
#endif
case MYSQL_OPT_LOCAL_INFILE:
if (!value || (*(uint*) value) ? 1 : 0) {
conn->options.flags |= CLIENT_LOCAL_FILES;
} else {
conn->options.flags &= ~CLIENT_LOCAL_FILES;
}
break;
#ifdef WHEN_SUPPORTED_BY_MYSQLI
case MYSQL_OPT_COMPRESS:
#endif
case MYSQL_INIT_COMMAND:
case MYSQL_READ_DEFAULT_FILE:
case MYSQL_READ_DEFAULT_GROUP:
#ifdef WHEN_SUPPORTED_BY_MYSQLI
case MYSQL_SET_CLIENT_IP:
case MYSQL_REPORT_DATA_TRUNCATION:
case MYSQL_OPT_SSL_VERIFY_SERVER_CERT:
#endif
/* currently not supported. Todo!! */
break;
case MYSQL_SET_CHARSET_NAME:
conn->options.charset_name = pestrdup(value, conn->persistent);
break;
#ifdef WHEN_SUPPORTED_BY_MYSQLI
case MYSQL_SET_CHARSET_DIR:
case MYSQL_OPT_RECONNECT:
case MYSQL_OPT_PROTOCOL:
/* we don't need external character sets, all character sets are
compiled in. For compatibility we just ignore this setting.
Same for protocol, we don't support old protocol */
case MYSQL_OPT_USE_REMOTE_CONNECTION:
case MYSQL_OPT_USE_EMBEDDED_CONNECTION:
case MYSQL_OPT_GUESS_CONNECTION:
/* todo: throw an error, we don't support embedded */
break;
#endif
#ifdef WHEN_SUPPORTED_BY_MYSQLI
case MYSQL_OPT_NAMED_PIPE:
case MYSQL_SHARED_MEMORY_BASE_NAME:
case MYSQL_OPT_USE_RESULT:
case MYSQL_SECURE_AUTH:
/* not sure, todo ? */
#endif
default:
DBG_RETURN(FAIL);
}
DBG_RETURN(PASS);
}
/* }}} */
/* {{{ _mysqlnd_conn::use_result */
MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_conn, use_result)(MYSQLND * const conn TSRMLS_DC)
{
MYSQLND_RES *result;
DBG_ENTER("mysqlnd_conn::use_result");
DBG_INF_FMT("conn=%llu", conn->thread_id);
if (!conn->current_result) {
DBG_RETURN(NULL);
}
/* Nothing to store for UPSERT/LOAD DATA */
if (conn->last_query_type != QUERY_SELECT || conn->state != CONN_FETCHING_DATA) {
SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
mysqlnd_out_of_sync);
DBG_ERR("Command out of sync");
DBG_RETURN(NULL);
}
MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_UNBUFFERED_SETS);
result = conn->current_result;
conn->current_result = NULL;
result->conn = conn->m->get_reference(conn);
result = result->m.use_result(result, FALSE TSRMLS_CC);
DBG_RETURN(result);
}
/* }}} */
/* {{{ _mysqlnd_conn::store_result */
MYSQLND_RES *
MYSQLND_METHOD(mysqlnd_conn, store_result)(MYSQLND * const conn TSRMLS_DC)
{
MYSQLND_RES *result;
DBG_ENTER("mysqlnd_conn::store_result");
DBG_INF_FMT("conn=%llu", conn->thread_id);
if (!conn->current_result) {
DBG_RETURN(NULL);
}
/* Nothing to store for UPSERT/LOAD DATA*/
if (conn->last_query_type != QUERY_SELECT || conn->state != CONN_FETCHING_DATA) {
SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE,
mysqlnd_out_of_sync);
DBG_ERR("Command out of sync");
DBG_RETURN(NULL);
}
MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_BUFFERED_SETS);
result = conn->current_result;
conn->current_result = NULL;
result = result->m.store_result(result, conn, FALSE TSRMLS_CC);
DBG_RETURN(result);
}
/* }}} */
/* {{{ mysqlnd_conn::get_connection_stats */
void
MYSQLND_METHOD(mysqlnd_conn, get_connection_stats)(const MYSQLND * const conn,
zval *return_value
TSRMLS_DC ZEND_FILE_LINE_DC)
{
DBG_ENTER("mysqlnd_conn::get_connection_stats");
DBG_INF_FMT("conn=%llu", conn->thread_id);
mysqlnd_fill_stats_hash(&(conn->stats), return_value TSRMLS_CC ZEND_FILE_LINE_CC);
DBG_VOID_RETURN;
}
/* }}} */
MYSQLND_STMT * _mysqlnd_stmt_init(MYSQLND * const conn TSRMLS_DC);
MYSQLND_CLASS_METHODS_START(mysqlnd_conn)
MYSQLND_METHOD(mysqlnd_conn, escape_string),
MYSQLND_METHOD(mysqlnd_conn, set_charset),
MYSQLND_METHOD(mysqlnd_conn, query),
MYSQLND_METHOD(mysqlnd_conn, use_result),
MYSQLND_METHOD(mysqlnd_conn, store_result),
MYSQLND_METHOD(mysqlnd_conn, next_result),
MYSQLND_METHOD(mysqlnd_conn, more_results),
_mysqlnd_stmt_init,
MYSQLND_METHOD(mysqlnd_conn, shutdown),
MYSQLND_METHOD(mysqlnd_conn, refresh),
MYSQLND_METHOD(mysqlnd_conn, ping),
MYSQLND_METHOD(mysqlnd_conn, kill),
MYSQLND_METHOD(mysqlnd_conn, select_db),
MYSQLND_METHOD(mysqlnd_conn, dump_debug_info),
MYSQLND_METHOD(mysqlnd_conn, change_user),
MYSQLND_METHOD(mysqlnd_conn, errno),
MYSQLND_METHOD(mysqlnd_conn, error),
MYSQLND_METHOD(mysqlnd_conn, sqlstate),
MYSQLND_METHOD(mysqlnd_conn, thread_id),
MYSQLND_METHOD(mysqlnd_conn, get_connection_stats),
MYSQLND_METHOD(mysqlnd_conn, get_server_version),
MYSQLND_METHOD(mysqlnd_conn, get_server_info),
MYSQLND_METHOD(mysqlnd_conn, stat),
MYSQLND_METHOD(mysqlnd_conn, get_host_info),
MYSQLND_METHOD(mysqlnd_conn, get_proto_info),
MYSQLND_METHOD(mysqlnd_conn, info),
MYSQLND_METHOD(mysqlnd_conn, charset_name),
MYSQLND_METHOD(mysqlnd_conn, list_fields),
MYSQLND_METHOD(mysqlnd_conn, list_method),
MYSQLND_METHOD(mysqlnd_conn, insert_id),
MYSQLND_METHOD(mysqlnd_conn, affected_rows),
MYSQLND_METHOD(mysqlnd_conn, warning_count),
MYSQLND_METHOD(mysqlnd_conn, field_count),
MYSQLND_METHOD(mysqlnd_conn, set_server_option),
MYSQLND_METHOD(mysqlnd_conn, set_client_option),
MYSQLND_METHOD(mysqlnd_conn, free_contents),
MYSQLND_METHOD(mysqlnd_conn, close),
MYSQLND_METHOD_PRIVATE(mysqlnd_conn, dtor),
MYSQLND_METHOD_PRIVATE(mysqlnd_conn, get_reference),
MYSQLND_METHOD_PRIVATE(mysqlnd_conn, free_reference),
MYSQLND_CLASS_METHODS_END;
/* {{{ mysqlnd_init */
PHPAPI MYSQLND *_mysqlnd_init(zend_bool persistent TSRMLS_DC)
{
MYSQLND *ret = mnd_pecalloc(1, sizeof(MYSQLND), persistent);
DBG_ENTER("mysqlnd_init");
DBG_INF_FMT("persistent=%d", persistent);
SET_ERROR_AFF_ROWS(ret);
ret->persistent = persistent;
ret->m = & mysqlnd_mysqlnd_conn_methods;
ret->m->get_reference(ret);
DBG_RETURN(ret);
}
/* }}} */
/* {{{ mysqlnd_functions[]
*
* Every user visible function must have an entry in mysqlnd_functions[].
*/
static zend_function_entry mysqlnd_functions[] = {
{NULL, NULL, NULL} /* Must be the last line in mysqlnd_functions[] */
};
/* }}} */
/* {{{ mysqlnd_minfo_print_hash */
#if PHP_MAJOR_VERSION >= 6
PHPAPI void mysqlnd_minfo_print_hash(zval *values)
{
zval **values_entry;
HashPosition pos_values;
zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(values), &pos_values);
while (zend_hash_get_current_data_ex(Z_ARRVAL_P(values),
(void **)&values_entry, &pos_values) == SUCCESS) {
TSRMLS_FETCH();
zstr string_key;
uint string_key_len;
ulong num_key;
char *s = NULL;
zend_hash_get_current_key_ex(Z_ARRVAL_P(values), &string_key, &string_key_len, &num_key, 0, &pos_values);
convert_to_string(*values_entry);
if (UG(unicode)) {
int s_len;
if (zend_unicode_to_string(ZEND_U_CONVERTER(UG(runtime_encoding_conv)),
&s, &s_len, string_key.u, string_key_len TSRMLS_CC) == SUCCESS) {
php_info_print_table_row(2, s, Z_STRVAL_PP(values_entry));
}
if (s) {
mnd_efree(s);
}
} else {
php_info_print_table_row(2, string_key.s, Z_STRVAL_PP(values_entry));
}
zend_hash_move_forward_ex(Z_ARRVAL_P(values), &pos_values);
}
}
#else
void mysqlnd_minfo_print_hash(zval *values)
{
zval **values_entry;
HashPosition pos_values;
zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(values), &pos_values);
while (zend_hash_get_current_data_ex(Z_ARRVAL_P(values), (void **)&values_entry, &pos_values) == SUCCESS) {
char *string_key;
uint string_key_len;
ulong num_key;
zend_hash_get_current_key_ex(Z_ARRVAL_P(values), &string_key, &string_key_len, &num_key, 0, &pos_values);
convert_to_string(*values_entry);
php_info_print_table_row(2, string_key, Z_STRVAL_PP(values_entry));
zend_hash_move_forward_ex(Z_ARRVAL_P(values), &pos_values);
}
}
#endif
/* }}} */
/* {{{ PHP_MINFO_FUNCTION
*/
PHP_MINFO_FUNCTION(mysqlnd)
{
char buf[32];
zval values;
php_info_print_table_start();
php_info_print_table_header(2, "mysqlnd", "enabled");
php_info_print_table_row(2, "Version", mysqlnd_get_client_info());
/* Print client stats */
php_info_print_table_header(2, "Client statistics", "");
mysqlnd_get_client_stats(&values);
mysqlnd_minfo_print_hash(&values);
php_info_print_table_row(2, "Collecting statistics", MYSQLND_G(collect_statistics)? "Yes":"No");
php_info_print_table_row(2, "Collecting memory statistics", MYSQLND_G(collect_memory_statistics)? "Yes":"No");
snprintf(buf, sizeof(buf), "%ld", MYSQLND_G(net_cmd_buffer_size));
php_info_print_table_row(2, "Command buffer size", buf);
snprintf(buf, sizeof(buf), "%ld", MYSQLND_G(net_read_buffer_size));
php_info_print_table_row(2, "Read buffer size", buf);
zval_dtor(&values);
php_info_print_table_end();
}
/* }}} */
ZEND_DECLARE_MODULE_GLOBALS(mysqlnd);
/* {{{ PHP_GINIT_FUNCTION
*/
static PHP_GINIT_FUNCTION(mysqlnd)
{
mysqlnd_globals->collect_statistics = TRUE;
mysqlnd_globals->collect_memory_statistics = FALSE;
mysqlnd_globals->debug = NULL; /* The actual string */
mysqlnd_globals->dbg = NULL; /* The DBG object*/
mysqlnd_globals->net_cmd_buffer_size = 2048;
mysqlnd_globals->net_read_buffer_size = 32768;
}
/* }}} */
/* {{{ PHP_INI_BEGIN
*/
PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN("mysqlnd.collect_statistics", "1", PHP_INI_ALL, OnUpdateBool, collect_statistics, zend_mysqlnd_globals, mysqlnd_globals)
STD_PHP_INI_BOOLEAN("mysqlnd.collect_memory_statistics", "0", PHP_INI_SYSTEM, OnUpdateBool, collect_memory_statistics, zend_mysqlnd_globals, mysqlnd_globals)
STD_PHP_INI_ENTRY("mysqlnd.debug", NULL, PHP_INI_SYSTEM, OnUpdateString, debug, zend_mysqlnd_globals, mysqlnd_globals)
STD_PHP_INI_ENTRY("mysqlnd.net_cmd_buffer_size", "2048", PHP_INI_ALL, OnUpdateLong, net_cmd_buffer_size, zend_mysqlnd_globals, mysqlnd_globals)
STD_PHP_INI_ENTRY("mysqlnd.net_read_buffer_size", "32768",PHP_INI_ALL, OnUpdateLong, net_read_buffer_size, zend_mysqlnd_globals, mysqlnd_globals)
PHP_INI_END()
/* }}} */
/* {{{ PHP_MINIT_FUNCTION
*/
static PHP_MINIT_FUNCTION(mysqlnd)
{
REGISTER_INI_ENTRIES();
mysqlnd_library_init();
return SUCCESS;
}
/* }}} */
/* {{{ PHP_MSHUTDOWN_FUNCTION
*/
static PHP_MSHUTDOWN_FUNCTION(mysqlnd)
{
mysqlnd_library_end();
UNREGISTER_INI_ENTRIES();
return SUCCESS;
}
/* }}} */
#if PHP_DEBUG
/* {{{ PHP_RINIT_FUNCTION
*/
static PHP_RINIT_FUNCTION(mysqlnd)
{
#ifdef PHP_DEBUG
if (MYSQLND_G(debug)) {
MYSQLND_DEBUG *dbg = mysqlnd_debug_init(TSRMLS_C);
if (!dbg) {
return FAILURE;
}
dbg->m->set_mode(dbg, MYSQLND_G(debug));
MYSQLND_G(dbg) = dbg;
}
#endif
return SUCCESS;
}
/* }}} */
/* {{{ PHP_RSHUTDOWN_FUNCTION
*/
static PHP_RSHUTDOWN_FUNCTION(mysqlnd)
{
#ifdef PHP_DEBUG
MYSQLND_DEBUG *dbg = MYSQLND_G(dbg);
DBG_ENTER("RSHUTDOWN");
if (dbg) {
dbg->m->close(dbg);
dbg->m->free_handle(dbg);
MYSQLND_G(dbg) = NULL;
}
#endif
return SUCCESS;
}
/* }}} */
#endif
/* {{{ mysqlnd_module_entry
*/
zend_module_entry mysqlnd_module_entry = {
STANDARD_MODULE_HEADER,
"mysqlnd",
mysqlnd_functions,
PHP_MINIT(mysqlnd),
PHP_MSHUTDOWN(mysqlnd),
#if PHP_DEBUG
PHP_RINIT(mysqlnd),
PHP_RSHUTDOWN(mysqlnd),
#else
NULL,
NULL,
#endif
PHP_MINFO(mysqlnd),
MYSQLND_VERSION,
PHP_MODULE_GLOBALS(mysqlnd),
PHP_GINIT(mysqlnd),
NULL,
NULL,
STANDARD_MODULE_PROPERTIES_EX
};
/* }}} */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* End:
* vim600: noet sw=4 ts=4 fdm=marker
* vim<600: noet sw=4 ts=4
*/