diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2013-03-14 05:42:27 +0000 |
---|---|---|
committer | <> | 2013-04-03 16:25:08 +0000 |
commit | c4dd7a1a684490673e25aaf4fabec5df138854c4 (patch) | |
tree | 4d57c44caae4480efff02b90b9be86f44bf25409 /ext/mysqlnd | |
download | php2-master.tar.gz |
Imported from /home/lorry/working-area/delta_php2/php-5.4.13.tar.bz2.HEADphp-5.4.13master
Diffstat (limited to 'ext/mysqlnd')
42 files changed, 21503 insertions, 0 deletions
diff --git a/ext/mysqlnd/CREDITS b/ext/mysqlnd/CREDITS new file mode 100644 index 0000000..5aed316 --- /dev/null +++ b/ext/mysqlnd/CREDITS @@ -0,0 +1,2 @@ +MySQLnd +Andrey Hristov, Ulf Wendel, Georg Richter diff --git a/ext/mysqlnd/config-win.h b/ext/mysqlnd/config-win.h new file mode 100644 index 0000000..bc44050 --- /dev/null +++ b/ext/mysqlnd/config-win.h @@ -0,0 +1,115 @@ +/* Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB +This file is public domain and comes with NO WARRANTY of any kind */ + +/* Defines for Win32 to make it compatible for MySQL */ + +#ifndef _MYSQLND_CONFIG_WIN_H +#define _MYSQLND_CONFIG_WIN_H + +#include <sys/locking.h> +#include <windows.h> +#include <math.h> /* Because of rint() */ +#include <fcntl.h> +#include <io.h> +#include <malloc.h> + +#include <win32/php_stdint.h> + +#ifndef HAVE_INT8_T +#define HAVE_INT8_T +#endif +#ifndef HAVE_UINT8_T +#define HAVE_UINT8_T +#endif +#ifndef HAVE_INT16_T +#define HAVE_INT16_T +#endif +#ifndef HAVE_UINT16_T +#define HAVE_UINT16_T +#endif +#ifndef HAVE_INT32_T +#define HAVE_INT32_T +#endif +#ifndef HAVE_UINT32_T +#define HAVE_UINT32_T +#endif +#ifndef HAVE_INT64_T +#define HAVE_INT64_T +#endif +#ifndef HAVE_UINT64_T +#define HAVE_UINT64_T +#endif + + +#ifndef _WIN64 +#ifndef _WIN32 +#define _WIN32 /* Compatible with old source */ +#endif +#ifndef __WIN32__ +#define __WIN32__ +#endif +#endif /* _WIN64 */ +#ifndef __WIN__ +#define __WIN__ /* To make it easier in VC++ */ +#endif + +/* Type information */ + +#define SIZEOF_CHAR 1 +#define SIZEOF_LONG 4 +#define SIZEOF_LONG_LONG 8 + + +#ifndef _WIN64 +/* Optimized store functions for Intel x86 */ + +#define sint2korr(A) (*((int16_t *) (A))) +#define sint3korr(A) ((int32_t) ((((zend_uchar) (A)[2]) & 128) ? \ + (((uint32_t) 255L << 24) | \ + (((uint32_t) (zend_uchar) (A)[2]) << 16) |\ + (((uint32_t) (zend_uchar) (A)[1]) << 8) | \ + ((uint32_t) (zend_uchar) (A)[0])) : \ + (((uint32_t) (zend_uchar) (A)[2]) << 16) |\ + (((uint32_t) (zend_uchar) (A)[1]) << 8) | \ + ((uint32_t) (zend_uchar) (A)[0]))) +#define sint4korr(A) (*((int32_t *) (A))) +#define uint2korr(A) (*((uint16_t *) (A))) +#define uint3korr(A) (int32_t) (*((uint32_t *) (A)) & 0xFFFFFF) +#define uint4korr(A) (*((uint32_t *) (A))) +#define uint5korr(A) ((uint64_t)(((uint32_t) ((zend_uchar) (A)[0])) +\ + (((uint32_t) ((zend_uchar) (A)[1])) << 8) +\ + (((uint32_t) ((zend_uchar) (A)[2])) << 16) +\ + (((uint32_t) ((zend_uchar) (A)[3])) << 24)) +\ + (((uint64_t) ((zend_uchar) (A)[4])) << 32)) +#define uint8korr(A) (*((uint64_t *) (A))) +#define sint8korr(A) (*((int64_t *) (A))) +#define int2store(T,A) *((uint16_t*) (T))= (uint16_t) (A) +#define int3store(T,A) { *(T)= (zend_uchar) ((A));\ + *(T+1)=(zend_uchar) (((uint32_t) (A) >> 8));\ + *(T+2)=(zend_uchar) (((A) >> 16)); } +#define int4store(T,A) *((int32_t *) (T))= (int32_t) (A) +#define int5store(T,A) { *(T)= (zend_uchar)((A));\ + *((T)+1)=(zend_uchar) (((A) >> 8));\ + *((T)+2)=(zend_uchar) (((A) >> 16));\ + *((T)+3)=(zend_uchar) (((A) >> 24)); \ + *((T)+4)=(zend_uchar) (((A) >> 32)); } +#define int8store(T,A) *((uint64_t *) (T))= (uint64_t) (A) + +#define float8get(V,M) { *((int32_t *) &V) = *((int32_t*) M); \ + *(((int32_t *) &V)+1) = *(((int32_t*) M)+1); } +#define float8store(T,V) { *((int32_t *) T) = *((int32_t*) &V); \ + *(((int32_t *) T)+1) = *(((int32_t*) &V)+1); } +#define float4get(V,M) { *((int32_t *) &(V)) = *((int32_t*) (M)); } + +#endif /* _WIN64 */ + +#endif /* _MYSQLND_CONFIG_WIN_H */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/config.w32 b/ext/mysqlnd/config.w32 new file mode 100644 index 0000000..a910a0e --- /dev/null +++ b/ext/mysqlnd/config.w32 @@ -0,0 +1,38 @@ +// $Id$ +// vim:ft=javascript + +ARG_WITH("mysqlnd", "Mysql Native Client Driver", "yes"); +if (PHP_MYSQLND != "no") { + + if (CHECK_LIB("ws2_32.lib", "mysqlnd")) { + mysqlnd_source = + "mysqlnd.c " + + "mysqlnd_alloc.c " + + "mysqlnd_auth.c " + + "mysqlnd_block_alloc.c " + + "mysqlnd_bt.c " + + "mysqlnd_charset.c " + + "mysqlnd_debug.c " + + "mysqlnd_driver.c " + + "mysqlnd_ext_plugin.c " + + "mysqlnd_loaddata.c " + + "mysqlnd_reverse_api.c " + + "mysqlnd_net.c " + + "mysqlnd_plugin.c " + + "mysqlnd_ps.c " + + "mysqlnd_ps_codec.c " + + "mysqlnd_result.c " + + "mysqlnd_result_meta.c " + + "mysqlnd_statistics.c " + + "mysqlnd_wireprotocol.c " + + "php_mysqlnd.c "; + EXTENSION("mysqlnd", mysqlnd_source, false); + if (((PHP_ZLIB=="no") && (CHECK_LIB("zlib_a.lib;zlib.lib", "mysqlnd", PHP_MYSQLND))) || + (PHP_ZLIB_SHARED && CHECK_LIB("zlib.lib", "mysqlnd", PHP_MYSQLND)) || (PHP_ZLIB == "yes" && (!PHP_ZLIB_SHARED))) + { + AC_DEFINE("MYSQLND_COMPRESSION_ENABLED", 1, "Compression support"); + AC_DEFINE("MYSQLND_SSL_SUPPORTED", 1, "SSL support"); + } + PHP_INSTALL_HEADERS("", "ext/mysqlnd"); + } +} diff --git a/ext/mysqlnd/config9.m4 b/ext/mysqlnd/config9.m4 new file mode 100644 index 0000000..2c15c34 --- /dev/null +++ b/ext/mysqlnd/config9.m4 @@ -0,0 +1,53 @@ +dnl +dnl $Id$ +dnl config.m4 for mysqlnd driver + +PHP_ARG_ENABLE(mysqlnd, whether to enable mysqlnd, + [ --enable-mysqlnd Enable mysqlnd explicitly, will be done implicitly + when required by other extensions], no, yes) + +PHP_ARG_ENABLE(mysqlnd_compression_support, whether to disable compressed protocol support in mysqlnd, +[ --disable-mysqlnd-compression-support + Disable support for the MySQL compressed protocol in mysqlnd], yes, no) + +if test -z "$PHP_ZLIB_DIR"; then + PHP_ARG_WITH(zlib-dir, for the location of libz, + [ --with-zlib-dir[=DIR] mysqlnd: Set the path to libz install prefix], no, no) +fi + +dnl If some extension uses mysqlnd it will get compiled in PHP core +if test "$PHP_MYSQLND" != "no" || test "$PHP_MYSQLND_ENABLED" = "yes"; then + mysqlnd_ps_sources="mysqlnd_ps.c mysqlnd_ps_codec.c" + mysqlnd_base_sources="mysqlnd.c mysqlnd_alloc.c mysqlnd_bt.c mysqlnd_charset.c mysqlnd_wireprotocol.c \ + mysqlnd_loaddata.c mysqlnd_reverse_api.c mysqlnd_net.c \ + mysqlnd_statistics.c mysqlnd_driver.c mysqlnd_ext_plugin.c mysqlnd_auth.c \ + mysqlnd_result.c mysqlnd_result_meta.c mysqlnd_debug.c\ + mysqlnd_block_alloc.c mysqlnd_plugin.c php_mysqlnd.c" + + + if test "$PHP_MYSQLND_COMPRESSION_SUPPORT" != "no"; then + AC_DEFINE([MYSQLND_COMPRESSION_WANTED], 1, [Enable compressed protocol support]) + fi + AC_DEFINE([MYSQLND_SSL_SUPPORTED], 1, [Enable SSL support]) + + mysqlnd_sources="$mysqlnd_base_sources $mysqlnd_ps_sources" + PHP_NEW_EXTENSION(mysqlnd, $mysqlnd_sources, $ext_shared) + PHP_ADD_BUILD_DIR([ext/mysqlnd], 1) + PHP_INSTALL_HEADERS([ext/mysqlnd/]) +fi + +if test "$PHP_MYSQLND" != "no" || test "$PHP_MYSQLND_ENABLED" = "yes" || test "$PHP_MYSQLI" != "no"; then + PHP_ADD_BUILD_DIR([ext/mysqlnd], 1) + + dnl This creates a file so it has to be after above macros + PHP_CHECK_TYPES([int8 uint8 int16 uint16 int32 uint32 uchar ulong int8_t uint8_t int16_t uint16_t int32_t uint32_t int64_t uint64_t], [ + ext/mysqlnd/php_mysqlnd_config.h + ],[ +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#ifdef HAVE_STDINT_H +#include <stdint.h> +#endif + ]) +fi diff --git a/ext/mysqlnd/mysqlnd.c b/ext/mysqlnd/mysqlnd.c new file mode 100644 index 0000000..90c2b73 --- /dev/null +++ b/ext/mysqlnd/mysqlnd.c @@ -0,0 +1,2764 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@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" + +/* + 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; + + + +PHPAPI const char * const mysqlnd_old_passwd = "mysqlnd cannot connect to MySQL 4.1+ using the old insecure authentication. " +"Please use an administration tool to reset your password with the command SET PASSWORD = PASSWORD('your_existing_password'). This will " +"store a new, and more secure, hash value in mysql.user. If this user is used in other scripts executed by PHP 5.2 or earlier you might need to remove the old-passwords " +"flag from your my.cnf file"; + +PHPAPI const char * const mysqlnd_server_gone = "MySQL server has gone away"; +PHPAPI const char * const mysqlnd_out_of_sync = "Commands out of sync; you can't run this command now"; +PHPAPI const char * const mysqlnd_out_of_memory = "Out of memory"; + +PHPAPI MYSQLND_STATS *mysqlnd_global_stats = NULL; + + +/* {{{ mysqlnd_conn_data::free_options */ +static void +MYSQLND_METHOD(mysqlnd_conn_data, free_options)(MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + zend_bool pers = conn->persistent; + + if (conn->options->charset_name) { + mnd_pefree(conn->options->charset_name, pers); + conn->options->charset_name = NULL; + } + if (conn->options->auth_protocol) { + mnd_pefree(conn->options->auth_protocol, pers); + conn->options->auth_protocol = NULL; + } + if (conn->options->num_commands) { + unsigned int i; + for (i = 0; i < conn->options->num_commands; i++) { + /* allocated with pestrdup */ + 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; + } +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::free_contents */ +static void +MYSQLND_METHOD(mysqlnd_conn_data, free_contents)(MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + zend_bool pers = conn->persistent; + + DBG_ENTER("mysqlnd_conn_data::free_contents"); + + mysqlnd_local_infile_default(conn); + if (conn->current_result) { + conn->current_result->m.free_result(conn->current_result, TRUE TSRMLS_CC); + conn->current_result = NULL; + } + + if (conn->net) { + conn->net->m.free_contents(conn->net TSRMLS_CC); + } + + DBG_INF("Freeing memory of members"); + + if (conn->host) { + mnd_pefree(conn->host, pers); + conn->host = NULL; + } + if (conn->user) { + mnd_pefree(conn->user, pers); + conn->user = NULL; + } + if (conn->passwd) { + mnd_pefree(conn->passwd, pers); + conn->passwd = NULL; + } + if (conn->connect_or_select_db) { + mnd_pefree(conn->connect_or_select_db, pers); + conn->connect_or_select_db = NULL; + } + if (conn->unix_socket) { + mnd_pefree(conn->unix_socket, pers); + conn->unix_socket = NULL; + } + DBG_INF_FMT("scheme=%s", conn->scheme); + if (conn->scheme) { + mnd_pefree(conn->scheme, pers); + conn->scheme = NULL; + } + if (conn->server_version) { + mnd_pefree(conn->server_version, pers); + conn->server_version = NULL; + } + if (conn->host_info) { + mnd_pefree(conn->host_info, pers); + conn->host_info = NULL; + } + if (conn->auth_plugin_data) { + mnd_pefree(conn->auth_plugin_data, pers); + conn->auth_plugin_data = NULL; + } + if (conn->last_message) { + mnd_pefree(conn->last_message, pers); + conn->last_message = NULL; + } + if (conn->error_info->error_list) { + zend_llist_clean(conn->error_info->error_list); + mnd_pefree(conn->error_info->error_list, pers); + conn->error_info->error_list = NULL; + } + conn->charset = NULL; + conn->greet_charset = NULL; + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::dtor */ +static void +MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, dtor)(MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn_data::dtor"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + conn->m->free_contents(conn TSRMLS_CC); + conn->m->free_options(conn TSRMLS_CC); + + if (conn->net) { + mysqlnd_net_free(conn->net, conn->stats, conn->error_info TSRMLS_CC); + conn->net = NULL; + } + + if (conn->protocol) { + mysqlnd_protocol_free(conn->protocol TSRMLS_CC); + conn->protocol = NULL; + } + + if (conn->stats) { + mysqlnd_stats_end(conn->stats); + } + + mnd_pefree(conn, conn->persistent); + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::simple_command_handle_response */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, simple_command_handle_response)(MYSQLND_CONN_DATA * conn, enum mysqlnd_packet_type ok_packet, + zend_bool silent, enum php_mysqlnd_server_command command, + zend_bool ignore_upsert_status TSRMLS_DC) +{ + enum_func_status ret = FAIL; + + DBG_ENTER("mysqlnd_conn_data::simple_command_handle_response"); + DBG_INF_FMT("silent=%u packet=%u command=%s", silent, ok_packet, mysqlnd_command_to_text[command]); + + switch (ok_packet) { + case PROT_OK_PACKET:{ + MYSQLND_PACKET_OK * ok_response = conn->protocol->m.get_ok_packet(conn->protocol, FALSE TSRMLS_CC); + if (!ok_response) { + SET_OOM_ERROR(*conn->error_info); + break; + } + if (FAIL == (ret = PACKET_READ(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=%u", + 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); + + if (!ignore_upsert_status) { + 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(ok_response); + break; + } + case PROT_EOF_PACKET:{ + MYSQLND_PACKET_EOF * ok_response = conn->protocol->m.get_eof_packet(conn->protocol, FALSE TSRMLS_CC); + if (!ok_response) { + SET_OOM_ERROR(*conn->error_info); + break; + } + if (FAIL == (ret = PACKET_READ(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(ok_response); + break; + } + default: + SET_CLIENT_ERROR(*conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, "Malformed packet"); + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Wrong response packet %u passed to the function", ok_packet); + break; + } + DBG_INF(ret == PASS ? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::simple_command */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, simple_command)(MYSQLND_CONN_DATA * conn, enum php_mysqlnd_server_command command, + const zend_uchar * const arg, size_t arg_len, enum mysqlnd_packet_type ok_packet, zend_bool silent, + zend_bool ignore_upsert_status TSRMLS_DC) +{ + enum_func_status ret = PASS; + MYSQLND_PACKET_COMMAND * cmd_packet; + + DBG_ENTER("mysqlnd_conn_data::simple_command"); + DBG_INF_FMT("command=%s ok_packet=%u silent=%u", mysqlnd_command_to_text[command], ok_packet, silent); + + switch (CONN_GET_STATE(conn)) { + 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_FMT("Command out of sync. State=%u", CONN_GET_STATE(conn)); + DBG_RETURN(FAIL); + } + + /* clean UPSERT info */ + if (!ignore_upsert_status) { + memset(conn->upsert_status, 0, sizeof(*conn->upsert_status)); + } + SET_ERROR_AFF_ROWS(conn); + SET_EMPTY_ERROR(*conn->error_info); + + cmd_packet = conn->protocol->m.get_command_packet(conn->protocol, FALSE TSRMLS_CC); + if (!cmd_packet) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(FAIL); + } + + cmd_packet->command = command; + if (arg && arg_len) { + cmd_packet->argument = arg; + cmd_packet->arg_len = arg_len; + } + + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_COM_QUIT + command - 1 /* because of COM_SLEEP */ ); + + if (! PACKET_WRITE(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()); + } + CONN_SET_STATE(conn, CONN_QUIT_SENT); + DBG_ERR("Server is gone"); + ret = FAIL; + } else if (ok_packet != PROT_LAST) { + ret = conn->m->simple_command_handle_response(conn, ok_packet, silent, command, ignore_upsert_status TSRMLS_CC); + } + + PACKET_FREE(cmd_packet); + DBG_INF(ret == PASS ? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::set_server_option */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, set_server_option)(MYSQLND_CONN_DATA * const conn, enum_mysqlnd_server_option option TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, set_server_option); + zend_uchar buffer[2]; + enum_func_status ret = FAIL; + DBG_ENTER("mysqlnd_conn_data::set_server_option"); + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + + int2store(buffer, (unsigned int) option); + ret = conn->m->simple_command(conn, COM_SET_OPTION, buffer, sizeof(buffer), PROT_EOF_PACKET, FALSE, TRUE TSRMLS_CC); + + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::restart_psession */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, restart_psession)(MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn_data::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_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::end_psession */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, end_psession)(MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn_data::end_psession"); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_switch_to_ssl_if_needed */ +static enum_func_status +mysqlnd_switch_to_ssl_if_needed( + MYSQLND_CONN_DATA * conn, + const MYSQLND_PACKET_GREET * const greet_packet, + const MYSQLND_OPTIONS * const options, + unsigned long mysql_flags + TSRMLS_DC + ) +{ + enum_func_status ret = FAIL; + const MYSQLND_CHARSET * charset; + MYSQLND_PACKET_AUTH * auth_packet; + DBG_ENTER("mysqlnd_switch_to_ssl_if_needed"); + + auth_packet = conn->protocol->m.get_auth_packet(conn->protocol, FALSE TSRMLS_CC); + if (!auth_packet) { + SET_OOM_ERROR(*conn->error_info); + goto end; + } + auth_packet->client_flags = mysql_flags; + auth_packet->max_packet_size = MYSQLND_ASSEMBLED_PACKET_MAX_SIZE; + + if (options->charset_name && (charset = mysqlnd_find_charset_name(options->charset_name))) { + auth_packet->charset_no = charset->nr; + } else { +#if MYSQLND_UNICODE + auth_packet->charset_no = 200;/* utf8 - swedish collation, check mysqlnd_charset.c */ +#else + auth_packet->charset_no = greet_packet->charset_no; +#endif + } + +#ifdef MYSQLND_SSL_SUPPORTED + if ((greet_packet->server_capabilities & CLIENT_SSL) && (mysql_flags & CLIENT_SSL)) { + zend_bool verify = mysql_flags & CLIENT_SSL_VERIFY_SERVER_CERT? TRUE:FALSE; + DBG_INF("Switching to SSL"); + if (!PACKET_WRITE(auth_packet, conn)) { + CONN_SET_STATE(conn, CONN_QUIT_SENT); + SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); + goto end; + } + + conn->net->m.set_client_option(conn->net, MYSQL_OPT_SSL_VERIFY_SERVER_CERT, (const char *) &verify TSRMLS_CC); + + if (FAIL == conn->net->m.enable_ssl(conn->net TSRMLS_CC)) { + goto end; + } + } +#endif + ret = PASS; +end: + PACKET_FREE(auth_packet); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_connect_run_authentication */ +static enum_func_status +mysqlnd_connect_run_authentication( + MYSQLND_CONN_DATA * conn, + const char * const user, + const char * const passwd, + const char * const db, + size_t db_len, + size_t passwd_len, + const MYSQLND_PACKET_GREET * const greet_packet, + const MYSQLND_OPTIONS * const options, + unsigned long mysql_flags + TSRMLS_DC) +{ + enum_func_status ret = FAIL; + DBG_ENTER("mysqlnd_connect_run_authentication"); + + ret = mysqlnd_switch_to_ssl_if_needed(conn, greet_packet, options, mysql_flags TSRMLS_CC); + if (PASS == ret) { + zend_bool first_call = TRUE; + + char * switch_to_auth_protocol = NULL; + size_t switch_to_auth_protocol_len = 0; + char * requested_protocol = NULL; + zend_uchar * plugin_data; + size_t plugin_data_len; + + plugin_data_len = greet_packet->auth_plugin_data_len; + plugin_data = mnd_emalloc(plugin_data_len + 1); + if (!plugin_data) { + ret = FAIL; + goto end; + } + memcpy(plugin_data, greet_packet->auth_plugin_data, plugin_data_len); + plugin_data[plugin_data_len] = '\0'; + + requested_protocol = mnd_pestrdup(greet_packet->auth_protocol? greet_packet->auth_protocol: "mysql_native_password", FALSE); + if (!requested_protocol) { + ret = FAIL; + goto end; + } + + do { + struct st_mysqlnd_authentication_plugin * auth_plugin; + { + char * plugin_name = NULL; + + mnd_sprintf(&plugin_name, 0, "auth_plugin_%s", requested_protocol); + + DBG_INF_FMT("looking for %s auth plugin", plugin_name); + auth_plugin = mysqlnd_plugin_find(plugin_name); + mnd_sprintf_free(plugin_name); + + if (!auth_plugin) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "The server requested authentication method unknown to the client [%s]", requested_protocol); + SET_CLIENT_ERROR(*conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "The server requested authentication method umknown to the client"); + break; + } + } + DBG_INF("plugin found"); + + { + zend_uchar * switch_to_auth_protocol_data = NULL; + size_t switch_to_auth_protocol_data_len = 0; + zend_uchar * scrambled_data = NULL; + size_t scrambled_data_len = 0; + + switch_to_auth_protocol = NULL; + switch_to_auth_protocol_len = 0; + + if (conn->auth_plugin_data) { + mnd_pefree(conn->auth_plugin_data, conn->persistent); + conn->auth_plugin_data = NULL; + } + conn->auth_plugin_data_len = plugin_data_len; + conn->auth_plugin_data = mnd_pemalloc(conn->auth_plugin_data_len, conn->persistent); + if (!conn->auth_plugin_data) { + SET_OOM_ERROR(*conn->error_info); + goto end; + } + memcpy(conn->auth_plugin_data, plugin_data, plugin_data_len); + + DBG_INF_FMT("salt=[%*s]", plugin_data_len - 1, plugin_data); + /* The data should be allocated with malloc() */ + scrambled_data = + auth_plugin->methods.get_auth_data(NULL, &scrambled_data_len, conn, user, passwd, passwd_len, + plugin_data, plugin_data_len, options, mysql_flags TSRMLS_CC); + + + ret = mysqlnd_auth_handshake(conn, user, passwd, passwd_len, db, db_len, options, mysql_flags, + greet_packet->charset_no, + first_call, + requested_protocol, + scrambled_data, scrambled_data_len, + &switch_to_auth_protocol, &switch_to_auth_protocol_len, + &switch_to_auth_protocol_data, &switch_to_auth_protocol_data_len + TSRMLS_CC); + first_call = FALSE; + free(scrambled_data); + + DBG_INF_FMT("switch_to_auth_protocol=%s", switch_to_auth_protocol? switch_to_auth_protocol:"n/a"); + if (requested_protocol && switch_to_auth_protocol) { + mnd_efree(requested_protocol); + requested_protocol = switch_to_auth_protocol; + } + + if (plugin_data) { + mnd_efree(plugin_data); + } + plugin_data_len = switch_to_auth_protocol_data_len; + plugin_data = switch_to_auth_protocol_data; + } + DBG_INF_FMT("conn->error_info->error_no = %d", conn->error_info->error_no); + } while (ret == FAIL && conn->error_info->error_no == 0 && switch_to_auth_protocol != NULL); + if (plugin_data) { + mnd_efree(plugin_data); + } + + if (ret == PASS) { + DBG_INF_FMT("saving requested_protocol=%s", requested_protocol); + conn->m->set_client_option(conn, MYSQLND_OPT_AUTH_PROTOCOL, requested_protocol TSRMLS_CC); + } + + if (requested_protocol) { + mnd_efree(requested_protocol); + } + } +end: + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::connect */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, connect)(MYSQLND_CONN_DATA * conn, + const char *host, const char *user, + const char *passwd, unsigned int passwd_len, + const char *db, unsigned int db_len, + unsigned int port, + const char *socket_or_pipe, + unsigned int mysql_flags + TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, connect); + size_t host_len; + zend_bool unix_socket = FALSE; + zend_bool named_pipe = FALSE; + zend_bool reconnect = FALSE; + zend_bool saved_compression = FALSE; + zend_bool local_tx_started = FALSE; + + MYSQLND_PACKET_GREET * greet_packet = NULL; + + DBG_ENTER("mysqlnd_conn_data::connect"); + + if (PASS != conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + goto err; + } + local_tx_started = TRUE; + + SET_EMPTY_ERROR(*conn->error_info); + SET_ERROR_AFF_ROWS(conn); + + DBG_INF_FMT("host=%s user=%s db=%s port=%u flags=%u persistent=%u state=%u", + host?host:"", user?user:"", db?db:"", port, mysql_flags, + conn? conn->persistent:0, conn? CONN_GET_STATE(conn):-1); + + if (CONN_GET_STATE(conn) > CONN_ALLOCED && CONN_GET_STATE(conn) ) { + DBG_INF("Connecting on a connected handle."); + + if (CONN_GET_STATE(conn) < CONN_QUIT_SENT) { + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_CLOSE_IMPLICIT); + reconnect = TRUE; + conn->m->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 (conn->net->compressed) { + /* + we need to save the state. As we will re-connect, net->compressed should be off, or + we will look for a compression header as part of the greet message, but there will + be none. + */ + saved_compression = TRUE; + conn->net->compressed = FALSE; + } + } else { + unsigned int max_allowed_size = MYSQLND_ASSEMBLED_PACKET_MAX_SIZE; + conn->m->set_client_option(conn, MYSQLND_OPT_MAX_ALLOWED_PACKET, (char *)&max_allowed_size TSRMLS_CC); + } + + if (!host || !host[0]) { + host = "localhost"; + } + if (!user) { + DBG_INF_FMT("no user given, using empty string"); + user = ""; + } + if (!passwd) { + DBG_INF_FMT("no password given, using empty string"); + passwd = ""; + passwd_len = 0; + } + if (!db) { + DBG_INF_FMT("no db given, using empty string"); + db = ""; + db_len = 0; + } + + host_len = strlen(host); + { + char * transport = NULL; + int transport_len; +#ifndef PHP_WIN32 + if (host_len == sizeof("localhost") - 1 && !strncasecmp(host, "localhost", host_len)) { + DBG_INF_FMT("socket=%s", socket_or_pipe? socket_or_pipe:"n/a"); + if (!socket_or_pipe) { + socket_or_pipe = "/tmp/mysql.sock"; + } + transport_len = mnd_sprintf(&transport, 0, "unix://%s", socket_or_pipe); + unix_socket = TRUE; +#else + if (host_len == sizeof(".") - 1 && host[0] == '.') { + /* named pipe in socket */ + if (!socket_or_pipe) { + socket_or_pipe = "\\\\.\\pipe\\MySQL"; + } + transport_len = mnd_sprintf(&transport, 0, "pipe://%s", socket_or_pipe); + named_pipe = TRUE; +#endif + } else { + if (!port) { + port = 3306; + } + transport_len = mnd_sprintf(&transport, 0, "tcp://%s:%u", host, port); + } + if (!transport) { + SET_OOM_ERROR(*conn->error_info); + goto err; /* OOM */ + } + DBG_INF_FMT("transport=%s conn->scheme=%s", transport, conn->scheme); + conn->scheme = mnd_pestrndup(transport, transport_len, conn->persistent); + conn->scheme_len = transport_len; + mnd_sprintf_free(transport); + transport = NULL; + if (!conn->scheme) { + goto err; /* OOM */ + } + } + + greet_packet = conn->protocol->m.get_greet_packet(conn->protocol, FALSE TSRMLS_CC); + if (!greet_packet) { + SET_OOM_ERROR(*conn->error_info); + goto err; /* OOM */ + } + + if (FAIL == conn->net->m.connect_ex(conn->net, conn->scheme, conn->scheme_len, conn->persistent, + conn->stats, conn->error_info TSRMLS_CC)) + { + goto err; + } + + DBG_INF_FMT("stream=%p", conn->net->stream); + + if (FAIL == PACKET_READ(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=%u 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 = mnd_pestrdup(greet_packet->server_version, conn->persistent); + + conn->greet_charset = mysqlnd_find_charset_nr(greet_packet->charset_no); + if (!conn->greet_charset) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Server sent charset (%d) unknown to the client. Please, report to the developers", greet_packet->charset_no); + SET_CLIENT_ERROR(*conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, + "Server sent charset unknown to the client. Please, report to the developers"); + goto err; + } + /* we allow load data local infile by default */ + mysql_flags |= MYSQLND_CAPABILITIES; + + mysql_flags |= conn->options->flags; /* use the flags from set_client_option() */ + + if (db) { + mysql_flags |= CLIENT_CONNECT_WITH_DB; + } + + if (PG(open_basedir) && strlen(PG(open_basedir))) { + mysql_flags ^= CLIENT_LOCAL_FILES; + } + +#ifndef MYSQLND_COMPRESSION_ENABLED + if (mysql_flags & CLIENT_COMPRESS) { + mysql_flags &= ~CLIENT_COMPRESS; + } +#else + if (conn->net->options.flags & MYSQLND_NET_FLAG_USE_COMPRESSION) { + mysql_flags |= CLIENT_COMPRESS; + } +#endif +#ifndef MYSQLND_SSL_SUPPORTED + if (mysql_flags & CLIENT_SSL) { + mysql_flags &= ~CLIENT_SSL; + } +#else + if (conn->net->options.ssl_key || conn->net->options.ssl_cert || + conn->net->options.ssl_ca || conn->net->options.ssl_capath || conn->net->options.ssl_cipher) + { + mysql_flags |= CLIENT_SSL; + } +#endif + + if (FAIL == mysqlnd_connect_run_authentication(conn, user, passwd, db, db_len, (size_t) passwd_len, + greet_packet, conn->options, mysql_flags TSRMLS_CC)) + { + goto err; + } + + { + CONN_SET_STATE(conn, CONN_READY); + + if (saved_compression) { + conn->net->compressed = TRUE; + } + /* + If a connect on a existing handle is performed and mysql_flags is + passed which doesn't CLIENT_COMPRESS, then we need to overwrite the value + which we set based on saved_compression. + */ + conn->net->compressed = mysql_flags & CLIENT_COMPRESS? TRUE:FALSE; + + conn->user = mnd_pestrdup(user, conn->persistent); + conn->user_len = strlen(conn->user); + conn->passwd = mnd_pestrndup(passwd, passwd_len, conn->persistent); + conn->passwd_len = passwd_len; + conn->port = port; + conn->connect_or_select_db = mnd_pestrndup(db, db_len, conn->persistent); + conn->connect_or_select_db_len = db_len; + + if (!conn->user || !conn->passwd || !conn->connect_or_select_db) { + SET_OOM_ERROR(*conn->error_info); + goto err; /* OOM */ + } + + if (!unix_socket && !named_pipe) { + conn->host = mnd_pestrdup(host, conn->persistent); + if (!conn->host) { + SET_OOM_ERROR(*conn->error_info); + goto err; /* OOM */ + } + conn->host_len = strlen(conn->host); + { + char *p; + mnd_sprintf(&p, 0, "%s via TCP/IP", conn->host); + if (!p) { + SET_OOM_ERROR(*conn->error_info); + goto err; /* OOM */ + } + conn->host_info = mnd_pestrdup(p, conn->persistent); + mnd_sprintf_free(p); + if (!conn->host_info) { + SET_OOM_ERROR(*conn->error_info); + goto err; /* OOM */ + } + } + } else { + conn->unix_socket = mnd_pestrdup(socket_or_pipe, conn->persistent); + if (unix_socket) { + conn->host_info = mnd_pestrdup("Localhost via UNIX socket", conn->persistent); + } else if (named_pipe) { + char *p; + mnd_sprintf(&p, 0, "%s via named pipe", conn->unix_socket); + if (!p) { + SET_OOM_ERROR(*conn->error_info); + goto err; /* OOM */ + } + conn->host_info = mnd_pestrdup(p, conn->persistent); + mnd_sprintf_free(p); + if (!conn->host_info) { + SET_OOM_ERROR(*conn->error_info); + goto err; /* OOM */ + } + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Impossible. Should be either socket or a pipe. Report a bug!"); + } + if (!conn->unix_socket || !conn->host_info) { + SET_OOM_ERROR(*conn->error_info); + goto err; /* OOM */ + } + conn->unix_socket_len = strlen(conn->unix_socket); + } + conn->client_flag = mysql_flags; + conn->max_packet_size = MYSQLND_ASSEMBLED_PACKET_MAX_SIZE; + /* todo: check if charset is available */ + 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_EMPTY_ERROR(*conn->error_info); + + mysqlnd_local_infile_default(conn); + +#if MYSQLND_UNICODE + { + unsigned int 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 + if (conn->options->init_commands) { + unsigned int current_command = 0; + for (; current_command < conn->options->num_commands; ++current_command) { + const char * const command = conn->options->init_commands[current_command]; + if (command) { + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_INIT_COMMAND_EXECUTED_COUNT); + if (PASS != conn->m->query(conn, command, strlen(command) TSRMLS_CC)) { + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_INIT_COMMAND_FAILED_COUNT); + goto err; + } + if (conn->last_query_type == QUERY_SELECT) { + MYSQLND_RES * result = conn->m->use_result(conn TSRMLS_CC); + if (result) { + result->m.free_result(result, TRUE TSRMLS_CC); + } + } + } + } + } + + + MYSQLND_INC_CONN_STATISTIC_W_VALUE2(conn->stats, STAT_CONNECT_SUCCESS, 1, STAT_OPENED_CONNECTIONS, 1); + if (reconnect) { + MYSQLND_INC_GLOBAL_STATISTIC(STAT_RECONNECT); + } + if (conn->persistent) { + MYSQLND_INC_CONN_STATISTIC_W_VALUE2(conn->stats, STAT_PCONNECT_SUCCESS, 1, STAT_OPENED_PERSISTENT_CONNECTIONS, 1); + } + + DBG_INF_FMT("connection_id=%llu", conn->thread_id); + + PACKET_FREE(greet_packet); + + conn->m->local_tx_end(conn, this_func, PASS TSRMLS_CC); + DBG_RETURN(PASS); + } +err: + PACKET_FREE(greet_packet); + + DBG_ERR_FMT("[%u] %.128s (trying to connect via %s)", conn->error_info->error_no, conn->error_info->error, conn->scheme); + if (!conn->error_info->error_no) { + SET_CLIENT_ERROR(*conn->error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, conn->error_info->error? conn->error_info->error:"Unknown error"); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "[%u] %.128s (trying to connect via %s)", + conn->error_info->error_no, conn->error_info->error, conn->scheme); + } + + conn->m->free_contents(conn TSRMLS_CC); + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_CONNECT_FAILURE); + if (TRUE == local_tx_started) { + conn->m->local_tx_end(conn, this_func, FAIL TSRMLS_CC); + } + + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::connect */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, connect)(MYSQLND * conn_handle, + const char * host, const char * user, + const char * passwd, unsigned int passwd_len, + const char * db, unsigned int db_len, + unsigned int port, + const char * socket_or_pipe, + unsigned int mysql_flags + TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, connect); + enum_func_status ret = FAIL; + MYSQLND_CONN_DATA * conn = conn_handle->data; + + DBG_ENTER("mysqlnd_conn::connect"); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + ret = conn->m->connect(conn, host, user, passwd, passwd_len, db, db_len, port, socket_or_pipe, mysql_flags TSRMLS_CC); + + conn->m->local_tx_end(conn, this_func, FAIL TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + +/* {{{ mysqlnd_connect */ +PHPAPI MYSQLND * mysqlnd_connect(MYSQLND * conn_handle, + const char * host, const char * user, + const char * passwd, unsigned int passwd_len, + const char * db, unsigned int db_len, + unsigned int port, + const char * socket_or_pipe, + unsigned int mysql_flags + TSRMLS_DC) +{ + enum_func_status ret = FAIL; + zend_bool self_alloced = FALSE; + + DBG_ENTER("mysqlnd_connect"); + DBG_INF_FMT("host=%s user=%s db=%s port=%u flags=%u", host?host:"", user?user:"", db?db:"", port, mysql_flags); + + if (!conn_handle) { + self_alloced = TRUE; + if (!(conn_handle = mysqlnd_init(FALSE))) { + /* OOM */ + DBG_RETURN(NULL); + } + } + + ret = conn_handle->m->connect(conn_handle, host, user, passwd, passwd_len, db, db_len, port, socket_or_pipe, mysql_flags TSRMLS_CC); + + if (ret == FAIL) { + if (self_alloced) { + /* + We have alloced, thus there are no references to this + object - we are free to kill it! + */ + conn_handle->m->dtor(conn_handle TSRMLS_CC); + } + DBG_RETURN(NULL); + } + DBG_RETURN(conn_handle); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::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_data, query)(MYSQLND_CONN_DATA * conn, const char * query, unsigned int query_len TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, query); + enum_func_status ret = FAIL; + DBG_ENTER("mysqlnd_conn_data::query"); + DBG_INF_FMT("conn=%llu query=%s", conn->thread_id, query); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + if (PASS == conn->m->send_query(conn, query, query_len TSRMLS_CC) && + PASS == conn->m->reap_query(conn TSRMLS_CC)) + { + ret = PASS; + if (conn->last_query_type == QUERY_UPSERT && conn->upsert_status->affected_rows) { + MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, STAT_ROWS_AFFECTED_NORMAL, conn->upsert_status->affected_rows); + } + } + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::send_query */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, send_query)(MYSQLND_CONN_DATA * conn, const char * query, unsigned int query_len TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, send_query); + enum_func_status ret; + DBG_ENTER("mysqlnd_conn_data::send_query"); + DBG_INF_FMT("conn=%llu query=%s", conn->thread_id, query); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + ret = conn->m->simple_command(conn, COM_QUERY, (zend_uchar *) query, query_len, + PROT_LAST /* we will handle the OK packet*/, + FALSE, FALSE TSRMLS_CC); + if (PASS == ret) { + CONN_SET_STATE(conn, CONN_QUERY_SENT); + } + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::reap_query */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, reap_query)(MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, reap_query); + enum_mysqlnd_connection_state state = CONN_GET_STATE(conn); + enum_func_status ret = FAIL; + DBG_ENTER("mysqlnd_conn_data::reap_query"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + if (state <= CONN_READY || state == CONN_QUIT_SENT) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Connection not opened, clear or has been closed"); + DBG_ERR_FMT("Connection not opened, clear or has been closed. State=%u", state); + DBG_RETURN(ret); + } + ret = conn->m->query_read_result_set_header(conn, NULL TSRMLS_CC); + + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +#include "php_network.h" + +MYSQLND ** mysqlnd_stream_array_check_for_readiness(MYSQLND ** conn_array TSRMLS_DC) +{ + int cnt = 0; + MYSQLND **p = conn_array, **p_p; + MYSQLND **ret = NULL; + + while (*p) { + if (CONN_GET_STATE((*p)->data) <= CONN_READY || CONN_GET_STATE((*p)->data) == CONN_QUIT_SENT) { + cnt++; + } + p++; + } + if (cnt) { + MYSQLND **ret_p = ret = ecalloc(cnt + 1, sizeof(MYSQLND *)); + p_p = p = conn_array; + while (*p) { + if (CONN_GET_STATE((*p)->data) <= CONN_READY || CONN_GET_STATE((*p)->data) == CONN_QUIT_SENT) { + *ret_p = *p; + *p = NULL; + ret_p++; + } else { + *p_p = *p; + p_p++; + } + p++; + } + *ret_p = NULL; + } + return ret; +} + + +/* {{{ stream_select mysqlnd_stream_array_to_fd_set functions */ +static int mysqlnd_stream_array_to_fd_set(MYSQLND ** conn_array, fd_set * fds, php_socket_t * max_fd TSRMLS_DC) +{ + php_socket_t this_fd; + int cnt = 0; + MYSQLND **p = conn_array; + + while (*p) { + /* get the fd. + * NB: Most other code will NOT use the PHP_STREAM_CAST_INTERNAL flag + * when casting. It is only used here so that the buffered data warning + * is not displayed. + * */ + if (SUCCESS == php_stream_cast((*p)->data->net->stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, + (void*)&this_fd, 1) && this_fd >= 0) { + + PHP_SAFE_FD_SET(this_fd, fds); + + if (this_fd > *max_fd) { + *max_fd = this_fd; + } + cnt++; + } + p++; + } + return cnt ? 1 : 0; +} + +static int mysqlnd_stream_array_from_fd_set(MYSQLND ** conn_array, fd_set * fds TSRMLS_DC) +{ + php_socket_t this_fd; + int ret = 0; + zend_bool disproportion = FALSE; + + + MYSQLND **fwd = conn_array, **bckwd = conn_array; + + while (*fwd) { + if (SUCCESS == php_stream_cast((*fwd)->data->net->stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, + (void*)&this_fd, 1) && this_fd >= 0) { + if (PHP_SAFE_FD_ISSET(this_fd, fds)) { + if (disproportion) { + *bckwd = *fwd; + } + bckwd++; + fwd++; + ret++; + continue; + } + } + disproportion = TRUE; + fwd++; + } + *bckwd = NULL;/* NULL-terminate the list */ + + return ret; +} +/* }}} */ + + +#ifndef PHP_WIN32 +#define php_select(m, r, w, e, t) select(m, r, w, e, t) +#else +#include "win32/select.h" +#endif + + +/* {{{ _mysqlnd_poll */ +PHPAPI enum_func_status +_mysqlnd_poll(MYSQLND **r_array, MYSQLND **e_array, MYSQLND ***dont_poll, long sec, long usec, uint * desc_num TSRMLS_DC) +{ + struct timeval tv; + struct timeval *tv_p = NULL; + fd_set rfds, wfds, efds; + php_socket_t max_fd = 0; + int retval, sets = 0; + int set_count, max_set_count = 0; + + DBG_ENTER("_mysqlnd_poll"); + if (sec < 0 || usec < 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Negative values passed for sec and/or usec"); + DBG_RETURN(FAIL); + } + + *dont_poll = mysqlnd_stream_array_check_for_readiness(r_array TSRMLS_CC); + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + + if (r_array != NULL) { + set_count = mysqlnd_stream_array_to_fd_set(r_array, &rfds, &max_fd TSRMLS_CC); + if (set_count > max_set_count) { + max_set_count = set_count; + } + sets += set_count; + } + + if (e_array != NULL) { + set_count = mysqlnd_stream_array_to_fd_set(e_array, &efds, &max_fd TSRMLS_CC); + if (set_count > max_set_count) { + max_set_count = set_count; + } + sets += set_count; + } + + if (!sets) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, *dont_poll ? "All arrays passed are clear":"No stream arrays were passed"); + DBG_ERR_FMT(*dont_poll ? "All arrays passed are clear":"No stream arrays were passed"); + DBG_RETURN(FAIL); + } + + PHP_SAFE_MAX_FD(max_fd, max_set_count); + + /* Solaris + BSD do not like microsecond values which are >= 1 sec */ + if (usec > 999999) { + tv.tv_sec = sec + (usec / 1000000); + tv.tv_usec = usec % 1000000; + } else { + tv.tv_sec = sec; + tv.tv_usec = usec; + } + + tv_p = &tv; + + retval = php_select(max_fd + 1, &rfds, &wfds, &efds, tv_p); + + if (retval == -1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "unable to select [%d]: %s (max_fd=%d)", + errno, strerror(errno), max_fd); + DBG_RETURN(FAIL); + } + + if (r_array != NULL) { + mysqlnd_stream_array_from_fd_set(r_array, &rfds TSRMLS_CC); + } + if (e_array != NULL) { + mysqlnd_stream_array_from_fd_set(e_array, &efds TSRMLS_CC); + } + + *desc_num = retval; + DBG_RETURN(PASS); +} +/* }}} */ + + +/* + 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_data::list_fields */ +MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_conn_data, list_fields)(MYSQLND_CONN_DATA * conn, const char *table, const char *achtung_wild TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, list_fields); + /* db + \0 + wild + \0 (for wild) */ + zend_uchar buff[MYSQLND_MAX_ALLOWED_DB_LEN * 2 + 1 + 1], *p; + size_t table_len, wild_len; + MYSQLND_RES * result = NULL; + DBG_ENTER("mysqlnd_conn_data::list_fields"); + DBG_INF_FMT("conn=%llu table=%s wild=%s", conn->thread_id, table? table:"",achtung_wild? achtung_wild:""); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + do { + p = buff; + if (table && (table_len = strlen(table))) { + size_t to_copy = MIN(table_len, MYSQLND_MAX_ALLOWED_DB_LEN); + memcpy(p, table, to_copy); + p += to_copy; + *p++ = '\0'; + } + + if (achtung_wild && (wild_len = strlen(achtung_wild))) { + size_t to_copy = MIN(wild_len, MYSQLND_MAX_ALLOWED_DB_LEN); + memcpy(p, achtung_wild, to_copy); + p += to_copy; + *p++ = '\0'; + } + + if (PASS != conn->m->simple_command(conn, COM_FIELD_LIST, buff, p - buff, + PROT_LAST /* we will handle the OK packet*/, + FALSE, TRUE TSRMLS_CC)) { + conn->m->local_tx_end(conn, 0, FAIL TSRMLS_CC); + break; + } + + /* + Prepare for the worst case. + MyISAM goes to 2500 BIT columns, double it for safety. + */ + result = conn->m->result_init(5000, conn->persistent TSRMLS_CC); + if (!result) { + break; + } + + 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); + result = NULL; + break; + } + + result->type = MYSQLND_RES_NORMAL; + result->m.fetch_row = result->m.fetch_row_normal_unbuffered; + result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED)); + if (!result->unbuf) { + /* OOM */ + SET_OOM_ERROR(*conn->error_info); + result->m.free_result(result, TRUE TSRMLS_CC); + result = NULL; + break; + } + result->unbuf->eof_reached = TRUE; + } while (0); + conn->m->local_tx_end(conn, this_func, result == NULL? FAIL:PASS TSRMLS_CC); + } + + DBG_RETURN(result); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::list_method */ +MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_conn_data, list_method)(MYSQLND_CONN_DATA * conn, const char * query, const char *achtung_wild, char *par1 TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, list_method); + char * show_query = NULL; + size_t show_query_len; + MYSQLND_RES * result = NULL; + + DBG_ENTER("mysqlnd_conn_data::list_method"); + DBG_INF_FMT("conn=%llu query=%s wild=%u", conn->thread_id, query, achtung_wild); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + if (par1) { + if (achtung_wild) { + show_query_len = mnd_sprintf(&show_query, 0, query, par1, achtung_wild); + } else { + show_query_len = mnd_sprintf(&show_query, 0, query, par1); + } + } else { + if (achtung_wild) { + show_query_len = mnd_sprintf(&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_sprintf_free(show_query); + } + conn->m->local_tx_end(conn, this_func, result == NULL? FAIL:PASS TSRMLS_CC); + } + DBG_RETURN(result); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::errno */ +static unsigned int +MYSQLND_METHOD(mysqlnd_conn_data, errno)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + return conn->error_info->error_no; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::error */ +static const char * +MYSQLND_METHOD(mysqlnd_conn_data, error)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + return conn->error_info->error; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::sqlstate */ +static const char * +MYSQLND_METHOD(mysqlnd_conn_data, sqlstate)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + 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, size_t 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_data::ssl_set */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, ssl_set)(MYSQLND_CONN_DATA * const conn, const char * key, const char * const cert, + const char * const ca, const char * const capath, const char * const cipher TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, ssl_set); + enum_func_status ret = FAIL; + DBG_ENTER("mysqlnd_conn_data::ssl_set"); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + ret = (PASS == conn->net->m.set_client_option(conn->net, MYSQLND_OPT_SSL_KEY, key TSRMLS_CC) && + PASS == conn->net->m.set_client_option(conn->net, MYSQLND_OPT_SSL_CERT, cert TSRMLS_CC) && + PASS == conn->net->m.set_client_option(conn->net, MYSQLND_OPT_SSL_CA, ca TSRMLS_CC) && + PASS == conn->net->m.set_client_option(conn->net, MYSQLND_OPT_SSL_CAPATH, capath TSRMLS_CC) && + PASS == conn->net->m.set_client_option(conn->net, MYSQLND_OPT_SSL_CIPHER, cipher TSRMLS_CC)) ? PASS : FAIL; + + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::escape_string */ +static ulong +MYSQLND_METHOD(mysqlnd_conn_data, escape_string)(MYSQLND_CONN_DATA * const conn, char * newstr, const char * escapestr, size_t escapestr_len TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, escape_string); + ulong ret; + DBG_ENTER("mysqlnd_conn_data::escape_string"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + if (conn->upsert_status->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES) { + ret = mysqlnd_cset_escape_quotes(conn->charset, newstr, escapestr, escapestr_len TSRMLS_CC); + } else { + ret = mysqlnd_cset_escape_slashes(conn->charset, newstr, escapestr, escapestr_len TSRMLS_CC); + } + conn->m->local_tx_end(conn, this_func, PASS TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::dump_debug_info */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, dump_debug_info)(MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, server_dump_debug_information); + enum_func_status ret = FAIL; + DBG_ENTER("mysqlnd_conn_data::dump_debug_info"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + ret = conn->m->simple_command(conn, COM_DEBUG, NULL, 0, PROT_EOF_PACKET, FALSE, TRUE TSRMLS_CC); + + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::select_db */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, select_db)(MYSQLND_CONN_DATA * const conn, const char * const db, unsigned int db_len TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, select_db); + enum_func_status ret = FAIL; + + DBG_ENTER("mysqlnd_conn_data::select_db"); + DBG_INF_FMT("conn=%llu db=%s", conn->thread_id, db); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + ret = conn->m->simple_command(conn, COM_INIT_DB, (zend_uchar*) db, db_len, PROT_OK_PACKET, FALSE, 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); + if (ret == PASS) { + if (conn->connect_or_select_db) { + mnd_pefree(conn->connect_or_select_db, conn->persistent); + } + conn->connect_or_select_db = mnd_pestrndup(db, db_len, conn->persistent); + conn->connect_or_select_db_len = db_len; + if (!conn->connect_or_select_db) { + /* OOM */ + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + } + } + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::ping */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, ping)(MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, ping); + enum_func_status ret = FAIL; + + DBG_ENTER("mysqlnd_conn_data::ping"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + ret = conn->m->simple_command(conn, COM_PING, NULL, 0, PROT_OK_PACKET, TRUE, 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); + + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + DBG_INF_FMT("ret=%u", ret); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::statistic */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, statistic)(MYSQLND_CONN_DATA * conn, char **message, unsigned int * message_len TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, get_server_statistics); + enum_func_status ret = FAIL; + MYSQLND_PACKET_STATS * stats_header; + + DBG_ENTER("mysqlnd_conn_data::statistic"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + do { + ret = conn->m->simple_command(conn, COM_STATISTICS, NULL, 0, PROT_LAST, FALSE, TRUE TSRMLS_CC); + if (FAIL == ret) { + break; + } + stats_header = conn->protocol->m.get_stats_packet(conn->protocol, FALSE TSRMLS_CC); + if (!stats_header) { + SET_OOM_ERROR(*conn->error_info); + break; + } + + if (PASS == (ret = PACKET_READ(stats_header, conn))) { + /* will be freed by Zend, thus don't use the mnd_ allocator */ + *message = estrndup(stats_header->message, stats_header->message_len); + *message_len = stats_header->message_len; + DBG_INF(*message); + } + PACKET_FREE(stats_header); + } while (0); + + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::kill */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, kill)(MYSQLND_CONN_DATA * conn, unsigned int pid TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, kill_connection); + enum_func_status ret = FAIL; + zend_uchar buff[4]; + + DBG_ENTER("mysqlnd_conn_data::kill"); + DBG_INF_FMT("conn=%llu pid=%u", conn->thread_id, pid); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + int4store(buff, pid); + + /* If we kill ourselves don't expect OK packet, PROT_LAST will skip it */ + if (pid != conn->thread_id) { + ret = conn->m->simple_command(conn, COM_PROCESS_KILL, buff, 4, PROT_OK_PACKET, FALSE, 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); + } else if (PASS == (ret = conn->m->simple_command(conn, COM_PROCESS_KILL, buff, 4, PROT_LAST, FALSE, TRUE TSRMLS_CC))) { + CONN_SET_STATE(conn, CONN_QUIT_SENT); + } + + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::set_charset */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, set_charset)(MYSQLND_CONN_DATA * const conn, const char * const csname TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, set_charset); + enum_func_status ret = FAIL; + const MYSQLND_CHARSET * const charset = mysqlnd_find_charset_name(csname); + + DBG_ENTER("mysqlnd_conn_data::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(ret); + } + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + char * query; + size_t query_len = mnd_sprintf(&query, 0, "SET NAMES %s", csname); + + if (FAIL == (ret = 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_sprintf_free(query); + + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + + DBG_INF(ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::refresh */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, refresh)(MYSQLND_CONN_DATA * const conn, uint8_t options TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, refresh_server); + enum_func_status ret = FAIL; + zend_uchar bits[1]; + DBG_ENTER("mysqlnd_conn_data::refresh"); + DBG_INF_FMT("conn=%llu options=%lu", conn->thread_id, options); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + int1store(bits, options); + + ret = conn->m->simple_command(conn, COM_REFRESH, bits, 1, PROT_OK_PACKET, FALSE, TRUE TSRMLS_CC); + + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::shutdown */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, shutdown)(MYSQLND_CONN_DATA * const conn, uint8_t level TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, shutdown_server); + enum_func_status ret = FAIL; + zend_uchar bits[1]; + DBG_ENTER("mysqlnd_conn_data::shutdown"); + DBG_INF_FMT("conn=%llu level=%lu", conn->thread_id, level); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + int1store(bits, level); + + ret = conn->m->simple_command(conn, COM_SHUTDOWN, bits, 1, PROT_OK_PACKET, FALSE, TRUE TSRMLS_CC); + + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_send_close */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, send_close)(MYSQLND_CONN_DATA * const 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); + + if (CONN_GET_STATE(conn) >= CONN_READY) { + MYSQLND_DEC_CONN_STATISTIC(conn->stats, STAT_OPENED_CONNECTIONS); + if (conn->persistent) { + MYSQLND_DEC_CONN_STATISTIC(conn->stats, STAT_OPENED_PERSISTENT_CONNECTIONS); + } + } + switch (CONN_GET_STATE(conn)) { + case CONN_READY: + DBG_INF("Connection clean, sending COM_QUIT"); + if (conn->net->stream) { + ret = conn->m->simple_command(conn, COM_QUIT, NULL, 0, PROT_LAST, TRUE, 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_SET_STATE(conn, CONN_QUIT_SENT); + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::get_reference */ +static MYSQLND_CONN_DATA * +MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, get_reference)(MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn_data::get_reference"); + ++conn->refcount; + DBG_INF_FMT("conn=%llu new_refcount=%u", conn->thread_id, conn->refcount); + DBG_RETURN(conn); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::free_reference */ +static enum_func_status +MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, free_reference)(MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + enum_func_status ret = PASS; + DBG_ENTER("mysqlnd_conn_data::free_reference"); + DBG_INF_FMT("conn=%llu old_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 = conn->m->send_close(conn TSRMLS_CC); + conn->m->dtor(conn TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::get_state */ +static enum mysqlnd_connection_state +MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, get_state)(MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn_data::get_state"); + DBG_RETURN(conn->state); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::set_state */ +static void +MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, set_state)(MYSQLND_CONN_DATA * const conn, enum mysqlnd_connection_state new_state TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn_data::set_state"); + DBG_INF_FMT("New state=%u", new_state); + conn->state = new_state; + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::field_count */ +static unsigned int +MYSQLND_METHOD(mysqlnd_conn_data, field_count)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + return conn->field_count; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::server_status */ +static unsigned int +MYSQLND_METHOD(mysqlnd_conn_data, server_status)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + return conn->upsert_status->server_status; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::insert_id */ +static uint64_t +MYSQLND_METHOD(mysqlnd_conn_data, insert_id)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + return conn->upsert_status->last_insert_id; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::affected_rows */ +static uint64_t +MYSQLND_METHOD(mysqlnd_conn_data, affected_rows)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + return conn->upsert_status->affected_rows; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::warning_count */ +static unsigned int +MYSQLND_METHOD(mysqlnd_conn_data, warning_count)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + return conn->upsert_status->warning_count; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::info */ +static const char * +MYSQLND_METHOD(mysqlnd_conn_data, info)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + 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_data::get_server_info */ +static const char * +MYSQLND_METHOD(mysqlnd_conn_data, get_server_info)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + return conn->server_version; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::get_host_info */ +static const char * +MYSQLND_METHOD(mysqlnd_conn_data, get_host_info)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + return conn->host_info; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::get_proto_info */ +static unsigned int +MYSQLND_METHOD(mysqlnd_conn_data, get_proto_info)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + return conn->protocol_version; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::charset_name */ +static const char * +MYSQLND_METHOD(mysqlnd_conn_data, charset_name)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + return conn->charset->name; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::thread_id */ +static uint64_t +MYSQLND_METHOD(mysqlnd_conn_data, thread_id)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + return conn->thread_id; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::get_server_version */ +static unsigned long +MYSQLND_METHOD(mysqlnd_conn_data, get_server_version)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + 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_data::more_results */ +static zend_bool +MYSQLND_METHOD(mysqlnd_conn_data, more_results)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn_data::more_results"); + /* (conn->state == CONN_NEXT_RESULT_PENDING) too */ + DBG_RETURN(conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS? TRUE:FALSE); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::next_result */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, next_result)(MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, next_result); + enum_func_status ret = FAIL; + + DBG_ENTER("mysqlnd_conn_data::next_result"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + do { + if (CONN_GET_STATE(conn) != CONN_NEXT_RESULT_PENDING) { + break; + } + + SET_EMPTY_ERROR(*conn->error_info); + SET_ERROR_AFF_ROWS(conn); + /* + 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 = conn->m->query_read_result_set_header(conn, NULL TSRMLS_CC))) { + /* + There can be an error in the middle of a multi-statement, which will cancel the multi-statement. + So there are no more results and we should just return FALSE, error_no has been set + */ + if (!conn->error_info->error_no) { + DBG_ERR_FMT("Serious error. %s::%u", __FILE__, __LINE__); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Serious error. PID=%d", getpid()); + CONN_SET_STATE(conn, CONN_QUIT_SENT); + } else { + DBG_INF_FMT("Error from the server : (%u) %s", conn->error_info->error_no, conn->error_info->error); + } + break; + } + if (conn->last_query_type == QUERY_UPSERT && conn->upsert_status->affected_rows) { + MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, STAT_ROWS_AFFECTED_NORMAL, conn->upsert_status->affected_rows); + } + } while (0); + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + + 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_data::change_user */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, change_user)(MYSQLND_CONN_DATA * const conn, + const char * user, + const char * passwd, + const char * db, + zend_bool silent, + size_t passwd_len + TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, change_user); + /* + 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 + buffer overflows. + */ + enum_func_status ret = FAIL; + zend_bool local_tx_started = FALSE; + + DBG_ENTER("mysqlnd_conn_data::change_user"); + DBG_INF_FMT("conn=%llu user=%s passwd=%s db=%s silent=%u", + conn->thread_id, user?user:"", passwd?"***":"null", db?db:"", (silent == TRUE)?1:0 ); + + if (PASS != conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + goto end; + } + local_tx_started = TRUE; + + SET_EMPTY_ERROR(*conn->error_info); + SET_ERROR_AFF_ROWS(conn); + + if (!user) { + user = ""; + } + if (!passwd) { + passwd = ""; + } + if (!db) { + db = ""; + } + + { + zend_bool first_call = TRUE; + char * switch_to_auth_protocol = NULL; + size_t switch_to_auth_protocol_len = 0; + char * requested_protocol = NULL; + zend_uchar * plugin_data; + size_t plugin_data_len; + + plugin_data_len = conn->auth_plugin_data_len; + plugin_data = mnd_emalloc(plugin_data_len); + if (!plugin_data) { + ret = FAIL; + goto end; + } + memcpy(plugin_data, conn->auth_plugin_data, plugin_data_len); + + requested_protocol = mnd_pestrdup(conn->options->auth_protocol? conn->options->auth_protocol:"mysql_native_password", FALSE); + if (!requested_protocol) { + ret = FAIL; + goto end; + } + + do { + struct st_mysqlnd_authentication_plugin * auth_plugin; + { + char * plugin_name = NULL; + + mnd_sprintf(&plugin_name, 0, "auth_plugin_%s", requested_protocol); + + DBG_INF_FMT("looking for %s auth plugin", plugin_name); + auth_plugin = mysqlnd_plugin_find(plugin_name); + mnd_sprintf_free(plugin_name); + + if (!auth_plugin) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "The server requested authentication method unknown to the client [%s]", requested_protocol); + SET_CLIENT_ERROR(*conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "The server requested authentication method umknown to the client"); + break; + } + } + DBG_INF("plugin found"); + + { + zend_uchar * switch_to_auth_protocol_data = NULL; + size_t switch_to_auth_protocol_data_len = 0; + zend_uchar * scrambled_data = NULL; + size_t scrambled_data_len = 0; + + switch_to_auth_protocol = NULL; + switch_to_auth_protocol_len = 0; + + if (conn->auth_plugin_data) { + mnd_pefree(conn->auth_plugin_data, conn->persistent); + conn->auth_plugin_data = NULL; + } + conn->auth_plugin_data_len = plugin_data_len; + conn->auth_plugin_data = mnd_pemalloc(conn->auth_plugin_data_len, conn->persistent); + if (!conn->auth_plugin_data) { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + goto end; + } + memcpy(conn->auth_plugin_data, plugin_data, plugin_data_len); + + DBG_INF_FMT("salt=[%*.s]", plugin_data_len - 1, plugin_data); + + /* The data should be allocated with malloc() */ + scrambled_data = + auth_plugin->methods.get_auth_data(NULL, &scrambled_data_len, conn, user, passwd, passwd_len, + plugin_data, plugin_data_len, 0, conn->server_capabilities TSRMLS_CC); + + + ret = mysqlnd_auth_change_user(conn, user, strlen(user), passwd, passwd_len, db, strlen(db), silent, + first_call, + requested_protocol, + scrambled_data, scrambled_data_len, + &switch_to_auth_protocol, &switch_to_auth_protocol_len, + &switch_to_auth_protocol_data, &switch_to_auth_protocol_data_len + TSRMLS_CC); + + first_call = FALSE; + free(scrambled_data); + + DBG_INF_FMT("switch_to_auth_protocol=%s", switch_to_auth_protocol? switch_to_auth_protocol:"n/a"); + if (requested_protocol) { + mnd_efree(requested_protocol); + } + requested_protocol = switch_to_auth_protocol; + + if (plugin_data) { + mnd_efree(plugin_data); + } + plugin_data_len = switch_to_auth_protocol_data_len; + plugin_data = switch_to_auth_protocol_data; + } + DBG_INF_FMT("conn->error_info->error_no = %d", conn->error_info->error_no); + } while (ret == FAIL && conn->error_info->error_no == 0 && switch_to_auth_protocol != NULL); + if (plugin_data) { + mnd_efree(plugin_data); + } + if (ret == PASS) { + conn->m->set_client_option(conn, MYSQLND_OPT_AUTH_PROTOCOL, requested_protocol TSRMLS_CC); + } + if (requested_protocol) { + mnd_efree(requested_protocol); + } + } + /* + Here we should close all statements. Unbuffered queries should not be a + problem as we won't allow sending COM_CHANGE_USER. + */ +end: + if (TRUE == local_tx_started) { + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + DBG_INF(ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::set_client_option */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, set_client_option)(MYSQLND_CONN_DATA * const conn, + enum mysqlnd_option option, + const char * const value + TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, set_client_option); + enum_func_status ret = PASS; + DBG_ENTER("mysqlnd_conn_data::set_client_option"); + DBG_INF_FMT("conn=%llu option=%u", conn->thread_id, option); + + if (PASS != conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + goto end; + } + switch (option) { + case MYSQL_OPT_COMPRESS: +#ifdef WHEN_SUPPORTED_BY_MYSQLI + case MYSQL_OPT_READ_TIMEOUT: + case MYSQL_OPT_WRITE_TIMEOUT: +#endif + case MYSQLND_OPT_SSL_KEY: + case MYSQLND_OPT_SSL_CERT: + case MYSQLND_OPT_SSL_CA: + case MYSQLND_OPT_SSL_CAPATH: + case MYSQLND_OPT_SSL_CIPHER: + case MYSQL_OPT_SSL_VERIFY_SERVER_CERT: + case MYSQL_OPT_CONNECT_TIMEOUT: + case MYSQLND_OPT_NET_CMD_BUFFER_SIZE: + case MYSQLND_OPT_NET_READ_BUFFER_SIZE: + ret = conn->net->m.set_client_option(conn->net, option, value TSRMLS_CC); + break; +#if MYSQLND_UNICODE + case MYSQLND_OPT_NUMERIC_AND_DATETIME_AS_UNICODE: + conn->options->numeric_and_datetime_as_unicode = *(unsigned int*) value; + break; +#endif +#ifdef MYSQLND_STRING_TO_INT_CONVERSION + case MYSQLND_OPT_INT_AND_FLOAT_NATIVE: + conn->options->int_and_float_native = *(unsigned int*) value; + break; +#endif + case MYSQL_OPT_LOCAL_INFILE: + if (value && (*(unsigned int*) value) ? 1 : 0) { + conn->options->flags |= CLIENT_LOCAL_FILES; + } else { + conn->options->flags &= ~CLIENT_LOCAL_FILES; + } + break; + case MYSQL_INIT_COMMAND: + { + char ** new_init_commands; + char * new_command; + /* when num_commands is 0, then realloc will be effectively a malloc call, internally */ + /* Don't assign to conn->options->init_commands because in case of OOM we will lose the pointer and leak */ + new_init_commands = mnd_perealloc(conn->options->init_commands, sizeof(char *) * (conn->options->num_commands + 1), conn->persistent); + if (!new_init_commands) { + goto oom; + } + conn->options->init_commands = new_init_commands; + new_command = mnd_pestrdup(value, conn->persistent); + if (!new_command) { + goto oom; + } + conn->options->init_commands[conn->options->num_commands] = new_command; + ++conn->options->num_commands; + break; + } + 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: +#endif + /* currently not supported. Todo!! */ + break; + case MYSQL_SET_CHARSET_NAME: + { + char * new_charset_name; + if (!mysqlnd_find_charset_name(value)) { + SET_CLIENT_ERROR(*conn->error_info, CR_CANT_FIND_CHARSET, UNKNOWN_SQLSTATE, "Unknown character set"); + ret = FAIL; + break; + } + + new_charset_name = mnd_pestrdup(value, conn->persistent); + if (!new_charset_name) { + goto oom; + } + if (conn->options->charset_name) { + mnd_pefree(conn->options->charset_name, conn->persistent); + } + conn->options->charset_name = new_charset_name; + DBG_INF_FMT("charset=%s", conn->options->charset_name); + break; + } + case MYSQL_OPT_NAMED_PIPE: + conn->options->protocol = MYSQL_PROTOCOL_PIPE; + break; + case MYSQL_OPT_PROTOCOL: + if (*(unsigned int*) value < MYSQL_PROTOCOL_LAST) { + conn->options->protocol = *(unsigned int*) value; + } + break; +#ifdef WHEN_SUPPORTED_BY_MYSQLI + case MYSQL_SET_CHARSET_DIR: + case MYSQL_OPT_RECONNECT: + /* 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 + case MYSQLND_OPT_MAX_ALLOWED_PACKET: + if (*(unsigned int*) value > (1<<16)) { + conn->options->max_allowed_packet = *(unsigned int*) value; + } + break; + case MYSQLND_OPT_AUTH_PROTOCOL: + { + char * new_auth_protocol = value? mnd_pestrdup(value, conn->persistent) : NULL; + if (value && !new_auth_protocol) { + goto oom; + } + if (conn->options->auth_protocol) { + mnd_pefree(conn->options->auth_protocol, conn->persistent); + } + conn->options->auth_protocol = new_auth_protocol; + DBG_INF_FMT("auth_protocol=%s", conn->options->auth_protocol); + break; + } + case MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS: + if (value && (*(unsigned int*) value) ? 1 : 0) { + conn->options->flags |= CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS; + } else { + conn->options->flags &= ~CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS; + } + break; +#ifdef WHEN_SUPPORTED_BY_MYSQLI + case MYSQL_SHARED_MEMORY_BASE_NAME: + case MYSQL_OPT_USE_RESULT: + case MYSQL_SECURE_AUTH: + /* not sure, todo ? */ +#endif + default: + ret = FAIL; + } + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + DBG_RETURN(ret); +oom: + SET_OOM_ERROR(*conn->error_info); + conn->m->local_tx_end(conn, this_func, FAIL TSRMLS_CC); +end: + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::use_result */ +static MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_conn_data, use_result)(MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, use_result); + MYSQLND_RES * result = NULL; + + DBG_ENTER("mysqlnd_conn_data::use_result"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + do { + if (!conn->current_result) { + break; + } + + /* Nothing to store for UPSERT/LOAD DATA */ + if (conn->last_query_type != QUERY_SELECT || CONN_GET_STATE(conn) != 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"); + break; + } + + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_UNBUFFERED_SETS); + + conn->current_result->conn = conn->m->get_reference(conn TSRMLS_CC); + result = conn->current_result->m.use_result(conn->current_result, FALSE TSRMLS_CC); + + if (!result) { + conn->current_result->m.free_result(conn->current_result, TRUE TSRMLS_CC); + } + conn->current_result = NULL; + } while (0); + + conn->m->local_tx_end(conn, this_func, result == NULL? FAIL:PASS TSRMLS_CC); + } + + DBG_RETURN(result); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::store_result */ +static MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_conn_data, store_result)(MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, store_result); + MYSQLND_RES * result = NULL; + + DBG_ENTER("mysqlnd_conn_data::store_result"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + do { + if (!conn->current_result) { + break; + } + + /* Nothing to store for UPSERT/LOAD DATA*/ + if (conn->last_query_type != QUERY_SELECT || CONN_GET_STATE(conn) != 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"); + break; + } + + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_BUFFERED_SETS); + + result = conn->current_result->m.store_result(conn->current_result, conn, FALSE TSRMLS_CC); + if (!result) { + conn->current_result->m.free_result(conn->current_result, TRUE TSRMLS_CC); + } + conn->current_result = NULL; + } while (0); + + conn->m->local_tx_end(conn, this_func, result == NULL? FAIL:PASS TSRMLS_CC); + } + DBG_RETURN(result); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::get_connection_stats */ +static void +MYSQLND_METHOD(mysqlnd_conn_data, get_connection_stats)(const MYSQLND_CONN_DATA * const conn, + zval * return_value TSRMLS_DC ZEND_FILE_LINE_DC) +{ + DBG_ENTER("mysqlnd_conn_data::get_connection_stats"); + mysqlnd_fill_stats_hash(conn->stats, mysqlnd_stats_values_names, return_value TSRMLS_CC ZEND_FILE_LINE_CC); + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::set_autocommit */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, set_autocommit)(MYSQLND_CONN_DATA * conn, unsigned int mode TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, set_autocommit); + enum_func_status ret = FAIL; + DBG_ENTER("mysqlnd_conn_data::set_autocommit"); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + ret = conn->m->query(conn, (mode) ? "SET AUTOCOMMIT=1":"SET AUTOCOMMIT=0", sizeof("SET AUTOCOMMIT=1") - 1 TSRMLS_CC); + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::tx_commit */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, tx_commit)(MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, tx_commit); + enum_func_status ret = FAIL; + DBG_ENTER("mysqlnd_conn_data::tx_commit"); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + ret = conn->m->query(conn, "COMMIT", sizeof("COMMIT") - 1 TSRMLS_CC); + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::tx_rollback */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, tx_rollback)(MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_data_methods, tx_rollback); + enum_func_status ret = FAIL; + DBG_ENTER("mysqlnd_conn_data::tx_rollback"); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + ret = conn->m->query(conn, "ROLLBACK", sizeof("ROLLBACK") - 1 TSRMLS_CC); + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::local_tx_start */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, local_tx_start)(MYSQLND_CONN_DATA * conn, size_t this_func TSRMLS_DC) +{ + enum_func_status ret = PASS; + DBG_ENTER("mysqlnd_conn_data::local_tx_start"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::local_tx_end */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, local_tx_end)(MYSQLND_CONN_DATA * conn, size_t this_func, enum_func_status status TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn_data::local_tx_end"); + DBG_RETURN(status); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::init */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn_data, init)(MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn_data::init"); + mysqlnd_stats_init(&conn->stats, STAT_LAST); + SET_ERROR_AFF_ROWS(conn); + + conn->net = mysqlnd_net_init(conn->persistent, conn->stats, conn->error_info TSRMLS_CC); + conn->protocol = mysqlnd_protocol_init(conn->persistent TSRMLS_CC); + + DBG_RETURN(conn->stats && conn->net && conn->protocol? PASS:FAIL); +} +/* }}} */ + + +MYSQLND_STMT * _mysqlnd_stmt_init(MYSQLND_CONN_DATA * const conn TSRMLS_DC); + + +MYSQLND_CLASS_METHODS_START(mysqlnd_conn_data) + MYSQLND_METHOD(mysqlnd_conn_data, init), + MYSQLND_METHOD(mysqlnd_conn_data, connect), + + MYSQLND_METHOD(mysqlnd_conn_data, escape_string), + MYSQLND_METHOD(mysqlnd_conn_data, set_charset), + MYSQLND_METHOD(mysqlnd_conn_data, query), + MYSQLND_METHOD(mysqlnd_conn_data, send_query), + MYSQLND_METHOD(mysqlnd_conn_data, reap_query), + MYSQLND_METHOD(mysqlnd_conn_data, use_result), + MYSQLND_METHOD(mysqlnd_conn_data, store_result), + MYSQLND_METHOD(mysqlnd_conn_data, next_result), + MYSQLND_METHOD(mysqlnd_conn_data, more_results), + + _mysqlnd_stmt_init, + + MYSQLND_METHOD(mysqlnd_conn_data, shutdown), + MYSQLND_METHOD(mysqlnd_conn_data, refresh), + + MYSQLND_METHOD(mysqlnd_conn_data, ping), + MYSQLND_METHOD(mysqlnd_conn_data, kill), + MYSQLND_METHOD(mysqlnd_conn_data, select_db), + MYSQLND_METHOD(mysqlnd_conn_data, dump_debug_info), + MYSQLND_METHOD(mysqlnd_conn_data, change_user), + + MYSQLND_METHOD(mysqlnd_conn_data, errno), + MYSQLND_METHOD(mysqlnd_conn_data, error), + MYSQLND_METHOD(mysqlnd_conn_data, sqlstate), + MYSQLND_METHOD(mysqlnd_conn_data, thread_id), + + MYSQLND_METHOD(mysqlnd_conn_data, get_connection_stats), + + MYSQLND_METHOD(mysqlnd_conn_data, get_server_version), + MYSQLND_METHOD(mysqlnd_conn_data, get_server_info), + MYSQLND_METHOD(mysqlnd_conn_data, statistic), + MYSQLND_METHOD(mysqlnd_conn_data, get_host_info), + MYSQLND_METHOD(mysqlnd_conn_data, get_proto_info), + MYSQLND_METHOD(mysqlnd_conn_data, info), + MYSQLND_METHOD(mysqlnd_conn_data, charset_name), + MYSQLND_METHOD(mysqlnd_conn_data, list_fields), + MYSQLND_METHOD(mysqlnd_conn_data, list_method), + + MYSQLND_METHOD(mysqlnd_conn_data, insert_id), + MYSQLND_METHOD(mysqlnd_conn_data, affected_rows), + MYSQLND_METHOD(mysqlnd_conn_data, warning_count), + MYSQLND_METHOD(mysqlnd_conn_data, field_count), + + MYSQLND_METHOD(mysqlnd_conn_data, server_status), + + MYSQLND_METHOD(mysqlnd_conn_data, set_server_option), + MYSQLND_METHOD(mysqlnd_conn_data, set_client_option), + MYSQLND_METHOD(mysqlnd_conn_data, free_contents), + MYSQLND_METHOD(mysqlnd_conn_data, free_options), + + MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, dtor), + + mysqlnd_query_read_result_set_header, + + MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, get_reference), + MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, free_reference), + MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, get_state), + MYSQLND_METHOD_PRIVATE(mysqlnd_conn_data, set_state), + + MYSQLND_METHOD(mysqlnd_conn_data, simple_command), + MYSQLND_METHOD(mysqlnd_conn_data, simple_command_handle_response), + MYSQLND_METHOD(mysqlnd_conn_data, restart_psession), + MYSQLND_METHOD(mysqlnd_conn_data, end_psession), + MYSQLND_METHOD(mysqlnd_conn_data, send_close), + + MYSQLND_METHOD(mysqlnd_conn_data, ssl_set), + mysqlnd_result_init, + MYSQLND_METHOD(mysqlnd_conn_data, set_autocommit), + MYSQLND_METHOD(mysqlnd_conn_data, tx_commit), + MYSQLND_METHOD(mysqlnd_conn_data, tx_rollback), + MYSQLND_METHOD(mysqlnd_conn_data, local_tx_start), + MYSQLND_METHOD(mysqlnd_conn_data, local_tx_end) +MYSQLND_CLASS_METHODS_END; + + +/* {{{ mysqlnd_conn::get_reference */ +static MYSQLND * +MYSQLND_METHOD(mysqlnd_conn, clone_object)(MYSQLND * const conn TSRMLS_DC) +{ + MYSQLND * ret; + DBG_ENTER("mysqlnd_conn::get_reference"); + ret = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).clone_connection_object(conn TSRMLS_CC); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::dtor */ +static void +MYSQLND_METHOD_PRIVATE(mysqlnd_conn, dtor)(MYSQLND * conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn::dtor"); + DBG_INF_FMT("conn=%llu", conn->data->thread_id); + + conn->data->m->free_reference(conn->data TSRMLS_CC); + + mnd_pefree(conn, conn->persistent); + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::close */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, close)(MYSQLND * conn_handle, enum_connection_close_type close_type TSRMLS_DC) +{ + size_t this_func = STRUCT_OFFSET(struct st_mysqlnd_conn_methods, close); + MYSQLND_CONN_DATA * conn = conn_handle->data; + enum_func_status ret = FAIL; + + DBG_ENTER("mysqlnd_conn::close"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { + if (CONN_GET_STATE(conn) >= CONN_READY) { + static enum_mysqlnd_collected_stats close_type_to_stat_map[MYSQLND_CLOSE_LAST] = { + STAT_CLOSE_EXPLICIT, + STAT_CLOSE_IMPLICIT, + STAT_CLOSE_DISCONNECT + }; + MYSQLND_INC_CONN_STATISTIC(conn->stats, close_type_to_stat_map[close_type]); + } + + /* + Close now, free_reference will try, + if we are last, but that's not a problem. + */ + ret = conn->m->send_close(conn TSRMLS_CC); + + /* do it after free_reference/dtor and we might crash */ + conn->m->local_tx_end(conn, this_func, ret TSRMLS_CC); + + conn_handle->m->dtor(conn_handle TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +MYSQLND_CLASS_METHODS_START(mysqlnd_conn) + MYSQLND_METHOD(mysqlnd_conn, connect), + MYSQLND_METHOD(mysqlnd_conn, clone_object), + MYSQLND_METHOD_PRIVATE(mysqlnd_conn, dtor), + MYSQLND_METHOD(mysqlnd_conn, close) +MYSQLND_CLASS_METHODS_END; + + +/* {{{ _mysqlnd_init */ +PHPAPI MYSQLND * +_mysqlnd_init(zend_bool persistent TSRMLS_DC) +{ + MYSQLND * ret; + DBG_ENTER("mysqlnd_init"); + ret = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_connection(persistent TSRMLS_CC); + DBG_RETURN(ret); +} +/* }}} */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd.h b/ext/mysqlnd/mysqlnd.h new file mode 100644 index 0000000..494fbcb --- /dev/null +++ b/ext/mysqlnd/mysqlnd.h @@ -0,0 +1,301 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ +/* $Id: e707c415db32080b3752b232487a435ee0372157 $ */ + +#ifndef MYSQLND_H +#define MYSQLND_H + +#define MYSQLND_VERSION "mysqlnd 5.0.10 - 20111026 - $Id: e707c415db32080b3752b232487a435ee0372157 $" +#define MYSQLND_VERSION_ID 50010 + +#define MYSQLND_PLUGIN_API_VERSION 1 + +#define MYSQLND_STRING_TO_INT_CONVERSION +/* + This force mysqlnd to do a single (or more depending on ammount of data) + non-blocking read() calls before sending a command to the server. Useful + for debugging, if previous function hasn't consumed all the output sent + to it - like stmt_send_long_data() error because the data was larger that + max_allowed_packet_size, and COM_STMT_SEND_LONG_DATA by protocol doesn't + use response packets, thus letting the next command to fail miserably, if + the connector implementor is not aware of this deficiency. Should be off + on production systems, if of course measured performance degradation is not + minimal. +*/ +#if A0 && PHP_DEBUG +#define MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND 1 +#endif + +#if PHP_DEBUG +#define MYSQLND_DBG_ENABLED 1 +#else +#define MYSQLND_DBG_ENABLED 0 +#endif + +#if defined(MYSQLND_COMPRESSION_WANTED) && defined(HAVE_ZLIB) +#define MYSQLND_COMPRESSION_ENABLED 1 +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#include "mysqlnd_portability.h" +#include "mysqlnd_enum_n_def.h" +#include "mysqlnd_structs.h" + + +/* Library related */ +PHPAPI void mysqlnd_library_init(TSRMLS_D); +PHPAPI void mysqlnd_library_end(TSRMLS_D); + +PHPAPI unsigned int mysqlnd_plugin_register(); +PHPAPI unsigned int mysqlnd_plugin_register_ex(struct st_mysqlnd_plugin_header * plugin TSRMLS_DC); +PHPAPI unsigned int mysqlnd_plugin_count(); +PHPAPI void * _mysqlnd_plugin_find(const char * const name TSRMLS_DC); +#define mysqlnd_plugin_find(name) _mysqlnd_plugin_find((name) TSRMLS_CC); + +PHPAPI void _mysqlnd_plugin_apply_with_argument(apply_func_arg_t apply_func, void * argument TSRMLS_DC); +#define mysqlnd_plugin_apply_with_argument(func, argument) _mysqlnd_plugin_apply_with_argument((func), (argument) TSRMLS_CC); + +#define mysqlnd_restart_psession(conn) ((conn)->data)->m->restart_psession((conn)->data TSRMLS_CC) +#define mysqlnd_end_psession(conn) ((conn)->data)->m->end_psession((conn)->data TSRMLS_CC) +PHPAPI void mysqlnd_minfo_print_hash(zval *values); +#define mysqlnd_thread_safe() TRUE + +PHPAPI const MYSQLND_CHARSET * mysqlnd_find_charset_nr(unsigned int charsetno); +PHPAPI const MYSQLND_CHARSET * mysqlnd_find_charset_name(const char * const charsetname); + + +/* Connect */ +#define mysqlnd_init(persistent) _mysqlnd_init((persistent) TSRMLS_CC) +PHPAPI MYSQLND * _mysqlnd_init(zend_bool persistent TSRMLS_DC); +PHPAPI MYSQLND * mysqlnd_connect(MYSQLND * conn, + const char * host, const char * user, + const char * passwd, unsigned int passwd_len, + const char * db, unsigned int db_len, + unsigned int port, + const char * socket_or_pipe, + unsigned int mysql_flags + TSRMLS_DC); + +#define mysqlnd_change_user(conn, user, passwd, db, silent) ((conn)->data)->m->change_user((conn)->data, (user), (passwd), (db), (silent), strlen((passwd)) TSRMLS_CC) +#define mysqlnd_change_user_ex(conn, user, passwd, db, silent, passwd_len) ((conn)->data)->m->change_user((conn)->data, (user), (passwd), (db), (silent), (passwd_len) TSRMLS_CC) + +#define mysqlnd_debug(x) _mysqlnd_debug((x) TSRMLS_CC) +PHPAPI void _mysqlnd_debug(const char *mode TSRMLS_DC); + +/* Query */ +#define mysqlnd_fetch_into(result, flags, ret_val, ext) (result)->m.fetch_into((result), (flags), (ret_val), (ext) TSRMLS_CC ZEND_FILE_LINE_CC) +#define mysqlnd_fetch_row_c(result) (result)->m.fetch_row_c((result) TSRMLS_CC) +#define mysqlnd_fetch_all(result, flags, return_value) (result)->m.fetch_all((result), (flags), (return_value) TSRMLS_CC ZEND_FILE_LINE_CC) +#define mysqlnd_result_fetch_field_data(res,offset,ret) (res)->m.fetch_field_data((res), (offset), (ret) TSRMLS_CC) +#define mysqlnd_get_connection_stats(conn, values) ((conn)->data)->m->get_statistics((conn)->data, (values) TSRMLS_CC ZEND_FILE_LINE_CC) +#define mysqlnd_get_client_stats(values) _mysqlnd_get_client_stats((values) TSRMLS_CC ZEND_FILE_LINE_CC) + +#define mysqlnd_close(conn,is_forced) (conn)->m->close((conn), (is_forced) TSRMLS_CC) +#define mysqlnd_query(conn, query_str, query_len) ((conn)->data)->m->query((conn)->data, (query_str), (query_len) TSRMLS_CC) +#define mysqlnd_async_query(conn, query_str, query_len) ((conn)->data)->m->send_query((conn)->data, (query_str), (query_len) TSRMLS_CC) +#define mysqlnd_poll(r, err, d_pull,sec,usec,desc_num) _mysqlnd_poll((r), (err), (d_pull), (sec), (usec), (desc_num) TSRMLS_CC) +#define mysqlnd_reap_async_query(conn) ((conn)->data)->m->reap_query((conn)->data TSRMLS_CC) +#define mysqlnd_unbuffered_skip_result(result) (result)->m.skip_result((result) TSRMLS_CC) + +PHPAPI enum_func_status _mysqlnd_poll(MYSQLND **r_array, MYSQLND **e_array, MYSQLND ***dont_poll, long sec, long usec, uint * desc_num TSRMLS_DC); + +#define mysqlnd_use_result(conn) ((conn)->data)->m->use_result((conn)->data TSRMLS_CC) +#define mysqlnd_store_result(conn) ((conn)->data)->m->store_result((conn)->data TSRMLS_CC) +#define mysqlnd_next_result(conn) ((conn)->data)->m->next_result((conn)->data TSRMLS_CC) +#define mysqlnd_more_results(conn) ((conn)->data)->m->more_results((conn)->data TSRMLS_CC) +#define mysqlnd_free_result(r,e_or_i) ((MYSQLND_RES*)r)->m.free_result(((MYSQLND_RES*)(r)), (e_or_i) TSRMLS_CC) +#define mysqlnd_data_seek(result, row) (result)->m.seek_data((result), (row) TSRMLS_CC) + +/* Errors */ +#define mysqlnd_errno(conn) ((conn)->data)->m->get_error_no((conn)->data TSRMLS_CC) +#define mysqlnd_error(conn) ((conn)->data)->m->get_error_str((conn)->data TSRMLS_CC) +#define mysqlnd_sqlstate(conn) ((conn)->data)->m->get_sqlstate((conn)->data TSRMLS_CC) + +/* Charset */ +#define mysqlnd_character_set_name(conn) ((conn)->data)->m->charset_name((conn)->data TSRMLS_CC) + +/* Simple metadata */ +#define mysqlnd_field_count(conn) ((conn)->data)->m->get_field_count((conn)->data TSRMLS_CC) +#define mysqlnd_insert_id(conn) ((conn)->data)->m->get_last_insert_id((conn)->data TSRMLS_CC) +#define mysqlnd_affected_rows(conn) ((conn)->data)->m->get_affected_rows((conn)->data TSRMLS_CC) +#define mysqlnd_warning_count(conn) ((conn)->data)->m->get_warning_count((conn)->data TSRMLS_CC) +#define mysqlnd_info(conn) ((conn)->data)->m->get_last_message((conn)->data TSRMLS_CC) +#define mysqlnd_get_server_info(conn) ((conn)->data)->m->get_server_information((conn)->data TSRMLS_CC) +#define mysqlnd_get_server_version(conn) ((conn)->data)->m->get_server_version((conn)->data TSRMLS_CC) +#define mysqlnd_get_host_info(conn) ((conn)->data)->m->get_host_information((conn)->data TSRMLS_CC) +#define mysqlnd_get_proto_info(conn) ((conn)->data)->m->get_protocol_information((conn)->data TSRMLS_CC) +#define mysqlnd_thread_id(conn) ((conn)->data)->m->get_thread_id((conn)->data TSRMLS_CC) +#define mysqlnd_get_server_status(conn) ((conn)->data)->m->get_server_status((conn)->data TSRMLS_CC) + +#define mysqlnd_num_rows(result) (result)->m.num_rows((result) TSRMLS_CC) +#define mysqlnd_num_fields(result) (result)->m.num_fields((result) TSRMLS_CC) + +#define mysqlnd_fetch_lengths(result) _mysqlnd_fetch_lengths((result) TSRMLS_CC) +PHPAPI unsigned long * _mysqlnd_fetch_lengths(MYSQLND_RES * const result TSRMLS_DC); + +#define mysqlnd_field_seek(result, ofs) (result)->m.seek_field((result), (ofs) TSRMLS_CC) +#define mysqlnd_field_tell(result) (result)->m.field_tell((result) TSRMLS_CC) +#define mysqlnd_fetch_field(result) (result)->m.fetch_field((result) TSRMLS_CC) +#define mysqlnd_fetch_field_direct(result,fnr) (result)->m.fetch_field_direct((result), (fnr) TSRMLS_CC) +#define mysqlnd_fetch_fields(result) (result)->m.fetch_fields((result) TSRMLS_CC) + +/* mysqlnd metadata */ +PHPAPI const char * mysqlnd_get_client_info(); +PHPAPI unsigned int mysqlnd_get_client_version(); + +#define mysqlnd_ssl_set(conn, key, cert, ca, capath, cipher) ((conn)->data)->m->ssl_set((conn)->data, (key), (cert), (ca), (capath), (cipher) TSRMLS_CC) + +/* PS */ +#define mysqlnd_stmt_insert_id(stmt) (stmt)->m->get_last_insert_id((stmt) TSRMLS_CC) +#define mysqlnd_stmt_affected_rows(stmt) (stmt)->m->get_affected_rows((stmt) TSRMLS_CC) +#define mysqlnd_stmt_num_rows(stmt) (stmt)->m->get_num_rows((stmt) TSRMLS_CC) +#define mysqlnd_stmt_param_count(stmt) (stmt)->m->get_param_count((stmt) TSRMLS_CC) +#define mysqlnd_stmt_field_count(stmt) (stmt)->m->get_field_count((stmt) TSRMLS_CC) +#define mysqlnd_stmt_warning_count(stmt) (stmt)->m->get_warning_count((stmt) TSRMLS_CC) +#define mysqlnd_stmt_server_status(stmt) (stmt)->m->get_server_status((stmt) TSRMLS_CC) +#define mysqlnd_stmt_errno(stmt) (stmt)->m->get_error_no((stmt) TSRMLS_CC) +#define mysqlnd_stmt_error(stmt) (stmt)->m->get_error_str((stmt) TSRMLS_CC) +#define mysqlnd_stmt_sqlstate(stmt) (stmt)->m->get_sqlstate((stmt) TSRMLS_CC) + + +PHPAPI void mysqlnd_efree_param_bind_dtor(MYSQLND_PARAM_BIND * param_bind TSRMLS_DC); +PHPAPI void mysqlnd_efree_result_bind_dtor(MYSQLND_RESULT_BIND * result_bind TSRMLS_DC); +PHPAPI void mysqlnd_free_param_bind_dtor(MYSQLND_PARAM_BIND * param_bind TSRMLS_DC); +PHPAPI void mysqlnd_free_result_bind_dtor(MYSQLND_RESULT_BIND * result_bind TSRMLS_DC); + + +PHPAPI const char * mysqlnd_field_type_name(enum mysqlnd_field_types field_type); + +/* LOAD DATA LOCAL */ +PHPAPI void mysqlnd_local_infile_default(MYSQLND_CONN_DATA * conn); +PHPAPI void mysqlnd_set_local_infile_handler(MYSQLND_CONN_DATA * const conn, const char * const funcname); + +/* Simple commands */ +#define mysqlnd_autocommit(conn, mode) ((conn)->data)->m->set_autocommit((conn)->data, (mode) TSRMLS_CC) +#define mysqlnd_commit(conn) ((conn)->data)->m->tx_commit((conn)->data TSRMLS_CC) +#define mysqlnd_rollback(conn) ((conn)->data)->m->tx_rollback((conn)->data TSRMLS_CC) +#define mysqlnd_list_dbs(conn, wild) ((conn)->data)->m->list_method((conn)->data, wild? "SHOW DATABASES LIKE %s":"SHOW DATABASES", (wild), NULL TSRMLS_CC) +#define mysqlnd_list_fields(conn, tab,wild) ((conn)->data)->m->list_fields((conn)->data, (tab), (wild) TSRMLS_CC) +#define mysqlnd_list_processes(conn) ((conn)->data)->m->list_method((conn)->data, "SHOW PROCESSLIST", NULL, NULL TSRMLS_CC) +#define mysqlnd_list_tables(conn, wild) ((conn)->data)->m->list_method((conn)->data, wild? "SHOW TABLES LIKE %s":"SHOW TABLES", (wild), NULL TSRMLS_CC) +#define mysqlnd_dump_debug_info(conn) ((conn)->data)->m->server_dump_debug_information((conn)->data TSRMLS_CC) +#define mysqlnd_select_db(conn, db, db_len) ((conn)->data)->m->select_db((conn)->data, (db), (db_len) TSRMLS_CC) +#define mysqlnd_ping(conn) ((conn)->data)->m->ping((conn)->data TSRMLS_CC) +#define mysqlnd_kill(conn, pid) ((conn)->data)->m->kill_connection((conn)->data, (pid) TSRMLS_CC) +#define mysqlnd_refresh(conn, options) ((conn)->data)->m->refresh_server((conn)->data, (options) TSRMLS_CC) +#define mysqlnd_shutdown(conn, level) ((conn)->data)->m->shutdown_server((conn)->data, (level) TSRMLS_CC) +#define mysqlnd_set_character_set(conn, cs) ((conn)->data)->m->set_charset((conn)->data, (cs) TSRMLS_CC) +#define mysqlnd_stat(conn, msg, msg_len) ((conn)->data)->m->get_server_statistics(((conn)->data), (msg), (msg_len) TSRMLS_CC) +#define mysqlnd_options(conn, opt, value) ((conn)->data)->m->set_client_option((conn)->data, (opt), (value) TSRMLS_CC) +#define mysqlnd_set_server_option(conn, op) ((conn)->data)->m->set_server_option((conn)->data, (op) TSRMLS_CC) + +/* Escaping */ +#define mysqlnd_real_escape_string(conn, newstr, escapestr, escapestr_len) \ + ((conn)->data)->m->escape_string((conn)->data, (newstr), (escapestr), (escapestr_len) TSRMLS_CC) +#define mysqlnd_escape_string(newstr, escapestr, escapestr_len) \ + mysqlnd_old_escape_string((newstr), (escapestr), (escapestr_len) TSRMLS_CC) + +PHPAPI ulong mysqlnd_old_escape_string(char * newstr, const char * escapestr, size_t escapestr_len TSRMLS_DC); + + +/* PS */ +#define mysqlnd_stmt_init(conn) ((conn)->data)->m->stmt_init(((conn)->data) TSRMLS_CC) +#define mysqlnd_stmt_store_result(stmt) (!mysqlnd_stmt_field_count((stmt)) ? PASS:((stmt)->m->store_result((stmt) TSRMLS_CC)? PASS:FAIL)) +#define mysqlnd_stmt_get_result(stmt) (stmt)->m->get_result((stmt) TSRMLS_CC) +#define mysqlnd_stmt_more_results(stmt) (stmt)->m->more_results((stmt) TSRMLS_CC) +#define mysqlnd_stmt_next_result(stmt) (stmt)->m->next_result((stmt) TSRMLS_CC) +#define mysqlnd_stmt_data_seek(stmt, row) (stmt)->m->seek_data((stmt), (row) TSRMLS_CC) +#define mysqlnd_stmt_prepare(stmt, q, qlen) (stmt)->m->prepare((stmt), (q), (qlen) TSRMLS_CC) +#define mysqlnd_stmt_execute(stmt) (stmt)->m->execute((stmt) TSRMLS_CC) +#define mysqlnd_stmt_send_long_data(stmt,p,d,l) (stmt)->m->send_long_data((stmt), (p), (d), (l) TSRMLS_CC) +#define mysqlnd_stmt_alloc_param_bind(stmt) (stmt)->m->alloc_parameter_bind((stmt) TSRMLS_CC) +#define mysqlnd_stmt_free_param_bind(stmt,bind) (stmt)->m->free_parameter_bind((stmt), (bind) TSRMLS_CC) +#define mysqlnd_stmt_bind_param(stmt,bind) (stmt)->m->bind_parameters((stmt), (bind) TSRMLS_CC) +#define mysqlnd_stmt_bind_one_param(stmt,n,z,t) (stmt)->m->bind_one_parameter((stmt), (n), (z), (t) TSRMLS_CC) +#define mysqlnd_stmt_refresh_bind_param(s) (s)->m->refresh_bind_param((s) TSRMLS_CC) +#define mysqlnd_stmt_alloc_result_bind(stmt) (stmt)->m->alloc_result_bind((stmt) TSRMLS_CC) +#define mysqlnd_stmt_free_result_bind(stmt,bind) (stmt)->m->free_result_bind((stmt), (bind) TSRMLS_CC) +#define mysqlnd_stmt_bind_result(stmt,bind) (stmt)->m->bind_result((stmt), (bind) TSRMLS_CC) +#define mysqlnd_stmt_bind_one_result(s,no) (s)->m->bind_one_result((s), (no) TSRMLS_CC) +#define mysqlnd_stmt_param_metadata(stmt) (stmt)->m->get_parameter_metadata((stmt)) +#define mysqlnd_stmt_result_metadata(stmt) (stmt)->m->get_result_metadata((stmt) TSRMLS_CC) + +#define mysqlnd_stmt_free_result(stmt) (stmt)->m->free_result((stmt) TSRMLS_CC) +#define mysqlnd_stmt_close(stmt, implicit) (stmt)->m->dtor((stmt), (implicit) TSRMLS_CC) +#define mysqlnd_stmt_reset(stmt) (stmt)->m->reset((stmt) TSRMLS_CC) +#define mysqlnd_stmt_flush(stmt) (stmt)->m->flush((stmt) TSRMLS_CC) + + +#define mysqlnd_stmt_attr_get(stmt, attr, value) (stmt)->m->get_attribute((stmt), (attr), (value) TSRMLS_CC) +#define mysqlnd_stmt_attr_set(stmt, attr, value) (stmt)->m->set_attribute((stmt), (attr), (value) TSRMLS_CC) + +#define mysqlnd_stmt_fetch(stmt, fetched) (stmt)->m->fetch((stmt), (fetched) TSRMLS_CC) + + +/* Performance statistics */ +PHPAPI void _mysqlnd_get_client_stats(zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC); + +/* double check the class name to avoid naming conflicts when using these: */ +#define MYSQLND_METHOD(class, method) php_##class##_##method##_pub +#define MYSQLND_METHOD_PRIVATE(class, method) php_##class##_##method##_priv + +ZEND_BEGIN_MODULE_GLOBALS(mysqlnd) + zend_bool collect_statistics; + zend_bool collect_memory_statistics; + char* debug; /* The actual string */ + MYSQLND_DEBUG *dbg; /* The DBG object */ + long net_cmd_buffer_size; + long net_read_buffer_size; + long log_mask; + long net_read_timeout; + long mempool_default_size; + long debug_emalloc_fail_threshold; + long debug_ecalloc_fail_threshold; + long debug_erealloc_fail_threshold; + long debug_malloc_fail_threshold; + long debug_calloc_fail_threshold; + long debug_realloc_fail_threshold; +ZEND_END_MODULE_GLOBALS(mysqlnd) + +PHPAPI ZEND_EXTERN_MODULE_GLOBALS(mysqlnd) + +#ifdef ZTS +#define MYSQLND_G(v) TSRMG(mysqlnd_globals_id, zend_mysqlnd_globals *, v) +#else +#define MYSQLND_G(v) (mysqlnd_globals.v) +#endif + + +PHPAPI void mysqlnd_minfo_print_hash(zval *values); + +#endif /* MYSQLND_H */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_alloc.c b/ext/mysqlnd/mysqlnd_alloc.c new file mode 100644 index 0000000..39c1711 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_alloc.c @@ -0,0 +1,796 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: mysqlnd_debug.c 309303 2011-03-16 12:42:59Z andrey $ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_debug.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_statistics.h" + + +static const char mysqlnd_emalloc_name[] = "_mysqlnd_emalloc"; +static const char mysqlnd_pemalloc_name[] = "_mysqlnd_pemalloc"; +static const char mysqlnd_ecalloc_name[] = "_mysqlnd_ecalloc"; +static const char mysqlnd_pecalloc_name[] = "_mysqlnd_pecalloc"; +static const char mysqlnd_erealloc_name[] = "_mysqlnd_erealloc"; +static const char mysqlnd_perealloc_name[] = "_mysqlnd_perealloc"; +static const char mysqlnd_efree_name[] = "_mysqlnd_efree"; +static const char mysqlnd_pefree_name[] = "_mysqlnd_pefree"; +static const char mysqlnd_malloc_name[] = "_mysqlnd_malloc"; +static const char mysqlnd_calloc_name[] = "_mysqlnd_calloc"; +static const char mysqlnd_realloc_name[] = "_mysqlnd_realloc"; +static const char mysqlnd_free_name[] = "_mysqlnd_free"; +static const char mysqlnd_pestrndup_name[] = "_mysqlnd_pestrndup"; +static const char mysqlnd_pestrdup_name[] = "_mysqlnd_pestrdup"; + +PHPAPI const char * mysqlnd_debug_std_no_trace_funcs[] = +{ + mysqlnd_emalloc_name, + mysqlnd_ecalloc_name, + mysqlnd_efree_name, + mysqlnd_erealloc_name, + mysqlnd_pemalloc_name, + mysqlnd_pecalloc_name, + mysqlnd_pefree_name, + mysqlnd_perealloc_name, + mysqlnd_malloc_name, + mysqlnd_calloc_name, + mysqlnd_realloc_name, + mysqlnd_free_name, + mysqlnd_pestrndup_name, + mysqlnd_read_header_name, + mysqlnd_read_body_name, + NULL /* must be always last */ +}; + + +#if ZEND_DEBUG +#else +#define __zend_filename "/unknown/unknown" +#define __zend_lineno 0 +#endif + +#define REAL_SIZE(s) (collect_memory_statistics? (s) + sizeof(size_t) : (s)) +#define REAL_PTR(p) (collect_memory_statistics && (p)? (((char *)(p)) - sizeof(size_t)) : (p)) +#define FAKE_PTR(p) (collect_memory_statistics && (p)? (((char *)(p)) + sizeof(size_t)) : (p)) + +/* {{{ _mysqlnd_emalloc */ +void * _mysqlnd_emalloc(size_t size MYSQLND_MEM_D) +{ + void *ret; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); +#if PHP_DEBUG + long * threshold = &MYSQLND_G(debug_emalloc_fail_threshold); +#endif + DBG_ENTER(mysqlnd_emalloc_name); + +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + +#if PHP_DEBUG + /* -1 is also "true" */ + if (*threshold) { +#endif + ret = emalloc(REAL_SIZE(size)); +#if PHP_DEBUG + --*threshold; + } else if (*threshold == 0) { + ret = NULL; + } +#endif + + DBG_INF_FMT("size=%lu ptr=%p", size, ret); + + if (ret && collect_memory_statistics) { + *(size_t *) ret = size; + MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_EMALLOC_COUNT, 1, STAT_MEM_EMALLOC_AMOUNT, size); + } + DBG_RETURN(FAKE_PTR(ret)); +} +/* }}} */ + + +/* {{{ _mysqlnd_pemalloc */ +void * _mysqlnd_pemalloc(size_t size, zend_bool persistent MYSQLND_MEM_D) +{ + void *ret; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); +#if PHP_DEBUG + long * threshold = persistent? &MYSQLND_G(debug_malloc_fail_threshold):&MYSQLND_G(debug_emalloc_fail_threshold); +#endif + DBG_ENTER(mysqlnd_pemalloc_name); +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + +#if PHP_DEBUG + /* -1 is also "true" */ + if (*threshold) { +#endif + ret = pemalloc(REAL_SIZE(size), persistent); +#if PHP_DEBUG + --*threshold; + } else if (*threshold == 0) { + ret = NULL; + } +#endif + + DBG_INF_FMT("size=%lu ptr=%p persistent=%u", size, ret, persistent); + + if (ret && collect_memory_statistics) { + enum mysqlnd_collected_stats s1 = persistent? STAT_MEM_MALLOC_COUNT:STAT_MEM_EMALLOC_COUNT; + enum mysqlnd_collected_stats s2 = persistent? STAT_MEM_MALLOC_AMOUNT:STAT_MEM_EMALLOC_AMOUNT; + *(size_t *) ret = size; + MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(s1, 1, s2, size); + } + + DBG_RETURN(FAKE_PTR(ret)); +} +/* }}} */ + + +/* {{{ _mysqlnd_ecalloc */ +void * _mysqlnd_ecalloc(unsigned int nmemb, size_t size MYSQLND_MEM_D) +{ + void *ret; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); +#if PHP_DEBUG + long * threshold = &MYSQLND_G(debug_ecalloc_fail_threshold); +#endif + DBG_ENTER(mysqlnd_ecalloc_name); +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + DBG_INF_FMT("before: %lu", zend_memory_usage(FALSE TSRMLS_CC)); + +#if PHP_DEBUG + /* -1 is also "true" */ + if (*threshold) { +#endif + ret = ecalloc(nmemb, REAL_SIZE(size)); +#if PHP_DEBUG + --*threshold; + } else if (*threshold == 0) { + ret = NULL; + } +#endif + + DBG_INF_FMT("after : %lu", zend_memory_usage(FALSE TSRMLS_CC)); + DBG_INF_FMT("size=%lu ptr=%p", size, ret); + if (ret && collect_memory_statistics) { + *(size_t *) ret = size; + MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_ECALLOC_COUNT, 1, STAT_MEM_ECALLOC_AMOUNT, size); + } + DBG_RETURN(FAKE_PTR(ret)); +} +/* }}} */ + + +/* {{{ _mysqlnd_pecalloc */ +void * _mysqlnd_pecalloc(unsigned int nmemb, size_t size, zend_bool persistent MYSQLND_MEM_D) +{ + void *ret; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); +#if PHP_DEBUG + long * threshold = persistent? &MYSQLND_G(debug_calloc_fail_threshold):&MYSQLND_G(debug_ecalloc_fail_threshold); +#endif + DBG_ENTER(mysqlnd_pecalloc_name); +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + +#if PHP_DEBUG + /* -1 is also "true" */ + if (*threshold) { +#endif + ret = pecalloc(nmemb, REAL_SIZE(size), persistent); +#if PHP_DEBUG + --*threshold; + } else if (*threshold == 0) { + ret = NULL; + } +#endif + + DBG_INF_FMT("size=%lu ptr=%p", size, ret); + + if (ret && collect_memory_statistics) { + enum mysqlnd_collected_stats s1 = persistent? STAT_MEM_CALLOC_COUNT:STAT_MEM_ECALLOC_COUNT; + enum mysqlnd_collected_stats s2 = persistent? STAT_MEM_CALLOC_AMOUNT:STAT_MEM_ECALLOC_AMOUNT; + *(size_t *) ret = size; + MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(s1, 1, s2, size); + } + + DBG_RETURN(FAKE_PTR(ret)); +} +/* }}} */ + + +/* {{{ _mysqlnd_erealloc */ +void * _mysqlnd_erealloc(void *ptr, size_t new_size MYSQLND_MEM_D) +{ + void *ret; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); + size_t old_size = collect_memory_statistics && ptr? *(size_t *) (((char*)ptr) - sizeof(size_t)) : 0; +#if PHP_DEBUG + long * threshold = &MYSQLND_G(debug_erealloc_fail_threshold); +#endif + DBG_ENTER(mysqlnd_erealloc_name); +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + DBG_INF_FMT("ptr=%p old_size=%lu, new_size=%lu", ptr, old_size, new_size); + +#if PHP_DEBUG + /* -1 is also "true" */ + if (*threshold) { +#endif + ret = erealloc(REAL_PTR(ptr), REAL_SIZE(new_size)); +#if PHP_DEBUG + --*threshold; + } else if (*threshold == 0) { + ret = NULL; + } +#endif + + DBG_INF_FMT("new_ptr=%p", (char*)ret); + if (ret && collect_memory_statistics) { + *(size_t *) ret = new_size; + MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_EREALLOC_COUNT, 1, STAT_MEM_EREALLOC_AMOUNT, new_size); + } + DBG_RETURN(FAKE_PTR(ret)); +} +/* }}} */ + + +/* {{{ _mysqlnd_perealloc */ +void * _mysqlnd_perealloc(void *ptr, size_t new_size, zend_bool persistent MYSQLND_MEM_D) +{ + void *ret; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); + size_t old_size = collect_memory_statistics && ptr? *(size_t *) (((char*)ptr) - sizeof(size_t)) : 0; +#if PHP_DEBUG + long * threshold = persistent? &MYSQLND_G(debug_realloc_fail_threshold):&MYSQLND_G(debug_erealloc_fail_threshold); +#endif + DBG_ENTER(mysqlnd_perealloc_name); +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + DBG_INF_FMT("ptr=%p old_size=%lu new_size=%lu persistent=%u", ptr, old_size, new_size, persistent); + +#if PHP_DEBUG + /* -1 is also "true" */ + if (*threshold) { +#endif + ret = perealloc(REAL_PTR(ptr), REAL_SIZE(new_size), persistent); +#if PHP_DEBUG + --*threshold; + } else if (*threshold == 0) { + ret = NULL; + } +#endif + + DBG_INF_FMT("new_ptr=%p", (char*)ret); + + if (ret && collect_memory_statistics) { + enum mysqlnd_collected_stats s1 = persistent? STAT_MEM_REALLOC_COUNT:STAT_MEM_EREALLOC_COUNT; + enum mysqlnd_collected_stats s2 = persistent? STAT_MEM_REALLOC_AMOUNT:STAT_MEM_EREALLOC_AMOUNT; + *(size_t *) ret = new_size; + MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(s1, 1, s2, new_size); + } + DBG_RETURN(FAKE_PTR(ret)); +} +/* }}} */ + + +/* {{{ _mysqlnd_efree */ +void _mysqlnd_efree(void *ptr MYSQLND_MEM_D) +{ + size_t free_amount = 0; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); + DBG_ENTER(mysqlnd_efree_name); +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + DBG_INF_FMT("ptr=%p", ptr); + + if (ptr) { + if (collect_memory_statistics) { + free_amount = *(size_t *)(((char*)ptr) - sizeof(size_t)); + DBG_INF_FMT("ptr=%p size=%u", ((char*)ptr) - sizeof(size_t), (unsigned int) free_amount); + } + efree(REAL_PTR(ptr)); + } + + if (collect_memory_statistics) { + MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_EFREE_COUNT, 1, STAT_MEM_EFREE_AMOUNT, free_amount); + } + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ _mysqlnd_pefree */ +void _mysqlnd_pefree(void *ptr, zend_bool persistent MYSQLND_MEM_D) +{ + size_t free_amount = 0; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); + DBG_ENTER(mysqlnd_pefree_name); +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + DBG_INF_FMT("ptr=%p persistent=%u", ptr, persistent); + + if (ptr) { + if (collect_memory_statistics) { + free_amount = *(size_t *)(((char*)ptr) - sizeof(size_t)); + DBG_INF_FMT("ptr=%p size=%u", ((char*)ptr) - sizeof(size_t), (unsigned int) free_amount); + } + pefree(REAL_PTR(ptr), persistent); + } + + if (collect_memory_statistics) { + MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(persistent? STAT_MEM_FREE_COUNT:STAT_MEM_EFREE_COUNT, 1, + persistent? STAT_MEM_FREE_AMOUNT:STAT_MEM_EFREE_AMOUNT, free_amount); + } + DBG_VOID_RETURN; +} + + +/* {{{ _mysqlnd_malloc */ +void * _mysqlnd_malloc(size_t size MYSQLND_MEM_D) +{ + void *ret; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); +#if PHP_DEBUG + long * threshold = &MYSQLND_G(debug_malloc_fail_threshold); +#endif + DBG_ENTER(mysqlnd_malloc_name); +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + +#if PHP_DEBUG + /* -1 is also "true" */ + if (*threshold) { +#endif + ret = malloc(REAL_SIZE(size)); +#if PHP_DEBUG + --*threshold; + } else if (*threshold == 0) { + ret = NULL; + } +#endif + + DBG_INF_FMT("size=%lu ptr=%p", size, ret); + if (ret && collect_memory_statistics) { + *(size_t *) ret = size; + MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_MALLOC_COUNT, 1, STAT_MEM_MALLOC_AMOUNT, size); + } + DBG_RETURN(FAKE_PTR(ret)); +} +/* }}} */ + + +/* {{{ _mysqlnd_calloc */ +void * _mysqlnd_calloc(unsigned int nmemb, size_t size MYSQLND_MEM_D) +{ + void *ret; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); +#if PHP_DEBUG + long * threshold = &MYSQLND_G(debug_calloc_fail_threshold); +#endif + DBG_ENTER(mysqlnd_calloc_name); +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + +#if PHP_DEBUG + /* -1 is also "true" */ + if (*threshold) { +#endif + ret = calloc(nmemb, REAL_SIZE(size)); +#if PHP_DEBUG + --*threshold; + } else if (*threshold == 0) { + ret = NULL; + } +#endif + + DBG_INF_FMT("size=%lu ptr=%p", size, ret); + if (ret && collect_memory_statistics) { + *(size_t *) ret = size; + MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_CALLOC_COUNT, 1, STAT_MEM_CALLOC_AMOUNT, size); + } + DBG_RETURN(FAKE_PTR(ret)); +} +/* }}} */ + + +/* {{{ _mysqlnd_realloc */ +void * _mysqlnd_realloc(void *ptr, size_t new_size MYSQLND_MEM_D) +{ + void *ret; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); +#if PHP_DEBUG + long * threshold = &MYSQLND_G(debug_realloc_fail_threshold); +#endif + DBG_ENTER(mysqlnd_realloc_name); +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + DBG_INF_FMT("ptr=%p new_size=%lu ", new_size, ptr); + DBG_INF_FMT("before: %lu", zend_memory_usage(TRUE TSRMLS_CC)); + +#if PHP_DEBUG + /* -1 is also "true" */ + if (*threshold) { +#endif + ret = realloc(REAL_PTR(ptr), REAL_SIZE(new_size)); +#if PHP_DEBUG + --*threshold; + } else if (*threshold == 0) { + ret = NULL; + } +#endif + + DBG_INF_FMT("new_ptr=%p", (char*)ret); + + if (ret && collect_memory_statistics) { + *(size_t *) ret = new_size; + MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_REALLOC_COUNT, 1, STAT_MEM_REALLOC_AMOUNT, new_size); + } + DBG_RETURN(FAKE_PTR(ret)); +} +/* }}} */ + + +/* {{{ _mysqlnd_free */ +void _mysqlnd_free(void *ptr MYSQLND_MEM_D) +{ + size_t free_amount = 0; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); + DBG_ENTER(mysqlnd_free_name); +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + DBG_INF_FMT("ptr=%p", ptr); + + if (ptr) { + if (collect_memory_statistics) { + free_amount = *(size_t *)(((char*)ptr) - sizeof(size_t)); + DBG_INF_FMT("ptr=%p size=%u", ((char*)ptr) - sizeof(size_t), (unsigned int) free_amount); + } + free(REAL_PTR(ptr)); + } + + if (collect_memory_statistics) { + MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_MEM_FREE_COUNT, 1, STAT_MEM_FREE_AMOUNT, free_amount); + } + DBG_VOID_RETURN; +} +/* }}} */ + +#define SMART_STR_START_SIZE 2048 +#define SMART_STR_PREALLOC 512 +#include "ext/standard/php_smart_str.h" + + +/* {{{ _mysqlnd_pestrndup */ +char * _mysqlnd_pestrndup(const char * const ptr, size_t length, zend_bool persistent MYSQLND_MEM_D) +{ + char * ret; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); + DBG_ENTER(mysqlnd_pestrndup_name); +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + DBG_INF_FMT("ptr=%p", ptr); + + ret = pemalloc(REAL_SIZE(length) + 1, persistent); + { + size_t l = length; + char * p = (char *) ptr; + char * dest = (char *) FAKE_PTR(ret); + while (*p && l--) { + *dest++ = *p++; + } + *dest = '\0'; + } + + if (collect_memory_statistics) { + *(size_t *) ret = length; + MYSQLND_INC_GLOBAL_STATISTIC(persistent? STAT_MEM_STRNDUP_COUNT : STAT_MEM_ESTRNDUP_COUNT); + } + + DBG_RETURN(FAKE_PTR(ret)); +} +/* }}} */ + + +/* {{{ _mysqlnd_pestrdup */ +char * _mysqlnd_pestrdup(const char * const ptr, zend_bool persistent MYSQLND_MEM_D) +{ + char * ret; + smart_str tmp_str = {0, 0, 0}; + const char * p = ptr; + zend_bool collect_memory_statistics = MYSQLND_G(collect_memory_statistics); + DBG_ENTER(mysqlnd_pestrdup_name); +#if PHP_DEBUG + { + char * fn = strrchr(__zend_filename, PHP_DIR_SEPARATOR); + DBG_INF_FMT("file=%-15s line=%4d", fn? fn + 1:__zend_filename, __zend_lineno); + } +#endif + DBG_INF_FMT("ptr=%p", ptr); + do { + smart_str_appendc(&tmp_str, *p); + } while (*p++); + + ret = pemalloc(tmp_str.len + sizeof(size_t), persistent); + memcpy(FAKE_PTR(ret), tmp_str.c, tmp_str.len); + + if (ret && collect_memory_statistics) { + *(size_t *) ret = tmp_str.len; + MYSQLND_INC_GLOBAL_STATISTIC(persistent? STAT_MEM_STRDUP_COUNT : STAT_MEM_ESTRDUP_COUNT); + } + smart_str_free(&tmp_str); + + DBG_RETURN(FAKE_PTR(ret)); +} +/* }}} */ + + +/* {{{ _mysqlnd_sprintf */ +PHPAPI int _mysqlnd_sprintf(char ** pbuf, size_t max_len, const char *format, ...) +{ + int len; + va_list ap; + va_start(ap, format); + len = vspprintf(pbuf, max_len, format, ap); + va_end(ap); + return len; +} +/* }}} */ + + +/* {{{ _mysqlnd_sprintf_free */ +PHPAPI void _mysqlnd_sprintf_free(char * p) +{ + efree(p); +} +/* }}} */ + + +PHPAPI int _mysqlnd_vsprintf(char ** pbuf, size_t max_len, const char * format, va_list ap) +{ + return vspprintf(pbuf, max_len, format, ap); +} +/* }}} */ + + +#define MYSQLND_DEBUG_MEMORY 1 + +#if MYSQLND_DEBUG_MEMORY == 0 + +/* {{{ mysqlnd_zend_mm_emalloc */ +static void * mysqlnd_zend_mm_emalloc(size_t size MYSQLND_MEM_D) +{ + return emalloc(size); +} +/* }}} */ + + +/* {{{ mysqlnd_zend_mm_pemalloc */ +static void * mysqlnd_zend_mm_pemalloc(size_t size, zend_bool persistent MYSQLND_MEM_D) +{ + return pemalloc(size, persistent); +} +/* }}} */ + + +/* {{{ mysqlnd_zend_mm_ecalloc */ +static void * mysqlnd_zend_mm_ecalloc(unsigned int nmemb, size_t size MYSQLND_MEM_D) +{ + return ecalloc(nmemb, size); +} +/* }}} */ + + +/* {{{ mysqlnd_zend_mm_pecalloc */ +static void * mysqlnd_zend_mm_pecalloc(unsigned int nmemb, size_t size, zend_bool persistent MYSQLND_MEM_D) +{ + return pecalloc(nmemb, size, persistent); +} +/* }}} */ + + +/* {{{ mysqlnd_zend_mm_erealloc */ +static void * mysqlnd_zend_mm_erealloc(void *ptr, size_t new_size MYSQLND_MEM_D) +{ + return erealloc(ptr, new_size); +} +/* }}} */ + + +/* {{{ mysqlnd_zend_mm_perealloc */ +static void * mysqlnd_zend_mm_perealloc(void *ptr, size_t new_size, zend_bool persistent MYSQLND_MEM_D) +{ + return perealloc(ptr, new_size, persistent); +} +/* }}} */ + + +/* {{{ mysqlnd_zend_mm_efree */ +static void mysqlnd_zend_mm_efree(void * ptr MYSQLND_MEM_D) +{ + efree(ptr); +} +/* }}} */ + + +/* {{{ mysqlnd_zend_mm_pefree */ +static void mysqlnd_zend_mm_pefree(void * ptr, zend_bool persistent MYSQLND_MEM_D) +{ + pefree(ptr, persistent); +} +/* }}} */ + + +/* {{{ mysqlnd_zend_mm_malloc */ +static void * mysqlnd_zend_mm_malloc(size_t size MYSQLND_MEM_D) +{ + return malloc(size); +} +/* }}} */ + + +/* {{{ mysqlnd_zend_mm_calloc */ +static void * mysqlnd_zend_mm_calloc(unsigned int nmemb, size_t size MYSQLND_MEM_D) +{ + return calloc(nmemb, size); +} +/* }}} */ + + +/* {{{ mysqlnd_zend_mm_realloc */ +static void * mysqlnd_zend_mm_realloc(void * ptr, size_t new_size MYSQLND_MEM_D) +{ + return realloc(ptr, new_size); +} +/* }}} */ + + +/* {{{ mysqlnd_zend_mm_free */ +static void mysqlnd_zend_mm_free(void * ptr MYSQLND_MEM_D) +{ + free(ptr); +} +/* }}} */ + + +/* {{{ mysqlnd_zend_mm_pestrndup */ +static char * mysqlnd_zend_mm_pestrndup(const char * const ptr, size_t length, zend_bool persistent MYSQLND_MEM_D) +{ + return pestrndup(ptr, length, persistent); +} +/* }}} */ + + +/* {{{ mysqlnd_zend_mm_pestrdup */ +static char * mysqlnd_zend_mm_pestrdup(const char * const ptr, zend_bool persistent MYSQLND_MEM_D) +{ + return pestrdup(ptr, persistent); +} +/* }}} */ + +#endif + + +PHPAPI struct st_mysqlnd_allocator_methods mysqlnd_allocator = +{ +#if MYSQLND_DEBUG_MEMORY + _mysqlnd_emalloc, + _mysqlnd_pemalloc, + _mysqlnd_ecalloc, + _mysqlnd_pecalloc, + _mysqlnd_erealloc, + _mysqlnd_perealloc, + _mysqlnd_efree, + _mysqlnd_pefree, + _mysqlnd_malloc, + _mysqlnd_calloc, + _mysqlnd_realloc, + _mysqlnd_free, + _mysqlnd_pestrndup, + _mysqlnd_pestrdup, + _mysqlnd_sprintf, + _mysqlnd_vsprintf, + _mysqlnd_sprintf_free +#else + mysqlnd_zend_mm_emalloc, + mysqlnd_zend_mm_pemalloc, + mysqlnd_zend_mm_ecalloc, + mysqlnd_zend_mm_pecalloc, + mysqlnd_zend_mm_erealloc, + mysqlnd_zend_mm_perealloc, + mysqlnd_zend_mm_efree, + mysqlnd_zend_mm_pefree, + mysqlnd_zend_mm_malloc, + mysqlnd_zend_mm_calloc, + mysqlnd_zend_mm_realloc, + mysqlnd_zend_mm_free, + mysqlnd_zend_mm_pestrndup, + mysqlnd_zend_mm_pestrdup + sprintf, + mysqlnd_zend_mm_efree, +#endif +}; + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_alloc.h b/ext/mysqlnd/mysqlnd_alloc.h new file mode 100644 index 0000000..33c0070 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_alloc.h @@ -0,0 +1,100 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: mysqlnd_debug.h 306938 2011-01-01 02:17:06Z felipe $ */ +/* $Id: mysqlnd_debug.h 306938 2011-01-01 02:17:06Z felipe $ */ + +#ifndef MYSQLND_ALLOC_H +#define MYSQLND_ALLOC_H + +PHPAPI extern const char * mysqlnd_debug_std_no_trace_funcs[]; + +#define MYSQLND_MEM_D TSRMLS_DC ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC +#define MYSQLND_MEM_C TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC + +struct st_mysqlnd_allocator_methods +{ + void * (*m_emalloc)(size_t size MYSQLND_MEM_D); + void * (*m_pemalloc)(size_t size, zend_bool persistent MYSQLND_MEM_D); + void * (*m_ecalloc)(unsigned int nmemb, size_t size MYSQLND_MEM_D); + void * (*m_pecalloc)(unsigned int nmemb, size_t size, zend_bool persistent MYSQLND_MEM_D); + void * (*m_erealloc)(void *ptr, size_t new_size MYSQLND_MEM_D); + void * (*m_perealloc)(void *ptr, size_t new_size, zend_bool persistent MYSQLND_MEM_D); + void (*m_efree)(void *ptr MYSQLND_MEM_D); + void (*m_pefree)(void *ptr, zend_bool persistent MYSQLND_MEM_D); + void * (*m_malloc)(size_t size MYSQLND_MEM_D); + void * (*m_calloc)(unsigned int nmemb, size_t size MYSQLND_MEM_D); + void * (*m_realloc)(void *ptr, size_t new_size MYSQLND_MEM_D); + void (*m_free)(void *ptr MYSQLND_MEM_D); + char * (*m_pestrndup)(const char * const ptr, size_t size, zend_bool persistent MYSQLND_MEM_D); + char * (*m_pestrdup)(const char * const ptr, zend_bool persistent MYSQLND_MEM_D); + int (*m_sprintf)(char **pbuf, size_t max_len, const char *format, ...); + int (*m_vsprintf)(char **pbuf, size_t max_len, const char *format, va_list ap); + void (*m_sprintf_free)(char * p); +}; + +PHPAPI extern struct st_mysqlnd_allocator_methods mysqlnd_allocator; + +PHPAPI void * _mysqlnd_emalloc(size_t size MYSQLND_MEM_D); +PHPAPI void * _mysqlnd_pemalloc(size_t size, zend_bool persistent MYSQLND_MEM_D); +PHPAPI void * _mysqlnd_ecalloc(unsigned int nmemb, size_t size MYSQLND_MEM_D); +PHPAPI void * _mysqlnd_pecalloc(unsigned int nmemb, size_t size, zend_bool persistent MYSQLND_MEM_D); +PHPAPI void * _mysqlnd_erealloc(void *ptr, size_t new_size MYSQLND_MEM_D); +PHPAPI void * _mysqlnd_perealloc(void *ptr, size_t new_size, zend_bool persistent MYSQLND_MEM_D); +PHPAPI void _mysqlnd_efree(void *ptr MYSQLND_MEM_D); +PHPAPI void _mysqlnd_pefree(void *ptr, zend_bool persistent MYSQLND_MEM_D); +PHPAPI void * _mysqlnd_malloc(size_t size MYSQLND_MEM_D); +PHPAPI void * _mysqlnd_calloc(unsigned int nmemb, size_t size MYSQLND_MEM_D); +PHPAPI void * _mysqlnd_realloc(void *ptr, size_t new_size MYSQLND_MEM_D); +PHPAPI void _mysqlnd_free(void *ptr MYSQLND_MEM_D); +PHPAPI char * _mysqlnd_pestrndup(const char * const ptr, size_t size, zend_bool persistent MYSQLND_MEM_D); +PHPAPI char * _mysqlnd_pestrdup(const char * const ptr, zend_bool persistent MYSQLND_MEM_D); +PHPAPI int _mysqlnd_sprintf(char **pbuf, size_t max_len, const char *format, ...); +PHPAPI void _mysqlnd_sprintf_free(char * p); +PHPAPI int _mysqlnd_vsprintf(char **pbuf, size_t max_len, const char *format, va_list ap); + +#define mnd_emalloc(size) mysqlnd_allocator.m_emalloc((size) MYSQLND_MEM_C) +#define mnd_pemalloc(size, pers) mysqlnd_allocator.m_pemalloc((size), (pers) MYSQLND_MEM_C) +#define mnd_ecalloc(nmemb, size) mysqlnd_allocator.m_ecalloc((nmemb), (size) MYSQLND_MEM_C) +#define mnd_pecalloc(nmemb, size, p) mysqlnd_allocator.m_pecalloc((nmemb), (size), (p) MYSQLND_MEM_C) +#define mnd_erealloc(ptr, new_size) mysqlnd_allocator.m_erealloc((ptr), (new_size) MYSQLND_MEM_C) +#define mnd_perealloc(ptr, new_size, p) mysqlnd_allocator.m_perealloc((ptr), (new_size), (p) MYSQLND_MEM_C) +#define mnd_efree(ptr) mysqlnd_allocator.m_efree((ptr) MYSQLND_MEM_C) +#define mnd_pefree(ptr, pers) mysqlnd_allocator.m_pefree((ptr), (pers) MYSQLND_MEM_C) +#define mnd_malloc(size) mysqlnd_allocator.m_malloc((size) MYSQLND_MEM_C) +#define mnd_calloc(nmemb, size) mysqlnd_allocator.m_calloc((nmemb), (size) MYSQLND_MEM_C) +#define mnd_realloc(ptr, new_size) mysqlnd_allocator.m_realloc((ptr), (new_size) MYSQLND_MEM_C) +#define mnd_free(ptr) mysqlnd_allocator.m_free((ptr) MYSQLND_MEM_C) +#define mnd_pestrndup(ptr, size, pers) mysqlnd_allocator.m_pestrndup((ptr), (size), (pers) MYSQLND_MEM_C) +#define mnd_pestrdup(ptr, pers) mysqlnd_allocator.m_pestrdup((ptr), (pers) MYSQLND_MEM_C) +#define mnd_sprintf(p, mx_len, fmt,...) mysqlnd_allocator.m_sprintf((p), (mx_len), (fmt), __VA_ARGS__) +#define mnd_vsprintf(p, mx_len, fmt,ap) mysqlnd_allocator.m_vsprintf((p), (mx_len), (fmt), (ap)) +#define mnd_sprintf_free(p) mysqlnd_allocator.m_sprintf_free((p)) + +#endif /* MYSQLND_ALLOC_H */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_auth.c b/ext/mysqlnd/mysqlnd_auth.c new file mode 100644 index 0000000..918697a --- /dev/null +++ b/ext/mysqlnd/mysqlnd_auth.c @@ -0,0 +1,477 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: mysqlnd.c 307377 2011-01-11 13:02:57Z andrey $ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_structs.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_result.h" +#include "mysqlnd_charset.h" +#include "mysqlnd_debug.h" + + +/* {{{ mysqlnd_auth_handshake */ +enum_func_status +mysqlnd_auth_handshake(MYSQLND_CONN_DATA * conn, + const char * const user, + const char * const passwd, + const size_t passwd_len, + const char * const db, + const size_t db_len, + const MYSQLND_OPTIONS * const options, + unsigned long mysql_flags, + unsigned int server_charset_no, + zend_bool use_full_blown_auth_packet, + const char * const auth_protocol, + const zend_uchar * const auth_plugin_data, + const size_t auth_plugin_data_len, + char ** switch_to_auth_protocol, + size_t * switch_to_auth_protocol_len, + zend_uchar ** switch_to_auth_protocol_data, + size_t * switch_to_auth_protocol_data_len + TSRMLS_DC) +{ + enum_func_status ret = FAIL; + const MYSQLND_CHARSET * charset = NULL; + MYSQLND_PACKET_CHANGE_AUTH_RESPONSE * change_auth_resp_packet = NULL; + MYSQLND_PACKET_AUTH_RESPONSE * auth_resp_packet = NULL; + MYSQLND_PACKET_AUTH * auth_packet = NULL; + + DBG_ENTER("mysqlnd_auth_handshake"); + + auth_resp_packet = conn->protocol->m.get_auth_response_packet(conn->protocol, FALSE TSRMLS_CC); + + if (!auth_resp_packet) { + SET_OOM_ERROR(*conn->error_info); + goto end; + } + + if (use_full_blown_auth_packet != TRUE) { + change_auth_resp_packet = conn->protocol->m.get_change_auth_response_packet(conn->protocol, FALSE TSRMLS_CC); + if (!change_auth_resp_packet) { + SET_OOM_ERROR(*conn->error_info); + goto end; + } + + change_auth_resp_packet->auth_data = auth_plugin_data; + change_auth_resp_packet->auth_data_len = auth_plugin_data_len; + + if (!PACKET_WRITE(change_auth_resp_packet, conn)) { + CONN_SET_STATE(conn, CONN_QUIT_SENT); + SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); + goto end; + } + } else { + auth_packet = conn->protocol->m.get_auth_packet(conn->protocol, FALSE TSRMLS_CC); + + auth_packet->client_flags = mysql_flags; + auth_packet->max_packet_size = options->max_allowed_packet; + if (options->charset_name && (charset = mysqlnd_find_charset_name(options->charset_name))) { + auth_packet->charset_no = charset->nr; + } else { +#if MYSQLND_UNICODE + auth_packet->charset_no = 200;/* utf8 - swedish collation, check mysqlnd_charset.c */ +#else + auth_packet->charset_no = server_charset_no; +#endif + } + + auth_packet->send_auth_data = TRUE; + auth_packet->user = user; + auth_packet->db = db; + auth_packet->db_len = db_len; + + auth_packet->auth_data = auth_plugin_data; + auth_packet->auth_data_len = auth_plugin_data_len; + auth_packet->auth_plugin_name = auth_protocol; + + if (!PACKET_WRITE(auth_packet, conn)) { + goto end; + } + } + if (use_full_blown_auth_packet == TRUE) { + conn->charset = mysqlnd_find_charset_nr(auth_packet->charset_no); + } + + if (FAIL == PACKET_READ(auth_resp_packet, conn) || auth_resp_packet->response_code >= 0xFE) { + if (auth_resp_packet->response_code == 0xFE) { + /* old authentication with new server !*/ + if (!auth_resp_packet->new_auth_protocol) { + DBG_ERR(mysqlnd_old_passwd); + SET_CLIENT_ERROR(*conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd); + } else { + *switch_to_auth_protocol = mnd_pestrndup(auth_resp_packet->new_auth_protocol, auth_resp_packet->new_auth_protocol_len, FALSE); + *switch_to_auth_protocol_len = auth_resp_packet->new_auth_protocol_len; + if (auth_resp_packet->new_auth_protocol_data) { + *switch_to_auth_protocol_data_len = auth_resp_packet->new_auth_protocol_data_len; + *switch_to_auth_protocol_data = mnd_emalloc(*switch_to_auth_protocol_data_len); + memcpy(*switch_to_auth_protocol_data, auth_resp_packet->new_auth_protocol_data, *switch_to_auth_protocol_data_len); + } else { + *switch_to_auth_protocol_data = NULL; + *switch_to_auth_protocol_data_len = 0; + } + } + } else if (auth_resp_packet->response_code == 0xFF) { + if (auth_resp_packet->sqlstate[0]) { + strlcpy(conn->error_info->sqlstate, auth_resp_packet->sqlstate, sizeof(conn->error_info->sqlstate)); + DBG_ERR_FMT("ERROR:%u [SQLSTATE:%s] %s", auth_resp_packet->error_no, auth_resp_packet->sqlstate, auth_resp_packet->error); + } + SET_CLIENT_ERROR(*conn->error_info, auth_resp_packet->error_no, UNKNOWN_SQLSTATE, auth_resp_packet->error); + } + goto end; + } + + SET_NEW_MESSAGE(conn->last_message, conn->last_message_len, auth_resp_packet->message, auth_resp_packet->message_len, conn->persistent); + ret = PASS; +end: + PACKET_FREE(change_auth_resp_packet); + PACKET_FREE(auth_packet); + PACKET_FREE(auth_resp_packet); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_auth_change_user */ +enum_func_status +mysqlnd_auth_change_user(MYSQLND_CONN_DATA * const conn, + const char * const user, + const size_t user_len, + const char * const passwd, + const size_t passwd_len, + const char * const db, + const size_t db_len, + const zend_bool silent, + zend_bool use_full_blown_auth_packet, + const char * const auth_protocol, + zend_uchar * auth_plugin_data, + size_t auth_plugin_data_len, + char ** switch_to_auth_protocol, + size_t * switch_to_auth_protocol_len, + zend_uchar ** switch_to_auth_protocol_data, + size_t * switch_to_auth_protocol_data_len + TSRMLS_DC) +{ + enum_func_status ret = FAIL; + const MYSQLND_CHARSET * old_cs = conn->charset; + MYSQLND_PACKET_CHANGE_AUTH_RESPONSE * change_auth_resp_packet = NULL; + MYSQLND_PACKET_CHG_USER_RESPONSE * chg_user_resp = NULL; + MYSQLND_PACKET_AUTH * auth_packet = NULL; + + DBG_ENTER("mysqlnd_auth_change_user"); + + chg_user_resp = conn->protocol->m.get_change_user_response_packet(conn->protocol, FALSE TSRMLS_CC); + + if (!chg_user_resp) { + SET_OOM_ERROR(*conn->error_info); + goto end; + } + + if (use_full_blown_auth_packet != TRUE) { + change_auth_resp_packet = conn->protocol->m.get_change_auth_response_packet(conn->protocol, FALSE TSRMLS_CC); + if (!change_auth_resp_packet) { + SET_OOM_ERROR(*conn->error_info); + goto end; + } + + change_auth_resp_packet->auth_data = auth_plugin_data; + change_auth_resp_packet->auth_data_len = auth_plugin_data_len; + + if (!PACKET_WRITE(change_auth_resp_packet, conn)) { + CONN_SET_STATE(conn, CONN_QUIT_SENT); + SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); + goto end; + } + } else { + auth_packet = conn->protocol->m.get_auth_packet(conn->protocol, FALSE TSRMLS_CC); + + if (!auth_packet) { + SET_OOM_ERROR(*conn->error_info); + goto end; + } + + auth_packet->is_change_user_packet = TRUE; + auth_packet->user = user; + auth_packet->db = db; + auth_packet->db_len = db_len; + auth_packet->silent = silent; + + auth_packet->auth_data = auth_plugin_data; + auth_packet->auth_data_len = auth_plugin_data_len; + auth_packet->auth_plugin_name = auth_protocol; + + + if (conn->m->get_server_version(conn TSRMLS_CC) >= 50123) { + auth_packet->charset_no = conn->charset->nr; + } + + if (!PACKET_WRITE(auth_packet, conn)) { + CONN_SET_STATE(conn, CONN_QUIT_SENT); + SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); + goto end; + } + } + + ret = PACKET_READ(chg_user_resp, conn); + COPY_CLIENT_ERROR(*conn->error_info, chg_user_resp->error_info); + + if (0xFE == chg_user_resp->response_code) { + ret = FAIL; + if (!chg_user_resp->new_auth_protocol) { + DBG_ERR(mysqlnd_old_passwd); + SET_CLIENT_ERROR(*conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd); + } else { + *switch_to_auth_protocol = mnd_pestrndup(chg_user_resp->new_auth_protocol, chg_user_resp->new_auth_protocol_len, FALSE); + *switch_to_auth_protocol_len = chg_user_resp->new_auth_protocol_len; + if (chg_user_resp->new_auth_protocol_data) { + *switch_to_auth_protocol_data_len = chg_user_resp->new_auth_protocol_data_len; + *switch_to_auth_protocol_data = mnd_emalloc(*switch_to_auth_protocol_data_len); + memcpy(*switch_to_auth_protocol_data, chg_user_resp->new_auth_protocol_data, *switch_to_auth_protocol_data_len); + } else { + *switch_to_auth_protocol_data = NULL; + *switch_to_auth_protocol_data_len = 0; + } + } + } + + 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 (conn->m->get_server_version(conn TSRMLS_CC) > 50113L &&conn->m->get_server_version(conn TSRMLS_CC) < 50118L) { + MYSQLND_PACKET_OK * redundant_error_packet = conn->protocol->m.get_ok_packet(conn->protocol, FALSE TSRMLS_CC); + if (redundant_error_packet) { + PACKET_READ(redundant_error_packet, conn); + PACKET_FREE(redundant_error_packet); + DBG_INF_FMT("Server is %u, buggy, sends two ERR messages", conn->m->get_server_version(conn TSRMLS_CC)); + } else { + SET_OOM_ERROR(*conn->error_info); + } + } + } + if (ret == PASS) { + char * tmp = NULL; + /* if we get conn->user as parameter and then we first free it, then estrndup it, we will crash */ + tmp = mnd_pestrndup(user, user_len, conn->persistent); + if (conn->user) { + mnd_pefree(conn->user, conn->persistent); + } + conn->user = tmp; + + tmp = mnd_pestrdup(passwd, conn->persistent); + if (conn->passwd) { + mnd_pefree(conn->passwd, conn->persistent); + } + conn->passwd = tmp; + + if (conn->last_message) { + mnd_pefree(conn->last_message, conn->persistent); + conn->last_message = NULL; + } + memset(conn->upsert_status, 0, sizeof(*conn->upsert_status)); + /* set charset for old servers */ + if (conn->m->get_server_version(conn TSRMLS_CC) < 50123) { + ret = conn->m->set_charset(conn, old_cs->name TSRMLS_CC); + } + } else if (ret == FAIL && chg_user_resp->server_asked_323_auth == TRUE) { + /* old authentication with new server !*/ + DBG_ERR(mysqlnd_old_passwd); + SET_CLIENT_ERROR(*conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, mysqlnd_old_passwd); + } +end: + PACKET_FREE(change_auth_resp_packet); + PACKET_FREE(auth_packet); + PACKET_FREE(chg_user_resp); + DBG_RETURN(ret); +} +/* }}} */ + + +/******************************************* MySQL Native Password ***********************************/ + +#include "ext/standard/sha1.h" + +/* {{{ php_mysqlnd_crypt */ +static void +php_mysqlnd_crypt(zend_uchar *buffer, const zend_uchar *s1, const zend_uchar *s2, size_t len) +{ + const zend_uchar *s1_end = s1 + len; + while (s1 < s1_end) { + *buffer++= *s1++ ^ *s2++; + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_scramble */ +void php_mysqlnd_scramble(zend_uchar * const buffer, const zend_uchar * const scramble, const zend_uchar * const password, size_t password_len) +{ + PHP_SHA1_CTX context; + zend_uchar sha1[SHA1_MAX_LENGTH]; + zend_uchar sha2[SHA1_MAX_LENGTH]; + + /* Phase 1: hash password */ + PHP_SHA1Init(&context); + PHP_SHA1Update(&context, password, password_len); + PHP_SHA1Final(sha1, &context); + + /* Phase 2: hash sha1 */ + PHP_SHA1Init(&context); + PHP_SHA1Update(&context, (zend_uchar*)sha1, SHA1_MAX_LENGTH); + PHP_SHA1Final(sha2, &context); + + /* Phase 3: hash scramble + sha2 */ + PHP_SHA1Init(&context); + PHP_SHA1Update(&context, scramble, SCRAMBLE_LENGTH); + PHP_SHA1Update(&context, (zend_uchar*)sha2, SHA1_MAX_LENGTH); + PHP_SHA1Final(buffer, &context); + + /* let's crypt buffer now */ + php_mysqlnd_crypt(buffer, (const zend_uchar *)buffer, (const zend_uchar *)sha1, SHA1_MAX_LENGTH); +} +/* }}} */ + + +/* {{{ mysqlnd_native_auth_get_auth_data */ +static zend_uchar * +mysqlnd_native_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self, + size_t * auth_data_len, + MYSQLND_CONN_DATA * conn, const char * const user, const char * const passwd, + const size_t passwd_len, zend_uchar * auth_plugin_data, size_t auth_plugin_data_len, + const MYSQLND_OPTIONS * const options, unsigned long mysql_flags + TSRMLS_DC) +{ + zend_uchar * ret = NULL; + DBG_ENTER("mysqlnd_native_auth_get_auth_data"); + *auth_data_len = 0; + + /* 5.5.x reports 21 as scramble length because it needs to show the length of the data before the plugin name */ + if (auth_plugin_data_len < SCRAMBLE_LENGTH) { + /* mysql_native_password only works with SCRAMBLE_LENGTH scramble */ + SET_CLIENT_ERROR(*conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, "The server sent wrong length for scramble"); + DBG_ERR_FMT("The server sent wrong length for scramble %u. Expected %u", auth_plugin_data_len, SCRAMBLE_LENGTH); + DBG_RETURN(NULL); + } + + /* copy scrambled pass*/ + if (passwd && passwd_len) { + ret = malloc(SCRAMBLE_LENGTH); + *auth_data_len = SCRAMBLE_LENGTH; + /* In 4.1 we use CLIENT_SECURE_CONNECTION and thus the len of the buf should be passed */ + php_mysqlnd_scramble((zend_uchar*)ret, auth_plugin_data, (zend_uchar*)passwd, passwd_len); + } + DBG_RETURN(ret); +} +/* }}} */ + + +static struct st_mysqlnd_authentication_plugin mysqlnd_native_auth_plugin = +{ + { + MYSQLND_PLUGIN_API_VERSION, + "auth_plugin_mysql_native_password", + MYSQLND_VERSION_ID, + MYSQLND_VERSION, + "PHP License 3.01", + "Andrey Hristov <andrey@mysql.com>, Ulf Wendel <uwendel@mysql.com>, Georg Richter <georg@mysql.com>", + { + NULL, /* no statistics , will be filled later if there are some */ + NULL, /* no statistics */ + }, + { + NULL /* plugin shutdown */ + } + }, + {/* methods */ + mysqlnd_native_auth_get_auth_data + } +}; + + +/******************************************* PAM Authentication ***********************************/ + +/* {{{ mysqlnd_pam_auth_get_auth_data */ +static zend_uchar * +mysqlnd_pam_auth_get_auth_data(struct st_mysqlnd_authentication_plugin * self, + size_t * auth_data_len, + MYSQLND_CONN_DATA * conn, const char * const user, const char * const passwd, + const size_t passwd_len, zend_uchar * auth_plugin_data, size_t auth_plugin_data_len, + const MYSQLND_OPTIONS * const options, unsigned long mysql_flags + TSRMLS_DC) +{ + zend_uchar * ret = NULL; + + /* copy pass*/ + if (passwd && passwd_len) { + ret = (zend_uchar*) zend_strndup(passwd, passwd_len); + } + *auth_data_len = passwd_len; + + return ret; +} +/* }}} */ + + +static struct st_mysqlnd_authentication_plugin mysqlnd_pam_authentication_plugin = +{ + { + MYSQLND_PLUGIN_API_VERSION, + "auth_plugin_mysql_clear_password", + MYSQLND_VERSION_ID, + MYSQLND_VERSION, + "PHP License 3.01", + "Andrey Hristov <andrey@mysql.com>, Ulf Wendel <uwendel@mysql.com>, Georg Richter <georg@mysql.com>", + { + NULL, /* no statistics , will be filled later if there are some */ + NULL, /* no statistics */ + }, + { + NULL /* plugin shutdown */ + } + }, + {/* methods */ + mysqlnd_pam_auth_get_auth_data + } +}; + + +/* {{{ mysqlnd_register_builtin_authentication_plugins */ +void +mysqlnd_register_builtin_authentication_plugins(TSRMLS_D) +{ + mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_native_auth_plugin TSRMLS_CC); + mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_pam_authentication_plugin TSRMLS_CC); +} +/* }}} */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_block_alloc.c b/ext/mysqlnd/mysqlnd_block_alloc.c new file mode 100644 index 0000000..b9d7887 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_block_alloc.c @@ -0,0 +1,194 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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_block_alloc.h" +#include "mysqlnd_debug.h" +#include "mysqlnd_priv.h" + + +/* {{{ mysqlnd_mempool_free_chunk */ +static void +mysqlnd_mempool_free_chunk(MYSQLND_MEMORY_POOL_CHUNK * chunk TSRMLS_DC) +{ + MYSQLND_MEMORY_POOL * pool = chunk->pool; + DBG_ENTER("mysqlnd_mempool_free_chunk"); + if (chunk->from_pool) { + /* Try to back-off and guess if this is the last block allocated */ + if (chunk->ptr == (pool->arena + (pool->arena_size - pool->free_size - chunk->size))) { + /* + This was the last allocation. Lucky us, we can free + a bit of memory from the pool. Next time we will return from the same ptr. + */ + pool->free_size += chunk->size; + } + pool->refcount--; + } else { + mnd_free(chunk->ptr); + } + mnd_free(chunk); + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_mempool_resize_chunk */ +static enum_func_status +mysqlnd_mempool_resize_chunk(MYSQLND_MEMORY_POOL_CHUNK * chunk, unsigned int size TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_mempool_resize_chunk"); + if (chunk->from_pool) { + MYSQLND_MEMORY_POOL * pool = chunk->pool; + /* Try to back-off and guess if this is the last block allocated */ + if (chunk->ptr == (pool->arena + (pool->arena_size - pool->free_size - chunk->size))) { + /* + This was the last allocation. Lucky us, we can free + a bit of memory from the pool. Next time we will return from the same ptr. + */ + if ((chunk->size + pool->free_size) < size) { + zend_uchar *new_ptr; + new_ptr = mnd_malloc(size); + if (!new_ptr) { + DBG_RETURN(FAIL); + } + memcpy(new_ptr, chunk->ptr, chunk->size); + chunk->ptr = new_ptr; + pool->free_size += chunk->size; + chunk->size = size; + chunk->pool = NULL; /* now we have no pool memory */ + pool->refcount--; + } else { + /* If the chunk is > than asked size then free_memory increases, otherwise decreases*/ + pool->free_size += (chunk->size - size); + } + } else { + /* Not last chunk, if the user asks for less, give it to him */ + if (chunk->size >= size) { + ; /* nop */ + } else { + zend_uchar *new_ptr; + new_ptr = mnd_malloc(size); + if (!new_ptr) { + DBG_RETURN(FAIL); + } + memcpy(new_ptr, chunk->ptr, chunk->size); + chunk->ptr = new_ptr; + chunk->size = size; + chunk->pool = NULL; /* now we have non-pool memory */ + pool->refcount--; + } + } + } else { + zend_uchar *new_ptr = mnd_realloc(chunk->ptr, size); + if (!new_ptr) { + DBG_RETURN(FAIL); + } + chunk->ptr = new_ptr; + } + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_mempool_get_chunk */ +static +MYSQLND_MEMORY_POOL_CHUNK * mysqlnd_mempool_get_chunk(MYSQLND_MEMORY_POOL * pool, unsigned int size TSRMLS_DC) +{ + MYSQLND_MEMORY_POOL_CHUNK *chunk = NULL; + DBG_ENTER("mysqlnd_mempool_get_chunk"); + + chunk = mnd_malloc(sizeof(MYSQLND_MEMORY_POOL_CHUNK)); + if (chunk) { + chunk->free_chunk = mysqlnd_mempool_free_chunk; + chunk->resize_chunk = mysqlnd_mempool_resize_chunk; + chunk->size = size; + /* + Should not go over MYSQLND_MAX_PACKET_SIZE, since we + expect non-arena memory in mysqlnd_wireprotocol.c . We + realloc the non-arena memory. + */ + chunk->pool = pool; + if (size > pool->free_size) { + chunk->from_pool = FALSE; + chunk->ptr = mnd_malloc(size); + if (!chunk->ptr) { + chunk->free_chunk(chunk TSRMLS_CC); + chunk = NULL; + } + } else { + chunk->from_pool = TRUE; + ++pool->refcount; + chunk->ptr = pool->arena + (pool->arena_size - pool->free_size); + /* Last step, update free_size */ + pool->free_size -= size; + } + } + DBG_RETURN(chunk); +} +/* }}} */ + + +/* {{{ mysqlnd_mempool_create */ +PHPAPI MYSQLND_MEMORY_POOL * +mysqlnd_mempool_create(size_t arena_size TSRMLS_DC) +{ + /* We calloc, because we free(). We don't mnd_calloc() for a reason. */ + MYSQLND_MEMORY_POOL * ret = mnd_calloc(1, sizeof(MYSQLND_MEMORY_POOL)); + DBG_ENTER("mysqlnd_mempool_create"); + if (ret) { + ret->get_chunk = mysqlnd_mempool_get_chunk; + ret->free_size = ret->arena_size = arena_size ? arena_size : 0; + ret->refcount = 0; + /* OOM ? */ + ret->arena = mnd_malloc(ret->arena_size); + if (!ret->arena) { + mysqlnd_mempool_destroy(ret TSRMLS_CC); + ret = NULL; + } + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_mempool_destroy */ +PHPAPI void +mysqlnd_mempool_destroy(MYSQLND_MEMORY_POOL * pool TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_mempool_destroy"); + /* mnd_free will reference LOCK_access and might crash, depending on the caller...*/ + mnd_free(pool->arena); + mnd_free(pool); + DBG_VOID_RETURN; +} +/* }}} */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_block_alloc.h b/ext/mysqlnd/mysqlnd_block_alloc.h new file mode 100644 index 0000000..39ccbdc --- /dev/null +++ b/ext/mysqlnd/mysqlnd_block_alloc.h @@ -0,0 +1,39 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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$ */ + +#ifndef MYSQLND_BLOCK_ALLOC_H +#define MYSQLND_BLOCK_ALLOC_H + +PHPAPI MYSQLND_MEMORY_POOL * mysqlnd_mempool_create(size_t arena_size TSRMLS_DC); +PHPAPI void mysqlnd_mempool_destroy(MYSQLND_MEMORY_POOL * pool TSRMLS_DC); + +#endif /* MYSQLND_BLOCK_ALLOC_H */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_bt.c b/ext/mysqlnd/mysqlnd_bt.c new file mode 100644 index 0000000..3aeb8f2 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_bt.c @@ -0,0 +1,484 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: mysqlnd_debug.c 309303 2011-03-16 12:42:59Z andrey $ */ + +#include "php.h" +#include "Zend/zend_builtin_functions.h" + +/* Follows code borrowed from zend_builtin_functions.c because the functions there are static */ + +#if MYSQLND_UNICODE +/* {{{ gettraceasstring() macros */ +#define TRACE_APPEND_CHR(chr) \ + *str = (char*)erealloc(*str, *len + 1 + 1); \ + (*str)[(*len)++] = chr + +#define TRACE_APPEND_STRL(val, vallen) \ + { \ + int l = vallen; \ + *str = (char*)erealloc(*str, *len + l + 1); \ + memcpy((*str) + *len, val, l); \ + *len += l; \ + } + +#define TRACE_APPEND_USTRL(val, vallen) \ + { \ + zval tmp, copy; \ + int use_copy; \ + ZVAL_UNICODEL(&tmp, val, vallen, 1); \ + zend_make_printable_zval(&tmp, ©, &use_copy); \ + TRACE_APPEND_STRL(Z_STRVAL(copy), Z_STRLEN(copy)); \ + zval_dtor(©); \ + zval_dtor(&tmp); \ + } + +#define TRACE_APPEND_ZVAL(zv) \ + if (Z_TYPE_P((zv)) == IS_UNICODE) { \ + zval copy; \ + int use_copy; \ + zend_make_printable_zval((zv), ©, &use_copy); \ + TRACE_APPEND_STRL(Z_STRVAL(copy), Z_STRLEN(copy)); \ + zval_dtor(©); \ + } else { \ + TRACE_APPEND_STRL(Z_STRVAL_P((zv)), Z_STRLEN_P((zv))); \ + } + +#define TRACE_APPEND_STR(val) \ + TRACE_APPEND_STRL(val, sizeof(val)-1) + +#define TRACE_APPEND_KEY(key) \ + if (zend_ascii_hash_find(ht, key, sizeof(key), (void**)&tmp) == SUCCESS) { \ + if (Z_TYPE_PP(tmp) == IS_UNICODE) { \ + zval copy; \ + int use_copy; \ + zend_make_printable_zval(*tmp, ©, &use_copy); \ + TRACE_APPEND_STRL(Z_STRVAL(copy), Z_STRLEN(copy)); \ + zval_dtor(©); \ + } else { \ + TRACE_APPEND_STRL(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp)); \ + } \ + } +/* }}} */ + + +/* {{{ mysqlnd_build_trace_args */ +static int mysqlnd_build_trace_args(zval **arg TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) +{ + char **str; + int *len; + + str = va_arg(args, char**); + len = va_arg(args, int*); + + /* the trivial way would be to do: + * conver_to_string_ex(arg); + * append it and kill the now tmp arg. + * but that could cause some E_NOTICE and also damn long lines. + */ + + switch (Z_TYPE_PP(arg)) { + case IS_NULL: + TRACE_APPEND_STR("NULL, "); + break; + case IS_STRING: { + int l_added; + TRACE_APPEND_CHR('\''); + if (Z_STRLEN_PP(arg) > 15) { + TRACE_APPEND_STRL(Z_STRVAL_PP(arg), 15); + TRACE_APPEND_STR("...', "); + l_added = 15 + 6 + 1; /* +1 because of while (--l_added) */ + } else { + l_added = Z_STRLEN_PP(arg); + TRACE_APPEND_STRL(Z_STRVAL_PP(arg), l_added); + TRACE_APPEND_STR("', "); + l_added += 3 + 1; + } + while (--l_added) { + if ((unsigned char)(*str)[*len - l_added] < 32) { + (*str)[*len - l_added] = '?'; + } + } + break; + } + case IS_UNICODE: { + int l_added; + + /* + * We do not want to apply current error mode here, since + * zend_make_printable_zval() uses output encoding converter. + * Temporarily set output encoding converter to escape offending + * chars with \uXXXX notation. + */ + zend_set_converter_error_mode(ZEND_U_CONVERTER(UG(output_encoding_conv)), ZEND_FROM_UNICODE, ZEND_CONV_ERROR_ESCAPE_JAVA); + TRACE_APPEND_CHR('\''); + if (Z_USTRLEN_PP(arg) > 15) { + TRACE_APPEND_USTRL(Z_USTRVAL_PP(arg), 15); + TRACE_APPEND_STR("...', "); + l_added = 15 + 6 + 1; /* +1 because of while (--l_added) */ + } else { + l_added = Z_USTRLEN_PP(arg); + TRACE_APPEND_USTRL(Z_USTRVAL_PP(arg), l_added); + TRACE_APPEND_STR("', "); + l_added += 3 + 1; + } + /* + * Reset output encoding converter error mode. + */ + zend_set_converter_error_mode(ZEND_U_CONVERTER(UG(output_encoding_conv)), ZEND_FROM_UNICODE, UG(from_error_mode)); + while (--l_added) { + if ((unsigned char)(*str)[*len - l_added] < 32) { + (*str)[*len - l_added] = '?'; + } + } + break; + } + case IS_BOOL: + if (Z_LVAL_PP(arg)) { + TRACE_APPEND_STR("true, "); + } else { + TRACE_APPEND_STR("false, "); + } + break; + case IS_RESOURCE: + TRACE_APPEND_STR("Resource id #"); + /* break; */ + case IS_LONG: { + long lval = Z_LVAL_PP(arg); + char s_tmp[MAX_LENGTH_OF_LONG + 1]; + int l_tmp = zend_sprintf(s_tmp, "%ld", lval); /* SAFE */ + TRACE_APPEND_STRL(s_tmp, l_tmp); + TRACE_APPEND_STR(", "); + break; + } + case IS_DOUBLE: { + double dval = Z_DVAL_PP(arg); + char *s_tmp; + int l_tmp; + + s_tmp = emalloc(MAX_LENGTH_OF_DOUBLE + EG(precision) + 1); + l_tmp = zend_sprintf(s_tmp, "%.*G", (int) EG(precision), dval); /* SAFE */ + TRACE_APPEND_STRL(s_tmp, l_tmp); + /* %G already handles removing trailing zeros from the fractional part, yay */ + efree(s_tmp); + TRACE_APPEND_STR(", "); + break; + } + case IS_ARRAY: + TRACE_APPEND_STR("Array, "); + break; + case IS_OBJECT: { + zval tmp; + zstr class_name; + zend_uint class_name_len; + int dup; + + TRACE_APPEND_STR("Object("); + + dup = zend_get_object_classname(*arg, &class_name, &class_name_len TSRMLS_CC); + + ZVAL_UNICODEL(&tmp, class_name.u, class_name_len, 1); + convert_to_string_with_converter(&tmp, ZEND_U_CONVERTER(UG(output_encoding_conv))); + TRACE_APPEND_STRL(Z_STRVAL(tmp), Z_STRLEN(tmp)); + zval_dtor(&tmp); + + if(!dup) { + efree(class_name.v); + } + + TRACE_APPEND_STR("), "); + break; + } + default: + break; + } + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + + +static int mysqlnd_build_trace_string(zval **frame TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */ +{ + char *s_tmp, **str; + int *len, *num; + long line; + HashTable *ht = Z_ARRVAL_PP(frame); + zval **file, **tmp; + uint * level; + + level = va_arg(args, uint *); + str = va_arg(args, char**); + len = va_arg(args, int*); + num = va_arg(args, int*); + + if (!*level) { + return ZEND_HASH_APPLY_KEEP; + } + --*level; + + s_tmp = emalloc(1 + MAX_LENGTH_OF_LONG + 1 + 1); + sprintf(s_tmp, "#%d ", (*num)++); + TRACE_APPEND_STRL(s_tmp, strlen(s_tmp)); + efree(s_tmp); + if (zend_ascii_hash_find(ht, "file", sizeof("file"), (void**)&file) == SUCCESS) { + if (zend_ascii_hash_find(ht, "line", sizeof("line"), (void**)&tmp) == SUCCESS) { + line = Z_LVAL_PP(tmp); + } else { + line = 0; + } + TRACE_APPEND_ZVAL(*file); + s_tmp = emalloc(MAX_LENGTH_OF_LONG + 2 + 1); + sprintf(s_tmp, "(%ld): ", line); + TRACE_APPEND_STRL(s_tmp, strlen(s_tmp)); + efree(s_tmp); + } else { + TRACE_APPEND_STR("[internal function]: "); + } + TRACE_APPEND_KEY("class"); + TRACE_APPEND_KEY("type"); + TRACE_APPEND_KEY("function"); + TRACE_APPEND_CHR('('); + if (zend_ascii_hash_find(ht, "args", sizeof("args"), (void**)&tmp) == SUCCESS) { + int last_len = *len; + zend_hash_apply_with_arguments(Z_ARRVAL_PP(tmp) TSRMLS_CC, (apply_func_args_t)mysqlnd_build_trace_args, 2, str, len); + if (last_len != *len) { + *len -= 2; /* remove last ', ' */ + } + } + TRACE_APPEND_STR(")\n"); + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + + +#else /* PHP 5*/ + + +/* {{{ gettraceasstring() macros */ +#define TRACE_APPEND_CHR(chr) \ + *str = (char*)erealloc(*str, *len + 1 + 1); \ + (*str)[(*len)++] = chr + +#define TRACE_APPEND_STRL(val, vallen) \ + { \ + int l = vallen; \ + *str = (char*)erealloc(*str, *len + l + 1); \ + memcpy((*str) + *len, val, l); \ + *len += l; \ + } + +#define TRACE_APPEND_STR(val) \ + TRACE_APPEND_STRL(val, sizeof(val)-1) + +#define TRACE_APPEND_KEY(key) \ + if (zend_hash_find(ht, key, sizeof(key), (void**)&tmp) == SUCCESS) { \ + TRACE_APPEND_STRL(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp)); \ + } + +/* }}} */ + + +static int mysqlnd_build_trace_args(zval **arg TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */ +{ + char **str; + int *len; + + str = va_arg(args, char**); + len = va_arg(args, int*); + + /* the trivial way would be to do: + * conver_to_string_ex(arg); + * append it and kill the now tmp arg. + * but that could cause some E_NOTICE and also damn long lines. + */ + + switch (Z_TYPE_PP(arg)) { + case IS_NULL: + TRACE_APPEND_STR("NULL, "); + break; + case IS_STRING: { + int l_added; + TRACE_APPEND_CHR('\''); + if (Z_STRLEN_PP(arg) > 15) { + TRACE_APPEND_STRL(Z_STRVAL_PP(arg), 15); + TRACE_APPEND_STR("...', "); + l_added = 15 + 6 + 1; /* +1 because of while (--l_added) */ + } else { + l_added = Z_STRLEN_PP(arg); + TRACE_APPEND_STRL(Z_STRVAL_PP(arg), l_added); + TRACE_APPEND_STR("', "); + l_added += 3 + 1; + } + while (--l_added) { + if ((*str)[*len - l_added] < 32) { + (*str)[*len - l_added] = '?'; + } + } + break; + } + case IS_BOOL: + if (Z_LVAL_PP(arg)) { + TRACE_APPEND_STR("true, "); + } else { + TRACE_APPEND_STR("false, "); + } + break; + case IS_RESOURCE: + TRACE_APPEND_STR("Resource id #"); + /* break; */ + case IS_LONG: { + long lval = Z_LVAL_PP(arg); + char s_tmp[MAX_LENGTH_OF_LONG + 1]; + int l_tmp = zend_sprintf(s_tmp, "%ld", lval); /* SAFE */ + TRACE_APPEND_STRL(s_tmp, l_tmp); + TRACE_APPEND_STR(", "); + break; + } + case IS_DOUBLE: { + double dval = Z_DVAL_PP(arg); + char *s_tmp; + int l_tmp; + + s_tmp = emalloc(MAX_LENGTH_OF_DOUBLE + EG(precision) + 1); + l_tmp = zend_sprintf(s_tmp, "%.*G", (int) EG(precision), dval); /* SAFE */ + TRACE_APPEND_STRL(s_tmp, l_tmp); + /* %G already handles removing trailing zeros from the fractional part, yay */ + efree(s_tmp); + TRACE_APPEND_STR(", "); + break; + } + case IS_ARRAY: + TRACE_APPEND_STR("Array, "); + break; + case IS_OBJECT: { + char *class_name; + zend_uint class_name_len; + int dupl; + + TRACE_APPEND_STR("Object("); + + dupl = zend_get_object_classname(*arg, (const char **)&class_name, &class_name_len TSRMLS_CC); + + TRACE_APPEND_STRL(class_name, class_name_len); + if (!dupl) { + efree(class_name); + } + + TRACE_APPEND_STR("), "); + break; + } + default: + break; + } + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +static int mysqlnd_build_trace_string(zval **frame TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */ +{ + char *s_tmp, **str; + int *len, *num; + long line; + HashTable *ht = Z_ARRVAL_PP(frame); + zval **file, **tmp; + uint * level; + + level = va_arg(args, uint *); + str = va_arg(args, char**); + len = va_arg(args, int*); + num = va_arg(args, int*); + + if (!*level) { + return ZEND_HASH_APPLY_KEEP; + } + --*level; + + s_tmp = emalloc(1 + MAX_LENGTH_OF_LONG + 1 + 1); + sprintf(s_tmp, "#%d ", (*num)++); + TRACE_APPEND_STRL(s_tmp, strlen(s_tmp)); + efree(s_tmp); + if (zend_hash_find(ht, "file", sizeof("file"), (void**)&file) == SUCCESS) { + if (zend_hash_find(ht, "line", sizeof("line"), (void**)&tmp) == SUCCESS) { + line = Z_LVAL_PP(tmp); + } else { + line = 0; + } + s_tmp = emalloc(Z_STRLEN_PP(file) + MAX_LENGTH_OF_LONG + 4 + 1); + sprintf(s_tmp, "%s(%ld): ", Z_STRVAL_PP(file), line); + TRACE_APPEND_STRL(s_tmp, strlen(s_tmp)); + efree(s_tmp); + } else { + TRACE_APPEND_STR("[internal function]: "); + } + TRACE_APPEND_KEY("class"); + TRACE_APPEND_KEY("type"); + TRACE_APPEND_KEY("function"); + TRACE_APPEND_CHR('('); + if (zend_hash_find(ht, "args", sizeof("args"), (void**)&tmp) == SUCCESS) { + int last_len = *len; + zend_hash_apply_with_arguments(Z_ARRVAL_PP(tmp) TSRMLS_CC, (apply_func_args_t)mysqlnd_build_trace_args, 2, str, len); + if (last_len != *len) { + *len -= 2; /* remove last ', ' */ + } + } + TRACE_APPEND_STR(")\n"); + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ +#endif + + +PHPAPI char * mysqlnd_get_backtrace(uint max_levels, size_t * length TSRMLS_DC) +{ + zval *trace; + char *res = estrdup(""), **str = &res, *s_tmp; + int res_len = 0, *len = &res_len, num = 0; + if (max_levels == 0) { + max_levels = 99999; + } + + MAKE_STD_ZVAL(trace); + zend_fetch_debug_backtrace(trace, 0, 0, 0 TSRMLS_CC); + + zend_hash_apply_with_arguments(Z_ARRVAL_P(trace) TSRMLS_CC, (apply_func_args_t)mysqlnd_build_trace_string, 4, &max_levels, str, len, &num); + zval_ptr_dtor(&trace); + + if (max_levels) { + s_tmp = emalloc(1 + MAX_LENGTH_OF_LONG + 7 + 1); + sprintf(s_tmp, "#%d {main}", num); + TRACE_APPEND_STRL(s_tmp, strlen(s_tmp)); + efree(s_tmp); + } + + res[res_len] = '\0'; + *length = res_len; + + return res; +} + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_charset.c b/ext/mysqlnd/mysqlnd_charset.c new file mode 100644 index 0000000..8766a4b --- /dev/null +++ b/ext/mysqlnd/mysqlnd_charset.c @@ -0,0 +1,864 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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> | + +----------------------------------------------------------------------+ +*/ +#include "php.h" +#include "php_globals.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_debug.h" +#include "mysqlnd_charset.h" + +/* {{{ utf8 functions */ +static unsigned int check_mb_utf8mb3_sequence(const char *start, const char *end) +{ + zend_uchar c; + + if (start >= end) { + return 0; + } + + c = (zend_uchar) start[0]; + + if (c < 0x80) { + return 1; /* single byte character */ + } + if (c < 0xC2) { + return 0; /* invalid mb character */ + } + if (c < 0xE0) { + if (start + 2 > end) { + return 0; /* too small */ + } + if (!(((zend_uchar)start[1] ^ 0x80) < 0x40)) { + return 0; + } + return 2; + } + if (c < 0xF0) { + if (start + 3 > end) { + return 0; /* too small */ + } + if (!(((zend_uchar)start[1] ^ 0x80) < 0x40 && ((zend_uchar)start[2] ^ 0x80) < 0x40 && + (c >= 0xE1 || (zend_uchar)start[1] >= 0xA0))) { + return 0; /* invalid utf8 character */ + } + return 3; + } + return 0; +} + + +static unsigned int check_mb_utf8_sequence(const char *start, const char *end) +{ + zend_uchar c; + + if (start >= end) { + return 0; + } + + c = (zend_uchar) start[0]; + + if (c < 0x80) { + return 1; /* single byte character */ + } + if (c < 0xC2) { + return 0; /* invalid mb character */ + } + if (c < 0xE0) { + if (start + 2 > end) { + return 0; /* too small */ + } + if (!(((zend_uchar)start[1] ^ 0x80) < 0x40)) { + return 0; + } + return 2; + } + if (c < 0xF0) { + if (start + 3 > end) { + return 0; /* too small */ + } + if (!(((zend_uchar)start[1] ^ 0x80) < 0x40 && ((zend_uchar)start[2] ^ 0x80) < 0x40 && + (c >= 0xE1 || (zend_uchar)start[1] >= 0xA0))) { + return 0; /* invalid utf8 character */ + } + return 3; + } + if (c < 0xF5) { + if (start + 4 > end) { /* We need 4 characters */ + return 0; /* too small */ + } + + /* + UTF-8 quick four-byte mask: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + Encoding allows to encode U+00010000..U+001FFFFF + + The maximum character defined in the Unicode standard is U+0010FFFF. + Higher characters U+00110000..U+001FFFFF are not used. + + 11110000.10010000.10xxxxxx.10xxxxxx == F0.90.80.80 == U+00010000 (min) + 11110100.10001111.10111111.10111111 == F4.8F.BF.BF == U+0010FFFF (max) + + Valid codes: + [F0][90..BF][80..BF][80..BF] + [F1][80..BF][80..BF][80..BF] + [F2][80..BF][80..BF][80..BF] + [F3][80..BF][80..BF][80..BF] + [F4][80..8F][80..BF][80..BF] + */ + + if (!(((zend_uchar)start[1] ^ 0x80) < 0x40 && + ((zend_uchar)start[2] ^ 0x80) < 0x40 && + ((zend_uchar)start[3] ^ 0x80) < 0x40 && + (c >= 0xf1 || (zend_uchar)start[1] >= 0x90) && + (c <= 0xf3 || (zend_uchar)start[1] <= 0x8F))) + { + return 0; /* invalid utf8 character */ + } + return 4; + } + return 0; +} + +static unsigned int check_mb_utf8mb3_valid(const char *start, const char *end) +{ + unsigned int len = check_mb_utf8mb3_sequence(start, end); + return (len > 1)? len:0; +} + +static unsigned int check_mb_utf8_valid(const char *start, const char *end) +{ + unsigned int len = check_mb_utf8_sequence(start, end); + return (len > 1)? len:0; +} + + +static unsigned int mysqlnd_mbcharlen_utf8mb3(unsigned int utf8) +{ + if (utf8 < 0x80) { + return 1; /* single byte character */ + } + if (utf8 < 0xC2) { + return 0; /* invalid multibyte header */ + } + if (utf8 < 0xE0) { + return 2; /* double byte character */ + } + if (utf8 < 0xF0) { + return 3; /* triple byte character */ + } + return 0; +} + + +static unsigned int mysqlnd_mbcharlen_utf8(unsigned int utf8) +{ + if (utf8 < 0x80) { + return 1; /* single byte character */ + } + if (utf8 < 0xC2) { + return 0; /* invalid multibyte header */ + } + if (utf8 < 0xE0) { + return 2; /* double byte character */ + } + if (utf8 < 0xF0) { + return 3; /* triple byte character */ + } + if (utf8 < 0xF8) { + return 4; /* four byte character */ + } + return 0; +} +/* }}} */ + + +/* {{{ big5 functions */ +#define valid_big5head(c) (0xA1 <= (unsigned int)(c) && (unsigned int)(c) <= 0xF9) +#define valid_big5tail(c) ((0x40 <= (unsigned int)(c) && (unsigned int)(c) <= 0x7E) || \ + (0xA1 <= (unsigned int)(c) && (unsigned int)(c) <= 0xFE)) + +#define isbig5code(c,d) (isbig5head(c) && isbig5tail(d)) + +static unsigned int check_mb_big5(const char *start, const char *end) +{ + return (valid_big5head(*(start)) && (end - start) > 1 && valid_big5tail(*(start + 1)) ? 2 : 0); +} + + +static unsigned int mysqlnd_mbcharlen_big5(unsigned int big5) +{ + return (valid_big5head(big5)) ? 2 : 1; +} +/* }}} */ + + +/* {{{ cp932 functions */ +#define valid_cp932head(c) ((0x81 <= (c) && (c) <= 0x9F) || (0xE0 <= (c) && c <= 0xFC)) +#define valid_cp932tail(c) ((0x40 <= (c) && (c) <= 0x7E) || (0x80 <= (c) && c <= 0xFC)) + + +static unsigned int check_mb_cp932(const char *start, const char *end) +{ + return (valid_cp932head((zend_uchar)start[0]) && (end - start > 1) && + valid_cp932tail((zend_uchar)start[1])) ? 2 : 0; +} + + +static unsigned int mysqlnd_mbcharlen_cp932(unsigned int cp932) +{ + return (valid_cp932head((zend_uchar)cp932)) ? 2 : 1; +} +/* }}} */ + + +/* {{{ euckr functions */ +#define valid_euckr(c) ((0xA1 <= (zend_uchar)(c) && (zend_uchar)(c) <= 0xFE)) + +static unsigned int check_mb_euckr(const char *start, const char *end) +{ + if (end - start <= 1) { + return 0; /* invalid length */ + } + if (*(zend_uchar *)start < 0x80) { + return 0; /* invalid euckr character */ + } + if (valid_euckr(start[1])) { + return 2; + } + return 0; +} + + +static unsigned int mysqlnd_mbcharlen_euckr(unsigned int kr) +{ + return (valid_euckr(kr)) ? 2 : 1; +} +/* }}} */ + + +/* {{{ eucjpms functions */ +#define valid_eucjpms(c) (((c) & 0xFF) >= 0xA1 && ((c) & 0xFF) <= 0xFE) +#define valid_eucjpms_kata(c) (((c) & 0xFF) >= 0xA1 && ((c) & 0xFF) <= 0xDF) +#define valid_eucjpms_ss2(c) (((c) & 0xFF) == 0x8E) +#define valid_eucjpms_ss3(c) (((c) & 0xFF) == 0x8F) + +static unsigned int check_mb_eucjpms(const char *start, const char *end) +{ + if (*((zend_uchar *)start) < 0x80) { + return 0; /* invalid eucjpms character */ + } + if (valid_eucjpms(start[0]) && (end - start) > 1 && valid_eucjpms(start[1])) { + return 2; + } + if (valid_eucjpms_ss2(start[0]) && (end - start) > 1 && valid_eucjpms_kata(start[1])) { + return 2; + } + if (valid_eucjpms_ss3(start[0]) && (end - start) > 2 && valid_eucjpms(start[1]) && + valid_eucjpms(start[2])) { + return 2; + } + return 0; +} + + +static unsigned int mysqlnd_mbcharlen_eucjpms(unsigned int jpms) +{ + if (valid_eucjpms(jpms) || valid_eucjpms_ss2(jpms)) { + return 2; + } + if (valid_eucjpms_ss3(jpms)) { + return 3; + } + return 1; +} +/* }}} */ + + +/* {{{ gb2312 functions */ +#define valid_gb2312_head(c) (0xA1 <= (zend_uchar)(c) && (zend_uchar)(c) <= 0xF7) +#define valid_gb2312_tail(c) (0xA1 <= (zend_uchar)(c) && (zend_uchar)(c) <= 0xFE) + + +static unsigned int check_mb_gb2312(const char *start, const char *end) +{ + return (valid_gb2312_head((unsigned int)start[0]) && end - start > 1 && + valid_gb2312_tail((unsigned int)start[1])) ? 2 : 0; +} + + +static unsigned int mysqlnd_mbcharlen_gb2312(unsigned int gb) +{ + return (valid_gb2312_head(gb)) ? 2 : 1; +} +/* }}} */ + + +/* {{{ gbk functions */ +#define valid_gbk_head(c) (0x81<=(zend_uchar)(c) && (zend_uchar)(c)<=0xFE) +#define valid_gbk_tail(c) ((0x40<=(zend_uchar)(c) && (zend_uchar)(c)<=0x7E) || (0x80<=(zend_uchar)(c) && (zend_uchar)(c)<=0xFE)) + +static unsigned int check_mb_gbk(const char *start, const char *end) +{ + return (valid_gbk_head(start[0]) && (end) - (start) > 1 && valid_gbk_tail(start[1])) ? 2 : 0; +} + +static unsigned int mysqlnd_mbcharlen_gbk(unsigned int gbk) +{ + return (valid_gbk_head(gbk) ? 2 : 1); +} +/* }}} */ + + +/* {{{ sjis functions */ +#define valid_sjis_head(c) ((0x81 <= (c) && (c) <= 0x9F) || (0xE0 <= (c) && (c) <= 0xFC)) +#define valid_sjis_tail(c) ((0x40 <= (c) && (c) <= 0x7E) || (0x80 <= (c) && (c) <= 0xFC)) + + +static unsigned int check_mb_sjis(const char *start, const char *end) +{ + return (valid_sjis_head((zend_uchar)start[0]) && (end - start) > 1 && valid_sjis_tail((zend_uchar)start[1])) ? 2 : 0; +} + + +static unsigned int mysqlnd_mbcharlen_sjis(unsigned int sjis) +{ + return (valid_sjis_head((zend_uchar)sjis)) ? 2 : 1; +} +/* }}} */ + + +/* {{{ ucs2 functions */ +static unsigned int check_mb_ucs2(const char *start __attribute((unused)), const char *end __attribute((unused))) +{ + return 2; /* always 2 */ +} + +static unsigned int mysqlnd_mbcharlen_ucs2(unsigned int ucs2 __attribute((unused))) +{ + return 2; /* always 2 */ +} +/* }}} */ + + +/* {{{ ujis functions */ +#define valid_ujis(c) ((0xA1 <= ((c)&0xFF) && ((c)&0xFF) <= 0xFE)) +#define valid_ujis_kata(c) ((0xA1 <= ((c)&0xFF) && ((c)&0xFF) <= 0xDF)) +#define valid_ujis_ss2(c) (((c)&0xFF) == 0x8E) +#define valid_ujis_ss3(c) (((c)&0xFF) == 0x8F) + +static unsigned int check_mb_ujis(const char *start, const char *end) +{ + if (*(zend_uchar*)start < 0x80) { + return 0; /* invalid ujis character */ + } + if (valid_ujis(*(start)) && valid_ujis(*((start)+1))) { + return 2; + } + if (valid_ujis_ss2(*(start)) && valid_ujis_kata(*((start)+1))) { + return 2; + } + if (valid_ujis_ss3(*(start)) && (end-start) > 2 && valid_ujis(*((start)+1)) && valid_ujis(*((start)+2))) { + return 3; + } + return 0; +} + + +static unsigned int mysqlnd_mbcharlen_ujis(unsigned int ujis) +{ + return (valid_ujis(ujis)? 2: valid_ujis_ss2(ujis)? 2: valid_ujis_ss3(ujis)? 3: 1); +} +/* }}} */ + + + +/* {{{ utf16 functions */ +#define UTF16_HIGH_HEAD(x) ((((zend_uchar) (x)) & 0xFC) == 0xD8) +#define UTF16_LOW_HEAD(x) ((((zend_uchar) (x)) & 0xFC) == 0xDC) + +static unsigned int check_mb_utf16(const char *start, const char *end) +{ + if (start + 2 > end) { + return 0; + } + + if (UTF16_HIGH_HEAD(*start)) { + return (start + 4 <= end) && UTF16_LOW_HEAD(start[2]) ? 4 : 0; + } + + if (UTF16_LOW_HEAD(*start)) { + return 0; + } + return 2; +} + + +static uint mysqlnd_mbcharlen_utf16(unsigned int utf16) +{ + return UTF16_HIGH_HEAD(utf16) ? 4 : 2; +} +/* }}} */ + + +/* {{{ utf32 functions */ +static uint +check_mb_utf32(const char *start __attribute((unused)), const char *end __attribute((unused))) +{ + return 4; +} + + +static uint +mysqlnd_mbcharlen_utf32(unsigned int utf32 __attribute((unused))) +{ + return 4; +} +/* }}} */ + +/* + The server compiles sometimes the full utf-8 (the mb4) as utf8m4, and the old as utf8, + for BC reasons. Sometimes, utf8mb4 is just utf8 but the old charsets are utf8mb3. + Change easily now, with a macro, could be made compilastion dependable. +*/ + +#define UTF8_MB4 "utf8mb4" +#define UTF8_MB3 "utf8" + +/* {{{ mysqlnd_charsets */ +const MYSQLND_CHARSET mysqlnd_charsets[] = +{ + { 1, "big5","big5_chinese_ci", 1, 2, "", mysqlnd_mbcharlen_big5, check_mb_big5}, + { 3, "dec8", "dec8_swedisch_ci", 1, 1, "", NULL, NULL}, + { 4, "cp850", "cp850_general_ci", 1, 1, "", NULL, NULL}, + { 6, "hp8", "hp8_english_ci", 1, 1, "", NULL, NULL}, + { 7, "koi8r", "koi8r_general_ci", 1, 1, "", NULL, NULL}, + { 8, "latin1", "latin1_swedish_ci", 1, 1, "", NULL, NULL}, + { 5, "latin1", "latin1_german_ci", 1, 1, "", NULL, NULL}, /* should be after 0x9 because swedish_ci is the default collation */ + { 9, "latin2", "latin2_general_ci", 1, 1, "", NULL, NULL}, + { 2, "latin2", "latin2_czech_cs", 1, 1, "", NULL, NULL}, /* should be after 0x9 because general_ci is the default collation */ + { 10, "swe7", "swe7_swedish_ci", 1, 1, "", NULL, NULL}, + { 11, "ascii", "ascii_general_ci", 1, 1, "", NULL, NULL}, + { 12, "ujis", "ujis_japanese_ci", 1, 3, "", mysqlnd_mbcharlen_ujis, check_mb_ujis}, + { 13, "sjis", "sjis_japanese_ci", 1, 2, "", mysqlnd_mbcharlen_sjis, check_mb_sjis}, + { 16, "hebrew", "hebrew_general_ci", 1, 1, "", NULL, NULL}, + { 17, "filename", "filename", 1, 5, "", NULL, NULL}, + { 18, "tis620", "tis620_thai_ci", 1, 1, "", NULL, NULL}, + { 19, "euckr", "euckr_korean_ci", 1, 2, "", mysqlnd_mbcharlen_euckr, check_mb_euckr}, + { 21, "latin2", "latin2_hungarian_ci", 1, 1, "", NULL, NULL}, + { 27, "latin2", "latin2_croatian_ci", 1, 1, "", NULL, NULL}, + { 22, "koi8u", "koi8u_general_ci", 1, 1, "", NULL, NULL}, + { 24, "gb2312", "gb2312_chinese_ci", 1, 2, "", mysqlnd_mbcharlen_gb2312, check_mb_gb2312}, + { 25, "greek", "greek_general_ci", 1, 1, "", NULL, NULL}, + { 26, "cp1250", "cp1250_general_ci", 1, 1, "", NULL, NULL}, + { 28, "gbk", "gbk_chinese_ci", 1, 2, "", mysqlnd_mbcharlen_gbk, check_mb_gbk}, + { 30, "latin5", "latin5_turkish_ci", 1, 1, "", NULL, NULL}, + { 31, "latin1", "latin1_german2_ci", 1, 1, "", NULL, NULL}, + { 15, "latin1", "latin1_danish_ci", 1, 1, "", NULL, NULL}, + { 32, "armscii8", "armscii8_general_ci", 1, 1, "", NULL, NULL}, + { 33, UTF8_MB3, UTF8_MB3"_general_ci", 1, 3, "UTF-8 Unicode", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 35, "ucs2", "ucs2_general_ci", 2, 2, "UCS-2 Unicode", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 36, "cp866", "cp866_general_ci", 1, 1, "", NULL, NULL}, + { 37, "keybcs2", "keybcs2_general_ci", 1, 1, "", NULL, NULL}, + { 38, "macce", "macce_general_ci", 1, 1, "", NULL, NULL}, + { 39, "macroman", "macroman_general_ci", 1, 1, "", NULL, NULL}, + { 40, "cp852", "cp852_general_ci", 1, 1, "", NULL, NULL}, + { 41, "latin7", "latin7_general_ci", 1, 1, "", NULL, NULL}, + { 20, "latin7", "latin7_estonian_cs", 1, 1, "", NULL, NULL}, + { 57, "cp1256", "cp1256_general_ci", 1, 1, "", NULL, NULL}, + { 59, "cp1257", "cp1257_general_ci", 1, 1, "", NULL, NULL}, + { 63, "binary", "binary", 1, 1, "", NULL, NULL}, + { 97, "eucjpms", "eucjpms_japanese_ci", 1, 3, "", mysqlnd_mbcharlen_eucjpms, check_mb_eucjpms}, + { 29, "cp1257", "cp1257_lithunian_ci", 1, 1, "", NULL, NULL}, + { 31, "latin1", "latin1_german2_ci", 1, 1, "", NULL, NULL}, + { 34, "cp1250", "cp1250_czech_cs", 1, 1, "", NULL, NULL}, + { 42, "latin7", "latin7_general_cs", 1, 1, "", NULL, NULL}, + { 43, "macce", "macce_bin", 1, 1, "", NULL, NULL}, + { 44, "cp1250", "cp1250_croatian_ci", 1, 1, "", NULL, NULL}, + { 45, UTF8_MB4, UTF8_MB4"_general_ci", 1, 4, "UTF-8 Unicode", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 46, UTF8_MB4, UTF8_MB4"_bin", 1, 4, "UTF-8 Unicode", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 47, "latin1", "latin1_bin", 1, 1, "", NULL, NULL}, + { 48, "latin1", "latin1_general_ci", 1, 1, "", NULL, NULL}, + { 49, "latin1", "latin1_general_cs", 1, 1, "", NULL, NULL}, + { 51, "cp1251", "cp1251_general_ci", 1, 1, "", NULL, NULL}, + { 14, "cp1251", "cp1251_bulgarian_ci", 1, 1, "", NULL, NULL}, + { 23, "cp1251", "cp1251_ukrainian_ci", 1, 1, "", NULL, NULL}, + { 50, "cp1251", "cp1251_bin", 1, 1, "", NULL, NULL}, + { 52, "cp1251", "cp1251_general_cs", 1, 1, "", NULL, NULL}, + { 53, "macroman", "macroman_bin", 1, 1, "", NULL, NULL}, + { 54, "utf16", "utf16_general_ci", 2, 4, "UTF-16 Unicode", mysqlnd_mbcharlen_utf16, check_mb_utf16}, + { 55, "utf16", "utf16_bin", 2, 4, "UTF-16 Unicode", mysqlnd_mbcharlen_utf16, check_mb_utf16}, + { 56, "utf16le", "utf16le_general_ci", 2, 4, "UTF-16LE Unicode", mysqlnd_mbcharlen_utf16, check_mb_utf16}, + { 58, "cp1257", "cp1257_bin", 1, 1, "", NULL, NULL}, +#ifdef USED_TO_BE_SO_BEFORE_MYSQL_5_5 + { 60, "armascii8", "armascii8_bin", 1, 1, "", NULL, NULL}, +#endif +/*55*/{ 60, "utf32", "utf32_general_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*55*/{ 61, "utf32", "utf32_bin", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, + { 62, "utf16le", "utf16le_bin", 2, 4, "UTF-16LE Unicode", mysqlnd_mbcharlen_utf16, check_mb_utf16}, + { 65, "ascii", "ascii_bin", 1, 1, "", NULL, NULL}, + { 66, "cp1250", "cp1250_bin", 1, 1, "", NULL, NULL}, + { 67, "cp1256", "cp1256_bin", 1, 1, "", NULL, NULL}, + { 68, "cp866", "cp866_bin", 1, 1, "", NULL, NULL}, + { 69, "dec8", "dec8_bin", 1, 1, "", NULL, NULL}, + { 70, "greek", "greek_bin", 1, 1, "", NULL, NULL}, + { 71, "hebew", "hebrew_bin", 1, 1, "", NULL, NULL}, + { 72, "hp8", "hp8_bin", 1, 1, "", NULL, NULL}, + { 73, "keybcs2", "keybcs2_bin", 1, 1, "", NULL, NULL}, + { 74, "koi8r", "koi8r_bin", 1, 1, "", NULL, NULL}, + { 75, "koi8u", "koi8u_bin", 1, 1, "", NULL, NULL}, + { 77, "latin2", "latin2_bin", 1, 1, "", NULL, NULL}, + { 78, "latin5", "latin5_bin", 1, 1, "", NULL, NULL}, + { 79, "latin7", "latin7_bin", 1, 1, "", NULL, NULL}, + { 80, "cp850", "cp850_bin", 1, 1, "", NULL, NULL}, + { 81, "cp852", "cp852_bin", 1, 1, "", NULL, NULL}, + { 82, "swe7", "swe7_bin", 1, 1, "", NULL, NULL}, + { 83, UTF8_MB3, UTF8_MB3"_bin", 1, 3, "UTF-8 Unicode", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 84, "big5", "big5_bin", 1, 2, "", mysqlnd_mbcharlen_big5, check_mb_big5}, + { 85, "euckr", "euckr_bin", 1, 2, "", mysqlnd_mbcharlen_euckr, check_mb_euckr}, + { 86, "gb2312", "gb2312_bin", 1, 2, "", mysqlnd_mbcharlen_gb2312, check_mb_gb2312}, + { 87, "gbk", "gbk_bin", 1, 2, "", mysqlnd_mbcharlen_gbk, check_mb_gbk}, + { 88, "sjis", "sjis_bin", 1, 2, "", mysqlnd_mbcharlen_sjis, check_mb_sjis}, + { 89, "tis620", "tis620_bin", 1, 1, "", NULL, NULL}, + { 90, "ucs2", "ucs2_bin", 2, 2, "UCS-2 Unicode", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 91, "ujis", "ujis_bin", 1, 3, "", mysqlnd_mbcharlen_ujis, check_mb_ujis}, + { 92, "geostd8", "geostd8_general_ci", 1, 1, "", NULL, NULL}, + { 93, "geostd8", "geostd8_bin", 1, 1, "", NULL, NULL}, + { 94, "latin1", "latin1_spanish_ci", 1, 1, "", NULL, NULL}, + { 95, "cp932", "cp932_japanese_ci", 1, 2, "", mysqlnd_mbcharlen_cp932, check_mb_cp932}, + { 96, "cp932", "cp932_bin", 1, 2, "", mysqlnd_mbcharlen_cp932, check_mb_cp932}, + { 97, "eucjpms", "eucjpms_japanese_ci", 1, 3, "", mysqlnd_mbcharlen_eucjpms, check_mb_eucjpms}, + { 98, "eucjpms", "eucjpms_bin", 1, 3, "", mysqlnd_mbcharlen_eucjpms, check_mb_eucjpms}, + { 99, "cp1250", "cp1250_polish_ci", 1, 1, "", NULL, NULL}, + { 128, "ucs2", "ucs2_unicode_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 129, "ucs2", "ucs2_icelandic_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 130, "ucs2", "ucs2_latvian_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 131, "ucs2", "ucs2_romanian_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 132, "ucs2", "ucs2_slovenian_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 133, "ucs2", "ucs2_polish_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 134, "ucs2", "ucs2_estonian_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 135, "ucs2", "ucs2_spanish_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 136, "ucs2", "ucs2_swedish_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 137, "ucs2", "ucs2_turkish_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 138, "ucs2", "ucs2_czech_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 139, "ucs2", "ucs2_danish_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 140, "ucs2", "ucs2_lithunian_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 141, "ucs2", "ucs2_slovak_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 142, "ucs2", "ucs2_spanish2_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 143, "ucs2", "ucs2_roman_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 144, "ucs2", "ucs2_persian_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 145, "ucs2", "ucs2_esperanto_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 146, "ucs2", "ucs2_hungarian_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 147, "ucs2", "ucs2_sinhala_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 148, "ucs2", "ucs2_german2_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 149, "ucs2", "ucs2_croatian_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 150, "ucs2", "ucs2_unicode_520_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 151, "ucs2", "ucs2_vietnamese_ci", 2, 2, "", mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + +/*56*/{160, "utf32", "utf32_unicode_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{161, "utf32", "utf32_icelandic_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{162, "utf32", "utf32_latvian_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{163, "utf32", "utf32_romanian_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{164, "utf32", "utf32_slovenian_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{165, "utf32", "utf32_polish_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{166, "utf32", "utf32_estonian_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{167, "utf32", "utf32_spanish_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{168, "utf32", "utf32_swedish_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{169, "utf32", "utf32_turkish_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{170, "utf32", "utf32_czech_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{171, "utf32", "utf32_danish_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{172, "utf32", "utf32_lithuanian_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{173, "utf32", "utf32_slovak_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{174, "utf32", "utf32_spanish2_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{175, "utf32", "utf32_roman_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{176, "utf32", "utf32_persian_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{177, "utf32", "utf32_esperanto_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{178, "utf32", "utf32_hungarian_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{179, "utf32", "utf32_sinhala_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{180, "utf32", "utf32_german2_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{181, "utf32", "utf32_croatian_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{182, "utf32", "utf32_unicode_520_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, +/*56*/{183, "utf32", "utf32_vietnamese_ci", 4, 4, "UTF-32 Unicode", mysqlnd_mbcharlen_utf32, check_mb_utf32}, + + { 192, UTF8_MB3, UTF8_MB3"_general_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 193, UTF8_MB3, UTF8_MB3"_icelandic_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 194, UTF8_MB3, UTF8_MB3"_latvian_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 195, UTF8_MB3, UTF8_MB3"_romanian_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 196, UTF8_MB3, UTF8_MB3"_slovenian_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 197, UTF8_MB3, UTF8_MB3"_polish_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 198, UTF8_MB3, UTF8_MB3"_estonian_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 199, UTF8_MB3, UTF8_MB3"_spanish_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 200, UTF8_MB3, UTF8_MB3"_swedish_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 201, UTF8_MB3, UTF8_MB3"_turkish_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 202, UTF8_MB3, UTF8_MB3"_czech_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 203, UTF8_MB3, UTF8_MB3"_danish_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid }, + { 204, UTF8_MB3, UTF8_MB3"_lithunian_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid }, + { 205, UTF8_MB3, UTF8_MB3"_slovak_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 206, UTF8_MB3, UTF8_MB3"_spanish2_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 207, UTF8_MB3, UTF8_MB3"_roman_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 208, UTF8_MB3, UTF8_MB3"_persian_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 209, UTF8_MB3, UTF8_MB3"_esperanto_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 210, UTF8_MB3, UTF8_MB3"_hungarian_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 211, UTF8_MB3, UTF8_MB3"_sinhala_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 211, UTF8_MB3, UTF8_MB3"_german2_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 213, UTF8_MB3, UTF8_MB3"_croatian_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 214, UTF8_MB3, UTF8_MB3"_unicode_520_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + { 215, UTF8_MB3, UTF8_MB3"_vietnamese_ci", 1, 3, "", mysqlnd_mbcharlen_utf8mb3, check_mb_utf8mb3_valid}, + + { 224, UTF8_MB4, UTF8_MB4"_unicode_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 225, UTF8_MB4, UTF8_MB4"_icelandic_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 226, UTF8_MB4, UTF8_MB4"_latvian_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 227, UTF8_MB4, UTF8_MB4"_romanian_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 228, UTF8_MB4, UTF8_MB4"_slovenian_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 229, UTF8_MB4, UTF8_MB4"_polish_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 230, UTF8_MB4, UTF8_MB4"_estonian_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 231, UTF8_MB4, UTF8_MB4"_spanish_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 232, UTF8_MB4, UTF8_MB4"_swedish_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 233, UTF8_MB4, UTF8_MB4"_turkish_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 234, UTF8_MB4, UTF8_MB4"_czech_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 235, UTF8_MB4, UTF8_MB4"_danish_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 236, UTF8_MB4, UTF8_MB4"_lithuanian_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 237, UTF8_MB4, UTF8_MB4"_slovak_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 238, UTF8_MB4, UTF8_MB4"_spanish2_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 239, UTF8_MB4, UTF8_MB4"_roman_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 240, UTF8_MB4, UTF8_MB4"_persian_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 241, UTF8_MB4, UTF8_MB4"_esperanto_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 242, UTF8_MB4, UTF8_MB4"_hungarian_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 243, UTF8_MB4, UTF8_MB4"_sinhala_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 244, UTF8_MB4, UTF8_MB4"_german2_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 245, UTF8_MB4, UTF8_MB4"_croatian_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 246, UTF8_MB4, UTF8_MB4"_unicode_520_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 247, UTF8_MB4, UTF8_MB4"_vietnamese_ci", 1, 4, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + + { 254, UTF8_MB3, UTF8_MB3"_general_cs", 1, 3, "", mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 0, NULL, NULL, 0, 0, NULL, NULL, NULL} +}; +/* }}} */ + + +/* {{{ mysqlnd_find_charset_nr */ +PHPAPI const MYSQLND_CHARSET * mysqlnd_find_charset_nr(unsigned int charsetnr) +{ + const MYSQLND_CHARSET * c = mysqlnd_charsets; + + do { + if (c->nr == charsetnr) { + return c; + } + ++c; + } while (c[0].nr != 0); + return NULL; +} +/* }}} */ + + +/* {{{ mysqlnd_find_charset_name */ +PHPAPI const MYSQLND_CHARSET * mysqlnd_find_charset_name(const char * const name) +{ + if (name) { + const MYSQLND_CHARSET * c = mysqlnd_charsets; + do { + if (!strcasecmp(c->name, name)) { + return c; + } + ++c; + } while (c[0].nr != 0); + } + return NULL; +} +/* }}} */ + + +/* {{{ mysqlnd_cset_escape_quotes */ +PHPAPI ulong mysqlnd_cset_escape_quotes(const MYSQLND_CHARSET * const cset, char *newstr, + const char * escapestr, size_t escapestr_len TSRMLS_DC) +{ + const char *newstr_s = newstr; + const char *newstr_e = newstr + 2 * escapestr_len; + const char *end = escapestr + escapestr_len; + zend_bool escape_overflow = FALSE; + + DBG_ENTER("mysqlnd_cset_escape_quotes"); + + for (;escapestr < end; escapestr++) { + unsigned int len = 0; + /* check unicode characters */ + + if (cset->char_maxlen > 1 && (len = cset->mb_valid(escapestr, end))) { + + /* check possible overflow */ + if ((newstr + len) > newstr_e) { + escape_overflow = TRUE; + break; + } + /* copy mb char without escaping it */ + while (len--) { + *newstr++ = *escapestr++; + } + escapestr--; + continue; + } + if (*escapestr == '\'') { + if (newstr + 2 > newstr_e) { + escape_overflow = TRUE; + break; + } + *newstr++ = '\''; + *newstr++ = '\''; + } else { + if (newstr + 1 > newstr_e) { + escape_overflow = TRUE; + break; + } + *newstr++ = *escapestr; + } + } + *newstr = '\0'; + + if (escape_overflow) { + DBG_RETURN((ulong)~0); + } + DBG_RETURN((ulong)(newstr - newstr_s)); +} +/* }}} */ + + +/* {{{ mysqlnd_cset_escape_slashes */ +PHPAPI ulong mysqlnd_cset_escape_slashes(const MYSQLND_CHARSET * const cset, char *newstr, + const char * escapestr, size_t escapestr_len TSRMLS_DC) +{ + const char *newstr_s = newstr; + const char *newstr_e = newstr + 2 * escapestr_len; + const char *end = escapestr + escapestr_len; + zend_bool escape_overflow = FALSE; + + DBG_ENTER("mysqlnd_cset_escape_slashes"); + DBG_INF_FMT("charset=%s", cset->name); + + for (;escapestr < end; escapestr++) { + char esc = '\0'; + unsigned int len = 0; + + /* check unicode characters */ + if (cset->char_maxlen > 1 && (len = cset->mb_valid(escapestr, end))) { + /* check possible overflow */ + if ((newstr + len) > newstr_e) { + escape_overflow = TRUE; + break; + } + /* copy mb char without escaping it */ + while (len--) { + *newstr++ = *escapestr++; + } + escapestr--; + continue; + } + if (cset->char_maxlen > 1 && cset->mb_charlen(*escapestr) > 1) { + esc = *escapestr; + } else { + switch (*escapestr) { + case 0: + esc = '0'; + break; + case '\n': + esc = 'n'; + break; + case '\r': + esc = 'r'; + break; + case '\\': + case '\'': + case '"': + esc = *escapestr; + break; + case '\032': + esc = 'Z'; + break; + } + } + if (esc) { + if (newstr + 2 > newstr_e) { + escape_overflow = TRUE; + break; + } + /* copy escaped character */ + *newstr++ = '\\'; + *newstr++ = esc; + } else { + if (newstr + 1 > newstr_e) { + escape_overflow = TRUE; + break; + } + /* copy non escaped character */ + *newstr++ = *escapestr; + } + } + *newstr = '\0'; + + if (escape_overflow) { + DBG_RETURN((ulong)~0); + } + DBG_RETURN((ulong)(newstr - newstr_s)); +} +/* }}} */ + + +static struct st_mysqlnd_plugin_charsets mysqlnd_plugin_charsets_plugin = +{ + { + MYSQLND_PLUGIN_API_VERSION, + "charsets", + MYSQLND_VERSION_ID, + MYSQLND_VERSION, + "PHP License 3.01", + "Andrey Hristov <andrey@mysql.com>, Ulf Wendel <uwendel@mysql.com>, Georg Richter <georg@mysql.com>", + { + NULL, /* no statistics , will be filled later if there are some */ + NULL, /* no statistics */ + }, + { + NULL /* plugin shutdown */ + } + }, + {/* methods */ + mysqlnd_find_charset_nr, + mysqlnd_find_charset_name, + mysqlnd_cset_escape_quotes, + mysqlnd_cset_escape_slashes + } +}; + + +/* {{{ mysqlnd_charsets_plugin_register */ +void +mysqlnd_charsets_plugin_register(TSRMLS_D) +{ + mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_plugin_charsets_plugin TSRMLS_CC); +} +/* }}} */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_charset.h b/ext/mysqlnd/mysqlnd_charset.h new file mode 100644 index 0000000..09d0eb4 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_charset.h @@ -0,0 +1,53 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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> | + +----------------------------------------------------------------------+ +*/ + +#ifndef MYSQLND_CHARSET_H +#define MYSQLND_CHARSET_H + +PHPAPI ulong mysqlnd_cset_escape_quotes(const MYSQLND_CHARSET * const charset, char *newstr, + const char *escapestr, size_t escapestr_len TSRMLS_DC); + +PHPAPI ulong mysqlnd_cset_escape_slashes(const MYSQLND_CHARSET * const cset, char *newstr, + const char *escapestr, size_t escapestr_len TSRMLS_DC); + +struct st_mysqlnd_plugin_charsets +{ + const struct st_mysqlnd_plugin_header plugin_header; + struct + { + const MYSQLND_CHARSET * (*const find_charset_by_nr)(unsigned int charsetnr); + const MYSQLND_CHARSET * (*const find_charset_by_name)(const char * const name); + unsigned long (*const escape_quotes)(const MYSQLND_CHARSET * const cset, char * newstr, const char * escapestr, size_t escapestr_len TSRMLS_DC); + unsigned long (*const escape_slashes)(const MYSQLND_CHARSET * const cset, char * newstr, const char * escapestr, size_t escapestr_len TSRMLS_DC); + } methods; +}; + +void mysqlnd_charsets_plugin_register(TSRMLS_D); + +#endif /* MYSQLND_CHARSET_H */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_debug.c b/ext/mysqlnd/mysqlnd_debug.c new file mode 100644 index 0000000..fb8a360 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_debug.c @@ -0,0 +1,811 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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_priv.h" +#include "mysqlnd_debug.h" + +static const char * const mysqlnd_debug_default_trace_file = "/tmp/mysqlnd.trace"; + +#ifdef ZTS +#define MYSQLND_ZTS(self) TSRMLS_D = (self)->TSRMLS_C +#else +#define MYSQLND_ZTS(self) +#endif + + +/* {{{ mysqlnd_debug::open */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_debug, open)(MYSQLND_DEBUG * self, zend_bool reopen) +{ + MYSQLND_ZTS(self); + + if (!self->file_name) { + return FAIL; + } + + self->stream = php_stream_open_wrapper(self->file_name, + reopen == TRUE || self->flags & MYSQLND_DEBUG_APPEND? "ab":"wb", + REPORT_ERRORS, NULL); + return self->stream? PASS:FAIL; +} +/* }}} */ + + +/* {{{ mysqlnd_debug::log */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_debug, log)(MYSQLND_DEBUG * self, + unsigned int line, const char * const file, + unsigned int level, const char * type, const char * message) +{ + char pipe_buffer[512]; + enum_func_status ret; + int i; + char * message_line; + unsigned int message_line_len; + unsigned int flags = self->flags; + char pid_buffer[10], time_buffer[30], file_buffer[200], + line_buffer[6], level_buffer[7]; + MYSQLND_ZTS(self); + + if (!self->stream && FAIL == self->m->open(self, FALSE)) { + return FAIL; + } + + if (level == -1) { + level = zend_stack_count(&self->call_stack); + } + i = MIN(level, sizeof(pipe_buffer) / 2 - 1); + pipe_buffer[i*2] = '\0'; + for (;i > 0;i--) { + pipe_buffer[i*2 - 1] = ' '; + pipe_buffer[i*2 - 2] = '|'; + } + + + if (flags & MYSQLND_DEBUG_DUMP_PID) { + snprintf(pid_buffer, sizeof(pid_buffer) - 1, "%5u: ", self->pid); + pid_buffer[sizeof(pid_buffer) - 1 ] = '\0'; + } + if (flags & MYSQLND_DEBUG_DUMP_TIME) { + /* The following from FF's DBUG library, which is in the public domain */ +#if defined(PHP_WIN32) + /* FIXME This doesn't give microseconds as in Unix case, and the resolution is + in system ticks, 10 ms intervals. See my_getsystime.c for high res */ + SYSTEMTIME loc_t; + GetLocalTime(&loc_t); + snprintf(time_buffer, sizeof(time_buffer) - 1, + /* "%04d-%02d-%02d " */ + "%02d:%02d:%02d.%06d ", + /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/ + loc_t.wHour, loc_t.wMinute, loc_t.wSecond, loc_t.wMilliseconds); + time_buffer[sizeof(time_buffer) - 1 ] = '\0'; +#else + struct timeval tv; + struct tm *tm_p; + if (gettimeofday(&tv, NULL) != -1) { + if ((tm_p= localtime((const time_t *)&tv.tv_sec))) { + snprintf(time_buffer, sizeof(time_buffer) - 1, + /* "%04d-%02d-%02d " */ + "%02d:%02d:%02d.%06d ", + /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/ + tm_p->tm_hour, tm_p->tm_min, tm_p->tm_sec, + (int) (tv.tv_usec)); + time_buffer[sizeof(time_buffer) - 1 ] = '\0'; + } + } +#endif + } + if (flags & MYSQLND_DEBUG_DUMP_FILE) { + snprintf(file_buffer, sizeof(file_buffer) - 1, "%14s: ", file); + file_buffer[sizeof(file_buffer) - 1 ] = '\0'; + } + if (flags & MYSQLND_DEBUG_DUMP_LINE) { + snprintf(line_buffer, sizeof(line_buffer) - 1, "%5u: ", line); + line_buffer[sizeof(line_buffer) - 1 ] = '\0'; + } + if (flags & MYSQLND_DEBUG_DUMP_LEVEL) { + snprintf(level_buffer, sizeof(level_buffer) - 1, "%4u: ", level); + level_buffer[sizeof(level_buffer) - 1 ] = '\0'; + } + + message_line_len = mnd_sprintf(&message_line, 0, "%s%s%s%s%s%s%s%s\n", + flags & MYSQLND_DEBUG_DUMP_PID? pid_buffer:"", + flags & MYSQLND_DEBUG_DUMP_TIME? time_buffer:"", + flags & MYSQLND_DEBUG_DUMP_FILE? file_buffer:"", + flags & MYSQLND_DEBUG_DUMP_LINE? line_buffer:"", + flags & MYSQLND_DEBUG_DUMP_LEVEL? level_buffer:"", + pipe_buffer, type? type:"", message); + + ret = php_stream_write(self->stream, message_line, message_line_len)? PASS:FAIL; + mnd_sprintf_free(message_line); + if (flags & MYSQLND_DEBUG_FLUSH) { + self->m->close(self); + self->m->open(self, TRUE); + } + return ret; +} +/* }}} */ + + +/* {{{ mysqlnd_debug::log_va */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_debug, log_va)(MYSQLND_DEBUG *self, + unsigned int line, const char * const file, + unsigned int level, const char * type, + const char *format, ...) +{ + char pipe_buffer[512]; + int i; + enum_func_status ret; + char * message_line, *buffer; + unsigned int message_line_len; + va_list args; + unsigned int flags = self->flags; + char pid_buffer[10], time_buffer[30], file_buffer[200], + line_buffer[6], level_buffer[7]; + MYSQLND_ZTS(self); + + if (!self->stream && FAIL == self->m->open(self, FALSE)) { + return FAIL; + } + + if (level == -1) { + level = zend_stack_count(&self->call_stack); + } + i = MIN(level, sizeof(pipe_buffer) / 2 - 1); + pipe_buffer[i*2] = '\0'; + for (;i > 0;i--) { + pipe_buffer[i*2 - 1] = ' '; + pipe_buffer[i*2 - 2] = '|'; + } + + + if (flags & MYSQLND_DEBUG_DUMP_PID) { + snprintf(pid_buffer, sizeof(pid_buffer) - 1, "%5u: ", self->pid); + pid_buffer[sizeof(pid_buffer) - 1 ] = '\0'; + } + if (flags & MYSQLND_DEBUG_DUMP_TIME) { + /* The following from FF's DBUG library, which is in the public domain */ +#if defined(PHP_WIN32) + /* FIXME This doesn't give microseconds as in Unix case, and the resolution is + in system ticks, 10 ms intervals. See my_getsystime.c for high res */ + SYSTEMTIME loc_t; + GetLocalTime(&loc_t); + snprintf(time_buffer, sizeof(time_buffer) - 1, + /* "%04d-%02d-%02d " */ + "%02d:%02d:%02d.%06d ", + /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/ + loc_t.wHour, loc_t.wMinute, loc_t.wSecond, loc_t.wMilliseconds); + time_buffer[sizeof(time_buffer) - 1 ] = '\0'; +#else + struct timeval tv; + struct tm *tm_p; + if (gettimeofday(&tv, NULL) != -1) { + if ((tm_p= localtime((const time_t *)&tv.tv_sec))) { + snprintf(time_buffer, sizeof(time_buffer) - 1, + /* "%04d-%02d-%02d " */ + "%02d:%02d:%02d.%06d ", + /*tm_p->tm_year + 1900, tm_p->tm_mon + 1, tm_p->tm_mday,*/ + tm_p->tm_hour, tm_p->tm_min, tm_p->tm_sec, + (int) (tv.tv_usec)); + time_buffer[sizeof(time_buffer) - 1 ] = '\0'; + } + } +#endif + } + if (flags & MYSQLND_DEBUG_DUMP_FILE) { + snprintf(file_buffer, sizeof(file_buffer) - 1, "%14s: ", file); + file_buffer[sizeof(file_buffer) - 1 ] = '\0'; + } + if (flags & MYSQLND_DEBUG_DUMP_LINE) { + snprintf(line_buffer, sizeof(line_buffer) - 1, "%5u: ", line); + line_buffer[sizeof(line_buffer) - 1 ] = '\0'; + } + if (flags & MYSQLND_DEBUG_DUMP_LEVEL) { + snprintf(level_buffer, sizeof(level_buffer) - 1, "%4u: ", level); + level_buffer[sizeof(level_buffer) - 1 ] = '\0'; + } + + va_start(args, format); + mnd_vsprintf(&buffer, 0, format, args); + va_end(args); + + message_line_len = mnd_sprintf(&message_line, 0, "%s%s%s%s%s%s%s%s\n", + flags & MYSQLND_DEBUG_DUMP_PID? pid_buffer:"", + flags & MYSQLND_DEBUG_DUMP_TIME? time_buffer:"", + flags & MYSQLND_DEBUG_DUMP_FILE? file_buffer:"", + flags & MYSQLND_DEBUG_DUMP_LINE? line_buffer:"", + flags & MYSQLND_DEBUG_DUMP_LEVEL? level_buffer:"", + pipe_buffer, type? type:"", buffer); + mnd_sprintf_free(buffer); + ret = php_stream_write(self->stream, message_line, message_line_len)? PASS:FAIL; + mnd_sprintf_free(message_line); + + if (flags & MYSQLND_DEBUG_FLUSH) { + self->m->close(self); + self->m->open(self, TRUE); + } + return ret; +} +/* }}} */ + + +/* FALSE - The DBG_ calls won't be traced, TRUE - will be traced */ +/* {{{ mysqlnd_debug::func_enter */ +static zend_bool +MYSQLND_METHOD(mysqlnd_debug, func_enter)(MYSQLND_DEBUG * self, + unsigned int line, const char * const file, + const char * const func_name, unsigned int func_name_len) +{ + if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) { + return FALSE; + } + if ((uint) zend_stack_count(&self->call_stack) >= self->nest_level_limit) { + return FALSE; + } + + if ((self->flags & MYSQLND_DEBUG_TRACE_MEMORY_CALLS) == 0 && self->skip_functions) { + const char ** p = self->skip_functions; + while (*p) { + if (*p == func_name) { + zend_stack_push(&self->call_stack, "", sizeof("")); +#ifndef MYSQLND_PROFILING_DISABLED + if (self->flags & MYSQLND_DEBUG_PROFILE_CALLS) { + uint64_t some_time = 0; + zend_stack_push(&self->call_time_stack, &some_time, sizeof(some_time)); + } +#endif + return FALSE; + } + p++; + } + } + + zend_stack_push(&self->call_stack, func_name, func_name_len + 1); +#ifndef MYSQLND_PROFILING_DISABLED + if (self->flags & MYSQLND_DEBUG_PROFILE_CALLS) { + uint64_t some_time = 0; + zend_stack_push(&self->call_time_stack, &some_time, sizeof(some_time)); + } +#endif + + if (zend_hash_num_elements(&self->not_filtered_functions) && + 0 == zend_hash_exists(&self->not_filtered_functions, func_name, strlen(func_name) + 1)) + { + return FALSE; + } + + self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, ">%s", func_name); + return TRUE; +} +/* }}} */ + +#ifndef MYSQLND_PROFILING_DISABLED +struct st_mysqlnd_dbg_function_profile { + uint64_t calls; + uint64_t min_own; + uint64_t max_own; + uint64_t avg_own; + uint64_t own_underporm_calls; + uint64_t min_in_calls; + uint64_t max_in_calls; + uint64_t avg_in_calls; + uint64_t in_calls_underporm_calls; + uint64_t min_total; + uint64_t max_total; + uint64_t avg_total; + uint64_t total_underporm_calls; +}; +#define PROFILE_UNDERPERFORM_THRESHOLD 10 +#endif + +/* {{{ mysqlnd_debug::func_leave */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_debug, func_leave)(MYSQLND_DEBUG * self, unsigned int line, const char * const file, uint64_t call_time) +{ + char *func_name; + uint64_t * parent_non_own_time_ptr = NULL, * mine_non_own_time_ptr = NULL; + uint64_t mine_non_own_time = 0; + zend_bool profile_calls = self->flags & MYSQLND_DEBUG_PROFILE_CALLS? TRUE:FALSE; + + if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) { + return PASS; + } + if ((uint) zend_stack_count(&self->call_stack) >= self->nest_level_limit) { + return PASS; + } + + zend_stack_top(&self->call_stack, (void **)&func_name); + +#ifndef MYSQLND_PROFILING_DISABLED + if (profile_calls) { + zend_stack_top(&self->call_time_stack, (void **)&mine_non_own_time_ptr); + mine_non_own_time = *mine_non_own_time_ptr; + zend_stack_del_top(&self->call_time_stack); /* callee - removing ourselves */ + } +#endif + + if (func_name[0] == '\0') { + ; /* don't log that function */ + } else if (!zend_hash_num_elements(&self->not_filtered_functions) || + 1 == zend_hash_exists(&self->not_filtered_functions, func_name, strlen(func_name) + 1)) + { +#ifndef MYSQLND_PROFILING_DISABLED + if (FALSE == profile_calls) { +#endif + self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, "<%s", func_name); + +#ifndef MYSQLND_PROFILING_DISABLED + } else { + struct st_mysqlnd_dbg_function_profile f_profile_stack = {0}; + struct st_mysqlnd_dbg_function_profile * f_profile = NULL; + uint64_t own_time = call_time - mine_non_own_time; + uint func_name_len = strlen(func_name); + + self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, "<%s (total=%u own=%u in_calls=%u)", + func_name, (unsigned int) call_time, (unsigned int) own_time, (unsigned int) mine_non_own_time + ); + + if (SUCCESS == zend_hash_find(&self->function_profiles, func_name, func_name_len + 1, (void **) &f_profile)) { + /* found */ + if (f_profile) { + if (mine_non_own_time < f_profile->min_in_calls) { + f_profile->min_in_calls = mine_non_own_time; + } else if (mine_non_own_time > f_profile->max_in_calls) { + f_profile->max_in_calls = mine_non_own_time; + } + f_profile->avg_in_calls = (f_profile->avg_in_calls * f_profile->calls + mine_non_own_time) / (f_profile->calls + 1); + + if (own_time < f_profile->min_own) { + f_profile->min_own = own_time; + } else if (own_time > f_profile->max_own) { + f_profile->max_own = own_time; + } + f_profile->avg_own = (f_profile->avg_own * f_profile->calls + own_time) / (f_profile->calls + 1); + + if (call_time < f_profile->min_total) { + f_profile->min_total = call_time; + } else if (call_time > f_profile->max_total) { + f_profile->max_total = call_time; + } + f_profile->avg_total = (f_profile->avg_total * f_profile->calls + call_time) / (f_profile->calls + 1); + + ++f_profile->calls; + if (f_profile->calls > PROFILE_UNDERPERFORM_THRESHOLD) { + if (f_profile->avg_in_calls < mine_non_own_time) { + f_profile->in_calls_underporm_calls++; + } + if (f_profile->avg_own < own_time) { + f_profile->own_underporm_calls++; + } + if (f_profile->avg_total < call_time) { + f_profile->total_underporm_calls++; + } + } + } + } else { + /* add */ + f_profile = &f_profile_stack; + f_profile->min_in_calls = f_profile->max_in_calls = f_profile->avg_in_calls = mine_non_own_time; + f_profile->min_total = f_profile->max_total = f_profile->avg_total = call_time; + f_profile->min_own = f_profile->max_own = f_profile->avg_own = own_time; + f_profile->calls = 1; + zend_hash_add(&self->function_profiles, func_name, func_name_len+1, f_profile, sizeof(struct st_mysqlnd_dbg_function_profile), NULL); + } + if ((uint) zend_stack_count(&self->call_time_stack)) { + uint64_t parent_non_own_time = 0; + + zend_stack_top(&self->call_time_stack, (void **)&parent_non_own_time_ptr); + parent_non_own_time = *parent_non_own_time_ptr; + parent_non_own_time += call_time; + zend_stack_del_top(&self->call_time_stack); /* the caller */ + zend_stack_push(&self->call_time_stack, &parent_non_own_time, sizeof(parent_non_own_time)); /* add back the caller */ + } + } +#endif + } + + return zend_stack_del_top(&self->call_stack) == SUCCESS? PASS:FAIL; +} +/* }}} */ + + +/* {{{ mysqlnd_debug::close */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_debug, close)(MYSQLND_DEBUG * self) +{ + MYSQLND_ZTS(self); + if (self->stream) { +#ifndef MYSQLND_PROFILING_DISABLED + if (!(self->flags & MYSQLND_DEBUG_FLUSH) && (self->flags & MYSQLND_DEBUG_PROFILE_CALLS)) { + struct st_mysqlnd_dbg_function_profile * f_profile; + HashPosition pos_values; + + self->m->log_va(self, __LINE__, __FILE__, 0, "info : ", + "number of functions: %d", zend_hash_num_elements(&self->function_profiles)); + zend_hash_internal_pointer_reset_ex(&self->function_profiles, &pos_values); + while (zend_hash_get_current_data_ex(&self->function_profiles, (void **) &f_profile, &pos_values) == SUCCESS) { + char *string_key = NULL; + uint string_key_len; + ulong num_key; + + zend_hash_get_current_key_ex(&self->function_profiles, &string_key, &string_key_len, &num_key, 0, &pos_values); + + self->m->log_va(self, __LINE__, __FILE__, -1, "info : ", + "%-40s\tcalls=%5llu own_slow=%5llu in_calls_slow=%5llu total_slow=%5llu" + " min_own=%5llu max_own=%7llu avg_own=%7llu " + " min_in_calls=%5llu max_in_calls=%7llu avg_in_calls=%7llu" + " min_total=%5llu max_total=%7llu avg_total=%7llu" + ,string_key + ,(uint64_t) f_profile->calls + ,(uint64_t) f_profile->own_underporm_calls + ,(uint64_t) f_profile->in_calls_underporm_calls + ,(uint64_t) f_profile->total_underporm_calls + + ,(uint64_t) f_profile->min_own + ,(uint64_t) f_profile->max_own + ,(uint64_t) f_profile->avg_own + ,(uint64_t) f_profile->min_in_calls + ,(uint64_t) f_profile->max_in_calls + ,(uint64_t) f_profile->avg_in_calls + ,(uint64_t) f_profile->min_total + ,(uint64_t) f_profile->max_total + ,(uint64_t) f_profile->avg_total + ); + zend_hash_move_forward_ex(&self->function_profiles, &pos_values); + } + } +#endif + + php_stream_free(self->stream, PHP_STREAM_FREE_CLOSE); + self->stream = NULL; + } + /* no DBG_RETURN please */ + return PASS; +} +/* }}} */ + + +/* {{{ mysqlnd_res_meta::free */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_debug, free)(MYSQLND_DEBUG * self) +{ + if (self->file_name && self->file_name != mysqlnd_debug_default_trace_file) { + efree(self->file_name); + self->file_name = NULL; + } + zend_stack_destroy(&self->call_stack); + zend_stack_destroy(&self->call_time_stack); + zend_hash_destroy(&self->not_filtered_functions); + zend_hash_destroy(&self->function_profiles); + free(self); + return PASS; +} +/* }}} */ + +enum mysqlnd_debug_parser_state +{ + PARSER_WAIT_MODIFIER, + PARSER_WAIT_COLON, + PARSER_WAIT_VALUE +}; + + +/* {{{ mysqlnd_res_meta::set_mode */ +static void +MYSQLND_METHOD(mysqlnd_debug, set_mode)(MYSQLND_DEBUG * self, const char * const mode) +{ + unsigned int mode_len, i; + enum mysqlnd_debug_parser_state state = PARSER_WAIT_MODIFIER; + + mode_len = mode? strlen(mode) : 0; + + self->flags = 0; + self->nest_level_limit = 0; + if (self->file_name && self->file_name != mysqlnd_debug_default_trace_file) { + efree(self->file_name); + self->file_name = NULL; + } + if (zend_hash_num_elements(&self->not_filtered_functions)) { + zend_hash_destroy(&self->not_filtered_functions); + zend_hash_init(&self->not_filtered_functions, 0, NULL, NULL, 0); + } + + for (i = 0; i < mode_len; i++) { + switch (mode[i]) { + case 'O': + case 'A': + self->flags |= MYSQLND_DEBUG_FLUSH; + case 'a': + case 'o': + if (mode[i] == 'a' || mode[i] == 'A') { + self->flags |= MYSQLND_DEBUG_APPEND; + } + if (i + 1 < mode_len && mode[i+1] == ',') { + unsigned int j = i + 2; +#ifdef PHP_WIN32 + if (i+4 < mode_len && mode[i+3] == ':' && (mode[i+4] == '\\' || mode[i+5] == '/')) { + j = i + 5; + } +#endif + while (j < mode_len) { + if (mode[j] == ':') { + break; + } + j++; + } + if (j > i + 2) { + self->file_name = estrndup(mode + i + 2, j - i - 2); + } + i = j; + } else { + if (!self->file_name) + self->file_name = (char *) mysqlnd_debug_default_trace_file; + } + state = PARSER_WAIT_COLON; + break; + case ':': +#if 0 + if (state != PARSER_WAIT_COLON) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Consecutive semicolons at position %u", i); + } +#endif + state = PARSER_WAIT_MODIFIER; + break; + case 'f': /* limit output to these functions */ + if (i + 1 < mode_len && mode[i+1] == ',') { + unsigned int j = i + 2; + i++; + while (j < mode_len) { + if (mode[j] == ':') { + /* function names with :: */ + if ((j + 1 < mode_len) && mode[j+1] == ':') { + j += 2; + continue; + } + } + if (mode[j] == ',' || mode[j] == ':') { + if (j > i + 2) { + char func_name[1024]; + unsigned int func_name_len = MIN(sizeof(func_name) - 1, j - i - 1); + memcpy(func_name, mode + i + 1, func_name_len); + func_name[func_name_len] = '\0'; + + zend_hash_add_empty_element(&self->not_filtered_functions, + func_name, func_name_len + 1); + i = j; + } + if (mode[j] == ':') { + break; + } + } + j++; + } + i = j; + } else { +#if 0 + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Expected list of functions for '%c' found none", mode[i]); +#endif + } + state = PARSER_WAIT_COLON; + break; + case 'D': + case 'd': + case 'g': + case 'p': + /* unsupported */ + if ((i + 1) < mode_len && mode[i+1] == ',') { + i+= 2; + while (i < mode_len) { + if (mode[i] == ':') { + break; + } + i++; + } + } + state = PARSER_WAIT_COLON; + break; + case 'F': + self->flags |= MYSQLND_DEBUG_DUMP_FILE; + state = PARSER_WAIT_COLON; + break; + case 'i': + self->flags |= MYSQLND_DEBUG_DUMP_PID; + state = PARSER_WAIT_COLON; + break; + case 'L': + self->flags |= MYSQLND_DEBUG_DUMP_LINE; + state = PARSER_WAIT_COLON; + break; + case 'n': + self->flags |= MYSQLND_DEBUG_DUMP_LEVEL; + state = PARSER_WAIT_COLON; + break; + case 't': + if (mode[i+1] == ',') { + unsigned int j = i + 2; + while (j < mode_len) { + if (mode[j] == ':') { + break; + } + j++; + } + if (j > i + 2) { + char *value_str = estrndup(mode + i + 2, j - i - 2); + self->nest_level_limit = atoi(value_str); + efree(value_str); + } + i = j; + } else { + self->nest_level_limit = 200; /* default value for FF DBUG */ + } + self->flags |= MYSQLND_DEBUG_DUMP_TRACE; + state = PARSER_WAIT_COLON; + break; + case 'T': + self->flags |= MYSQLND_DEBUG_DUMP_TIME; + state = PARSER_WAIT_COLON; + break; + case 'N': + case 'P': + case 'r': + case 'S': + state = PARSER_WAIT_COLON; + break; + case 'm': /* mysqlnd extension - trace memory functions */ + self->flags |= MYSQLND_DEBUG_TRACE_MEMORY_CALLS; + state = PARSER_WAIT_COLON; + break; + case 'x': /* mysqlnd extension - profile calls */ + self->flags |= MYSQLND_DEBUG_PROFILE_CALLS; + state = PARSER_WAIT_COLON; + break; + default: + if (state == PARSER_WAIT_MODIFIER) { +#if 0 + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unrecognized format '%c'", mode[i]); +#endif + if (i+1 < mode_len && mode[i+1] == ',') { + i+= 2; + while (i < mode_len) { + if (mode[i] == ':') { + break; + } + i++; + } + } + state = PARSER_WAIT_COLON; + } else if (state == PARSER_WAIT_COLON) { +#if 0 + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Colon expected, '%c' found", mode[i]); +#endif + } + break; + } + } +} +/* }}} */ + +MYSQLND_CLASS_METHODS_START(mysqlnd_debug) + MYSQLND_METHOD(mysqlnd_debug, open), + MYSQLND_METHOD(mysqlnd_debug, set_mode), + MYSQLND_METHOD(mysqlnd_debug, log), + MYSQLND_METHOD(mysqlnd_debug, log_va), + MYSQLND_METHOD(mysqlnd_debug, func_enter), + MYSQLND_METHOD(mysqlnd_debug, func_leave), + MYSQLND_METHOD(mysqlnd_debug, close), + MYSQLND_METHOD(mysqlnd_debug, free), +MYSQLND_CLASS_METHODS_END; + + +/* {{{ mysqlnd_debug_init */ +PHPAPI MYSQLND_DEBUG * +mysqlnd_debug_init(const char * skip_functions[] TSRMLS_DC) +{ + MYSQLND_DEBUG *ret = calloc(1, sizeof(MYSQLND_DEBUG)); +#ifdef ZTS + ret->TSRMLS_C = TSRMLS_C; +#endif + ret->nest_level_limit = 0; + ret->pid = getpid(); + zend_stack_init(&ret->call_stack); + zend_stack_init(&ret->call_time_stack); + zend_hash_init(&ret->not_filtered_functions, 0, NULL, NULL, 0); + zend_hash_init(&ret->function_profiles, 0, NULL, NULL, 0); + + ret->m = & mysqlnd_mysqlnd_debug_methods; + ret->skip_functions = skip_functions; + + return ret; +} +/* }}} */ + + +/* {{{ _mysqlnd_debug */ +PHPAPI void _mysqlnd_debug(const char * mode TSRMLS_DC) +{ +#if PHP_DEBUG + MYSQLND_DEBUG *dbg = MYSQLND_G(dbg); + if (!dbg) { + MYSQLND_G(dbg) = dbg = mysqlnd_debug_init(mysqlnd_debug_std_no_trace_funcs TSRMLS_CC); + if (!dbg) { + return; + } + } + + dbg->m->close(dbg); + dbg->m->set_mode(dbg, mode); + while (zend_stack_count(&dbg->call_stack)) { + zend_stack_del_top(&dbg->call_stack); + } + while (zend_stack_count(&dbg->call_time_stack)) { + zend_stack_del_top(&dbg->call_time_stack); + } +#endif +} +/* }}} */ + + +static struct st_mysqlnd_plugin_trace_log mysqlnd_plugin_trace_log_plugin = +{ + { + MYSQLND_PLUGIN_API_VERSION, + "debug_trace", + MYSQLND_VERSION_ID, + MYSQLND_VERSION, + "PHP License 3.01", + "Andrey Hristov <andrey@mysql.com>, Ulf Wendel <uwendel@mysql.com>, Georg Richter <georg@mysql.com>", + { + NULL, /* no statistics , will be filled later if there are some */ + NULL, /* no statistics */ + }, + { + NULL /* plugin shutdown */ + } + }, + {/* methods */ + mysqlnd_debug_init, + mysqlnd_get_backtrace + } +}; + + +/* {{{ mysqlnd_debug_trace_plugin_register */ +void +mysqlnd_debug_trace_plugin_register(TSRMLS_D) +{ + mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_plugin_trace_log_plugin TSRMLS_CC); +} +/* }}} */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_debug.h b/ext/mysqlnd/mysqlnd_debug.h new file mode 100644 index 0000000..f98c163 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_debug.h @@ -0,0 +1,193 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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$ */ + +#ifndef MYSQLND_DEBUG_H +#define MYSQLND_DEBUG_H + +#include "mysqlnd_alloc.h" +#include "zend_stack.h" + +struct st_mysqlnd_debug_methods +{ + enum_func_status (*open)(MYSQLND_DEBUG * self, zend_bool reopen); + void (*set_mode)(MYSQLND_DEBUG * self, const char * const mode); + enum_func_status (*log)(MYSQLND_DEBUG * self, unsigned int line, const char * const file, + unsigned int level, const char * type, const char *message); + enum_func_status (*log_va)(MYSQLND_DEBUG * self, unsigned int line, const char * const file, + unsigned int level, const char * type, const char *format, ...); + zend_bool (*func_enter)(MYSQLND_DEBUG * self, unsigned int line, const char * const file, + const char * const func_name, unsigned int func_name_len); + enum_func_status (*func_leave)(MYSQLND_DEBUG * self, unsigned int line, const char * const file, uint64_t call_time); + enum_func_status (*close)(MYSQLND_DEBUG * self); + enum_func_status (*free_handle)(MYSQLND_DEBUG * self); +}; + + +struct st_mysqlnd_debug +{ + php_stream *stream; +#ifdef ZTS + TSRMLS_D; +#endif + unsigned int flags; + unsigned int nest_level_limit; + int pid; + char * file_name; + zend_stack call_stack; + zend_stack call_time_stack; + HashTable not_filtered_functions; + HashTable function_profiles; + struct st_mysqlnd_debug_methods *m; + const char ** skip_functions; +}; + +struct st_mysqlnd_plugin_trace_log +{ + struct st_mysqlnd_plugin_header plugin_header; + struct + { + MYSQLND_DEBUG * (*trace_instance_init)(const char * skip_functions[] TSRMLS_DC); + char * (*get_backtrace)(uint max_levels, size_t * length TSRMLS_DC); + } methods; +}; + +void mysqlnd_debug_trace_plugin_register(TSRMLS_D); + +PHPAPI MYSQLND_DEBUG * mysqlnd_debug_init(const char * skip_functions[] TSRMLS_DC); + +PHPAPI char * mysqlnd_get_backtrace(uint max_levels, size_t * length TSRMLS_DC); + +#if defined(__GNUC__) || (defined(_MSC_VER) && (_MSC_VER >= 1400)) +#ifdef PHP_WIN32 +#include "win32/time.h" +#elif defined(NETWARE) +#include <sys/timeval.h> +#include <sys/time.h> +#else +#include <sys/time.h> +#endif + +#ifndef MYSQLND_PROFILING_DISABLED +#define DBG_PROFILE_TIMEVAL_TO_DOUBLE(tp) ((tp.tv_sec * 1000000LL)+ tp.tv_usec) +#define DBG_PROFILE_START_TIME() gettimeofday(&__dbg_prof_tp, NULL); __dbg_prof_start = DBG_PROFILE_TIMEVAL_TO_DOUBLE(__dbg_prof_tp); +#define DBG_PROFILE_END_TIME(duration) gettimeofday(&__dbg_prof_tp, NULL); (duration) = (DBG_PROFILE_TIMEVAL_TO_DOUBLE(__dbg_prof_tp) - __dbg_prof_start); +#else +#define DBG_PROFILE_TIMEVAL_TO_DOUBLE(tp) +#define DBG_PROFILE_START_TIME() +#define DBG_PROFILE_END_TIME(duration) +#endif + +#define DBG_INF_EX(dbg_obj, msg) do { if (dbg_skip_trace == FALSE) (dbg_obj)->m->log((dbg_obj), __LINE__, __FILE__, -1, "info : ", (msg)); } while (0) +#define DBG_ERR_EX(dbg_obj, msg) do { if (dbg_skip_trace == FALSE) (dbg_obj)->m->log((dbg_obj), __LINE__, __FILE__, -1, "error: ", (msg)); } while (0) +#define DBG_INF_FMT_EX(dbg_obj, ...) do { if (dbg_skip_trace == FALSE) (dbg_obj)->m->log_va((dbg_obj), __LINE__, __FILE__, -1, "info : ", __VA_ARGS__); } while (0) +#define DBG_ERR_FMT_EX(dbg_obj, ...) do { if (dbg_skip_trace == FALSE) (dbg_obj)->m->log_va((dbg_obj), __LINE__, __FILE__, -1, "error: ", __VA_ARGS__); } while (0) + +#define DBG_BLOCK_ENTER_EX(dbg_obj, block_name) \ + { \ + DBG_ENTER_EX(dbg_obj, (block_name)); + +#define DBG_BLOCK_LEAVE_EX(dbg_obj) \ + DBG_LEAVE_EX((dbg_obj), ;) \ + } \ + + +#define DBG_ENTER_EX(dbg_obj, func_name) \ + struct timeval __dbg_prof_tp = {0}; \ + uint64_t __dbg_prof_start = 0; /* initialization is needed */ \ + zend_bool dbg_skip_trace = TRUE; \ + if ((dbg_obj)) { \ + dbg_skip_trace = !(dbg_obj)->m->func_enter((dbg_obj), __LINE__, __FILE__, func_name, strlen(func_name)); \ + } \ + do { \ + if ((dbg_obj) && (dbg_obj)->flags & MYSQLND_DEBUG_PROFILE_CALLS) { \ + DBG_PROFILE_START_TIME(); \ + } \ + } while (0); + +#define DBG_LEAVE_EX(dbg_obj, leave) \ + do {\ + if ((dbg_obj)) { \ + uint64_t this_call_duration = 0; \ + if ((dbg_obj)->flags & MYSQLND_DEBUG_PROFILE_CALLS) { \ + DBG_PROFILE_END_TIME(this_call_duration); \ + } \ + (dbg_obj)->m->func_leave((dbg_obj), __LINE__, __FILE__, this_call_duration); \ + } \ + leave \ + } while (0); + +#define DBG_RETURN_EX(dbg_obj, value) DBG_LEAVE_EX(dbg_obj, return (value);) + +#define DBG_VOID_RETURN_EX(dbg_obj) DBG_LEAVE_EX(dbg_obj, return;) + + + +#else +static inline void DBG_INF_EX(MYSQLND_DEBUG * dbg_obj, const char * const msg) {} +static inline void DBG_ERR_EX(MYSQLND_DEBUG * dbg_obj, const char * const msg) {} +static inline void DBG_INF_FMT_EX(MYSQLND_DEBUG * dbg_obj, ...) {} +static inline void DBG_ERR_FMT_EX(MYSQLND_DEBUG * dbg_obj, ...) {} +static inline void DBG_ENTER_EX(MYSQLND_DEBUG * dbg_obj, const char * const func_name) {} +#define DBG_BLOCK_ENTER(bname) { +#define DBG_RETURN_EX(dbg_obj, value) return (value) +#define DBG_VOID_RETURN_EX(dbg_obj) return +#define DBG_BLOCK_LEAVE_EX(dbg_obj) } + +#endif + +#if MYSQLND_DBG_ENABLED == 1 + +#define DBG_INF(msg) DBG_INF_EX(MYSQLND_G(dbg), (msg)) +#define DBG_ERR(msg) DBG_ERR_EX(MYSQLND_G(dbg), (msg)) +#define DBG_INF_FMT(...) DBG_INF_FMT_EX(MYSQLND_G(dbg), __VA_ARGS__) +#define DBG_ERR_FMT(...) DBG_ERR_FMT_EX(MYSQLND_G(dbg), __VA_ARGS__) + +#define DBG_ENTER(func_name) DBG_ENTER_EX(MYSQLND_G(dbg), (func_name)) +#define DBG_BLOCK_ENTER(bname) DBG_BLOCK_ENTER_EX(MYSQLND_G(dbg), (bname)) +#define DBG_RETURN(value) DBG_RETURN_EX(MYSQLND_G(dbg), (value)) +#define DBG_VOID_RETURN DBG_VOID_RETURN_EX(MYSQLND_G(dbg)) +#define DBG_BLOCK_LEAVE DBG_BLOCK_LEAVE_EX(MYSQLND_G(dbg)) + +#elif MYSQLND_DBG_ENABLED == 0 + +static inline void DBG_INF(const char * const msg) {} +static inline void DBG_ERR(const char * const msg) {} +static inline void DBG_INF_FMT(const char * const format, ...) {} +static inline void DBG_ERR_FMT(const char * const format, ...) {} +static inline void DBG_ENTER(const char * const func_name) {} +#define DBG_BLOCK_ENTER(bname) { +#define DBG_RETURN(value) return (value) +#define DBG_VOID_RETURN return +#define DBG_BLOCK_LEAVE } + +#endif + +#endif /* MYSQLND_DEBUG_H */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_driver.c b/ext/mysqlnd/mysqlnd_driver.c new file mode 100644 index 0000000..e209a71 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_driver.c @@ -0,0 +1,306 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: mysqlnd.c 317989 2011-10-10 20:49:28Z andrey $ */ +#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 "mysqlnd_reverse_api.h" +#include "mysqlnd_ext_plugin.h" + +static zend_bool mysqlnd_library_initted = FALSE; + +static struct st_mysqlnd_plugin_core mysqlnd_plugin_core = +{ + { + MYSQLND_PLUGIN_API_VERSION, + "mysqlnd", + MYSQLND_VERSION_ID, + MYSQLND_VERSION, + "PHP License 3.01", + "Andrey Hristov <andrey@mysql.com>, Ulf Wendel <uwendel@mysql.com>, Georg Richter <georg@mysql.com>", + { + NULL, /* will be filled later */ + mysqlnd_stats_values_names, + }, + { + NULL /* plugin shutdown */ + } + } +}; + + +/* {{{ mysqlnd_library_end */ +PHPAPI void mysqlnd_library_end(TSRMLS_D) +{ + if (mysqlnd_library_initted == TRUE) { + mysqlnd_plugin_subsystem_end(TSRMLS_C); + mysqlnd_stats_end(mysqlnd_global_stats); + mysqlnd_global_stats = NULL; + mysqlnd_library_initted = FALSE; + mysqlnd_reverse_api_end(TSRMLS_C); + } +} +/* }}} */ + + +/* {{{ mysqlnd_library_init */ +PHPAPI void mysqlnd_library_init(TSRMLS_D) +{ + if (mysqlnd_library_initted == FALSE) { + mysqlnd_library_initted = TRUE; + mysqlnd_conn_set_methods(&MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_conn)); + mysqlnd_conn_data_set_methods(&MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_conn_data)); + _mysqlnd_init_ps_subsystem(); + /* Should be calloc, as mnd_calloc will reference LOCK_access*/ + mysqlnd_stats_init(&mysqlnd_global_stats, STAT_LAST); + mysqlnd_plugin_subsystem_init(TSRMLS_C); + { + mysqlnd_plugin_core.plugin_header.plugin_stats.values = mysqlnd_global_stats; + mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_plugin_core TSRMLS_CC); + } + mysqlnd_example_plugin_register(TSRMLS_C); + mysqlnd_debug_trace_plugin_register(TSRMLS_C); + mysqlnd_register_builtin_authentication_plugins(TSRMLS_C); + + mysqlnd_reverse_api_init(TSRMLS_C); + } +} +/* }}} */ + + +/* {{{ mysqlnd_error_list_pdtor */ +static void +mysqlnd_error_list_pdtor(void * pDest) +{ + MYSQLND_ERROR_LIST_ELEMENT * element = (MYSQLND_ERROR_LIST_ELEMENT *) pDest; +#ifdef ZTS + TSRMLS_FETCH(); +#endif + DBG_ENTER("mysqlnd_error_list_pdtor"); + if (element->error) { + mnd_pefree(element->error, TRUE); + } + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_object_factory::get_connection */ +static MYSQLND * +MYSQLND_METHOD(mysqlnd_object_factory, get_connection)(zend_bool persistent TSRMLS_DC) +{ + size_t alloc_size_ret = sizeof(MYSQLND) + mysqlnd_plugin_count() * sizeof(void *); + size_t alloc_size_ret_data = sizeof(MYSQLND_CONN_DATA) + mysqlnd_plugin_count() * sizeof(void *); + MYSQLND * new_object; + MYSQLND_CONN_DATA * data; + + DBG_ENTER("mysqlnd_driver::get_connection"); + DBG_INF_FMT("persistent=%u", persistent); + new_object = mnd_pecalloc(1, alloc_size_ret, persistent); + if (!new_object) { + DBG_RETURN(NULL); + } + new_object->data = mnd_pecalloc(1, alloc_size_ret_data, persistent); + if (!new_object->data) { + mnd_pefree(new_object, persistent); + DBG_RETURN(NULL); + } + new_object->persistent = persistent; + new_object->m = mysqlnd_conn_get_methods(); + data = new_object->data; + + data->error_info = &(data->error_info_impl); + data->options = &(data->options_impl); + data->upsert_status = &(data->upsert_status_impl); + + data->persistent = persistent; + data->m = mysqlnd_conn_data_get_methods(); + CONN_SET_STATE(data, CONN_ALLOCED); + data->m->get_reference(data TSRMLS_CC); + + if (PASS != data->m->init(data TSRMLS_CC)) { + new_object->m->dtor(new_object TSRMLS_CC); + DBG_RETURN(NULL); + } + + data->error_info->error_list = mnd_pecalloc(1, sizeof(zend_llist), persistent); + if (!data->error_info->error_list) { + new_object->m->dtor(new_object TSRMLS_CC); + DBG_RETURN(NULL); + } else { + zend_llist_init(data->error_info->error_list, sizeof(MYSQLND_ERROR_LIST_ELEMENT), (llist_dtor_func_t)mysqlnd_error_list_pdtor, persistent); + } + + DBG_RETURN(new_object); +} +/* }}} */ + + +/* {{{ mysqlnd_object_factory::clone_connection_object */ +static MYSQLND * +MYSQLND_METHOD(mysqlnd_object_factory, clone_connection_object)(MYSQLND * to_be_cloned TSRMLS_DC) +{ + size_t alloc_size_ret = sizeof(MYSQLND) + mysqlnd_plugin_count() * sizeof(void *); + MYSQLND * new_object; + + DBG_ENTER("mysqlnd_driver::clone_connection_object"); + DBG_INF_FMT("persistent=%u", to_be_cloned->persistent); + if (!to_be_cloned || !to_be_cloned->data) { + DBG_RETURN(NULL); + } + new_object = mnd_pecalloc(1, alloc_size_ret, to_be_cloned->persistent); + if (!new_object) { + DBG_RETURN(NULL); + } + new_object->persistent = to_be_cloned->persistent; + new_object->m = to_be_cloned->m; + + new_object->data = to_be_cloned->data->m->get_reference(to_be_cloned->data TSRMLS_CC); + if (!new_object->data) { + new_object->m->dtor(new_object TSRMLS_CC); + new_object = NULL; + } + DBG_RETURN(new_object); +} +/* }}} */ + + +/* {{{ mysqlnd_object_factory::get_prepared_statement */ +static MYSQLND_STMT * +MYSQLND_METHOD(mysqlnd_object_factory, get_prepared_statement)(MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + size_t alloc_size = sizeof(MYSQLND_STMT) + mysqlnd_plugin_count() * sizeof(void *); + MYSQLND_STMT * ret = mnd_pecalloc(1, alloc_size, conn->persistent); + MYSQLND_STMT_DATA * stmt = NULL; + + DBG_ENTER("mysqlnd_object_factory::get_prepared_statement"); + do { + if (!ret) { + break; + } + ret->m = mysqlnd_stmt_get_methods(); + ret->persistent = conn->persistent; + + stmt = ret->data = mnd_pecalloc(1, sizeof(MYSQLND_STMT_DATA), conn->persistent); + DBG_INF_FMT("stmt=%p", stmt); + if (!stmt) { + break; + } + stmt->persistent = conn->persistent; + stmt->error_info = &(stmt->error_info_impl); + stmt->upsert_status = &(stmt->upsert_status_impl); + stmt->state = MYSQLND_STMT_INITTED; + stmt->execute_cmd_buffer.length = 4096; + stmt->execute_cmd_buffer.buffer = mnd_pemalloc(stmt->execute_cmd_buffer.length, stmt->persistent); + if (!stmt->execute_cmd_buffer.buffer) { + break; + } + + stmt->prefetch_rows = MYSQLND_DEFAULT_PREFETCH_ROWS; + /* + Mark that we reference the connection, thus it won't be + be destructed till there is open statements. The last statement + or normal query result will close it then. + */ + stmt->conn = conn->m->get_reference(conn TSRMLS_CC); + stmt->error_info->error_list = mnd_pecalloc(1, sizeof(zend_llist), ret->persistent); + if (!stmt->error_info->error_list) { + break; + } + + zend_llist_init(stmt->error_info->error_list, sizeof(MYSQLND_ERROR_LIST_ELEMENT), (llist_dtor_func_t) mysqlnd_error_list_pdtor, conn->persistent); + + DBG_RETURN(ret); + } while (0); + + SET_OOM_ERROR(*conn->error_info); + if (ret) { + ret->m->dtor(ret, TRUE TSRMLS_CC); + ret = NULL; + } + DBG_RETURN(NULL); +} +/* }}} */ + + +/* {{{ mysqlnd_object_factory::get_io_channel */ +PHPAPI MYSQLND_NET * +MYSQLND_METHOD(mysqlnd_object_factory, get_io_channel)(zend_bool persistent, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info TSRMLS_DC) +{ + size_t alloc_size = sizeof(MYSQLND_NET) + mysqlnd_plugin_count() * sizeof(void *); + MYSQLND_NET * net = mnd_pecalloc(1, alloc_size, persistent); + + DBG_ENTER("mysqlnd_object_factory::get_io_channel"); + DBG_INF_FMT("persistent=%u", persistent); + if (net) { + net->persistent = persistent; + net->m = *mysqlnd_net_get_methods(); + + if (PASS != net->m.init(net, stats, error_info TSRMLS_CC)) { + net->m.dtor(net, stats, error_info TSRMLS_CC); + net = NULL; + } + } + DBG_RETURN(net); +} +/* }}} */ + + +/* {{{ mysqlnd_object_factory::get_protocol_decoder */ +PHPAPI MYSQLND_PROTOCOL * +MYSQLND_METHOD(mysqlnd_object_factory, get_protocol_decoder)(zend_bool persistent TSRMLS_DC) +{ + size_t alloc_size = sizeof(MYSQLND_PROTOCOL) + mysqlnd_plugin_count() * sizeof(void *); + MYSQLND_PROTOCOL *ret = mnd_pecalloc(1, alloc_size, persistent); + + DBG_ENTER("mysqlnd_object_factory::get_protocol_decoder"); + DBG_INF_FMT("persistent=%u", persistent); + if (ret) { + ret->persistent = persistent; + ret->m = mysqlnd_mysqlnd_protocol_methods; + } + + DBG_RETURN(ret); +} +/* }}} */ + + +MYSQLND_CLASS_METHODS_START(mysqlnd_object_factory) + MYSQLND_METHOD(mysqlnd_object_factory, get_connection), + MYSQLND_METHOD(mysqlnd_object_factory, clone_connection_object), + MYSQLND_METHOD(mysqlnd_object_factory, get_prepared_statement), + MYSQLND_METHOD(mysqlnd_object_factory, get_io_channel), + MYSQLND_METHOD(mysqlnd_object_factory, get_protocol_decoder) +MYSQLND_CLASS_METHODS_END; + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_enum_n_def.h b/ext/mysqlnd/mysqlnd_enum_n_def.h new file mode 100644 index 0000000..92f6cb3 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_enum_n_def.h @@ -0,0 +1,605 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#ifndef MYSQLND_ENUM_N_DEF_H +#define MYSQLND_ENUM_N_DEF_H + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + + +#define MYSQLND_MIN_COMPRESS_LEN 0 + +#define MYSQLND_MAX_PACKET_SIZE (256L*256L*256L-1) + +#define MYSQLND_ASSEMBLED_PACKET_MAX_SIZE 3UL*1024UL*1024UL*1024UL + +#define MYSQLND_ERRMSG_SIZE 512 +#define MYSQLND_SQLSTATE_LENGTH 5 +#define MYSQLND_SQLSTATE_NULL "00000" + +#define MYSQLND_MAX_ALLOWED_USER_LEN 256 /* 64 char * 4byte . MySQL supports now only 16 char, but let it be forward compatible */ +#define MYSQLND_MAX_ALLOWED_DB_LEN 1024 /* 256 char * 4byte. MySQL supports now only 64 char in the tables, but on the FS could be different. Forward compatible. */ + +#define MYSQLND_NET_CMD_BUFFER_MIN_SIZE 4096 +#define MYSQLND_NET_CMD_BUFFER_MIN_SIZE_STR "4096" + +#define SERVER_STATUS_IN_TRANS 1 /* Transaction has started */ +#define SERVER_STATUS_AUTOCOMMIT 2 /* Server in auto_commit mode */ +#define SERVER_MORE_RESULTS_EXISTS 8 /* Multi query - next query exists */ +#define SERVER_QUERY_NO_GOOD_INDEX_USED 16 +#define SERVER_QUERY_NO_INDEX_USED 32 +/* + The server was able to fulfill the clients request and opened a + read-only non-scrollable cursor for a query. This flag comes + in reply to COM_STMT_EXECUTE and COM_STMT_FETCH commands. +*/ +#define SERVER_STATUS_CURSOR_EXISTS 64 +/* + This flag is sent when a read-only cursor is exhausted, in reply to + COM_STMT_FETCH command. +*/ +#define SERVER_STATUS_LAST_ROW_SENT 128 +#define SERVER_STATUS_DB_DROPPED 256 /* A database was dropped */ +#define SERVER_STATUS_NO_BACKSLASH_ESCAPES 512 +#define SERVER_QUERY_WAS_SLOW 2048 +#define SERVER_PS_OUT_PARAMS 4096 + +#define MYSQLND_NO_DATA 100 +#define MYSQLND_DATA_TRUNCATED 101 + +#define SHA1_MAX_LENGTH 20 +#define SCRAMBLE_LENGTH 20 +#define SCRAMBLE_LENGTH_323 8 + +#define CLIENT_LONG_PASSWORD 1 /* new more secure passwords */ +#define CLIENT_FOUND_ROWS 2 /* Found instead of affected rows */ +#define CLIENT_LONG_FLAG 4 /* Get all column flags */ +#define CLIENT_CONNECT_WITH_DB 8 /* One can specify db on connect */ +#define CLIENT_NO_SCHEMA 16 /* Don't allow database.table.column */ +#define CLIENT_COMPRESS 32 /* Can use compression protocol */ +#define CLIENT_ODBC 64 /* Odbc client */ +#define CLIENT_LOCAL_FILES 128 /* Can use LOAD DATA LOCAL */ +#define CLIENT_IGNORE_SPACE 256 /* Ignore spaces before '(' */ +#define CLIENT_PROTOCOL_41 512 /* New 4.1 protocol */ +#define CLIENT_INTERACTIVE 1024 /* This is an interactive client */ +#define CLIENT_SSL 2048 /* Switch to SSL after handshake */ +#define CLIENT_IGNORE_SIGPIPE 4096 /* IGNORE sigpipes */ +#define CLIENT_TRANSACTIONS 8192 /* Client knows about transactions */ +#define CLIENT_RESERVED 16384 /* Old flag for 4.1 protocol */ +#define CLIENT_SECURE_CONNECTION 32768 /* New 4.1 authentication */ +#define CLIENT_MULTI_STATEMENTS (1UL << 16) /* Enable/disable multi-stmt support */ +#define CLIENT_MULTI_RESULTS (1UL << 17) /* Enable/disable multi-results */ +#define CLIENT_PS_MULTI_RESULTS (1UL << 18) /* Multi-results in PS-protocol */ +#define CLIENT_PLUGIN_AUTH (1UL << 19) /* Client supports plugin authentication */ +#define CLIENT_CONNECT_ATTRS (1UL << 20) /* Client supports connection attributes */ +#define CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA (1UL << 21) /* Enable authentication response packet to be larger than 255 bytes. */ +#define CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS (1UL << 22) /* Don't close the connection for a connection with expired password. */ +#define CLIENT_SSL_VERIFY_SERVER_CERT (1UL << 30) + +#define MYSQLND_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | \ + CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | \ + CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS | CLIENT_LOCAL_FILES | CLIENT_PLUGIN_AUTH) + +#define MYSQLND_NET_FLAG_USE_COMPRESSION 1 + +typedef enum mysqlnd_extension +{ + MYSQLND_MYSQL = 0, + MYSQLND_MYSQLI +} enum_mysqlnd_extension; + +enum +{ + MYSQLND_FETCH_ASSOC = 1, + MYSQLND_FETCH_NUM = 2, + MYSQLND_FETCH_BOTH = 1|2 +}; + +/* Follow libmysql convention */ +typedef enum func_status +{ + PASS = 0, + FAIL = 1 +} enum_func_status; + +typedef enum mysqlnd_query_type +{ + QUERY_UPSERT, + QUERY_SELECT, + QUERY_LOAD_LOCAL +} enum_mysqlnd_query_type; + +typedef enum mysqlnd_res_type +{ + MYSQLND_RES_NORMAL = 1, + MYSQLND_RES_PS_BUF, + MYSQLND_RES_PS_UNBUF +} enum_mysqlnd_res_type; + +typedef enum mysqlnd_option +{ + MYSQL_OPT_CONNECT_TIMEOUT, + MYSQL_OPT_COMPRESS, + MYSQL_OPT_NAMED_PIPE, + MYSQL_INIT_COMMAND, + MYSQL_READ_DEFAULT_FILE, + MYSQL_READ_DEFAULT_GROUP, + MYSQL_SET_CHARSET_DIR, + MYSQL_SET_CHARSET_NAME, + MYSQL_OPT_LOCAL_INFILE, + MYSQL_OPT_PROTOCOL, + MYSQL_SHARED_MEMORY_BASE_NAME, + MYSQL_OPT_READ_TIMEOUT, + MYSQL_OPT_WRITE_TIMEOUT, + MYSQL_OPT_USE_RESULT, + MYSQL_OPT_USE_REMOTE_CONNECTION, + MYSQL_OPT_USE_EMBEDDED_CONNECTION, + MYSQL_OPT_GUESS_CONNECTION, + MYSQL_SET_CLIENT_IP, + MYSQL_SECURE_AUTH, + MYSQL_REPORT_DATA_TRUNCATION, + MYSQL_OPT_RECONNECT, + MYSQL_OPT_SSL_VERIFY_SERVER_CERT, + MYSQL_PLUGIN_DIR, + MYSQL_DEFAULT_AUTH, + MYSQL_SERVER_PUBLIC_KEY, + MYSQL_ENABLE_CLEARTEXT_PLUGIN, + MYSQL_OPT_CAN_HANDLE_EXPIRED_PASSWORDS, +#if MYSQLND_UNICODE + MYSQLND_OPT_NUMERIC_AND_DATETIME_AS_UNICODE = 200, +#endif +#ifdef MYSQLND_STRING_TO_INT_CONVERSION + MYSQLND_OPT_INT_AND_FLOAT_NATIVE = 201, +#endif + MYSQLND_OPT_NET_CMD_BUFFER_SIZE = 202, + MYSQLND_OPT_NET_READ_BUFFER_SIZE = 203, + MYSQLND_OPT_SSL_KEY = 204, + MYSQLND_OPT_SSL_CERT = 205, + MYSQLND_OPT_SSL_CA = 206, + MYSQLND_OPT_SSL_CAPATH = 207, + MYSQLND_OPT_SSL_CIPHER = 208, + MYSQLND_OPT_SSL_PASSPHRASE = 209, + MYSQLND_OPT_MAX_ALLOWED_PACKET = 210, + MYSQLND_OPT_AUTH_PROTOCOL = 211 +} enum_mysqlnd_option; + +typedef enum mysqlnd_protocol_type +{ + MYSQL_PROTOCOL_DEFAULT = 0, + MYSQL_PROTOCOL_TCP, /* all, supported */ + MYSQL_PROTOCOL_SOCKET, /* unix, supported */ + MYSQL_PROTOCOL_PIPE, /* win32, not-supported */ + MYSQL_PROTOCOL_MEMORY, /* win32, not-supported */ + MYSQL_PROTOCOL_LAST +} enum_mysqlnd_protocol_type; + +typedef enum mysqlnd_field_types +{ + MYSQL_TYPE_DECIMAL, + MYSQL_TYPE_TINY, + MYSQL_TYPE_SHORT, + MYSQL_TYPE_LONG, + MYSQL_TYPE_FLOAT, + MYSQL_TYPE_DOUBLE, + MYSQL_TYPE_NULL, + MYSQL_TYPE_TIMESTAMP, + MYSQL_TYPE_LONGLONG, + MYSQL_TYPE_INT24, + MYSQL_TYPE_DATE, + MYSQL_TYPE_TIME, + MYSQL_TYPE_DATETIME, + MYSQL_TYPE_YEAR, + MYSQL_TYPE_NEWDATE, + MYSQL_TYPE_VARCHAR, + MYSQL_TYPE_BIT, + MYSQL_TYPE_NEWDECIMAL=246, + MYSQL_TYPE_ENUM=247, + MYSQL_TYPE_SET=248, + MYSQL_TYPE_TINY_BLOB=249, + MYSQL_TYPE_MEDIUM_BLOB=250, + MYSQL_TYPE_LONG_BLOB=251, + MYSQL_TYPE_BLOB=252, + MYSQL_TYPE_VAR_STRING=253, + MYSQL_TYPE_STRING=254, + MYSQL_TYPE_GEOMETRY=255 +} enum_mysqlnd_field_types; + +/* Please update this if there is a new type after MYSQL_TYPE_GEOMETRY */ +#define MYSQL_TYPE_LAST MYSQL_TYPE_GEOMETRY + + +typedef enum mysqlnd_server_option +{ + MYSQL_OPTION_MULTI_STATEMENTS_ON, + MYSQL_OPTION_MULTI_STATEMENTS_OFF +} enum_mysqlnd_server_option; + + +#define FIELD_TYPE_DECIMAL MYSQL_TYPE_DECIMAL +#define FIELD_TYPE_NEWDECIMAL MYSQL_TYPE_NEWDECIMAL +#define FIELD_TYPE_TINY MYSQL_TYPE_TINY +#define FIELD_TYPE_SHORT MYSQL_TYPE_SHORT +#define FIELD_TYPE_LONG MYSQL_TYPE_LONG +#define FIELD_TYPE_FLOAT MYSQL_TYPE_FLOAT +#define FIELD_TYPE_DOUBLE MYSQL_TYPE_DOUBLE +#define FIELD_TYPE_NULL MYSQL_TYPE_NULL +#define FIELD_TYPE_TIMESTAMP MYSQL_TYPE_TIMESTAMP +#define FIELD_TYPE_LONGLONG MYSQL_TYPE_LONGLONG +#define FIELD_TYPE_INT24 MYSQL_TYPE_INT24 +#define FIELD_TYPE_DATE MYSQL_TYPE_DATE +#define FIELD_TYPE_TIME MYSQL_TYPE_TIME +#define FIELD_TYPE_DATETIME MYSQL_TYPE_DATETIME +#define FIELD_TYPE_YEAR MYSQL_TYPE_YEAR +#define FIELD_TYPE_NEWDATE MYSQL_TYPE_NEWDATE +#define FIELD_TYPE_ENUM MYSQL_TYPE_ENUM +#define FIELD_TYPE_SET MYSQL_TYPE_SET +#define FIELD_TYPE_TINY_BLOB MYSQL_TYPE_TINY_BLOB +#define FIELD_TYPE_MEDIUM_BLOB MYSQL_TYPE_MEDIUM_BLOB +#define FIELD_TYPE_LONG_BLOB MYSQL_TYPE_LONG_BLOB +#define FIELD_TYPE_BLOB MYSQL_TYPE_BLOB +#define FIELD_TYPE_VAR_STRING MYSQL_TYPE_VAR_STRING +#define FIELD_TYPE_STRING MYSQL_TYPE_STRING +#define FIELD_TYPE_CHAR MYSQL_TYPE_TINY +#define FIELD_TYPE_INTERVAL MYSQL_TYPE_ENUM +#define FIELD_TYPE_GEOMETRY MYSQL_TYPE_GEOMETRY +#define FIELD_TYPE_BIT MYSQL_TYPE_BIT + +#define NOT_NULL_FLAG 1 +#define PRI_KEY_FLAG 2 +#define UNIQUE_KEY_FLAG 4 +#define MULTIPLE_KEY_FLAG 8 +#define BLOB_FLAG 16 +#define UNSIGNED_FLAG 32 +#define ZEROFILL_FLAG 64 +#define BINARY_FLAG 128 +#define ENUM_FLAG 256 +#define AUTO_INCREMENT_FLAG 512 +#define TIMESTAMP_FLAG 1024 +#define SET_FLAG 2048 +#define NO_DEFAULT_VALUE_FLAG 4096 +#define ON_UPDATE_NOW_FLAG 8192 +#define PART_KEY_FLAG 16384 +#define GROUP_FLAG 32768 +#define NUM_FLAG 32768 + +#define IS_PRI_KEY(n) ((n) & PRI_KEY_FLAG) +#define IS_NOT_NULL(n) ((n) & NOT_NULL_FLAG) +#define IS_BLOB(n) ((n) & BLOB_FLAG) +#define IS_NUM(t) ((t) <= FIELD_TYPE_INT24 || (t) == FIELD_TYPE_YEAR || (t) == FIELD_TYPE_NEWDECIMAL) + + +/* see mysqlnd_charset.c for more information */ +#define MYSQLND_BINARY_CHARSET_NR 63 + + +/* + /-----> CONN_CLOSE <---------------\ + | ^ \ + | | \ + CONN_READY -> CONN_QUERY_SENT -> CONN_FETCHING_DATA + ^ | + \-------------------------------------/ +*/ +typedef enum mysqlnd_connection_state +{ + CONN_ALLOCED = 0, + CONN_READY, + CONN_QUERY_SENT, + CONN_SENDING_LOAD_DATA, + CONN_FETCHING_DATA, + CONN_NEXT_RESULT_PENDING, + CONN_QUIT_SENT /* object is "destroyed" at this stage */ +} enum_mysqlnd_connection_state; + + +typedef enum mysqlnd_stmt_state +{ + MYSQLND_STMT_INITTED = 0, + MYSQLND_STMT_PREPARED, + MYSQLND_STMT_EXECUTED, + MYSQLND_STMT_WAITING_USE_OR_STORE, + MYSQLND_STMT_USE_OR_STORE_CALLED, + MYSQLND_STMT_USER_FETCHING /* fetch_row_buff or fetch_row_unbuf */ +} enum_mysqlnd_stmt_state; + + +typedef enum param_bind_flags +{ + MYSQLND_PARAM_BIND_BLOB_USED = 1 +} enum_param_bind_flags; + + +/* PS */ +enum mysqlnd_stmt_attr +{ + STMT_ATTR_UPDATE_MAX_LENGTH, + STMT_ATTR_CURSOR_TYPE, + STMT_ATTR_PREFETCH_ROWS +}; + +enum myslqnd_cursor_type +{ + CURSOR_TYPE_NO_CURSOR= 0, + CURSOR_TYPE_READ_ONLY= 1, + CURSOR_TYPE_FOR_UPDATE= 2, + CURSOR_TYPE_SCROLLABLE= 4 +}; + +typedef enum mysqlnd_connection_close_type +{ + MYSQLND_CLOSE_EXPLICIT = 0, + MYSQLND_CLOSE_IMPLICIT, + MYSQLND_CLOSE_DISCONNECTED, + MYSQLND_CLOSE_LAST /* for checking, should always be last */ +} enum_connection_close_type; + + +typedef enum mysqlnd_collected_stats +{ + STAT_BYTES_SENT, + STAT_BYTES_RECEIVED, + STAT_PACKETS_SENT, + STAT_PACKETS_RECEIVED, + STAT_PROTOCOL_OVERHEAD_IN, + STAT_PROTOCOL_OVERHEAD_OUT, + STAT_BYTES_RECEIVED_OK, + STAT_BYTES_RECEIVED_EOF, + STAT_BYTES_RECEIVED_RSET_HEADER, + STAT_BYTES_RECEIVED_RSET_FIELD_META, + STAT_BYTES_RECEIVED_RSET_ROW, + STAT_BYTES_RECEIVED_PREPARE_RESPONSE, + STAT_BYTES_RECEIVED_CHANGE_USER, + STAT_PACKETS_SENT_CMD, + STAT_PACKETS_RECEIVED_OK, + STAT_PACKETS_RECEIVED_EOF, + STAT_PACKETS_RECEIVED_RSET_HEADER, + STAT_PACKETS_RECEIVED_RSET_FIELD_META, + STAT_PACKETS_RECEIVED_RSET_ROW, + STAT_PACKETS_RECEIVED_PREPARE_RESPONSE, + STAT_PACKETS_RECEIVED_CHANGE_USER, + STAT_RSET_QUERY, + STAT_NON_RSET_QUERY, + STAT_NO_INDEX_USED, + STAT_BAD_INDEX_USED, + STAT_QUERY_WAS_SLOW, + STAT_BUFFERED_SETS, + STAT_UNBUFFERED_SETS, + STAT_PS_BUFFERED_SETS, + STAT_PS_UNBUFFERED_SETS, + STAT_FLUSHED_NORMAL_SETS, + STAT_FLUSHED_PS_SETS, + STAT_PS_PREPARED_NEVER_EXECUTED, + STAT_PS_PREPARED_ONCE_USED, + STAT_ROWS_FETCHED_FROM_SERVER_NORMAL, + STAT_ROWS_FETCHED_FROM_SERVER_PS, + STAT_ROWS_BUFFERED_FROM_CLIENT_NORMAL, + STAT_ROWS_BUFFERED_FROM_CLIENT_PS, + STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF, + STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF, + STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF, + STAT_ROWS_FETCHED_FROM_CLIENT_PS_UNBUF, + STAT_ROWS_FETCHED_FROM_CLIENT_PS_CURSOR, + STAT_ROWS_AFFECTED_NORMAL, + STAT_ROWS_AFFECTED_PS, + STAT_ROWS_SKIPPED_NORMAL, + STAT_ROWS_SKIPPED_PS, + STAT_COPY_ON_WRITE_SAVED, + STAT_COPY_ON_WRITE_PERFORMED, + STAT_CMD_BUFFER_TOO_SMALL, + STAT_CONNECT_SUCCESS, + STAT_CONNECT_FAILURE, + STAT_CONNECT_REUSED, + STAT_RECONNECT, + STAT_PCONNECT_SUCCESS, + STAT_OPENED_CONNECTIONS, + STAT_OPENED_PERSISTENT_CONNECTIONS, + STAT_CLOSE_EXPLICIT, + STAT_CLOSE_IMPLICIT, + STAT_CLOSE_DISCONNECT, + STAT_CLOSE_IN_MIDDLE, + STAT_FREE_RESULT_EXPLICIT, + STAT_FREE_RESULT_IMPLICIT, + STAT_STMT_CLOSE_EXPLICIT, + STAT_STMT_CLOSE_IMPLICIT, + STAT_MEM_EMALLOC_COUNT, + STAT_MEM_EMALLOC_AMOUNT, + STAT_MEM_ECALLOC_COUNT, + STAT_MEM_ECALLOC_AMOUNT, + STAT_MEM_EREALLOC_COUNT, + STAT_MEM_EREALLOC_AMOUNT, + STAT_MEM_EFREE_COUNT, + STAT_MEM_EFREE_AMOUNT, + STAT_MEM_MALLOC_COUNT, + STAT_MEM_MALLOC_AMOUNT, + STAT_MEM_CALLOC_COUNT, + STAT_MEM_CALLOC_AMOUNT, + STAT_MEM_REALLOC_COUNT, + STAT_MEM_REALLOC_AMOUNT, + STAT_MEM_FREE_COUNT, + STAT_MEM_FREE_AMOUNT, + STAT_MEM_ESTRNDUP_COUNT, + STAT_MEM_STRNDUP_COUNT, + STAT_MEM_ESTRDUP_COUNT, + STAT_MEM_STRDUP_COUNT, + STAT_TEXT_TYPE_FETCHED_NULL, + STAT_TEXT_TYPE_FETCHED_BIT, + STAT_TEXT_TYPE_FETCHED_INT8, + STAT_TEXT_TYPE_FETCHED_INT16, + STAT_TEXT_TYPE_FETCHED_INT24, + STAT_TEXT_TYPE_FETCHED_INT32, + STAT_TEXT_TYPE_FETCHED_INT64, + STAT_TEXT_TYPE_FETCHED_DECIMAL, + STAT_TEXT_TYPE_FETCHED_FLOAT, + STAT_TEXT_TYPE_FETCHED_DOUBLE, + STAT_TEXT_TYPE_FETCHED_DATE, + STAT_TEXT_TYPE_FETCHED_YEAR, + STAT_TEXT_TYPE_FETCHED_TIME, + STAT_TEXT_TYPE_FETCHED_DATETIME, + STAT_TEXT_TYPE_FETCHED_TIMESTAMP, + STAT_TEXT_TYPE_FETCHED_STRING, + STAT_TEXT_TYPE_FETCHED_BLOB, + STAT_TEXT_TYPE_FETCHED_ENUM, + STAT_TEXT_TYPE_FETCHED_SET, + STAT_TEXT_TYPE_FETCHED_GEOMETRY, + STAT_TEXT_TYPE_FETCHED_OTHER, + STAT_BINARY_TYPE_FETCHED_NULL, + STAT_BINARY_TYPE_FETCHED_BIT, + STAT_BINARY_TYPE_FETCHED_INT8, + STAT_BINARY_TYPE_FETCHED_INT16, + STAT_BINARY_TYPE_FETCHED_INT24, + STAT_BINARY_TYPE_FETCHED_INT32, + STAT_BINARY_TYPE_FETCHED_INT64, + STAT_BINARY_TYPE_FETCHED_DECIMAL, + STAT_BINARY_TYPE_FETCHED_FLOAT, + STAT_BINARY_TYPE_FETCHED_DOUBLE, + STAT_BINARY_TYPE_FETCHED_DATE, + STAT_BINARY_TYPE_FETCHED_YEAR, + STAT_BINARY_TYPE_FETCHED_TIME, + STAT_BINARY_TYPE_FETCHED_DATETIME, + STAT_BINARY_TYPE_FETCHED_TIMESTAMP, + STAT_BINARY_TYPE_FETCHED_STRING, + STAT_BINARY_TYPE_FETCHED_BLOB, + STAT_BINARY_TYPE_FETCHED_ENUM, + STAT_BINARY_TYPE_FETCHED_SET, + STAT_BINARY_TYPE_FETCHED_GEOMETRY, + STAT_BINARY_TYPE_FETCHED_OTHER, + STAT_INIT_COMMAND_EXECUTED_COUNT, + STAT_INIT_COMMAND_FAILED_COUNT, + STAT_COM_QUIT, + STAT_COM_INIT_DB, + STAT_COM_QUERY, + STAT_COM_FIELD_LIST, + STAT_COM_CREATE_DB, + STAT_COM_DROP_DB, + STAT_COM_REFRESH, + STAT_COM_SHUTDOWN, + STAT_COM_STATISTICS, + STAT_COM_PROCESS_INFO, + STAT_COM_CONNECT, + STAT_COM_PROCESS_KILL, + STAT_COM_DEBUG, + STAT_COM_PING, + STAT_COM_TIME, + STAT_COM_DELAYED_INSERT, + STAT_COM_CHANGE_USER, + STAT_COM_BINLOG_DUMP, + STAT_COM_TABLE_DUMP, + STAT_COM_CONNECT_OUT, + STAT_COM_REGISTER_SLAVE, + STAT_COM_STMT_PREPARE, + STAT_COM_STMT_EXECUTE, + STAT_COM_STMT_SEND_LONG_DATA, + STAT_COM_STMT_CLOSE, + STAT_COM_STMT_RESET, + STAT_COM_SET_OPTION, + STAT_COM_STMT_FETCH, + STAT_COM_DAEMON, + STAT_BYTES_RECEIVED_PURE_DATA_TEXT, + STAT_BYTES_RECEIVED_PURE_DATA_PS, + STAT_LAST /* Should be always the last */ +} enum_mysqlnd_collected_stats; + + +/* Enums */ +enum mysqlnd_packet_type +{ + PROT_GREET_PACKET= 0, + PROT_AUTH_PACKET, + PROT_AUTH_RESP_PACKET, + PROT_CHANGE_AUTH_RESP_PACKET, + PROT_OK_PACKET, + PROT_EOF_PACKET, + PROT_CMD_PACKET, + PROT_RSET_HEADER_PACKET, + PROT_RSET_FLD_PACKET, + PROT_ROW_PACKET, + PROT_STATS_PACKET, + PROT_PREPARE_RESP_PACKET, + PROT_CHG_USER_RESP_PACKET, + PROT_LAST /* should always be last */ +}; + + +enum php_mysqlnd_server_command +{ + COM_SLEEP = 0, + COM_QUIT, + COM_INIT_DB, + COM_QUERY, + COM_FIELD_LIST, + COM_CREATE_DB, + COM_DROP_DB, + COM_REFRESH, + COM_SHUTDOWN, + COM_STATISTICS, + COM_PROCESS_INFO, + COM_CONNECT, + COM_PROCESS_KILL, + COM_DEBUG, + COM_PING, + COM_TIME = 15, + COM_DELAYED_INSERT, + COM_CHANGE_USER, + COM_BINLOG_DUMP, + COM_TABLE_DUMP, + COM_CONNECT_OUT = 20, + COM_REGISTER_SLAVE, + COM_STMT_PREPARE = 22, + COM_STMT_EXECUTE = 23, + COM_STMT_SEND_LONG_DATA = 24, + COM_STMT_CLOSE = 25, + COM_STMT_RESET = 26, + COM_SET_OPTION = 27, + COM_STMT_FETCH = 28, + COM_DAEMON, + COM_END +}; + + +#define MYSQLND_DEFAULT_PREFETCH_ROWS (ulong) 1 + +#define MYSQLND_REFRESH_GRANT 1 /* Refresh grant tables */ +#define MYSQLND_REFRESH_LOG 2 /* Start on new log file */ +#define MYSQLND_REFRESH_TABLES 4 /* close all tables */ +#define MYSQLND_REFRESH_HOSTS 8 /* Flush host cache */ +#define MYSQLND_REFRESH_STATUS 16 /* Flush status variables */ +#define MYSQLND_REFRESH_THREADS 32 /* Flush thread cache */ +#define MYSQLND_REFRESH_SLAVE 64 /* Reset master info and restart slave */ +#define MYSQLND_REFRESH_MASTER 128 /* Remove all bin logs in the index */ +#define MYSQLND_REFRESH_BACKUP_LOG 0x200000L + +#endif /* MYSQLND_ENUM_N_DEF_H */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_ext_plugin.c b/ext/mysqlnd/mysqlnd_ext_plugin.c new file mode 100644 index 0000000..21be3fd --- /dev/null +++ b/ext/mysqlnd/mysqlnd_ext_plugin.c @@ -0,0 +1,226 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: mysqlnd.c 318221 2011-10-19 15:04:12Z andrey $ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_result.h" +#include "mysqlnd_debug.h" + +static struct st_mysqlnd_conn_methods * mysqlnd_conn_methods; +static struct st_mysqlnd_conn_data_methods * mysqlnd_conn_data_methods; +static struct st_mysqlnd_stmt_methods * mysqlnd_stmt_methods; + +/* {{{ _mysqlnd_plugin_get_plugin_connection_data */ +PHPAPI void ** +_mysqlnd_plugin_get_plugin_connection_data(const MYSQLND * conn, unsigned int plugin_id TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_plugin_get_plugin_connection_data"); + DBG_INF_FMT("plugin_id=%u", plugin_id); + if (!conn || plugin_id >= mysqlnd_plugin_count()) { + return NULL; + } + DBG_RETURN((void *)((char *)conn + sizeof(MYSQLND) + plugin_id * sizeof(void *))); +} +/* }}} */ + + +/* {{{ _mysqlnd_plugin_get_plugin_connection_data_data */ +PHPAPI void ** +_mysqlnd_plugin_get_plugin_connection_data_data(const MYSQLND_CONN_DATA * conn, unsigned int plugin_id TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_plugin_get_plugin_connection_data_data"); + DBG_INF_FMT("plugin_id=%u", plugin_id); + if (!conn || plugin_id >= mysqlnd_plugin_count()) { + return NULL; + } + DBG_RETURN((void *)((char *)conn + sizeof(MYSQLND_CONN_DATA) + plugin_id * sizeof(void *))); +} +/* }}} */ + + +/* {{{ _mysqlnd_plugin_get_plugin_result_data */ +PHPAPI void ** _mysqlnd_plugin_get_plugin_result_data(const MYSQLND_RES * result, unsigned int plugin_id TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_plugin_get_plugin_result_data"); + DBG_INF_FMT("plugin_id=%u", plugin_id); + if (!result || plugin_id >= mysqlnd_plugin_count()) { + return NULL; + } + DBG_RETURN((void *)((char *)result + sizeof(MYSQLND_RES) + plugin_id * sizeof(void *))); +} +/* }}} */ + + +/* {{{ _mysqlnd_plugin_get_plugin_protocol_data */ +PHPAPI void ** +_mysqlnd_plugin_get_plugin_protocol_data(const MYSQLND_PROTOCOL * protocol, unsigned int plugin_id TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_plugin_get_plugin_protocol_data"); + DBG_INF_FMT("plugin_id=%u", plugin_id); + if (!protocol || plugin_id >= mysqlnd_plugin_count()) { + return NULL; + } + DBG_RETURN((void *)((char *)protocol + sizeof(MYSQLND_PROTOCOL) + plugin_id * sizeof(void *))); +} +/* }}} */ + + +/* {{{ _mysqlnd_plugin_get_plugin_stmt_data */ +PHPAPI void ** _mysqlnd_plugin_get_plugin_stmt_data(const MYSQLND_STMT * stmt, unsigned int plugin_id TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_plugin_get_plugin_stmt_data"); + DBG_INF_FMT("plugin_id=%u", plugin_id); + if (!stmt || plugin_id >= mysqlnd_plugin_count()) { + return NULL; + } + DBG_RETURN((void *)((char *)stmt + sizeof(MYSQLND_STMT) + plugin_id * sizeof(void *))); +} +/* }}} */ + + +/* {{{ _mysqlnd_plugin_get_plugin_net_data */ +PHPAPI void ** _mysqlnd_plugin_get_plugin_net_data(const MYSQLND_NET * net, unsigned int plugin_id TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_plugin_get_plugin_net_data"); + DBG_INF_FMT("plugin_id=%u", plugin_id); + if (!net || plugin_id >= mysqlnd_plugin_count()) { + return NULL; + } + DBG_RETURN((void *)((char *)net + sizeof(MYSQLND_NET) + plugin_id * sizeof(void *))); +} +/* }}} */ + + + +/* {{{ mysqlnd_conn_get_methods */ +PHPAPI struct st_mysqlnd_conn_methods * +mysqlnd_conn_get_methods() +{ + return mysqlnd_conn_methods; +} +/* }}} */ + +/* {{{ mysqlnd_conn_set_methods */ +PHPAPI void mysqlnd_conn_set_methods(struct st_mysqlnd_conn_methods *methods) +{ + mysqlnd_conn_methods = methods; +} +/* }}} */ + + +/* {{{ mysqlnd_conn_get_methods */ +PHPAPI struct st_mysqlnd_conn_data_methods * +mysqlnd_conn_data_get_methods() +{ + return mysqlnd_conn_data_methods; +} +/* }}} */ + +/* {{{ mysqlnd_conn_set_methods */ +PHPAPI void mysqlnd_conn_data_set_methods(struct st_mysqlnd_conn_data_methods * methods) +{ + mysqlnd_conn_data_methods = methods; +} +/* }}} */ + + +/* {{{ mysqlnd_result_get_methods */ +PHPAPI struct st_mysqlnd_res_methods * +mysqlnd_result_get_methods() +{ + return &MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_res); +} +/* }}} */ + + +/* {{{ mysqlnd_result_set_methods */ +PHPAPI void +mysqlnd_result_set_methods(struct st_mysqlnd_res_methods * methods) +{ + MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_res) = *methods; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_get_methods */ +PHPAPI struct st_mysqlnd_stmt_methods * +mysqlnd_stmt_get_methods() +{ + return mysqlnd_stmt_methods; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_set_methods */ +PHPAPI void +mysqlnd_stmt_set_methods(struct st_mysqlnd_stmt_methods *methods) +{ + mysqlnd_stmt_methods = methods; +} +/* }}} */ + + +/* {{{ mysqlnd_protocol_get_methods */ +PHPAPI struct st_mysqlnd_protocol_methods * +mysqlnd_protocol_get_methods() +{ + return &MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_protocol); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol_set_methods */ +PHPAPI void +mysqlnd_protocol_set_methods(struct st_mysqlnd_protocol_methods * methods) +{ + MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_protocol) = *methods; +} +/* }}} */ + + +/* {{{ mysqlnd_net_get_methods */ +PHPAPI struct st_mysqlnd_net_methods * +mysqlnd_net_get_methods() +{ + return &MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_net); +} +/* }}} */ + + +/* {{{ mysqlnd_net_set_methods */ +PHPAPI void +mysqlnd_net_set_methods(struct st_mysqlnd_net_methods * methods) +{ + MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_net) = *methods; +} +/* }}} */ + + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_ext_plugin.h b/ext/mysqlnd/mysqlnd_ext_plugin.h new file mode 100644 index 0000000..ef9d085 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_ext_plugin.h @@ -0,0 +1,72 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ +/* $Id: mysqlnd.h 318221 2011-10-19 15:04:12Z andrey $ */ + +#ifndef MYSQLND_EXT_PLUGIN_H +#define MYSQLND_EXT_PLUGIN_H + +PHPAPI void ** _mysqlnd_plugin_get_plugin_connection_data(const MYSQLND * conn, unsigned int plugin_id TSRMLS_DC); +#define mysqlnd_plugin_get_plugin_connection_data(c, p_id) _mysqlnd_plugin_get_plugin_connection_data((c), (p_id) TSRMLS_CC) + +PHPAPI void ** _mysqlnd_plugin_get_plugin_connection_data_data(const MYSQLND_CONN_DATA * conn, unsigned int plugin_id TSRMLS_DC); +#define mysqlnd_plugin_get_plugin_connection_data_data(c, p_id) _mysqlnd_plugin_get_plugin_connection_data_data((c), (p_id) TSRMLS_CC) + +PHPAPI void ** _mysqlnd_plugin_get_plugin_result_data(const MYSQLND_RES * result, unsigned int plugin_id TSRMLS_DC); +#define mysqlnd_plugin_get_plugin_result_data(r, p_id) _mysqlnd_plugin_get_plugin_result_data((r), (p_id) TSRMLS_CC) + +PHPAPI void ** _mysqlnd_plugin_get_plugin_stmt_data(const MYSQLND_STMT * stmt, unsigned int plugin_id TSRMLS_DC); +#define mysqlnd_plugin_get_plugin_stmt_data(s, p_id) _mysqlnd_plugin_get_plugin_stmt_data((s), (p_id) TSRMLS_CC) + +PHPAPI void ** _mysqlnd_plugin_get_plugin_protocol_data(const MYSQLND_PROTOCOL * protocol, unsigned int plugin_id TSRMLS_DC); +#define mysqlnd_plugin_get_plugin_protocol_data(p, p_id) _mysqlnd_plugin_get_plugin_protocol_data((p), (p_id) TSRMLS_CC) + +PHPAPI void ** _mysqlnd_plugin_get_plugin_net_data(const MYSQLND_NET * net, unsigned int plugin_id TSRMLS_DC); +#define mysqlnd_plugin_get_plugin_net_data(n, p_id) _mysqlnd_plugin_get_plugin_net_data((n), (p_id) TSRMLS_CC) + + +PHPAPI struct st_mysqlnd_conn_methods * mysqlnd_conn_get_methods(); +PHPAPI void mysqlnd_conn_set_methods(struct st_mysqlnd_conn_methods * methods); + +PHPAPI struct st_mysqlnd_conn_data_methods * mysqlnd_conn_data_get_methods(); +PHPAPI void mysqlnd_conn_data_set_methods(struct st_mysqlnd_conn_data_methods * methods); + +PHPAPI struct st_mysqlnd_res_methods * mysqlnd_result_get_methods(); +PHPAPI void mysqlnd_result_set_methods(struct st_mysqlnd_res_methods * methods); + +PHPAPI struct st_mysqlnd_stmt_methods * mysqlnd_stmt_get_methods(); +PHPAPI void mysqlnd_stmt_set_methods(struct st_mysqlnd_stmt_methods * methods); + +PHPAPI struct st_mysqlnd_protocol_methods * mysqlnd_protocol_get_methods(); +PHPAPI void mysqlnd_protocol_set_methods(struct st_mysqlnd_protocol_methods * methods); + +PHPAPI struct st_mysqlnd_net_methods * mysqlnd_net_get_methods(); +PHPAPI void mysqlnd_net_set_methods(struct st_mysqlnd_net_methods * methods); + + +#endif /* MYSQLND_EXT_PLUGIN_H */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_libmysql_compat.h b/ext/mysqlnd/mysqlnd_libmysql_compat.h new file mode 100644 index 0000000..c967fb3 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_libmysql_compat.h @@ -0,0 +1,135 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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> | + +----------------------------------------------------------------------+ + +*/ + +#ifndef MYSQLND_LIBMYSQL_COMPAT_H +#define MYSQLND_LIBMYSQL_COMPAT_H + +/* Global types and definitions*/ +#define MYSQL_NO_DATA MYSQLND_NO_DATA +#define MYSQL_DATA_TRUNCATED MYSQLND_DATA_TRUNCATED +#define MYSQL_STMT MYSQLND_STMT +#define MYSQL_FIELD MYSQLND_FIELD +#define MYSQL_RES MYSQLND_RES +#define MYSQL_ROW MYSQLND_ROW_C +#define MYSQL MYSQLND +#define my_bool zend_bool +#define my_ulonglong uint64_t + +#define MYSQL_VERSION_ID MYSQLND_VERSION_ID +#define MYSQL_SERVER_VERSION MYSQLND_VERSION +#define MYSQL_ERRMSG_SIZE MYSQLND_ERRMSG_SIZE +#define SQLSTATE_LENGTH MYSQLND_SQLSTATE_LENGTH + +/* functions */ +#define mysql_affected_rows(r) mysqlnd_affected_rows((r)) +#define mysql_autocommit(r,m) mysqlnd_autocommit((r),(m)) +#define mysql_change_user(r,a,b,c) mysqlnd_change_user((r), (a), (b), (c), FALSE) +#define mysql_character_set_name(c) mysqlnd_character_set_name((c)) +#define mysql_close(r) mysqlnd_close((r), MYSQLND_CLOSE_EXPLICIT) +#define mysql_commit(r) mysqlnd_commit((r)) +#define mysql_data_seek(r,o) mysqlnd_data_seek((r),(o)) +#define mysql_debug(x) mysqlnd_debug((x)) +#define mysql_dump_debug_info(r) mysqlnd_dump_debug_info((r)) +#define mysql_errno(r) mysqlnd_errno((r)) +#define mysql_error(r) mysqlnd_error((r)) +#define mysql_escape_string(a,b,c) mysqlnd_escape_string((a), (b), (c)) +#define mysql_fetch_field(r) mysqlnd_fetch_field((r)) +#define mysql_fetch_field_direct(r,o) mysqlnd_fetch_field_direct((r), (o)) +#define mysql_fetch_fields(r) mysqlnd_fetch_fields((r)) +#define mysql_fetch_lengths(r) mysqlnd_fetch_lengths((r)) +#define mysql_fetch_row(r) mysqlnd_fetch_row_c((r)) +#define mysql_field_count(r) mysqlnd_field_count((r)) +#define mysql_field_seek(r,o) mysqlnd_field_seek((r), (o)) +#define mysql_field_tell(r) mysqlnd_field_tell((r)) +#define mysql_init(a) mysqlnd_init((a)) +#define mysql_insert_id(r) mysqlnd_insert_id((r)) +#define mysql_kill(r,n) mysqlnd_kill((r), (n)) +#define mysql_list_dbs(c, wild) mysqlnd_list_dbs((c), (wild)) +#define mysql_list_fields(c, tab, wild) mysqlnd_list_fields((c), (tab), (wild)) +#define mysql_list_processes(c) mysqlnd_list_processes((c)) +#define mysql_list_tables(c, wild) mysqlnd_list_tables((c), (wild)) +#define mysql_more_results(r) mysqlnd_more_results((r)) +#define mysql_next_result(r) mysqlnd_next_result((r)) +#define mysql_num_fields(r) mysqlnd_num_fields((r)) +#define mysql_num_rows(r) mysqlnd_num_rows((r)) +#define mysql_ping(r) mysqlnd_ping((r)) +#define mysql_real_escape_string(r,a,b,c) mysqlnd_real_escape_string((r), (a), (b), (c)) +#define mysql_real_query(r,a,b) mysqlnd_query((r), (a), (b)) +#define mysql_refresh(conn, options) mysqlnd_refresh((conn), (options)) +#define mysql_rollback(r) mysqlnd_rollback((r)) +#define mysql_select_db(r,a) mysqlnd_select_db((r), (a) ,strlen((a))) +#define mysql_set_server_option(r,o) mysqlnd_set_server_option((r), (o)) +#define mysql_set_character_set(r,a) mysqlnd_set_character_set((r), (a)) +#define mysql_sqlstate(r) mysqlnd_sqlstate((r)) +#define mysql_ssl_set(c,key,cert,ca,capath,cipher) mysqlnd_ssl_set((c), (key), (cert), (ca), (capath), (cipher)) +#define mysql_stmt_affected_rows(s) mysqlnd_stmt_affected_rows((s)) +#define mysql_stmt_field_count(s) mysqlnd_stmt_field_count((s)) +#define mysql_stmt_param_count(s) mysqlnd_stmt_param_count((s)) +#define mysql_stmt_num_rows(s) mysqlnd_stmt_num_rows((s)) +#define mysql_stmt_insert_id(s) mysqlnd_stmt_insert_id((s)) +#define mysql_stmt_close(s) mysqlnd_stmt_close((s)) +#define mysql_stmt_bind_param(s,b) mysqlnd_stmt_bind_param((s), (b)) +#define mysql_stmt_bind_result(s,b) mysqlnd_stmt_bind_result((s), (b)) +#define mysql_stmt_errno(s) mysqlnd_stmt_errno((s)) +#define mysql_stmt_error(s) mysqlnd_stmt_error((s)) +#define mysql_stmt_sqlstate(s) mysqlnd_stmt_sqlstate((s)) +#define mysql_stmt_prepare(s,q,l) mysqlnd_stmt_prepare((s), (q), (l)) +#define mysql_stmt_execute(s) mysqlnd_stmt_execute((s)) +#define mysql_stmt_reset(s) mysqlnd_stmt_reset((s)) +#define mysql_stmt_store_result(s) mysqlnd_stmt_store_result((s)) +#define mysql_stmt_free_result(s) mysqlnd_stmt_free_result((s)) +#define mysql_stmt_data_seek(s,r) mysqlnd_stmt_data_seek((s), (r)) +#define mysql_stmt_send_long_data(s,p,d,l) mysqlnd_stmt_send_long_data((s), (p), (d), (l)) +#define mysql_stmt_attr_get(s,a,v) mysqlnd_stmt_attr_get((s), (a), (v)) +#define mysql_stmt_attr_set(s,a,v) mysqlnd_stmt_attr_set((s), (a), (v)) +#define mysql_stmt_param_metadata(s) mysqlnd_stmt_param_metadata((s)) +#define mysql_stmt_result_metadata(s) mysqlnd_stmt_result_metadata((s)) +#define mysql_stmt_next_result(s) mysqlnd_stmt_next_result((s)) +#define mysql_stmt_more_results(s) mysqlnd_stmt_more_results((s)) +#define mysql_thread_safe() mysqlnd_thread_safe() +#define mysql_info(r) mysqlnd_info((r)) +#define mysql_options(r,a,b) mysqlnd_options((r), (a), (b)) +#define mysql_stmt_init(r) mysqlnd_stmt_init((r)) +#define mysql_free_result(r) mysqlnd_free_result((r), FALSE) +#define mysql_store_result(r) mysqlnd_store_result((r)) +#define mysql_use_result(r) mysqlnd_use_result((r)) +#define mysql_async_store_result(r) mysqlnd_async_store_result((r)) +#define mysql_thread_id(r) mysqlnd_thread_id((r)) +#define mysql_get_client_info() mysqlnd_get_client_info() +#define mysql_get_client_version() mysqlnd_get_client_version() +#define mysql_get_host_info(r) mysqlnd_get_host_info((r)) +#define mysql_get_proto_info(r) mysqlnd_get_proto_info((r)) +#define mysql_get_server_info(r) mysqlnd_get_server_info((r)) +#define mysql_get_server_version(r) mysqlnd_get_server_version((r)) +#define mysql_warning_count(r) mysqlnd_warning_count((r)) +#define mysql_eof(r) (((r)->unbuf && (r)->unbuf->eof_reached) || (r)->stored_data) + +#define REFRESH_GRANT MYSQLND_REFRESH_GRANT +#define REFRESH_LOG MYSQLND_REFRESH_LOG +#define REFRESH_TABLES MYSQLND_REFRESH_TABLES +#define REFRESH_HOSTS MYSQLND_REFRESH_HOSTS +#define REFRESH_STATUS MYSQLND_REFRESH_STATUS +#define REFRESH_THREADS MYSQLND_REFRESH_THREADS +#define REFRESH_SLAVE MYSQLND_REFRESH_SLAVE +#define REFRESH_MASTER MYSQLND_REFRESH_MASTER +#define REFRESH_BACKUP_LOG MYSQLND_REFRESH_BACKUP_LOG + +#endif /* MYSQLND_LIBMYSQL_COMPAT_H */ diff --git a/ext/mysqlnd/mysqlnd_loaddata.c b/ext/mysqlnd/mysqlnd_loaddata.c new file mode 100644 index 0000000..40de45b --- /dev/null +++ b/ext/mysqlnd/mysqlnd_loaddata.c @@ -0,0 +1,249 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "php_globals.h" +#include "mysqlnd.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_debug.h" + +/* {{{ mysqlnd_local_infile_init */ +static +int mysqlnd_local_infile_init(void ** ptr, char * filename, void ** userdata TSRMLS_DC) +{ + MYSQLND_INFILE_INFO *info; + php_stream_context *context = NULL; + + DBG_ENTER("mysqlnd_local_infile_init"); + + info = ((MYSQLND_INFILE_INFO *)mnd_ecalloc(1, sizeof(MYSQLND_INFILE_INFO))); + if (!info) { + DBG_RETURN(1); + } + + *ptr = info; + + /* check open_basedir */ + if (PG(open_basedir)) { + if (php_check_open_basedir_ex(filename, 0 TSRMLS_CC) == -1) { + strcpy(info->error_msg, "open_basedir restriction in effect. Unable to open file"); + info->error_no = CR_UNKNOWN_ERROR; + DBG_RETURN(1); + } + } + + info->filename = filename; + info->fd = php_stream_open_wrapper_ex((char *)filename, "r", 0, NULL, context); + + if (info->fd == NULL) { + snprintf((char *)info->error_msg, sizeof(info->error_msg), "Can't find file '%-.64s'.", filename); + info->error_no = MYSQLND_EE_FILENOTFOUND; + DBG_RETURN(1); + } + + DBG_RETURN(0); +} +/* }}} */ + + +/* {{{ mysqlnd_local_infile_read */ +static +int mysqlnd_local_infile_read(void * ptr, zend_uchar * buf, unsigned int buf_len TSRMLS_DC) +{ + MYSQLND_INFILE_INFO *info = (MYSQLND_INFILE_INFO *)ptr; + int count; + + DBG_ENTER("mysqlnd_local_infile_read"); + + count = (int)php_stream_read(info->fd, (char *) buf, buf_len); + + if (count < 0) { + strcpy(info->error_msg, "Error reading file"); + info->error_no = CR_UNKNOWN_ERROR; + } + + DBG_RETURN(count); +} +/* }}} */ + + +/* {{{ mysqlnd_local_infile_error */ +static +int mysqlnd_local_infile_error(void * ptr, char *error_buf, unsigned int error_buf_len TSRMLS_DC) +{ + MYSQLND_INFILE_INFO *info = (MYSQLND_INFILE_INFO *)ptr; + + DBG_ENTER("mysqlnd_local_infile_error"); + + if (info) { + strlcpy(error_buf, info->error_msg, error_buf_len); + DBG_INF_FMT("have info, %d", info->error_no); + DBG_RETURN(info->error_no); + } + + strlcpy(error_buf, "Unknown error", error_buf_len); + DBG_INF_FMT("no info, %d", CR_UNKNOWN_ERROR); + DBG_RETURN(CR_UNKNOWN_ERROR); +} +/* }}} */ + + +/* {{{ mysqlnd_local_infile_end */ +static +void mysqlnd_local_infile_end(void * ptr TSRMLS_DC) +{ + MYSQLND_INFILE_INFO *info = (MYSQLND_INFILE_INFO *)ptr; + + if (info) { + /* php_stream_close segfaults on NULL */ + if (info->fd) { + php_stream_close(info->fd); + info->fd = NULL; + } + mnd_efree(info); + } +} +/* }}} */ + + +/* {{{ mysqlnd_local_infile_default */ +PHPAPI void +mysqlnd_local_infile_default(MYSQLND_CONN_DATA * conn) +{ + conn->infile.local_infile_init = mysqlnd_local_infile_init; + conn->infile.local_infile_read = mysqlnd_local_infile_read; + conn->infile.local_infile_error = mysqlnd_local_infile_error; + conn->infile.local_infile_end = mysqlnd_local_infile_end; +} +/* }}} */ + + +/* {{{ mysqlnd_set_local_infile_handler */ +PHPAPI void +mysqlnd_set_local_infile_handler(MYSQLND_CONN_DATA * const conn, const char * const funcname) +{ + if (!conn->infile.callback) { + MAKE_STD_ZVAL(conn->infile.callback); + } else { + zval_dtor(conn->infile.callback); + } + ZVAL_STRING(conn->infile.callback, (char*) funcname, 1); +} +/* }}} */ + + +static const char *lost_conn = "Lost connection to MySQL server during LOAD DATA of local file"; + + +/* {{{ mysqlnd_handle_local_infile */ +enum_func_status +mysqlnd_handle_local_infile(MYSQLND_CONN_DATA * conn, const char * filename, zend_bool * is_warning TSRMLS_DC) +{ + zend_uchar *buf = NULL; + zend_uchar empty_packet[MYSQLND_HEADER_SIZE]; + enum_func_status result = FAIL; + unsigned int buflen = 4096; + void *info = NULL; + int bufsize; + size_t ret; + MYSQLND_INFILE infile; + MYSQLND_NET * net = conn->net; + + DBG_ENTER("mysqlnd_handle_local_infile"); + + if (!(conn->options->flags & CLIENT_LOCAL_FILES)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "LOAD DATA LOCAL INFILE forbidden"); + /* write empty packet to server */ + ret = net->m.send_ex(net, empty_packet, 0, conn->stats, conn->error_info TSRMLS_CC); + *is_warning = TRUE; + goto infile_error; + } + + infile = conn->infile; + /* allocate buffer for reading data */ + buf = (zend_uchar *) mnd_ecalloc(1, buflen); + + *is_warning = FALSE; + + /* init handler: allocate read buffer and open file */ + if (infile.local_infile_init(&info, (char *)filename, conn->infile.userdata TSRMLS_CC)) { + char tmp_buf[sizeof(conn->error_info->error)]; + int tmp_error_no; + *is_warning = TRUE; + /* error occurred */ + tmp_error_no = infile.local_infile_error(info, tmp_buf, sizeof(tmp_buf) TSRMLS_CC); + SET_CLIENT_ERROR(*conn->error_info, tmp_error_no, UNKNOWN_SQLSTATE, tmp_buf); + /* write empty packet to server */ + ret = net->m.send_ex(net, empty_packet, 0, conn->stats, conn->error_info TSRMLS_CC); + goto infile_error; + } + + /* read data */ + while ((bufsize = infile.local_infile_read (info, buf + MYSQLND_HEADER_SIZE, buflen - MYSQLND_HEADER_SIZE TSRMLS_CC)) > 0) { + if ((ret = net->m.send_ex(net, buf, bufsize, conn->stats, conn->error_info TSRMLS_CC)) == 0) { + DBG_ERR_FMT("Error during read : %d %s %s", CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn); + SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn); + goto infile_error; + } + } + + /* send empty packet for eof */ + if ((ret = net->m.send_ex(net, empty_packet, 0, conn->stats, conn->error_info TSRMLS_CC)) == 0) { + SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn); + goto infile_error; + } + + /* error during read occurred */ + if (bufsize < 0) { + char tmp_buf[sizeof(conn->error_info->error)]; + int tmp_error_no; + *is_warning = TRUE; + DBG_ERR_FMT("Bufsize < 0, warning, %d %s %s", CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn); + tmp_error_no = infile.local_infile_error(info, tmp_buf, sizeof(tmp_buf) TSRMLS_CC); + SET_CLIENT_ERROR(*conn->error_info, tmp_error_no, UNKNOWN_SQLSTATE, tmp_buf); + goto infile_error; + } + + result = PASS; + +infile_error: + /* get response from server and update upsert values */ + if (FAIL == conn->m->simple_command_handle_response(conn, PROT_OK_PACKET, FALSE, COM_QUERY, FALSE TSRMLS_CC)) { + result = FAIL; + } + + (*conn->infile.local_infile_end)(info TSRMLS_CC); + if (buf) { + mnd_efree(buf); + } + DBG_INF_FMT("%s", result == PASS? "PASS":"FAIL"); + DBG_RETURN(result); +} +/* }}} */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_net.c b/ext/mysqlnd/mysqlnd_net.c new file mode 100644 index 0000000..21241b3 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_net.c @@ -0,0 +1,1069 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: mysqlnd_ps.c 316906 2011-09-17 10:24:18Z pajoye $ */ +#include "php.h" +#include "php_globals.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_statistics.h" +#include "mysqlnd_debug.h" +#include "mysqlnd_ext_plugin.h" +#include "php_network.h" +#include "zend_ini.h" +#ifdef MYSQLND_COMPRESSION_ENABLED +#include <zlib.h> +#endif + +#ifndef PHP_WIN32 +#include <netinet/tcp.h> +#else +#include <winsock.h> +#endif + + +/* {{{ mysqlnd_set_sock_no_delay */ +static int +mysqlnd_set_sock_no_delay(php_stream * stream TSRMLS_DC) +{ + + int socketd = ((php_netstream_data_t*)stream->abstract)->socket; + int ret = SUCCESS; + int flag = 1; + int result = setsockopt(socketd, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)); + + DBG_ENTER("mysqlnd_set_sock_no_delay"); + + if (result == -1) { + ret = FAILURE; + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_net::network_read_ex */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_net, network_read_ex)(MYSQLND_NET * const net, zend_uchar * const buffer, const size_t count, + MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC) +{ + enum_func_status return_value = PASS; + size_t to_read = count, ret; + size_t old_chunk_size = net->stream->chunk_size; + zend_uchar * p = buffer; + + DBG_ENTER("mysqlnd_net::network_read_ex"); + DBG_INF_FMT("count="MYSQLND_SZ_T_SPEC, count); + + net->stream->chunk_size = MIN(to_read, net->options.net_read_buffer_size); + while (to_read) { + if (!(ret = php_stream_read(net->stream, (char *) p, to_read))) { + DBG_ERR_FMT("Error while reading header from socket"); + return_value = FAIL; + break; + } + p += ret; + to_read -= ret; + } + MYSQLND_INC_CONN_STATISTIC_W_VALUE(stats, STAT_BYTES_RECEIVED, count - to_read); + net->stream->chunk_size = old_chunk_size; + DBG_RETURN(return_value); +} +/* }}} */ + + +/* {{{ mysqlnd_net::network_write_ex */ +static size_t +MYSQLND_METHOD(mysqlnd_net, network_write_ex)(MYSQLND_NET * const net, const zend_uchar * const buffer, const size_t count, + MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC) +{ + size_t ret; + DBG_ENTER("mysqlnd_net::network_write_ex"); + ret = php_stream_write(net->stream, (char *)buffer, count); + DBG_RETURN(ret); +} +/* }}} */ + +/* {{{ mysqlnd_net::open_pipe */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_net, open_pipe)(MYSQLND_NET * const net, const char * const scheme, const size_t scheme_len, + const zend_bool persistent, + MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC) +{ +#if PHP_API_VERSION < 20100412 + unsigned int streams_options = ENFORCE_SAFE_MODE; +#else + unsigned int streams_options = 0; +#endif + DBG_ENTER("mysqlnd_net::open_pipe"); + if (persistent) { + streams_options |= STREAM_OPEN_PERSISTENT; + } + streams_options |= IGNORE_URL; + net->stream = php_stream_open_wrapper((char*) scheme + sizeof("pipe://") - 1, "r+", streams_options, NULL); + if (!net->stream) { + SET_CLIENT_ERROR(*error_info, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, "Unknown errror while connecting"); + DBG_RETURN(FAIL); + } + /* + Streams are not meant for C extensions! Thus we need a hack. Every connected stream will + be registered as resource (in EG(regular_list). So far, so good. However, it won't be + unregistered yntil the script ends. So, we need to take care of that. + */ + net->stream->in_free = 1; + zend_hash_index_del(&EG(regular_list), net->stream->rsrc_id); + net->stream->in_free = 0; + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_net::open_tcp_or_unix */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_net, open_tcp_or_unix)(MYSQLND_NET * const net, const char * const scheme, const size_t scheme_len, + const zend_bool persistent, + MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC) +{ +#if PHP_API_VERSION < 20100412 + unsigned int streams_options = ENFORCE_SAFE_MODE; +#else + unsigned int streams_options = 0; +#endif + unsigned int streams_flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT; + char * hashed_details = NULL; + int hashed_details_len = 0; + char * errstr = NULL; + int errcode = 0; + struct timeval tv; + + DBG_ENTER("mysqlnd_net::open_tcp_or_unix"); + + if (persistent) { + hashed_details_len = mnd_sprintf(&hashed_details, 0, "%p", net); + DBG_INF_FMT("hashed_details=%s", hashed_details); + } + + if (net->options.timeout_connect) { + tv.tv_sec = net->options.timeout_connect; + tv.tv_usec = 0; + } + + DBG_INF_FMT("calling php_stream_xport_create"); + net->stream = php_stream_xport_create(scheme, scheme_len, streams_options, streams_flags, + hashed_details, (net->options.timeout_connect) ? &tv : NULL, + NULL /*ctx*/, &errstr, &errcode); + if (errstr || !net->stream) { + DBG_ERR("Error"); + if (hashed_details) { + mnd_sprintf_free(hashed_details); + } + errcode = CR_CONNECTION_ERROR; + SET_CLIENT_ERROR(*error_info, errcode? errcode:CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, errstr); + if (errstr) { + /* no mnd_ since we don't allocate it */ + efree(errstr); + } + DBG_RETURN(FAIL); + } + 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. + */ + net->stream->in_free = 1; + zend_hash_del(&EG(persistent_list), hashed_details, hashed_details_len + 1); + net->stream->in_free = 0; + } +#if ZEND_DEBUG + /* Shut-up the streams, they don't know what they are doing */ + net->stream->__exposed = 1; +#endif + mnd_sprintf_free(hashed_details); + } + + /* + Streams are not meant for C extensions! Thus we need a hack. Every connected stream will + be registered as resource (in EG(regular_list). So far, so good. However, it won't be + unregistered yntil the script ends. So, we need to take care of that. + */ + net->stream->in_free = 1; + zend_hash_index_del(&EG(regular_list), net->stream->rsrc_id); + net->stream->in_free = 0; + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_net::connect_ex */ +static void +MYSQLND_METHOD(mysqlnd_net, post_connect_set_opt)(MYSQLND_NET * const net, + const char * const scheme, const size_t scheme_len, + MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_net::post_connect_set_opt"); + if (net->options.timeout_read) { + struct timeval tv; + DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->options.timeout_read); + tv.tv_sec = net->options.timeout_read; + tv.tv_usec = 0; + php_stream_set_option(net->stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv); + } + + if (!memcmp(scheme, "tcp://", sizeof("tcp://") - 1)) { + /* TCP -> Set TCP_NODELAY */ + mysqlnd_set_sock_no_delay(net->stream TSRMLS_CC); + } + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_net::connect_ex */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_net, connect_ex)(MYSQLND_NET * const net, const char * const scheme, const size_t scheme_len, + const zend_bool persistent, + MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC) +{ + enum_func_status ret = FAIL; + func_mysqlnd_net__open_stream open_stream = NULL; + DBG_ENTER("mysqlnd_net::connect_ex"); + + net->packet_no = net->compressed_envelope_packet_no = 0; + + net->m.close_stream(net, conn_stats, error_info TSRMLS_CC); + + open_stream = (scheme_len > (sizeof("pipe://") - 1) && !memcmp(scheme, "pipe://", sizeof("pipe://") - 1))? net->m.open_pipe: + net->m.open_tcp_or_unix; + + if (PASS == (ret = open_stream(net, scheme, scheme_len, persistent, conn_stats, error_info TSRMLS_CC))) { + net->m.post_connect_set_opt(net, scheme, scheme_len, conn_stats, error_info TSRMLS_CC); + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* We assume that MYSQLND_HEADER_SIZE is 4 bytes !! */ +#define COPY_HEADER(T,A) do { \ + *(((char *)(T))) = *(((char *)(A)));\ + *(((char *)(T))+1) = *(((char *)(A))+1);\ + *(((char *)(T))+2) = *(((char *)(A))+2);\ + *(((char *)(T))+3) = *(((char *)(A))+3); } while (0) +#define STORE_HEADER_SIZE(safe_storage, buffer) COPY_HEADER((safe_storage), (buffer)) +#define RESTORE_HEADER_SIZE(buffer, safe_storage) STORE_HEADER_SIZE((safe_storage), (buffer)) + + +/* {{{ mysqlnd_net::send_ex */ +/* + IMPORTANT : It's expected that buffer has place in the beginning for MYSQLND_HEADER_SIZE !!!! + This is done for performance reasons in the caller of this function. + Otherwise we will have to do send two TCP packets, or do new alloc and memcpy. + Neither are quick, thus the clients of this function are obligated to do + what they are asked for. + + `count` is actually the length of the payload data. Thus : + count + MYSQLND_HEADER_SIZE = sizeof(buffer) (not the pointer but the actual buffer) +*/ +static size_t +MYSQLND_METHOD(mysqlnd_net, send_ex)(MYSQLND_NET * const net, zend_uchar * const buffer, const size_t count, + MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC) +{ + zend_uchar safe_buf[((MYSQLND_HEADER_SIZE) + (sizeof(zend_uchar)) - 1) / (sizeof(zend_uchar))]; + zend_uchar * safe_storage = safe_buf; + size_t bytes_sent, packets_sent = 1; + size_t left = count; + zend_uchar * p = (zend_uchar *) buffer; + zend_uchar * compress_buf = NULL; + size_t to_be_sent; + + DBG_ENTER("mysqlnd_net::send_ex"); + DBG_INF_FMT("count=" MYSQLND_SZ_T_SPEC " compression=%u", count, net->compressed); + + if (net->compressed == TRUE) { + size_t comp_buf_size = MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE + MIN(left, MYSQLND_MAX_PACKET_SIZE); + DBG_INF_FMT("compress_buf_size="MYSQLND_SZ_T_SPEC, comp_buf_size); + compress_buf = mnd_emalloc(comp_buf_size); + } + + do { + to_be_sent = MIN(left, MYSQLND_MAX_PACKET_SIZE); +#ifdef MYSQLND_COMPRESSION_ENABLED + if (net->compressed == TRUE) { + /* here we need to compress the data and then write it, first comes the compressed header */ + size_t tmp_complen = to_be_sent; + size_t payload_size; + zend_uchar * uncompressed_payload = p; /* should include the header */ + + STORE_HEADER_SIZE(safe_storage, uncompressed_payload); + int3store(uncompressed_payload, to_be_sent); + int1store(uncompressed_payload + 3, net->packet_no); + if (PASS == net->m.encode((compress_buf + COMPRESSED_HEADER_SIZE + MYSQLND_HEADER_SIZE), &tmp_complen, + uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE TSRMLS_CC)) + { + int3store(compress_buf + MYSQLND_HEADER_SIZE, to_be_sent + MYSQLND_HEADER_SIZE); + payload_size = tmp_complen; + } else { + int3store(compress_buf + MYSQLND_HEADER_SIZE, 0); + memcpy(compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, uncompressed_payload, to_be_sent + MYSQLND_HEADER_SIZE); + payload_size = to_be_sent + MYSQLND_HEADER_SIZE; + } + RESTORE_HEADER_SIZE(uncompressed_payload, safe_storage); + + int3store(compress_buf, payload_size); + int1store(compress_buf + 3, net->packet_no); + DBG_INF_FMT("writing "MYSQLND_SZ_T_SPEC" bytes to the network", payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE); + bytes_sent = net->m.network_write_ex(net, compress_buf, payload_size + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, + conn_stats, error_info TSRMLS_CC); + net->compressed_envelope_packet_no++; + #if WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY + if (res == Z_OK) { + size_t decompressed_size = left + MYSQLND_HEADER_SIZE; + zend_uchar * decompressed_data = mnd_malloc(decompressed_size); + int error = net->m.decode(decompressed_data, decompressed_size, + compress_buf + MYSQLND_HEADER_SIZE + COMPRESSED_HEADER_SIZE, payload_size); + if (error == Z_OK) { + int i; + DBG_INF("success decompressing"); + for (i = 0 ; i < decompressed_size; i++) { + if (i && (i % 30 == 0)) { + printf("\n\t\t"); + } + printf("%.2X ", (int)*((char*)&(decompressed_data[i]))); + DBG_INF_FMT("%.2X ", (int)*((char*)&(decompressed_data[i]))); + } + } else { + DBG_INF("error decompressing"); + } + mnd_free(decompressed_data); + } + #endif /* WHEN_WE_NEED_TO_CHECK_WHETHER_COMPRESSION_WORKS_CORRECTLY */ + } else +#endif /* MYSQLND_COMPRESSION_ENABLED */ + { + DBG_INF("no compression"); + STORE_HEADER_SIZE(safe_storage, p); + int3store(p, to_be_sent); + int1store(p + 3, net->packet_no); + bytes_sent = net->m.network_write_ex(net, p, to_be_sent + MYSQLND_HEADER_SIZE, conn_stats, error_info TSRMLS_CC); + RESTORE_HEADER_SIZE(p, safe_storage); + net->compressed_envelope_packet_no++; + } + net->packet_no++; + + p += to_be_sent; + left -= to_be_sent; + packets_sent++; + /* + if left is 0 then there is nothing more to send, but if the last packet was exactly + with the size MYSQLND_MAX_PACKET_SIZE we need to send additional packet, which has + empty payload. Thus if left == 0 we check for to_be_sent being the max size. If it is + indeed it then loop once more, then to_be_sent will become 0, left will stay 0. Empty + packet will be sent and this loop will end. + */ + } while (bytes_sent && (left > 0 || to_be_sent == MYSQLND_MAX_PACKET_SIZE)); + + DBG_INF_FMT("packet_size="MYSQLND_SZ_T_SPEC" packet_no=%u", left, net->packet_no); + + MYSQLND_INC_CONN_STATISTIC_W_VALUE3(conn_stats, + STAT_BYTES_SENT, count + packets_sent * MYSQLND_HEADER_SIZE, + STAT_PROTOCOL_OVERHEAD_OUT, packets_sent * MYSQLND_HEADER_SIZE, + STAT_PACKETS_SENT, packets_sent); + + if (compress_buf) { + mnd_efree(compress_buf); + } + + /* Even for zero size payload we have to send a packet */ + if (!bytes_sent) { + DBG_ERR_FMT("Can't %u send bytes", count); + SET_CLIENT_ERROR(*error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); + } + DBG_RETURN(bytes_sent); +} +/* }}} */ + + +#ifdef MYSQLND_COMPRESSION_ENABLED +/* {{{ php_mysqlnd_read_buffer_is_empty */ +static zend_bool +php_mysqlnd_read_buffer_is_empty(MYSQLND_READ_BUFFER * buffer) +{ + return buffer->len? FALSE:TRUE; +} +/* }}} */ + + +/* {{{ php_mysqlnd_read_buffer_read */ +static void +php_mysqlnd_read_buffer_read(MYSQLND_READ_BUFFER * buffer, size_t count, zend_uchar * dest) +{ + if (buffer->len >= count) { + memcpy(dest, buffer->data + buffer->offset, count); + buffer->offset += count; + buffer->len -= count; + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_read_buffer_bytes_left */ +static size_t +php_mysqlnd_read_buffer_bytes_left(MYSQLND_READ_BUFFER * buffer) +{ + return buffer->len; +} +/* }}} */ + + +/* {{{ php_mysqlnd_read_buffer_free */ +static void +php_mysqlnd_read_buffer_free(MYSQLND_READ_BUFFER ** buffer TSRMLS_DC) +{ + DBG_ENTER("php_mysqlnd_read_buffer_free"); + if (*buffer) { + mnd_efree((*buffer)->data); + mnd_efree(*buffer); + *buffer = NULL; + } + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ php_mysqlnd_create_read_buffer */ +static MYSQLND_READ_BUFFER * +mysqlnd_create_read_buffer(size_t count TSRMLS_DC) +{ + MYSQLND_READ_BUFFER * ret = mnd_emalloc(sizeof(MYSQLND_READ_BUFFER)); + DBG_ENTER("mysqlnd_create_read_buffer"); + ret->is_empty = php_mysqlnd_read_buffer_is_empty; + ret->read = php_mysqlnd_read_buffer_read; + ret->bytes_left = php_mysqlnd_read_buffer_bytes_left; + ret->free_buffer = php_mysqlnd_read_buffer_free; + ret->data = mnd_emalloc(count); + ret->size = ret->len = count; + ret->offset = 0; + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_net::read_compressed_packet_from_stream_and_fill_read_buffer */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_net, read_compressed_packet_from_stream_and_fill_read_buffer) + (MYSQLND_NET * net, size_t net_payload_size, MYSQLND_STATS * conn_stats, MYSQLND_ERROR_INFO * error_info TSRMLS_DC) +{ + size_t decompressed_size; + enum_func_status ret = PASS; + zend_uchar * compressed_data = NULL; + zend_uchar comp_header[COMPRESSED_HEADER_SIZE]; + DBG_ENTER("mysqlnd_net::read_compressed_packet_from_stream_and_fill_read_buffe"); + + /* Read the compressed header */ + if (FAIL == net->m.network_read_ex(net, comp_header, COMPRESSED_HEADER_SIZE, conn_stats, error_info TSRMLS_CC)) { + DBG_RETURN(FAIL); + } + decompressed_size = uint3korr(comp_header); + + /* When decompressed_size is 0, then the data is not compressed, and we have wasted 3 bytes */ + /* we need to decompress the data */ + + if (decompressed_size) { + compressed_data = mnd_emalloc(net_payload_size); + if (FAIL == net->m.network_read_ex(net, compressed_data, net_payload_size, conn_stats, error_info TSRMLS_CC)) { + ret = FAIL; + goto end; + } + net->uncompressed_data = mysqlnd_create_read_buffer(decompressed_size TSRMLS_CC); + ret = net->m.decode(net->uncompressed_data->data, decompressed_size, compressed_data, net_payload_size TSRMLS_CC); + if (ret == FAIL) { + goto end; + } + } else { + DBG_INF_FMT("The server decided not to compress the data. Our job is easy. Copying %u bytes", net_payload_size); + net->uncompressed_data = mysqlnd_create_read_buffer(net_payload_size TSRMLS_CC); + if (FAIL == net->m.network_read_ex(net, net->uncompressed_data->data, net_payload_size, conn_stats, error_info TSRMLS_CC)) { + ret = FAIL; + goto end; + } + } +end: + if (compressed_data) { + mnd_efree(compressed_data); + } + DBG_RETURN(ret); +} +/* }}} */ +#endif /* MYSQLND_COMPRESSION_ENABLED */ + + +/* {{{ mysqlnd_net::decode */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_net, decode)(zend_uchar * uncompressed_data, const size_t uncompressed_data_len, + const zend_uchar * const compressed_data, const size_t compressed_data_len TSRMLS_DC) +{ +#ifdef MYSQLND_COMPRESSION_ENABLED + int error; + uLongf tmp_complen = uncompressed_data_len; + DBG_ENTER("mysqlnd_net::decode"); + error = uncompress(uncompressed_data, &tmp_complen, compressed_data, compressed_data_len); + + DBG_INF_FMT("compressed data: decomp_len=%lu compressed_size="MYSQLND_SZ_T_SPEC, tmp_complen, compressed_data_len); + if (error != Z_OK) { + DBG_INF_FMT("decompression NOT successful. error=%d Z_OK=%d Z_BUF_ERROR=%d Z_MEM_ERROR=%d", error, Z_OK, Z_BUF_ERROR, Z_MEM_ERROR); + } + DBG_RETURN(error == Z_OK? PASS:FAIL); +#else + DBG_ENTER("mysqlnd_net::decode"); + DBG_RETURN(FAIL); +#endif +} +/* }}} */ + + +/* {{{ mysqlnd_net::encode */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_net, encode)(zend_uchar * compress_buffer, size_t * compress_buffer_len, + const zend_uchar * const uncompressed_data, const size_t uncompressed_data_len TSRMLS_DC) +{ +#ifdef MYSQLND_COMPRESSION_ENABLED + int error; + uLongf tmp_complen = *compress_buffer_len; + DBG_ENTER("mysqlnd_net::encode"); + error = compress(compress_buffer, &tmp_complen, uncompressed_data, uncompressed_data_len); + + if (error != Z_OK) { + DBG_INF_FMT("compression NOT successful. error=%d Z_OK=%d Z_BUF_ERROR=%d Z_MEM_ERROR=%d", error, Z_OK, Z_BUF_ERROR, Z_MEM_ERROR); + } else { + *compress_buffer_len = tmp_complen; + DBG_INF_FMT("compression successful. compressed size=%lu", tmp_complen); + } + + DBG_RETURN(error == Z_OK? PASS:FAIL); +#else + DBG_ENTER("mysqlnd_net::encode"); + DBG_RETURN(FAIL); +#endif +} +/* }}} */ + + +/* {{{ mysqlnd_net::receive_ex */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_net, receive_ex)(MYSQLND_NET * const net, zend_uchar * const buffer, const size_t count, + MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC) +{ + size_t to_read = count; + zend_uchar * p = buffer; + + DBG_ENTER("mysqlnd_net::receive_ex"); +#ifdef MYSQLND_COMPRESSION_ENABLED + if (net->compressed) { + if (net->uncompressed_data) { + size_t to_read_from_buffer = MIN(net->uncompressed_data->bytes_left(net->uncompressed_data), to_read); + DBG_INF_FMT("reading %u from uncompressed_data buffer", to_read_from_buffer); + if (to_read_from_buffer) { + net->uncompressed_data->read(net->uncompressed_data, to_read_from_buffer, (zend_uchar *) p); + p += to_read_from_buffer; + to_read -= to_read_from_buffer; + } + DBG_INF_FMT("left %u to read", to_read); + if (TRUE == net->uncompressed_data->is_empty(net->uncompressed_data)) { + /* Everything was consumed. This should never happen here, but for security */ + net->uncompressed_data->free_buffer(&net->uncompressed_data TSRMLS_CC); + } + } + if (to_read) { + zend_uchar net_header[MYSQLND_HEADER_SIZE]; + size_t net_payload_size; + zend_uchar packet_no; + + if (FAIL == net->m.network_read_ex(net, net_header, MYSQLND_HEADER_SIZE, conn_stats, error_info TSRMLS_CC)) { + DBG_RETURN(FAIL); + } + net_payload_size = uint3korr(net_header); + packet_no = uint1korr(net_header + 3); + if (net->compressed_envelope_packet_no != packet_no) { + DBG_ERR_FMT("Transport level: packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC, + net->compressed_envelope_packet_no, packet_no, net_payload_size); + + php_error(E_WARNING, "Packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC, + net->compressed_envelope_packet_no, packet_no, net_payload_size); + DBG_RETURN(FAIL); + } + net->compressed_envelope_packet_no++; +#ifdef MYSQLND_DUMP_HEADER_N_BODY + DBG_INF_FMT("HEADER: hwd_packet_no=%u size=%3u", packet_no, (unsigned long) net_payload_size); +#endif + /* Now let's read from the wire, decompress it and fill the read buffer */ + net->m.read_compressed_packet_from_stream_and_fill_read_buffer(net, net_payload_size, conn_stats, error_info TSRMLS_CC); + + /* + Now a bit of recursion - read from the read buffer, + if the data which we have just read from the wire + is not enough, then the recursive call will try to + satisfy it until it is satisfied. + */ + DBG_RETURN(net->m.receive_ex(net, p, to_read, conn_stats, error_info TSRMLS_CC)); + } + DBG_RETURN(PASS); + } +#endif /* MYSQLND_COMPRESSION_ENABLED */ + DBG_RETURN(net->m.network_read_ex(net, p, to_read, conn_stats, error_info TSRMLS_CC)); +} +/* }}} */ + + +/* {{{ mysqlnd_net::set_client_option */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_net, set_client_option)(MYSQLND_NET * const net, enum mysqlnd_option option, const char * const value TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_net::set_client_option"); + DBG_INF_FMT("option=%u", option); + switch (option) { + case MYSQLND_OPT_NET_CMD_BUFFER_SIZE: + DBG_INF("MYSQLND_OPT_NET_CMD_BUFFER_SIZE"); + if (*(unsigned int*) value < MYSQLND_NET_CMD_BUFFER_MIN_SIZE) { + DBG_RETURN(FAIL); + } + net->cmd_buffer.length = *(unsigned int*) value; + DBG_INF_FMT("new_length=%u", net->cmd_buffer.length); + if (!net->cmd_buffer.buffer) { + net->cmd_buffer.buffer = mnd_pemalloc(net->cmd_buffer.length, net->persistent); + } else { + net->cmd_buffer.buffer = mnd_perealloc(net->cmd_buffer.buffer, net->cmd_buffer.length, net->persistent); + } + break; + case MYSQLND_OPT_NET_READ_BUFFER_SIZE: + DBG_INF("MYSQLND_OPT_NET_READ_BUFFER_SIZE"); + net->options.net_read_buffer_size = *(unsigned int*) value; + DBG_INF_FMT("new_length=%u", net->options.net_read_buffer_size); + break; + case MYSQL_OPT_CONNECT_TIMEOUT: + DBG_INF("MYSQL_OPT_CONNECT_TIMEOUT"); + net->options.timeout_connect = *(unsigned int*) value; + break; + case MYSQLND_OPT_SSL_KEY: + { + zend_bool pers = net->persistent; + if (net->options.ssl_key) { + mnd_pefree(net->options.ssl_key, pers); + } + net->options.ssl_key = value? mnd_pestrdup(value, pers) : NULL; + break; + } + case MYSQLND_OPT_SSL_CERT: + { + zend_bool pers = net->persistent; + if (net->options.ssl_cert) { + mnd_pefree(net->options.ssl_cert, pers); + } + net->options.ssl_cert = value? mnd_pestrdup(value, pers) : NULL; + break; + } + case MYSQLND_OPT_SSL_CA: + { + zend_bool pers = net->persistent; + if (net->options.ssl_ca) { + mnd_pefree(net->options.ssl_ca, pers); + } + net->options.ssl_ca = value? mnd_pestrdup(value, pers) : NULL; + break; + } + case MYSQLND_OPT_SSL_CAPATH: + { + zend_bool pers = net->persistent; + if (net->options.ssl_capath) { + mnd_pefree(net->options.ssl_capath, pers); + } + net->options.ssl_capath = value? mnd_pestrdup(value, pers) : NULL; + break; + } + case MYSQLND_OPT_SSL_CIPHER: + { + zend_bool pers = net->persistent; + if (net->options.ssl_cipher) { + mnd_pefree(net->options.ssl_cipher, pers); + } + net->options.ssl_cipher = value? mnd_pestrdup(value, pers) : NULL; + break; + } + case MYSQLND_OPT_SSL_PASSPHRASE: + { + zend_bool pers = net->persistent; + if (net->options.ssl_passphrase) { + mnd_pefree(net->options.ssl_passphrase, pers); + } + net->options.ssl_passphrase = value? mnd_pestrdup(value, pers) : NULL; + break; + } + case MYSQL_OPT_SSL_VERIFY_SERVER_CERT: + net->options.ssl_verify_peer = value? ((*(zend_bool *)value)? TRUE:FALSE): FALSE; + break; + case MYSQL_OPT_READ_TIMEOUT: + net->options.timeout_read = *(unsigned int*) value; + break; +#ifdef WHEN_SUPPORTED_BY_MYSQLI + case MYSQL_OPT_WRITE_TIMEOUT: + net->options.timeout_write = *(unsigned int*) value; + break; +#endif + case MYSQL_OPT_COMPRESS: + net->options.flags |= MYSQLND_NET_FLAG_USE_COMPRESSION; + break; + default: + DBG_RETURN(FAIL); + } + DBG_RETURN(PASS); +} +/* }}} */ + +/* {{{ mysqlnd_net::consume_uneaten_data */ +size_t +MYSQLND_METHOD(mysqlnd_net, consume_uneaten_data)(MYSQLND_NET * const net, enum php_mysqlnd_server_command cmd TSRMLS_DC) +{ +#ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND + /* + Switch to non-blocking mode and try to consume something from + the line, if possible, then continue. This saves us from looking for + the actuall place where out-of-order packets have been sent. + If someone is completely sure that everything is fine, he can switch it + off. + */ + char tmp_buf[256]; + size_t skipped_bytes = 0; + int opt = PHP_STREAM_OPTION_BLOCKING; + int was_blocked = net->stream->ops->set_option(net->stream, opt, 0, NULL TSRMLS_CC); + + DBG_ENTER("mysqlnd_net::consume_uneaten_data"); + + if (PHP_STREAM_OPTION_RETURN_ERR != was_blocked) { + /* Do a read of 1 byte */ + int bytes_consumed; + + do { + skipped_bytes += (bytes_consumed = php_stream_read(net->stream, tmp_buf, sizeof(tmp_buf))); + } while (bytes_consumed == sizeof(tmp_buf)); + + if (was_blocked) { + net->stream->ops->set_option(net->stream, opt, 1, NULL TSRMLS_CC); + } + + if (bytes_consumed) { + DBG_ERR_FMT("Skipped %u bytes. Last command %s hasn't consumed all the output from the server", + bytes_consumed, mysqlnd_command_to_text[net->last_command]); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Skipped %u bytes. Last command %s hasn't " + "consumed all the output from the server", + bytes_consumed, mysqlnd_command_to_text[net->last_command]); + } + } + net->last_command = cmd; + + DBG_RETURN(skipped_bytes); +#else + return 0; +#endif +} +/* }}} */ + +/* + in libmyusql, if cert and !key then key=cert +*/ +/* {{{ mysqlnd_net::enable_ssl */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_net, enable_ssl)(MYSQLND_NET * const net TSRMLS_DC) +{ +#ifdef MYSQLND_SSL_SUPPORTED + php_stream_context *context = php_stream_context_alloc(TSRMLS_C); + DBG_ENTER("mysqlnd_net::enable_ssl"); + if (!context) { + DBG_RETURN(FAIL); + } + + if (net->options.ssl_key) { + zval key_zval; + ZVAL_STRING(&key_zval, net->options.ssl_key, 0); + php_stream_context_set_option(context, "ssl", "local_pk", &key_zval); + } + if (net->options.ssl_verify_peer) { + zval verify_peer_zval; + ZVAL_TRUE(&verify_peer_zval); + php_stream_context_set_option(context, "ssl", "verify_peer", &verify_peer_zval); + } + if (net->options.ssl_cert) { + zval cert_zval; + ZVAL_STRING(&cert_zval, net->options.ssl_cert, 0); + php_stream_context_set_option(context, "ssl", "local_cert", &cert_zval); + if (!net->options.ssl_key) { + php_stream_context_set_option(context, "ssl", "local_pk", &cert_zval); + } + } + if (net->options.ssl_ca) { + zval cafile_zval; + ZVAL_STRING(&cafile_zval, net->options.ssl_ca, 0); + php_stream_context_set_option(context, "ssl", "cafile", &cafile_zval); + } + if (net->options.ssl_capath) { + zval capath_zval; + ZVAL_STRING(&capath_zval, net->options.ssl_capath, 0); + php_stream_context_set_option(context, "ssl", "cafile", &capath_zval); + } + if (net->options.ssl_passphrase) { + zval passphrase_zval; + ZVAL_STRING(&passphrase_zval, net->options.ssl_passphrase, 0); + php_stream_context_set_option(context, "ssl", "passphrase", &passphrase_zval); + } + if (net->options.ssl_cipher) { + zval cipher_zval; + ZVAL_STRING(&cipher_zval, net->options.ssl_cipher, 0); + php_stream_context_set_option(context, "ssl", "ciphers", &cipher_zval); + } + php_stream_context_set(net->stream, context); + if (php_stream_xport_crypto_setup(net->stream, STREAM_CRYPTO_METHOD_TLS_CLIENT, NULL TSRMLS_CC) < 0 || + php_stream_xport_crypto_enable(net->stream, 1 TSRMLS_CC) < 0) + { + DBG_ERR("Cannot connect to MySQL by using SSL"); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot connect to MySQL by using SSL"); + DBG_RETURN(FAIL); + } + /* + get rid of the context. we are persistent and if this is a real pconn used by mysql/mysqli, + then the context would not survive cleaning of EG(regular_list), where it is registered, as a + resource. What happens is that after this destruction any use of the network will mean usage + of the context, which means usage of already freed memory, bad. Actually we don't need this + context anymore after we have enabled SSL on the connection. Thus it is very simple, we remove it. + */ + php_stream_context_set(net->stream, NULL); + + if (net->options.timeout_read) { + struct timeval tv; + DBG_INF_FMT("setting %u as PHP_STREAM_OPTION_READ_TIMEOUT", net->options.timeout_read); + tv.tv_sec = net->options.timeout_read; + tv.tv_usec = 0; + php_stream_set_option(net->stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv); + } + + DBG_RETURN(PASS); +#else + DBG_ENTER("mysqlnd_net::enable_ssl"); + DBG_RETURN(PASS); +#endif +} +/* }}} */ + + +/* {{{ mysqlnd_net::disable_ssl */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_net, disable_ssl)(MYSQLND_NET * const net TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_net::disable_ssl"); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_net::free_contents */ +static void +MYSQLND_METHOD(mysqlnd_net, free_contents)(MYSQLND_NET * net TSRMLS_DC) +{ + zend_bool pers = net->persistent; + DBG_ENTER("mysqlnd_net::free_contents"); + +#ifdef MYSQLND_COMPRESSION_ENABLED + if (net->uncompressed_data) { + net->uncompressed_data->free_buffer(&net->uncompressed_data TSRMLS_CC); + } +#endif + if (net->options.ssl_key) { + mnd_pefree(net->options.ssl_key, pers); + net->options.ssl_key = NULL; + } + if (net->options.ssl_cert) { + mnd_pefree(net->options.ssl_cert, pers); + net->options.ssl_cert = NULL; + } + if (net->options.ssl_ca) { + mnd_pefree(net->options.ssl_ca, pers); + net->options.ssl_ca = NULL; + } + if (net->options.ssl_capath) { + mnd_pefree(net->options.ssl_capath, pers); + net->options.ssl_capath = NULL; + } + if (net->options.ssl_cipher) { + mnd_pefree(net->options.ssl_cipher, pers); + net->options.ssl_cipher = NULL; + } + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_net::close_stream */ +static void +MYSQLND_METHOD(mysqlnd_net, close_stream)(MYSQLND_NET * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_net::close_stream"); + if (net && net->stream) { + zend_bool pers = net->persistent; + DBG_INF_FMT("Freeing stream. abstract=%p", net->stream->abstract); + if (pers) { + if (EG(active)) { + php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR); + } else { + /* + otherwise we will crash because the EG(persistent_list) has been freed already, + before the modules are shut down + */ + php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE | PHP_STREAM_FREE_RSRC_DTOR); + } + } else { + php_stream_free(net->stream, PHP_STREAM_FREE_CLOSE); + } + net->stream = NULL; + } + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_net::init */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_net, init)(MYSQLND_NET * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC) +{ + unsigned int buf_size; + DBG_ENTER("mysqlnd_net::init"); + + buf_size = MYSQLND_G(net_cmd_buffer_size); /* this is long, cast to unsigned int*/ + net->m.set_client_option(net, MYSQLND_OPT_NET_CMD_BUFFER_SIZE, (char *) &buf_size TSRMLS_CC); + + buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to unsigned int*/ + net->m.set_client_option(net, MYSQLND_OPT_NET_READ_BUFFER_SIZE, (char *)&buf_size TSRMLS_CC); + + buf_size = MYSQLND_G(net_read_timeout); /* this is long, cast to unsigned int*/ + net->m.set_client_option(net, MYSQL_OPT_READ_TIMEOUT, (char *)&buf_size TSRMLS_CC); + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_net::dtor */ +static void +MYSQLND_METHOD(mysqlnd_net, dtor)(MYSQLND_NET * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_net::dtor"); + if (net) { + zend_bool pers = net->persistent; + + net->m.free_contents(net TSRMLS_CC); + net->m.close_stream(net, stats, error_info TSRMLS_CC); + if (net->cmd_buffer.buffer) { + DBG_INF("Freeing cmd buffer"); + mnd_pefree(net->cmd_buffer.buffer, pers); + net->cmd_buffer.buffer = NULL; + } + mnd_pefree(net, pers); + } + DBG_VOID_RETURN; +} +/* }}} */ + + +MYSQLND_CLASS_METHODS_START(mysqlnd_net) + MYSQLND_METHOD(mysqlnd_net, init), + MYSQLND_METHOD(mysqlnd_net, dtor), + MYSQLND_METHOD(mysqlnd_net, connect_ex), + MYSQLND_METHOD(mysqlnd_net, close_stream), + MYSQLND_METHOD(mysqlnd_net, open_pipe), + MYSQLND_METHOD(mysqlnd_net, open_tcp_or_unix), + NULL, /* unused 1 */ + NULL, /* unused 2 */ + MYSQLND_METHOD(mysqlnd_net, post_connect_set_opt), + MYSQLND_METHOD(mysqlnd_net, set_client_option), + MYSQLND_METHOD(mysqlnd_net, decode), + MYSQLND_METHOD(mysqlnd_net, encode), + MYSQLND_METHOD(mysqlnd_net, consume_uneaten_data), + MYSQLND_METHOD(mysqlnd_net, free_contents), + MYSQLND_METHOD(mysqlnd_net, enable_ssl), + MYSQLND_METHOD(mysqlnd_net, disable_ssl), + MYSQLND_METHOD(mysqlnd_net, network_read_ex), + MYSQLND_METHOD(mysqlnd_net, network_write_ex), + MYSQLND_METHOD(mysqlnd_net, send_ex), + MYSQLND_METHOD(mysqlnd_net, receive_ex), +#ifdef MYSQLND_COMPRESSION_ENABLED + MYSQLND_METHOD(mysqlnd_net, read_compressed_packet_from_stream_and_fill_read_buffer) +#else + NULL +#endif +MYSQLND_CLASS_METHODS_END; + + +/* {{{ mysqlnd_net_init */ +PHPAPI MYSQLND_NET * +mysqlnd_net_init(zend_bool persistent, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info TSRMLS_DC) +{ + MYSQLND_NET * net; + DBG_ENTER("mysqlnd_net_init"); + net = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_io_channel(persistent, stats, error_info TSRMLS_CC); + DBG_RETURN(net); +} +/* }}} */ + + +/* {{{ mysqlnd_net_free */ +PHPAPI void +mysqlnd_net_free(MYSQLND_NET * const net, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_net_free"); + if (net) { + net->m.dtor(net, stats, error_info TSRMLS_CC); + } + DBG_VOID_RETURN; +} +/* }}} */ + + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_net.h b/ext/mysqlnd/mysqlnd_net.h new file mode 100644 index 0000000..568a0e1 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_net.h @@ -0,0 +1,38 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: mysqlnd_wireprotocol.h 291983 2009-12-11 11:58:57Z andrey $ */ + +#ifndef MYSQLND_NET_H +#define MYSQLND_NET_H + +PHPAPI MYSQLND_NET * mysqlnd_net_init(zend_bool persistent, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info TSRMLS_DC); +PHPAPI void mysqlnd_net_free(MYSQLND_NET * const net, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info TSRMLS_DC); + +#endif /* MYSQLND_NET_H */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_plugin.c b/ext/mysqlnd/mysqlnd_plugin.c new file mode 100644 index 0000000..f32fe4a --- /dev/null +++ b/ext/mysqlnd/mysqlnd_plugin.c @@ -0,0 +1,209 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: mysqlnd.c 306407 2010-12-16 12:56:19Z andrey $ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_statistics.h" +#include "mysqlnd_debug.h" + +/*--------------------------------------------------------------------*/ + +static enum_func_status mysqlnd_example_plugin_end(void * p TSRMLS_DC); + +static MYSQLND_STATS * mysqlnd_plugin_example_stats = NULL; + +enum mysqlnd_plugin_example_collected_stats +{ + EXAMPLE_STAT1, + EXAMPLE_STAT2, + EXAMPLE_STAT_LAST +}; + +static const MYSQLND_STRING mysqlnd_plugin_example_stats_values_names[EXAMPLE_STAT_LAST] = +{ + { MYSQLND_STR_W_LEN("stat1") }, + { MYSQLND_STR_W_LEN("stat2") } +}; + +static struct st_mysqlnd_typeii_plugin_example mysqlnd_example_plugin = +{ + { + MYSQLND_PLUGIN_API_VERSION, + "example", + 10001L, + "1.00.01", + "PHP License", + "Andrey Hristov <andrey@php.net>", + { + NULL, /* will be filled later */ + mysqlnd_plugin_example_stats_values_names, + }, + { + mysqlnd_example_plugin_end + } + }, + NULL, /* methods */ +}; + + +/* {{{ mysqlnd_example_plugin_end */ +static +enum_func_status mysqlnd_example_plugin_end(void * p TSRMLS_DC) +{ + struct st_mysqlnd_typeii_plugin_example * plugin = (struct st_mysqlnd_typeii_plugin_example *) p; + DBG_ENTER("mysqlnd_example_plugin_end"); + mysqlnd_stats_end(plugin->plugin_header.plugin_stats.values); + plugin->plugin_header.plugin_stats.values = NULL; + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_example_plugin_register */ +void +mysqlnd_example_plugin_register(TSRMLS_D) +{ + mysqlnd_stats_init(&mysqlnd_plugin_example_stats, EXAMPLE_STAT_LAST); + mysqlnd_example_plugin.plugin_header.plugin_stats.values = mysqlnd_plugin_example_stats; + mysqlnd_plugin_register_ex((struct st_mysqlnd_plugin_header *) &mysqlnd_example_plugin TSRMLS_CC); +} +/* }}} */ + +/*--------------------------------------------------------------------*/ + +static HashTable mysqlnd_registered_plugins; + +static unsigned int mysqlnd_plugins_counter = 0; + + +/* {{{ mysqlnd_plugin_subsystem_init */ +void +mysqlnd_plugin_subsystem_init(TSRMLS_D) +{ + zend_hash_init(&mysqlnd_registered_plugins, 4 /* initial hash size */, NULL /* hash_func */, NULL /* dtor */, TRUE /* pers */); +} +/* }}} */ + + +/* {{{ mysqlnd_plugin_end_apply_func */ +int +mysqlnd_plugin_end_apply_func(void *pDest TSRMLS_DC) +{ + struct st_mysqlnd_plugin_header * plugin_header = *(struct st_mysqlnd_plugin_header **) pDest; + if (plugin_header->m.plugin_shutdown) { + plugin_header->m.plugin_shutdown(plugin_header TSRMLS_CC); + } + return ZEND_HASH_APPLY_REMOVE; +} +/* }}} */ + + +/* {{{ mysqlnd_plugin_subsystem_end */ +void +mysqlnd_plugin_subsystem_end(TSRMLS_D) +{ + zend_hash_apply(&mysqlnd_registered_plugins, mysqlnd_plugin_end_apply_func TSRMLS_CC); + zend_hash_destroy(&mysqlnd_registered_plugins); +} +/* }}} */ + + +/* {{{ mysqlnd_plugin_register */ +PHPAPI unsigned int mysqlnd_plugin_register() +{ + TSRMLS_FETCH(); + return mysqlnd_plugin_register_ex(NULL TSRMLS_CC); +} +/* }}} */ + + +/* {{{ mysqlnd_plugin_register_ex */ +PHPAPI unsigned int mysqlnd_plugin_register_ex(struct st_mysqlnd_plugin_header * plugin TSRMLS_DC) +{ + if (plugin) { + if (plugin->plugin_api_version == MYSQLND_PLUGIN_API_VERSION) { + zend_hash_update(&mysqlnd_registered_plugins, plugin->plugin_name, strlen(plugin->plugin_name) + 1, &plugin, sizeof(void *), NULL); + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Plugin API version mismatch while loading plugin %s. Expected %d, got %d", + plugin->plugin_name, MYSQLND_PLUGIN_API_VERSION, plugin->plugin_api_version); + return 0xCAFE; + } + } + return mysqlnd_plugins_counter++; +} +/* }}} */ + + +/* {{{ mysqlnd_plugin_find */ +PHPAPI void * _mysqlnd_plugin_find(const char * const name TSRMLS_DC) +{ + void * plugin; + if (SUCCESS == zend_hash_find(&mysqlnd_registered_plugins, name, strlen(name) + 1, (void **) &plugin)) { + return (void *)*(char **) plugin; + } + return NULL; + +} +/* }}} */ + + +/* {{{ _mysqlnd_plugin_apply_with_argument */ +PHPAPI void _mysqlnd_plugin_apply_with_argument(apply_func_arg_t apply_func, void * argument TSRMLS_DC) +{ + /* Note: We want to be thread-safe (read-only), so we can use neither + * zend_hash_apply_with_argument nor zend_hash_internal_pointer_reset and + * friends + */ + Bucket *p; + + p = mysqlnd_registered_plugins.pListHead; + while (p != NULL) { + int result = apply_func(p->pData, argument TSRMLS_CC); + + if (result & ZEND_HASH_APPLY_REMOVE) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "mysqlnd_plugin_apply_with_argument must not remove table entries"); + } + p = p->pListNext; + if (result & ZEND_HASH_APPLY_STOP) { + break; + } + } +} +/* }}} */ + + +/* {{{ mysqlnd_plugin_count */ +PHPAPI unsigned int mysqlnd_plugin_count() +{ + return mysqlnd_plugins_counter; +} +/* }}} */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_portability.h b/ext/mysqlnd/mysqlnd_portability.h new file mode 100644 index 0000000..b947915 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_portability.h @@ -0,0 +1,506 @@ +/* Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB +This file is public domain and comes with NO WARRANTY of any kind */ + +/* + Parts of the original, which are not applicable to mysqlnd have been removed. + + With small modifications, mostly casting but adding few more macros by + Andrey Hristov <andrey@mysql.com> . The additions are in the public domain and + were added to improve the header file, to get it more consistent. +*/ + +#ifndef MYSQLND_PORTABILITY_H +#define MYSQLND_PORTABILITY_H + + + +/* Comes from global.h as OFFSET, renamed to STRUCT_OFFSET */ +#define STRUCT_OFFSET(t, f) ((size_t)(char *)&((t *)0)->f) + +#ifndef __attribute +#if !defined(__GNUC__) +#define __attribute(A) +#endif +#endif + +#ifdef __CYGWIN__ +/* We use a Unix API, so pretend it's not Windows */ +#undef WIN +#undef WIN32 +#undef _WIN +#undef _WIN32 +#undef _WIN64 +#undef __WIN__ +#undef __WIN32__ +#endif /* __CYGWIN__ */ + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(WIN32) +# include "ext/mysqlnd/config-win.h" +#else +# include <ext/mysqlnd/php_mysqlnd_config.h> +#endif /* _WIN32... */ + +#if __STDC_VERSION__ < 199901L && !defined(atoll) + /* "inline" is a keyword */ + #define atoll atol +#endif + + +#ifdef HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#ifdef HAVE_STDINT_H +#include <stdint.h> +#endif + +#if SIZEOF_LONG_LONG > 4 && !defined(_LONG_LONG) +#define _LONG_LONG 1 /* For AIX string library */ +#endif + + +/* Go around some bugs in different OS and compilers */ +#if defined(_HPUX_SOURCE) && defined(HAVE_SYS_STREAM_H) +#include <sys/stream.h> /* HPUX 10.20 defines ulong here. UGLY !!! */ +#define HAVE_ULONG +#endif + + +#if SIZEOF_LONG_LONG > 4 +#define HAVE_LONG_LONG 1 +#endif + + +/* Typdefs for easyier portability */ +#ifndef HAVE_INT8_T +#ifndef HAVE_INT8 +typedef signed char int8_t; /* Signed integer >= 8 bits */ +#else +typedef int8 int8_t; /* Signed integer >= 8 bits */ +#endif +#endif + +#ifndef HAVE_UINT8_T +#ifndef HAVE_UINT8 +typedef unsigned char uint8_t; /* Unsigned integer >= 8 bits */ +#else +typedef uint8 uint8_t; /* Signed integer >= 8 bits */ +#endif +#endif + +#ifndef HAVE_INT16_T +#ifndef HAVE_INT16 +typedef signed short int16_t; /* Signed integer >= 16 bits */ +#else +typedef int16 int16_t; /* Signed integer >= 16 bits */ +#endif +#endif + +#ifndef HAVE_UINT16_T +#ifndef HAVE_UINT16 +typedef unsigned short uint16_t; /* Signed integer >= 16 bits */ +#else +typedef uint16 uint16_t; /* Signed integer >= 16 bits */ +#endif +#endif + + +#ifndef HAVE_INT32_T +#ifdef HAVE_INT32 +typedef int32 int32_t; +#elif SIZEOF_INT == 4 +typedef signed int int32_t; +#elif SIZEOF_LONG == 4 +typedef signed long int32_t; +#else +error "Neither int nor long is of 4 bytes width" +#endif +#endif /* HAVE_INT32_T */ + +#ifndef HAVE_UINT32_T +#ifdef HAVE_UINT32 +typedef uint32 uint32_t; +#elif SIZEOF_INT == 4 +typedef unsigned int uint32_t; +#elif SIZEOF_LONG == 4 +typedef unsigned long uint32_t; +#else +#error "Neither int nor long is of 4 bytes width" +#endif +#endif /* HAVE_UINT32_T */ + +#ifndef HAVE_INT64_T +#ifdef HAVE_INT64 +typedef int64 int64_t; +#elif SIZEOF_INT == 8 +typedef signed int int64_t; +#elif SIZEOF_LONG == 8 +typedef signed long int64_t; +#elif SIZEOF_LONG_LONG == 8 +#ifdef PHP_WIN32 +typedef __int64 int64_t; +#else +typedef signed long long int64_t; +#endif +#else +#error "Neither int nor long nor long long is of 8 bytes width" +#endif +#endif /* HAVE_INT64_T */ + +#ifndef HAVE_UINT64_T +#ifdef HAVE_UINT64 +typedef uint64 uint64_t; +#elif SIZEOF_INT == 8 +typedef unsigned int uint64_t; +#elif SIZEOF_LONG == 8 +typedef unsigned long uint64_t; +#elif SIZEOF_LONG_LONG == 8 +#ifdef PHP_WIN32 +typedef unsigned __int64 uint64_t; +#else +typedef unsigned long long uint64_t; +#endif +#else +#error "Neither int nor long nor long long is of 8 bytes width" +#endif +#endif /* HAVE_INT64_T */ + + +#ifdef PHP_WIN32 +#define MYSQLND_LLU_SPEC "%I64u" +#define MYSQLND_LL_SPEC "%I64d" +#define MYSQLND_SZ_T_SPEC "%Id" +#ifndef L64 +#define L64(x) x##i64 +#endif +#else + +#if __i386__ +#define MYSQLND_LL_SPEC "%lli" +#define MYSQLND_LLU_SPEC "%llu" +#endif + +#if __ia64__ +#define MYSQLND_LL_SPEC "%li" +#define MYSQLND_LLU_SPEC "%lu" +#endif + +#if __powerpc64__ || __ppc64__ +#define MYSQLND_LL_SPEC "%li" +#define MYSQLND_LLU_SPEC "%lu" +#endif + +#if (__powerpc__ || __ppc__ ) && !(__powerpc64__ || __ppc64__) +#define MYSQLND_LL_SPEC "%lli" +#define MYSQLND_LLU_SPEC "%llu" +#endif + +#if __x86_64__ +#define MYSQLND_LL_SPEC "%li" +#define MYSQLND_LLU_SPEC "%lu" +#endif + +#if __s390x__ +#define MYSQLND_LL_SPEC "%li" +#define MYSQLND_LLU_SPEC "%lu" +#endif + +#if __s390__ && !__s390x__ +#define MYSQLND_LL_SPEC "%lli" +#define MYSQLND_LLU_SPEC "%llu" +#endif + +#ifdef _AIX +#define MYSQLND_LL_SPEC "%lli" +#define MYSQLND_LLU_SPEC "%llu" +#endif + +#ifndef MYSQLND_LL_SPEC + #if SIZEOF_LONG == 8 + #define MYSQLND_LL_SPEC "%li" + #elif SIZEOF_LONG == 4 + #define MYSQLND_LL_SPEC "%lli" + #endif +#endif + +#ifndef MYSQLND_LLU_SPEC + #if SIZEOF_LONG == 8 + #define MYSQLND_LLU_SPEC "%lu" + #elif SIZEOF_LONG == 4 + #define MYSQLND_LLU_SPEC "%llu" + #endif +#endif /* MYSQLND_LLU_SPEC*/ + + +#define MYSQLND_SZ_T_SPEC "%zd" +#ifndef L64 +#define L64(x) x##LL +#endif +#endif + + +#define int1store(T,A) do { *((int8_t*) (T)) = (A); } while(0) +#define uint1korr(A) (*(((uint8_t*)(A)))) + +/* Bit values are sent in reverted order of bytes, compared to normal !!! */ +#define bit_uint2korr(A) ((uint16_t) (((uint16_t) (((unsigned char*) (A))[1])) +\ + ((uint16_t) (((unsigned char*) (A))[0]) << 8))) +#define bit_uint3korr(A) ((uint32_t) (((uint32_t) (((unsigned char*) (A))[2])) +\ + (((uint32_t) (((unsigned char*) (A))[1])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[0])) << 16))) +#define bit_uint4korr(A) ((uint32_t) (((uint32_t) (((unsigned char*) (A))[3])) +\ + (((uint32_t) (((unsigned char*) (A))[2])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[1])) << 16) +\ + (((uint32_t) (((unsigned char*) (A))[0])) << 24))) +#define bit_uint5korr(A) ((uint64_t)(((uint32_t) (((unsigned char*) (A))[4])) +\ + (((uint32_t) (((unsigned char*) (A))[3])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[2])) << 16) +\ + (((uint32_t) (((unsigned char*) (A))[1])) << 24)) +\ + (((uint64_t) (((unsigned char*) (A))[0])) << 32)) +#define bit_uint6korr(A) ((uint64_t)(((uint32_t) (((unsigned char*) (A))[5])) +\ + (((uint32_t) (((unsigned char*) (A))[4])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[3])) << 16) +\ + (((uint32_t) (((unsigned char*) (A))[2])) << 24)) +\ + (((uint64_t) (((uint32_t) (((unsigned char*) (A))[1])) +\ + (((uint32_t) (((unsigned char*) (A))[0]) << 8)))) <<\ + 32)) +#define bit_uint7korr(A) ((uint64_t)(((uint32_t) (((unsigned char*) (A))[6])) +\ + (((uint32_t) (((unsigned char*) (A))[5])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[4])) << 16) +\ + (((uint32_t) (((unsigned char*) (A))[3])) << 24)) +\ + (((uint64_t) (((uint32_t) (((unsigned char*) (A))[2])) +\ + (((uint32_t) (((unsigned char*) (A))[1])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[0])) << 16))) <<\ + 32)) +#define bit_uint8korr(A) ((uint64_t)(((uint32_t) (((unsigned char*) (A))[7])) +\ + (((uint32_t) (((unsigned char*) (A))[6])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[5])) << 16) +\ + (((uint32_t) (((unsigned char*) (A))[4])) << 24)) +\ + (((uint64_t) (((uint32_t) (((unsigned char*) (A))[3])) +\ + (((uint32_t) (((unsigned char*) (A))[2])) << 8) +\ + (((uint32_t) (((unsigned char*) (A))[1])) << 16) +\ + (((uint32_t) (((unsigned char*) (A))[0])) << 24))) <<\ + 32)) + + +/* +** Define-funktions for reading and storing in machine independent format +** (low byte first) +*/ + +/* Optimized store functions for Intel x86, non-valid for WIN64. __i386__ is GCC */ +#if defined(__i386__) && !defined(_WIN64) +#define sint2korr(A) (*((int16_t *) (A))) +#define sint3korr(A) ((int32_t) ((((zend_uchar) (A)[2]) & 128) ? \ + (((uint32_t) 255L << 24) | \ + (((uint32_t) (zend_uchar) (A)[2]) << 16) |\ + (((uint32_t) (zend_uchar) (A)[1]) << 8) | \ + ((uint32_t) (zend_uchar) (A)[0])) : \ + (((uint32_t) (zend_uchar) (A)[2]) << 16) |\ + (((uint32_t) (zend_uchar) (A)[1]) << 8) | \ + ((uint32_t) (zend_uchar) (A)[0]))) +#define sint4korr(A) (*((long *) (A))) + +#define uint2korr(A) (*((uint16_t *) (A))) +#define uint3korr(A) (uint32_t) (((uint32_t) ((zend_uchar) (A)[0])) +\ + (((uint32_t) ((zend_uchar) (A)[1])) << 8) +\ + (((uint32_t) ((zend_uchar) (A)[2])) << 16)) +#define uint4korr(A) (*((unsigned long *) (A))) + + + +#define uint8korr(A) (*((uint64_t *) (A))) +#define sint8korr(A) (*((int64_t *) (A))) +#define int2store(T,A) *((uint16_t*) (T))= (uint16_t) (A) +#define int3store(T,A) { \ + *(T)= (zend_uchar) ((A));\ + *(T+1)=(zend_uchar) (((uint32_t) (A) >> 8));\ + *(T+2)=(zend_uchar) (((A) >> 16)); } +#define int4store(T,A) *((long *) (T))= (long) (A) +#define int5store(T,A) { \ + *((zend_uchar *)(T))= (zend_uchar)((A));\ + *(((zend_uchar *)(T))+1)=(zend_uchar) (((A) >> 8));\ + *(((zend_uchar *)(T))+2)=(zend_uchar) (((A) >> 16));\ + *(((zend_uchar *)(T))+3)=(zend_uchar) (((A) >> 24)); \ + *(((zend_uchar *)(T))+4)=(zend_uchar) (((A) >> 32)); } + +/* From Andrey Hristov, based on int5store() */ +#define int6store(T,A) { \ + *(((zend_uchar *)(T)))= (zend_uchar)((A));\ + *(((zend_uchar *)(T))+1))=(zend_uchar) (((A) >> 8));\ + *(((zend_uchar *)(T))+2))=(zend_uchar) (((A) >> 16));\ + *(((zend_uchar *)(T))+3))=(zend_uchar) (((A) >> 24)); \ + *(((zend_uchar *)(T))+4))=(zend_uchar) (((A) >> 32)); \ + *(((zend_uchar *)(T))+5))=(zend_uchar) (((A) >> 40)); } + +#define int8store(T,A) *((uint64_t *) (T))= (uint64_t) (A) + +typedef union { + double v; + long m[2]; +} float8get_union; +#define float8get(V,M) { ((float8get_union *)&(V))->m[0] = *((long*) (M)); \ + ((float8get_union *)&(V))->m[1] = *(((long*) (M))+1); } +#define float8store(T,V) { *((long *) (T)) = ((float8get_union *)&(V))->m[0]; \ + *(((long *) (T))+1) = ((float8get_union *)&(V))->m[1]; } +#define float4get(V,M) { *((float *) &(V)) = *((float*) (M)); } +/* From Andrey Hristov based on float8get */ +#define floatget(V,M) memcpy((char*) &(V),(char*) (M),sizeof(float)) +#endif /* __i386__ */ + + +/* If we haven't defined sint2korr, which is because the platform is not x86 or it's WIN64 */ +#ifndef sint2korr +#define sint2korr(A) (int16_t) (((int16_t) ((zend_uchar) (A)[0])) +\ + ((int16_t) ((int16_t) (A)[1]) << 8)) +#define sint3korr(A) ((int32_t) ((((zend_uchar) (A)[2]) & 128) ? \ + (((uint32_t) 255L << 24) | \ + (((uint32_t) (zend_uchar) (A)[2]) << 16) |\ + (((uint32_t) (zend_uchar) (A)[1]) << 8) | \ + ((uint32_t) (zend_uchar) (A)[0])) : \ + (((uint32_t) (zend_uchar) (A)[2]) << 16) |\ + (((uint32_t) (zend_uchar) (A)[1]) << 8) | \ + ((uint32_t) (zend_uchar) (A)[0]))) +#define sint4korr(A) (int32_t) (((int32_t) ((zend_uchar) (A)[0])) +\ + (((int32_t) ((zend_uchar) (A)[1]) << 8)) +\ + (((int32_t) ((zend_uchar) (A)[2]) << 16)) +\ + (((int32_t) ((int16_t) (A)[3]) << 24))) + +#define sint8korr(A) (int64_t) uint8korr(A) +#define uint2korr(A) (uint16_t) (((uint16_t) ((zend_uchar) (A)[0])) +\ + ((uint16_t) ((zend_uchar) (A)[1]) << 8)) +#define uint3korr(A) (uint32_t) (((uint32_t) ((zend_uchar) (A)[0])) +\ + (((uint32_t) ((zend_uchar) (A)[1])) << 8) +\ + (((uint32_t) ((zend_uchar) (A)[2])) << 16)) +#define uint4korr(A) (uint32_t) (((uint32_t) ((zend_uchar) (A)[0])) +\ + (((uint32_t) ((zend_uchar) (A)[1])) << 8) +\ + (((uint32_t) ((zend_uchar) (A)[2])) << 16) +\ + (((uint32_t) ((zend_uchar) (A)[3])) << 24)) + +#define uint8korr(A) ((uint64_t)(((uint32_t) ((zend_uchar) (A)[0])) +\ + (((uint32_t) ((zend_uchar) (A)[1])) << 8) +\ + (((uint32_t) ((zend_uchar) (A)[2])) << 16) +\ + (((uint32_t) ((zend_uchar) (A)[3])) << 24)) +\ + (((uint64_t) (((uint32_t) ((zend_uchar) (A)[4])) +\ + (((uint32_t) ((zend_uchar) (A)[5])) << 8) +\ + (((uint32_t) ((zend_uchar) (A)[6])) << 16) +\ + (((uint32_t) ((zend_uchar) (A)[7])) << 24))) << 32)) + + +#define int2store(T,A) do { uint32_t def_temp= (uint32_t) (A) ;\ + *((zend_uchar*) (T)) = (zend_uchar)(def_temp); \ + *((zend_uchar*) (T+1)) = (zend_uchar)((def_temp >> 8)); } while (0) +#define int3store(T,A) do { /*lint -save -e734 */\ + *(((char *)(T))) = (char) ((A));\ + *(((char *)(T))+1) = (char) (((A) >> 8));\ + *(((char *)(T))+2) = (char) (((A) >> 16)); \ + /*lint -restore */} while (0) +#define int4store(T,A) do { \ + *(((char *)(T))) = (char) ((A));\ + *(((char *)(T))+1) = (char) (((A) >> 8));\ + *(((char *)(T))+2) = (char) (((A) >> 16));\ + *(((char *)(T))+3) = (char) (((A) >> 24)); } while (0) +#define int5store(T,A) do { \ + *(((char *)(T))) = (char)((A));\ + *(((char *)(T))+1) = (char)(((A) >> 8));\ + *(((char *)(T))+2) = (char)(((A) >> 16));\ + *(((char *)(T))+3) = (char)(((A) >> 24)); \ + *(((char *)(T))+4) = (char)(((A) >> 32)); } while (0) +/* Based on int5store() from Andrey Hristov */ +#define int6store(T,A) do { \ + *(((char *)(T))) = (char)((A));\ + *(((char *)(T))+1) = (char)(((A) >> 8));\ + *(((char *)(T))+2) = (char)(((A) >> 16));\ + *(((char *)(T))+3) = (char)(((A) >> 24)); \ + *(((char *)(T))+4) = (char)(((A) >> 32)); \ + *(((char *)(T))+5) = (char)(((A) >> 40)); } while (0) +#define int8store(T,A) { uint32_t def_temp= (uint32_t) (A), def_temp2= (uint32_t) ((A) >> 32); \ + int4store((T),def_temp); \ + int4store((T+4),def_temp2); \ + } +#ifdef WORDS_BIGENDIAN +#define float4get(V,M) do { float def_temp;\ + ((char*) &def_temp)[0] = (M)[3];\ + ((char*) &def_temp)[1] = (M)[2];\ + ((char*) &def_temp)[2] = (M)[1];\ + ((char*) &def_temp)[3] = (M)[0];\ + (V)=def_temp; } while (0) +#define float8store(T,V) do { \ + *(((char *)(T))) = (char) ((char *) &(V))[7];\ + *(((char *)(T))+1) = (char) ((char *) &(V))[6];\ + *(((char *)(T))+2) = (char) ((char *) &(V))[5];\ + *(((char *)(T))+3) = (char) ((char *) &(V))[4];\ + *(((char *)(T))+4) = (char) ((char *) &(V))[3];\ + *(((char *)(T))+5) = (char) ((char *) &(V))[2];\ + *(((char *)(T))+6) = (char) ((char *) &(V))[1];\ + *(((char *)(T))+7) = (char) ((char *) &(V))[0]; } while (0) + +#define float8get(V,M) do { double def_temp;\ + ((char*) &def_temp)[0] = (M)[7];\ + ((char*) &def_temp)[1] = (M)[6];\ + ((char*) &def_temp)[2] = (M)[5];\ + ((char*) &def_temp)[3] = (M)[4];\ + ((char*) &def_temp)[4] = (M)[3];\ + ((char*) &def_temp)[5] = (M)[2];\ + ((char*) &def_temp)[6] = (M)[1];\ + ((char*) &def_temp)[7] = (M)[0];\ + (V) = def_temp; \ + } while (0) +#else +#define float4get(V,M) memcpy((char*) &(V),(char*) (M),sizeof(float)) + +#if defined(__FLOAT_WORD_ORDER) && (__FLOAT_WORD_ORDER == __BIG_ENDIAN) +#define float8store(T,V) do { \ + *(((char *)(T)))= ((char *) &(V))[4];\ + *(((char *)(T))+1)=(char) ((char *) &(V))[5];\ + *(((char *)(T))+2)=(char) ((char *) &(V))[6];\ + *(((char *)(T))+3)=(char) ((char *) &(V))[7];\ + *(((char *)(T))+4)=(char) ((char *) &(V))[0];\ + *(((char *)(T))+5)=(char) ((char *) &(V))[1];\ + *(((char *)(T))+6)=(char) ((char *) &(V))[2];\ + *(((char *)(T))+7)=(char) ((char *) &(V))[3];} while (0) +#define float8get(V,M) do { double def_temp;\ + ((char*) &def_temp)[0]=(M)[4];\ + ((char*) &def_temp)[1]=(M)[5];\ + ((char*) &def_temp)[2]=(M)[6];\ + ((char*) &def_temp)[3]=(M)[7];\ + ((char*) &def_temp)[4]=(M)[0];\ + ((char*) &def_temp)[5]=(M)[1];\ + ((char*) &def_temp)[6]=(M)[2];\ + ((char*) &def_temp)[7]=(M)[3];\ + (V) = def_temp; } while (0) +#endif /* __FLOAT_WORD_ORDER */ + +#endif /* WORDS_BIGENDIAN */ + +#endif /* sint2korr */ +/* To here if the platform is not x86 or it's WIN64 */ + + +/* Define-funktions for reading and storing in machine format from/to + short/long to/from some place in memory V should be a (not + register) variable, M is a pointer to byte */ + +#ifndef float8get + +#ifdef WORDS_BIGENDIAN +#define float8get(V,M) memcpy((char*) &(V),(char*) (M), sizeof(double)) +#define float8store(T,V) memcpy((char*) (T),(char*) &(V), sizeof(double)) +#else +#define float8get(V,M) memcpy((char*) &(V),(char*) (M),sizeof(double)) +#define float8store(T,V) memcpy((char*) (T),(char*) &(V),sizeof(double)) +#endif /* WORDS_BIGENDIAN */ + +#endif /* float8get */ + +#endif /* MYSQLND_PORTABILITY_H */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_priv.h b/ext/mysqlnd/mysqlnd_priv.h new file mode 100644 index 0000000..190ed69 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_priv.h @@ -0,0 +1,273 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef MYSQLND_PRIV_H +#define MYSQLND_PRIV_H + +#ifndef Z_ADDREF_P +/* PHP 5.2, old GC */ +#define Z_ADDREF_P(pz) (++(pz)->refcount) +#define Z_DELREF_P(pz) (--(pz)->refcount) +#define Z_REFCOUNT_P(pz) ((pz)->refcount) +#define Z_SET_REFCOUNT_P(pz, rc) ((pz)->refcount = rc) +#define Z_REFCOUNT_PP(ppz) Z_REFCOUNT_P(*(ppz)) +#define Z_DELREF_PP(ppz) Z_DELREF_P(*(ppz)) +#endif + +#if PHP_MAJOR_VERSION >= 6 +#define MYSQLND_UNICODE 1 +#else +#define MYSQLND_UNICODE 0 +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#ifndef pestrndup +#define pestrndup(s, length, persistent) ((persistent)?zend_strndup((s),(length)):estrndup((s),(length))) +#endif + +#if MYSQLND_UNICODE +#define mysqlnd_array_init(arg, field_count) \ +{ \ + ALLOC_HASHTABLE_REL(Z_ARRVAL_P(arg));\ + zend_u_hash_init(Z_ARRVAL_P(arg), (field_count), NULL, ZVAL_PTR_DTOR, 0, 0);\ + Z_TYPE_P(arg) = IS_ARRAY;\ +} +#else +#define mysqlnd_array_init(arg, field_count) \ +{ \ + ALLOC_HASHTABLE_REL(Z_ARRVAL_P(arg));\ + zend_hash_init(Z_ARRVAL_P(arg), (field_count), NULL, ZVAL_PTR_DTOR, 0); \ + Z_TYPE_P(arg) = IS_ARRAY;\ +} +#endif + +#define MYSQLND_STR_W_LEN(str) str, (sizeof(str) - 1) + +#define MYSQLND_DEBUG_DUMP_TIME 1 +#define MYSQLND_DEBUG_DUMP_TRACE 2 +#define MYSQLND_DEBUG_DUMP_PID 4 +#define MYSQLND_DEBUG_DUMP_LINE 8 +#define MYSQLND_DEBUG_DUMP_FILE 16 +#define MYSQLND_DEBUG_DUMP_LEVEL 32 +#define MYSQLND_DEBUG_APPEND 64 +#define MYSQLND_DEBUG_FLUSH 128 +#define MYSQLND_DEBUG_TRACE_MEMORY_CALLS 256 +#define MYSQLND_DEBUG_PROFILE_CALLS 512 + + +/* Client Error codes */ +#define CR_UNKNOWN_ERROR 2000 +#define CR_CONNECTION_ERROR 2002 +#define CR_SERVER_GONE_ERROR 2006 +#define CR_OUT_OF_MEMORY 2008 +#define CR_SERVER_LOST 2013 +#define CR_COMMANDS_OUT_OF_SYNC 2014 +#define CR_CANT_FIND_CHARSET 2019 +#define CR_MALFORMED_PACKET 2027 +#define CR_NOT_IMPLEMENTED 2054 +#define CR_NO_PREPARE_STMT 2030 +#define CR_PARAMS_NOT_BOUND 2031 +#define CR_INVALID_PARAMETER_NO 2034 +#define CR_INVALID_BUFFER_USE 2035 + +#define MYSQLND_EE_FILENOTFOUND 7890 + +#define UNKNOWN_SQLSTATE "HY000" + +#define MAX_CHARSET_LEN 32 + + +#define SET_ERROR_AFF_ROWS(s) (s)->upsert_status->affected_rows = (uint64_t) ~0 + +/* Error handling */ +#define SET_NEW_MESSAGE(buf, buf_len, message, len, persistent) \ + {\ + if ((buf)) { \ + mnd_pefree((buf), (persistent)); \ + } \ + if ((message)) { \ + (buf) = mnd_pestrndup((message), (len), (persistent)); \ + } else { \ + (buf) = NULL; \ + } \ + (buf_len) = (len); \ + } + +#define SET_EMPTY_MESSAGE(buf, buf_len, persistent) \ + {\ + if ((buf)) { \ + mnd_pefree((buf), (persistent)); \ + (buf) = NULL; \ + } \ + (buf_len) = 0; \ + } + + +#define SET_EMPTY_ERROR(error_info) \ + { \ + (error_info).error_no = 0; \ + (error_info).error[0] = '\0'; \ + strlcpy((error_info).sqlstate, "00000", sizeof((error_info).sqlstate)); \ + if ((error_info).error_list) { \ + zend_llist_clean((error_info).error_list); \ + } \ + } + + +#define SET_CLIENT_ERROR(error_info, a, b, c) \ +{ \ + if (0 == (a)) { \ + SET_EMPTY_ERROR((error_info)); \ + } else { \ + (error_info).error_no = (a); \ + strlcpy((error_info).sqlstate, (b), sizeof((error_info).sqlstate)); \ + strlcpy((error_info).error, (c), sizeof((error_info).error)); \ + if ((error_info).error_list) {\ + MYSQLND_ERROR_LIST_ELEMENT error_for_the_list = {0}; \ + \ + error_for_the_list.error_no = (a); \ + strlcpy(error_for_the_list.sqlstate, (b), sizeof(error_for_the_list.sqlstate)); \ + error_for_the_list.error = mnd_pestrdup((c), TRUE); \ + if (error_for_the_list.error) { \ + DBG_INF_FMT("adding error [%s] to the list", error_for_the_list.error); \ + zend_llist_add_element((error_info).error_list, &error_for_the_list); \ + } \ + } \ + } \ +} + + +#define COPY_CLIENT_ERROR(error_info_to, error_info_from) \ + { \ + SET_CLIENT_ERROR((error_info_to), (error_info_from).error_no, (error_info_from).sqlstate, (error_info_from).error); \ + } + + +#define SET_OOM_ERROR(error_info) SET_CLIENT_ERROR((error_info), CR_OUT_OF_MEMORY, UNKNOWN_SQLSTATE, mysqlnd_out_of_memory) + + +#define SET_STMT_ERROR(stmt, a, b, c) SET_CLIENT_ERROR(*(stmt)->error_info, a, b, c) + +#define CONN_GET_STATE(c) (c)->m->get_state((c) TSRMLS_CC) +#define CONN_SET_STATE(c, s) (c)->m->set_state((c), (s) TSRMLS_CC) + +/* PS stuff */ +typedef void (*ps_field_fetch_func)(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, + zend_bool everything_as_unicode TSRMLS_DC); +struct st_mysqlnd_perm_bind { + ps_field_fetch_func func; + /* should be signed int */ + int pack_len; + unsigned int php_type; + zend_bool is_possibly_blob; + zend_bool can_ret_as_str_in_uni; +}; + +extern struct st_mysqlnd_perm_bind mysqlnd_ps_fetch_functions[MYSQL_TYPE_LAST + 1]; + +PHPAPI extern const char * const mysqlnd_old_passwd; +PHPAPI extern const char * const mysqlnd_out_of_sync; +PHPAPI extern const char * const mysqlnd_server_gone; +PHPAPI extern const char * const mysqlnd_out_of_memory; + +extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_object_factory); +extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_conn); +extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_conn_data); +extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_res); +extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_protocol); +extern MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(mysqlnd_net); + +enum_func_status mysqlnd_handle_local_infile(MYSQLND_CONN_DATA * conn, const char *filename, zend_bool *is_warning TSRMLS_DC); + + + +void _mysqlnd_init_ps_subsystem();/* This one is private, mysqlnd_library_init() will call it */ +void _mysqlnd_init_ps_fetch_subsystem(); + +void ps_fetch_from_1_to_8_bytes(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, zend_bool as_unicode, + unsigned int byte_count TSRMLS_DC); + +void mysqlnd_plugin_subsystem_init(TSRMLS_D); +void mysqlnd_plugin_subsystem_end(TSRMLS_D); + +void mysqlnd_register_builtin_authentication_plugins(TSRMLS_D); + +void mysqlnd_example_plugin_register(TSRMLS_D); + +struct st_mysqlnd_packet_greet; +struct st_mysqlnd_authentication_plugin; + +enum_func_status +mysqlnd_auth_handshake(MYSQLND_CONN_DATA * conn, + const char * const user, + const char * const passwd, + const size_t passwd_len, + const char * const db, + const size_t db_len, + const MYSQLND_OPTIONS * const options, + unsigned long mysql_flags, + unsigned int server_charset_no, + zend_bool use_full_blown_auth_packet, + const char * const auth_protocol, + const zend_uchar * const auth_plugin_data, + const size_t auth_plugin_data_len, + char ** switch_to_auth_protocol, + size_t * switch_to_auth_protocol_len, + zend_uchar ** switch_to_auth_protocol_data, + size_t * switch_to_auth_protocol_data_len + TSRMLS_DC); + +enum_func_status +mysqlnd_auth_change_user(MYSQLND_CONN_DATA * const conn, + const char * const user, + const size_t user_len, + const char * const passwd, + const size_t passwd_len, + const char * const db, + const size_t db_len, + const zend_bool silent, + zend_bool use_full_blown_auth_packet, + const char * const auth_protocol, + zend_uchar * auth_plugin_data, + size_t auth_plugin_data_len, + char ** switch_to_auth_protocol, + size_t * switch_to_auth_protocol_len, + zend_uchar ** switch_to_auth_protocol_data, + size_t * switch_to_auth_protocol_data_len + TSRMLS_DC); + +#endif /* MYSQLND_PRIV_H */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_ps.c b/ext/mysqlnd/mysqlnd_ps.c new file mode 100644 index 0000000..99bd021 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_ps.c @@ -0,0 +1,2374 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_result.h" +#include "mysqlnd_result_meta.h" +#include "mysqlnd_statistics.h" +#include "mysqlnd_debug.h" +#include "mysqlnd_block_alloc.h" +#include "mysqlnd_ext_plugin.h" + +#define MYSQLND_SILENT + + +const char * const mysqlnd_not_bound_as_blob = "Can't send long data for non-string/non-binary data types"; +const char * const mysqlnd_stmt_not_prepared = "Statement not prepared"; + +/* Exported by mysqlnd_ps_codec.c */ +enum_func_status mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer TSRMLS_DC); + +enum_func_status mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES *result, void *param, + unsigned int flags, + zend_bool *fetched_anything TSRMLS_DC); + +enum_func_status mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, + unsigned int flags, + zend_bool *fetched_anything TSRMLS_DC); + +static void mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const stmt TSRMLS_DC); +static void mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const stmt, unsigned int param_no TSRMLS_DC); + + +/* {{{ mysqlnd_stmt::store_result */ +static MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + enum_func_status ret; + MYSQLND_CONN_DATA * conn; + MYSQLND_RES * result; + + DBG_ENTER("mysqlnd_stmt::store_result"); + if (!stmt || !stmt->conn || !stmt->result) { + DBG_RETURN(NULL); + } + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + conn = stmt->conn; + + /* be compliant with libmysql - NULL will turn */ + if (!stmt->field_count) { + DBG_RETURN(NULL); + } + + if (stmt->cursor_exists) { + /* Silently convert buffered to unbuffered, for now */ + DBG_RETURN(s->m->use_result(s TSRMLS_CC)); + } + + /* Nothing to store for UPSERT/LOAD DATA*/ + if (CONN_GET_STATE(conn) != CONN_FETCHING_DATA || + stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE) + { + SET_CLIENT_ERROR(*conn->error_info, CR_COMMANDS_OUT_OF_SYNC, + UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); + DBG_RETURN(NULL); + } + + stmt->default_rset_handler = s->m->store_result; + + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*conn->error_info); + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_PS_BUFFERED_SETS); + + result = stmt->result; + result->type = MYSQLND_RES_PS_BUF; + result->m.fetch_row = mysqlnd_stmt_fetch_row_buffered; + result->m.fetch_lengths = NULL;/* makes no sense */ + result->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; + + result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC); + + ret = result->m.store_result_fetch_data(conn, result, result->meta, TRUE TSRMLS_CC); + + if (PASS == ret) { + /* libmysql API docs say it should be so for SELECT statements */ + stmt->upsert_status->affected_rows = stmt->result->stored_data->row_count; + + stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED; + } else { + COPY_CLIENT_ERROR(*conn->error_info, result->stored_data->error_info); + stmt->result->m.free_result_contents(stmt->result TSRMLS_CC); + mnd_efree(stmt->result); + stmt->result = NULL; + stmt->state = MYSQLND_STMT_PREPARED; + } + + DBG_RETURN(result); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::get_result */ +static MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + MYSQLND_CONN_DATA * conn; + MYSQLND_RES *result; + + DBG_ENTER("mysqlnd_stmt::get_result"); + if (!stmt || !stmt->conn || !stmt->result) { + DBG_RETURN(NULL); + } + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + conn = stmt->conn; + + /* be compliant with libmysql - NULL will turn */ + if (!stmt->field_count) { + DBG_RETURN(NULL); + } + + if (stmt->cursor_exists) { + /* Silently convert buffered to unbuffered, for now */ + DBG_RETURN(s->m->use_result(s TSRMLS_CC)); + } + + /* Nothing to store for UPSERT/LOAD DATA*/ + if (CONN_GET_STATE(conn) != CONN_FETCHING_DATA || stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE) { + SET_CLIENT_ERROR(*conn->error_info, CR_COMMANDS_OUT_OF_SYNC, + UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); + DBG_RETURN(NULL); + } + + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*conn->error_info); + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_BUFFERED_SETS); + + do { + result = conn->m->result_init(stmt->result->field_count, stmt->persistent TSRMLS_CC); + if (!result) { + SET_OOM_ERROR(*conn->error_info); + break; + } + + result->meta = stmt->result->meta->m->clone_metadata(stmt->result->meta, FALSE TSRMLS_CC); + if (!result->meta) { + SET_OOM_ERROR(*conn->error_info); + break; + } + + if ((result = result->m.store_result(result, conn, TRUE TSRMLS_CC))) { + stmt->upsert_status->affected_rows = result->stored_data->row_count; + stmt->state = MYSQLND_STMT_PREPARED; + result->type = MYSQLND_RES_PS_BUF; + } else { + COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info); + stmt->state = MYSQLND_STMT_PREPARED; + break; + } + DBG_RETURN(result); + } while (0); + + if (result) { + result->m.free_result(result, TRUE TSRMLS_CC); + } + DBG_RETURN(NULL); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::more_results */ +static zend_bool +MYSQLND_METHOD(mysqlnd_stmt, more_results)(const MYSQLND_STMT * s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt::more_results"); + /* (conn->state == CONN_NEXT_RESULT_PENDING) too */ + DBG_RETURN((stmt && stmt->conn && (stmt->conn->m->get_server_status(stmt->conn TSRMLS_CC) & SERVER_MORE_RESULTS_EXISTS))? + TRUE: + FALSE); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::next_result */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, next_result)(MYSQLND_STMT * s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + MYSQLND_CONN_DATA * conn; + + DBG_ENTER("mysqlnd_stmt::next_result"); + if (!stmt || !stmt->conn || !stmt->result) { + DBG_RETURN(FAIL); + } + conn = stmt->conn; + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + if (CONN_GET_STATE(conn) != CONN_NEXT_RESULT_PENDING || !(conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS)) { + DBG_RETURN(FAIL); + } + + DBG_INF_FMT("server_status=%u cursor=%u", stmt->upsert_status->server_status, stmt->upsert_status->server_status & SERVER_STATUS_CURSOR_EXISTS); + + /* Free space for next result */ + s->m->free_stmt_content(s TSRMLS_CC); + { + enum_func_status ret = s->m->parse_execute_response(s TSRMLS_CC); + DBG_RETURN(ret); + } +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_skip_metadata */ +static enum_func_status +mysqlnd_stmt_skip_metadata(MYSQLND_STMT * s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + /* Follows parameter metadata, we have just to skip it, as libmysql does */ + unsigned int i = 0; + enum_func_status ret = FAIL; + MYSQLND_PACKET_RES_FIELD * field_packet; + + DBG_ENTER("mysqlnd_stmt_skip_metadata"); + if (!stmt || !stmt->conn || !stmt->conn->protocol) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + field_packet = stmt->conn->protocol->m.get_result_field_packet(stmt->conn->protocol, FALSE TSRMLS_CC); + if (!field_packet) { + SET_OOM_ERROR(*stmt->error_info); + SET_OOM_ERROR(*stmt->conn->error_info); + } else { + ret = PASS; + field_packet->skip_parsing = TRUE; + for (;i < stmt->param_count; i++) { + if (FAIL == PACKET_READ(field_packet, stmt->conn)) { + ret = FAIL; + break; + } + } + PACKET_FREE(field_packet); + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_read_prepare_response */ +static enum_func_status +mysqlnd_stmt_read_prepare_response(MYSQLND_STMT * s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + MYSQLND_PACKET_PREPARE_RESPONSE * prepare_resp; + enum_func_status ret = FAIL; + + DBG_ENTER("mysqlnd_stmt_read_prepare_response"); + if (!stmt || !stmt->conn || !stmt->conn->protocol) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + prepare_resp = stmt->conn->protocol->m.get_prepare_response_packet(stmt->conn->protocol, FALSE TSRMLS_CC); + if (!prepare_resp) { + SET_OOM_ERROR(*stmt->error_info); + SET_OOM_ERROR(*stmt->conn->error_info); + goto done; + } + + if (FAIL == PACKET_READ(prepare_resp, stmt->conn)) { + goto done; + } + + if (0xFF == prepare_resp->error_code) { + COPY_CLIENT_ERROR(*stmt->error_info, prepare_resp->error_info); + COPY_CLIENT_ERROR(*stmt->conn->error_info, prepare_resp->error_info); + goto done; + } + ret = PASS; + stmt->stmt_id = prepare_resp->stmt_id; + stmt->warning_count = stmt->conn->upsert_status->warning_count = prepare_resp->warning_count; + stmt->field_count = stmt->conn->field_count = prepare_resp->field_count; + stmt->param_count = prepare_resp->param_count; +done: + PACKET_FREE(prepare_resp); + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_prepare_read_eof */ +static enum_func_status +mysqlnd_stmt_prepare_read_eof(MYSQLND_STMT * s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + MYSQLND_PACKET_EOF * fields_eof; + enum_func_status ret = FAIL; + + DBG_ENTER("mysqlnd_stmt_prepare_read_eof"); + if (!stmt || !stmt->conn || !stmt->conn->protocol) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + fields_eof = stmt->conn->protocol->m.get_eof_packet(stmt->conn->protocol, FALSE TSRMLS_CC); + if (!fields_eof) { + SET_OOM_ERROR(*stmt->error_info); + SET_OOM_ERROR(*stmt->conn->error_info); + } else { + if (FAIL == (ret = PACKET_READ(fields_eof, stmt->conn))) { + if (stmt->result) { + stmt->result->m.free_result_contents(stmt->result TSRMLS_CC); + mnd_efree(stmt->result); + memset(stmt, 0, sizeof(MYSQLND_STMT_DATA)); + stmt->state = MYSQLND_STMT_INITTED; + } + } else { + stmt->upsert_status->server_status = fields_eof->server_status; + stmt->upsert_status->warning_count = fields_eof->warning_count; + stmt->state = MYSQLND_STMT_PREPARED; + } + PACKET_FREE(fields_eof); + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::prepare */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const s, const char * const query, unsigned int query_len TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + MYSQLND_STMT * s_to_prepare = s; + MYSQLND_STMT_DATA * stmt_to_prepare = stmt; + + DBG_ENTER("mysqlnd_stmt::prepare"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + DBG_INF_FMT("query=%s", query); + + SET_ERROR_AFF_ROWS(stmt); + SET_ERROR_AFF_ROWS(stmt->conn); + + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*stmt->conn->error_info); + + if (stmt->state > MYSQLND_STMT_INITTED) { + /* See if we have to clean the wire */ + if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) { + /* Do implicit use_result and then flush the result */ + stmt->default_rset_handler = s->m->use_result; + stmt->default_rset_handler(s TSRMLS_CC); + } + /* No 'else' here please :) */ + if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE && stmt->result) { + stmt->result->m.skip_result(stmt->result TSRMLS_CC); + } + /* + Create a new test statement, which we will prepare, but if anything + fails, we will scrap it. + */ + s_to_prepare = stmt->conn->m->stmt_init(stmt->conn TSRMLS_CC); + if (!s_to_prepare) { + goto fail; + } + stmt_to_prepare = s_to_prepare->data; + } + + if (FAIL == stmt_to_prepare->conn->m->simple_command(stmt_to_prepare->conn, COM_STMT_PREPARE, (const zend_uchar *) query, query_len, PROT_LAST, FALSE, TRUE TSRMLS_CC) || + FAIL == mysqlnd_stmt_read_prepare_response(s_to_prepare TSRMLS_CC)) + { + goto fail; + } + + if (stmt_to_prepare->param_count) { + if (FAIL == mysqlnd_stmt_skip_metadata(s_to_prepare TSRMLS_CC) || + FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare TSRMLS_CC)) + { + goto fail; + } + } + + /* + Read metadata only if there is actual result set. + Beware that SHOW statements bypass the PS framework and thus they send + no metadata at prepare. + */ + if (stmt_to_prepare->field_count) { + MYSQLND_RES * result = stmt->conn->m->result_init(stmt_to_prepare->field_count, stmt_to_prepare->persistent TSRMLS_CC); + if (!result) { + SET_OOM_ERROR(*stmt->conn->error_info); + goto fail; + } + /* Allocate the result now as it is needed for the reading of metadata */ + stmt_to_prepare->result = result; + + result->conn = stmt_to_prepare->conn->m->get_reference(stmt_to_prepare->conn TSRMLS_CC); + + result->type = MYSQLND_RES_PS_BUF; + + if (FAIL == result->m.read_result_metadata(result, stmt_to_prepare->conn TSRMLS_CC) || + FAIL == mysqlnd_stmt_prepare_read_eof(s_to_prepare TSRMLS_CC)) + { + goto fail; + } + } + + if (stmt_to_prepare != stmt) { + /* swap */ + size_t real_size = sizeof(MYSQLND_STMT) + mysqlnd_plugin_count() * sizeof(void *); + char * tmp_swap = mnd_malloc(real_size); + memcpy(tmp_swap, s, real_size); + memcpy(s, s_to_prepare, real_size); + memcpy(s_to_prepare, tmp_swap, real_size); + mnd_free(tmp_swap); + { + MYSQLND_STMT_DATA * tmp_swap_data = stmt_to_prepare; + stmt_to_prepare = stmt; + stmt = tmp_swap_data; + } + s_to_prepare->m->dtor(s_to_prepare, TRUE TSRMLS_CC); + } + stmt->state = MYSQLND_STMT_PREPARED; + DBG_INF("PASS"); + DBG_RETURN(PASS); + +fail: + if (stmt_to_prepare != stmt && s_to_prepare) { + s_to_prepare->m->dtor(s_to_prepare, TRUE TSRMLS_CC); + } + stmt->state = MYSQLND_STMT_INITTED; + + DBG_INF("FAIL"); + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_execute_parse_response */ +static enum_func_status +mysqlnd_stmt_execute_parse_response(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + enum_func_status ret; + MYSQLND_CONN_DATA * conn; + + DBG_ENTER("mysqlnd_stmt_execute_parse_response"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + conn = stmt->conn; + CONN_SET_STATE(conn, CONN_QUERY_SENT); + + ret = mysqlnd_query_read_result_set_header(stmt->conn, s TSRMLS_CC); + if (ret == FAIL) { + COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info); + stmt->upsert_status->affected_rows = conn->upsert_status->affected_rows; + if (CONN_GET_STATE(conn) == CONN_QUIT_SENT) { + /* close the statement here, the connection has been closed */ + } + stmt->state = MYSQLND_STMT_PREPARED; + stmt->send_types_to_server = 1; + } else { + /* + stmt->send_types_to_server has already been set to 0 in + mysqlnd_stmt_execute_generate_request / mysqlnd_stmt_execute_store_params + In case there is a situation in which binding was done for integer and the + value is > LONG_MAX or < LONG_MIN, there is string conversion and we have + to resend the types. Next execution will also need to resend the type. + */ + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*stmt->conn->error_info); + *stmt->upsert_status = *conn->upsert_status; /* copy status */ + stmt->state = MYSQLND_STMT_EXECUTED; + if (conn->last_query_type == QUERY_UPSERT || conn->last_query_type == QUERY_LOAD_LOCAL) { + DBG_INF("PASS"); + DBG_RETURN(PASS); + } + + stmt->result->type = MYSQLND_RES_PS_BUF; + if (!stmt->result->conn) { + /* + For SHOW we don't create (bypasses PS in server) + a result set at prepare and thus a connection was missing + */ + stmt->result->conn = stmt->conn->m->get_reference(stmt->conn TSRMLS_CC); + } + + /* Update stmt->field_count as SHOW sets it to 0 at prepare */ + stmt->field_count = stmt->result->field_count = conn->field_count; + stmt->result->lengths = NULL; + if (stmt->field_count) { + stmt->state = MYSQLND_STMT_WAITING_USE_OR_STORE; + /* + We need to set this because the user might not call + use_result() or store_result() and we should be able to scrap the + data on the line, if he just decides to close the statement. + */ + DBG_INF_FMT("server_status=%u cursor=%u", stmt->upsert_status->server_status, + stmt->upsert_status->server_status & SERVER_STATUS_CURSOR_EXISTS); + + if (stmt->upsert_status->server_status & SERVER_STATUS_CURSOR_EXISTS) { + DBG_INF("cursor exists"); + stmt->cursor_exists = TRUE; + CONN_SET_STATE(conn, CONN_READY); + /* Only cursor read */ + stmt->default_rset_handler = s->m->use_result; + DBG_INF("use_result"); + } else if (stmt->flags & CURSOR_TYPE_READ_ONLY) { + DBG_INF("asked for cursor but got none"); + /* + We have asked for CURSOR but got no cursor, because the condition + above is not fulfilled. Then... + + This is a single-row result set, a result set with no rows, EXPLAIN, + SHOW VARIABLES, or some other command which either a) bypasses the + cursors framework in the server and writes rows directly to the + network or b) is more efficient if all (few) result set rows are + precached on client and server's resources are freed. + */ + /* preferred is buffered read */ + stmt->default_rset_handler = s->m->store_result; + DBG_INF("store_result"); + } else { + DBG_INF("no cursor"); + /* preferred is unbuffered read */ + stmt->default_rset_handler = s->m->use_result; + DBG_INF("use_result"); + } + } + } +#ifndef MYSQLND_DONT_SKIP_OUT_PARAMS_RESULTSET + if (stmt->upsert_status->server_status & SERVER_PS_OUT_PARAMS) { + s->m->free_stmt_content(s TSRMLS_CC); + DBG_INF("PS OUT Variable RSet, skipping"); + /* OUT params result set. Skip for now to retain compatibility */ + ret = mysqlnd_stmt_execute_parse_response(s TSRMLS_CC); + } +#endif + + DBG_INF(ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::execute */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + enum_func_status ret; + MYSQLND_CONN_DATA * conn; + zend_uchar *request = NULL; + size_t request_len; + zend_bool free_request; + + DBG_ENTER("mysqlnd_stmt::execute"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + conn = stmt->conn; + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + SET_ERROR_AFF_ROWS(stmt); + SET_ERROR_AFF_ROWS(stmt->conn); + + if (stmt->result && stmt->state >= MYSQLND_STMT_PREPARED && stmt->field_count) { + /* + We don need to copy the data from the buffers which we will clean. + Because it has already been copied. See + #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF + */ +#ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF + if (stmt->result_bind && + stmt->result_zvals_separated_once == TRUE && + stmt->state >= MYSQLND_STMT_USER_FETCHING) + { + /* + We need to copy the data from the buffers which we will clean. + The bound variables point to them only if the user has started + to fetch data (MYSQLND_STMT_USER_FETCHING). + We need to check 'result_zvals_separated_once' or we will leak + in the following scenario + prepare("select 1 from dual"); + execute(); + fetch(); <-- no binding, but that's not a problem + bind_result(); + execute(); <-- here we will leak because we separate without need + */ + unsigned int i; + for (i = 0; i < stmt->field_count; i++) { + if (stmt->result_bind[i].bound == TRUE) { + zval_copy_ctor(stmt->result_bind[i].zv); + } + } + } +#endif + + s->m->flush(s TSRMLS_CC); + + /* + Executed, but the user hasn't started to fetch + This will clean also the metadata, but after the EXECUTE call we will + have it again. + */ + stmt->result->m.free_result_buffers(stmt->result TSRMLS_CC); + + stmt->state = MYSQLND_STMT_PREPARED; + } else if (stmt->state < MYSQLND_STMT_PREPARED) { + /* Only initted - error */ + SET_CLIENT_ERROR(*conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, + mysqlnd_out_of_sync); + SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); + DBG_INF("FAIL"); + DBG_RETURN(FAIL); + } + + if (stmt->param_count) { + unsigned int i, not_bound = 0; + if (!stmt->param_bind) { + SET_STMT_ERROR(stmt, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE, + "No data supplied for parameters in prepared statement"); + DBG_INF("FAIL"); + DBG_RETURN(FAIL); + } + for (i = 0; i < stmt->param_count; i++) { + if (stmt->param_bind[i].zv == NULL) { + not_bound++; + } + } + if (not_bound) { + char * msg; + mnd_sprintf(&msg, 0, "No data supplied for %u parameter%s in prepared statement", + not_bound, not_bound>1 ?"s":""); + SET_STMT_ERROR(stmt, CR_PARAMS_NOT_BOUND, UNKNOWN_SQLSTATE, msg); + if (msg) { + mnd_sprintf_free(msg); + } + DBG_INF("FAIL"); + DBG_RETURN(FAIL); + } + } + ret = s->m->generate_execute_request(s, &request, &request_len, &free_request TSRMLS_CC); + if (ret == PASS) { + /* support for buffer types should be added here ! */ + ret = stmt->conn->m->simple_command(stmt->conn, COM_STMT_EXECUTE, request, request_len, + PROT_LAST /* we will handle the response packet*/, + FALSE, FALSE TSRMLS_CC); + } else { + SET_STMT_ERROR(stmt, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "Couldn't generate the request. Possibly OOM."); + } + + if (free_request) { + mnd_efree(request); + } + + if (ret == FAIL) { + COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info); + DBG_INF("FAIL"); + DBG_RETURN(FAIL); + } + stmt->execute_count++; + + ret = s->m->parse_execute_response(s TSRMLS_CC); + + DBG_INF_FMT("server_status=%u cursor=%u", stmt->upsert_status->server_status, stmt->upsert_status->server_status & SERVER_STATUS_CURSOR_EXISTS); + + if (ret == PASS && conn->last_query_type == QUERY_UPSERT && stmt->upsert_status->affected_rows) { + MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, STAT_ROWS_AFFECTED_PS, stmt->upsert_status->affected_rows); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_fetch_row_buffered */ +enum_func_status +mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +{ + MYSQLND_STMT * s = (MYSQLND_STMT *) param; + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + MYSQLND_RES_BUFFERED *set = result->stored_data; + unsigned int field_count = result->meta->field_count; + + DBG_ENTER("mysqlnd_stmt_fetch_row_buffered"); + *fetched_anything = FALSE; + DBG_INF_FMT("stmt=%lu", stmt != NULL ? stmt->stmt_id : 0L); + + /* If we haven't read everything */ + if (set->data_cursor && + (set->data_cursor - set->data) < (set->row_count * field_count)) + { + /* The user could have skipped binding - don't crash*/ + if (stmt->result_bind) { + unsigned int i; + MYSQLND_RES_METADATA * meta = result->meta; + zval **current_row = set->data_cursor; + + if (NULL == current_row[0]) { + uint64_t row_num = (set->data_cursor - set->data) / field_count; + enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num], + current_row, + meta->field_count, + meta->fields, + result->conn->options->numeric_and_datetime_as_unicode, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); + if (PASS != rc) { + DBG_RETURN(FAIL); + } + set->initialized_rows++; + if (stmt->update_max_length) { + for (i = 0; i < result->field_count; i++) { + /* + NULL fields are 0 length, 0 is not more than 0 + String of zero size, definitely can't be the next max_length. + Thus for NULL and zero-length we are quite efficient. + */ + if (Z_TYPE_P(current_row[i]) >= IS_STRING) { + unsigned long len = Z_STRLEN_P(current_row[i]); + if (meta->fields[i].max_length < len) { + meta->fields[i].max_length = len; + } + } + } + } + } + + for (i = 0; i < result->field_count; i++) { + /* Clean what we copied last time */ +#ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF + if (stmt->result_bind[i].zv) { + zval_dtor(stmt->result_bind[i].zv); + } +#endif + /* copy the type */ + if (stmt->result_bind[i].bound == TRUE) { + DBG_INF_FMT("i=%u type=%u", i, Z_TYPE_P(current_row[i])); + if (Z_TYPE_P(current_row[i]) != IS_NULL) { + /* + Copy the value. + Pre-condition is that the zvals in the result_bind buffer + have been ZVAL_NULL()-ed or to another simple type + (int, double, bool but not string). Because of the reference + counting the user can't delete the strings the variables point to. + */ + + Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(current_row[i]); + stmt->result_bind[i].zv->value = current_row[i]->value; +#ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF + zval_copy_ctor(stmt->result_bind[i].zv); +#endif + } else { + ZVAL_NULL(stmt->result_bind[i].zv); + } + } + } + } + set->data_cursor += field_count; + *fetched_anything = TRUE; + /* buffered result sets don't have a connection */ + MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF); + DBG_INF("row fetched"); + } else { + set->data_cursor = NULL; + DBG_INF("no more data"); + } + DBG_INF("PASS"); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_fetch_row_unbuffered */ +static enum_func_status +mysqlnd_stmt_fetch_row_unbuffered(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +{ + enum_func_status ret; + MYSQLND_STMT * s = (MYSQLND_STMT *) param; + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + MYSQLND_PACKET_ROW * row_packet; + + DBG_ENTER("mysqlnd_stmt_fetch_row_unbuffered"); + + *fetched_anything = FALSE; + + if (result->unbuf->eof_reached) { + /* No more rows obviously */ + DBG_INF("EOF already reached"); + DBG_RETURN(PASS); + } + if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) { + SET_CLIENT_ERROR(*result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, + UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); + DBG_ERR("command out of sync"); + DBG_RETURN(FAIL); + } + if (!(row_packet = result->row_packet)) { + DBG_RETURN(FAIL); + } + + /* Let the row packet fill our buffer and skip additional malloc + memcpy */ + row_packet->skip_extraction = stmt && stmt->result_bind? FALSE:TRUE; + + /* + If we skip rows (stmt == NULL || stmt->result_bind == NULL) we have to + result->m.unbuffered_free_last_data() before it. The function returns always true. + */ + if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) { + unsigned int i, field_count = result->field_count; + + if (!row_packet->skip_extraction) { + result->m.unbuffered_free_last_data(result TSRMLS_CC); + + result->unbuf->last_row_data = row_packet->fields; + result->unbuf->last_row_buffer = row_packet->row_buffer; + row_packet->fields = NULL; + row_packet->row_buffer = NULL; + + if (PASS != result->m.row_decoder(result->unbuf->last_row_buffer, + result->unbuf->last_row_data, + row_packet->field_count, + row_packet->fields_metadata, + result->conn->options->numeric_and_datetime_as_unicode, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC)) + { + DBG_RETURN(FAIL); + } + + for (i = 0; i < field_count; i++) { + if (stmt->result_bind[i].bound == TRUE) { + zval *data = result->unbuf->last_row_data[i]; + /* + stmt->result_bind[i].zv has been already destructed + in result->m.unbuffered_free_last_data() + */ +#ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF + zval_dtor(stmt->result_bind[i].zv); +#endif + if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data)) ) { + if ( + (Z_TYPE_P(data) == IS_STRING +#if MYSQLND_UNICODE + || Z_TYPE_P(data) == IS_UNICODE +#endif + ) + && (result->meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data))) + { + result->meta->fields[i].max_length = Z_STRLEN_P(data); + } + stmt->result_bind[i].zv->value = data->value; + /* copied data, thus also the ownership. Thus null data */ + ZVAL_NULL(data); + } + } + } + MYSQLND_INC_CONN_STATISTIC(stmt->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_UNBUF); + } else { + DBG_INF("skipping extraction"); + /* + Data has been allocated and usually result->m.unbuffered_free_last_data() + frees it but we can't call this function as it will cause problems with + the bound variables. Thus we need to do part of what it does or Zend will + report leaks. + */ + row_packet->row_buffer->free_chunk(row_packet->row_buffer TSRMLS_CC); + row_packet->row_buffer = NULL; + } + + result->unbuf->row_count++; + *fetched_anything = TRUE; + } else if (ret == FAIL) { + if (row_packet->error_info.error_no) { + COPY_CLIENT_ERROR(*stmt->conn->error_info, row_packet->error_info); + COPY_CLIENT_ERROR(*stmt->error_info, row_packet->error_info); + } + CONN_SET_STATE(result->conn, CONN_READY); + result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */ + } else if (row_packet->eof) { + DBG_INF("EOF"); + /* Mark the connection as usable again */ + result->unbuf->eof_reached = TRUE; + result->conn->upsert_status->warning_count = row_packet->warning_count; + result->conn->upsert_status->server_status = row_packet->server_status; + /* + result->row_packet will be cleaned when + destroying the result object + */ + if (result->conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) { + CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING); + } else { + CONN_SET_STATE(result->conn, CONN_READY); + } + } + + DBG_INF_FMT("ret=%s fetched_anything=%u", ret == PASS? "PASS":"FAIL", *fetched_anything); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::use_result */ +static MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT * s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + MYSQLND_RES * result; + MYSQLND_CONN_DATA * conn; + + DBG_ENTER("mysqlnd_stmt::use_result"); + if (!stmt || !stmt->conn || !stmt->result) { + DBG_RETURN(NULL); + } + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + conn = stmt->conn; + + if (!stmt->field_count || + (!stmt->cursor_exists && CONN_GET_STATE(conn) != CONN_FETCHING_DATA) || + (stmt->cursor_exists && CONN_GET_STATE(conn) != CONN_READY) || + (stmt->state != MYSQLND_STMT_WAITING_USE_OR_STORE)) + { + 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); + } + + SET_EMPTY_ERROR(*stmt->error_info); + + MYSQLND_INC_CONN_STATISTIC(stmt->conn->stats, STAT_PS_UNBUFFERED_SETS); + result = stmt->result; + + result->m.use_result(stmt->result, TRUE TSRMLS_CC); + result->m.fetch_row = stmt->cursor_exists? mysqlnd_fetch_stmt_row_cursor: + mysqlnd_stmt_fetch_row_unbuffered; + stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED; + + DBG_INF_FMT("%p", result); + DBG_RETURN(result); +} +/* }}} */ + + +#define STMT_ID_LENGTH 4 + +/* {{{ mysqlnd_fetch_row_cursor */ +enum_func_status +mysqlnd_fetch_stmt_row_cursor(MYSQLND_RES *result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +{ + enum_func_status ret; + MYSQLND_STMT * s = (MYSQLND_STMT *) param; + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + zend_uchar buf[STMT_ID_LENGTH /* statement id */ + 4 /* number of rows to fetch */]; + MYSQLND_PACKET_ROW * row_packet; + + DBG_ENTER("mysqlnd_fetch_stmt_row_cursor"); + + if (!stmt || !stmt->conn || !result || !result->conn || !result->unbuf) { + DBG_ERR("no statement"); + DBG_RETURN(FAIL); + } + + DBG_INF_FMT("stmt=%lu flags=%u", stmt->stmt_id, flags); + + if (stmt->state < MYSQLND_STMT_USER_FETCHING) { + /* Only initted - error */ + SET_CLIENT_ERROR(*stmt->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, + mysqlnd_out_of_sync); + DBG_ERR("command out of sync"); + DBG_RETURN(FAIL); + } + if (!(row_packet = result->row_packet)) { + DBG_RETURN(FAIL); + } + + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*stmt->conn->error_info); + + int4store(buf, stmt->stmt_id); + int4store(buf + STMT_ID_LENGTH, 1); /* for now fetch only one row */ + + if (FAIL == stmt->conn->m->simple_command(stmt->conn, COM_STMT_FETCH, buf, sizeof(buf), + PROT_LAST /* we will handle the response packet*/, + FALSE, TRUE TSRMLS_CC)) { + COPY_CLIENT_ERROR(*stmt->error_info, *stmt->conn->error_info); + DBG_RETURN(FAIL); + } + + row_packet->skip_extraction = stmt->result_bind? FALSE:TRUE; + + if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) { + unsigned int i, field_count = result->field_count; + + if (!row_packet->skip_extraction) { + result->m.unbuffered_free_last_data(result TSRMLS_CC); + + result->unbuf->last_row_data = row_packet->fields; + result->unbuf->last_row_buffer = row_packet->row_buffer; + row_packet->fields = NULL; + row_packet->row_buffer = NULL; + + if (PASS != result->m.row_decoder(result->unbuf->last_row_buffer, + result->unbuf->last_row_data, + row_packet->field_count, + row_packet->fields_metadata, + result->conn->options->numeric_and_datetime_as_unicode, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC)) + { + DBG_RETURN(FAIL); + } + + /* If no result bind, do nothing. We consumed the data */ + for (i = 0; i < field_count; i++) { + if (stmt->result_bind[i].bound == TRUE) { + zval *data = result->unbuf->last_row_data[i]; + /* + stmt->result_bind[i].zv has been already destructed + in result->m.unbuffered_free_last_data() + */ +#ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF + zval_dtor(stmt->result_bind[i].zv); +#endif + DBG_INF_FMT("i=%u bound_var=%p type=%u refc=%u", i, stmt->result_bind[i].zv, + Z_TYPE_P(data), Z_REFCOUNT_P(stmt->result_bind[i].zv)); + if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data))) { + if ((Z_TYPE_P(data) == IS_STRING +#if MYSQLND_UNICODE + || Z_TYPE_P(data) == IS_UNICODE +#endif + ) + && (result->meta->fields[i].max_length < (unsigned long) Z_STRLEN_P(data))) + { + result->meta->fields[i].max_length = Z_STRLEN_P(data); + } + stmt->result_bind[i].zv->value = data->value; + /* copied data, thus also the ownership. Thus null data */ + ZVAL_NULL(data); + } + } + } + } else { + DBG_INF("skipping extraction"); + /* + Data has been allocated and usually result->m.unbuffered_free_last_data() + frees it but we can't call this function as it will cause problems with + the bound variables. Thus we need to do part of what it does or Zend will + report leaks. + */ + row_packet->row_buffer->free_chunk(row_packet->row_buffer TSRMLS_CC); + row_packet->row_buffer = NULL; + } + /* We asked for one row, the next one should be EOF, eat it */ + ret = PACKET_READ(row_packet, result->conn); + if (row_packet->row_buffer) { + row_packet->row_buffer->free_chunk(row_packet->row_buffer TSRMLS_CC); + row_packet->row_buffer = NULL; + } + MYSQLND_INC_CONN_STATISTIC(stmt->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_CURSOR); + + result->unbuf->row_count++; + *fetched_anything = TRUE; + } else { + *fetched_anything = FALSE; + + stmt->upsert_status->warning_count = + stmt->conn->upsert_status->warning_count = + row_packet->warning_count; + + stmt->upsert_status->server_status = + stmt->conn->upsert_status->server_status = + row_packet->server_status; + + result->unbuf->eof_reached = row_packet->eof; + } + stmt->upsert_status->warning_count = + stmt->conn->upsert_status->warning_count = + row_packet->warning_count; + stmt->upsert_status->server_status = + stmt->conn->upsert_status->server_status = + row_packet->server_status; + + DBG_INF_FMT("ret=%s fetched=%u server_status=%u warnings=%u eof=%u", + ret == PASS? "PASS":"FAIL", *fetched_anything, + row_packet->server_status, row_packet->warning_count, + result->unbuf->eof_reached); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::fetch */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, fetch)(MYSQLND_STMT * const s, zend_bool * const fetched_anything TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + enum_func_status ret; + DBG_ENTER("mysqlnd_stmt::fetch"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + if (!stmt->result || + stmt->state < MYSQLND_STMT_WAITING_USE_OR_STORE) { + SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); + + DBG_ERR("command out of sync"); + DBG_RETURN(FAIL); + } else if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) { + /* Execute only once. We have to free the previous contents of user's bound vars */ + + stmt->default_rset_handler(s TSRMLS_CC); + } + stmt->state = MYSQLND_STMT_USER_FETCHING; + + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*stmt->conn->error_info); + + DBG_INF_FMT("result_bind=%p separated_once=%u", stmt->result_bind, stmt->result_zvals_separated_once); + /* + The user might have not bound any variables for result. + Do the binding once she does it. + */ + if (stmt->result_bind && !stmt->result_zvals_separated_once) { + unsigned int i; + /* + mysqlnd_stmt_store_result() has been called free the bind + variables to prevent leaking of their previous content. + */ + for (i = 0; i < stmt->result->field_count; i++) { + if (stmt->result_bind[i].bound == TRUE) { + zval_dtor(stmt->result_bind[i].zv); + ZVAL_NULL(stmt->result_bind[i].zv); + } + } + stmt->result_zvals_separated_once = TRUE; + } + + ret = stmt->result->m.fetch_row(stmt->result, (void*)s, 0, fetched_anything TSRMLS_CC); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::reset */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, reset)(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + enum_func_status ret = PASS; + zend_uchar cmd_buf[STMT_ID_LENGTH /* statement id */]; + + DBG_ENTER("mysqlnd_stmt::reset"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*stmt->conn->error_info); + + if (stmt->stmt_id) { + MYSQLND_CONN_DATA * conn = stmt->conn; + if (stmt->param_bind) { + unsigned int i; + DBG_INF("resetting long data"); + /* Reset Long Data */ + for (i = 0; i < stmt->param_count; i++) { + if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) { + stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED; + } + } + } + + s->m->flush(s TSRMLS_CC); + + /* + Don't free now, let the result be usable. When the stmt will again be + executed then the result set will be cleaned, the bound variables will + be separated before that. + */ + + int4store(cmd_buf, stmt->stmt_id); + if (CONN_GET_STATE(conn) == CONN_READY && + FAIL == (ret = conn->m->simple_command(conn, COM_STMT_RESET, cmd_buf, + sizeof(cmd_buf), PROT_OK_PACKET, + FALSE, TRUE TSRMLS_CC))) { + COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info); + } + *stmt->upsert_status = *conn->upsert_status; + } + DBG_INF(ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::flush */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, flush)(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + enum_func_status ret = PASS; + + DBG_ENTER("mysqlnd_stmt::flush"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + if (stmt->stmt_id) { + /* + If the user decided to close the statement right after execute() + We have to call the appropriate use_result() or store_result() and + clean. + */ + do { + if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) { + DBG_INF("fetching result set header"); + stmt->default_rset_handler(s TSRMLS_CC); + stmt->state = MYSQLND_STMT_USER_FETCHING; + } + + if (stmt->result) { + DBG_INF("skipping result"); + stmt->result->m.skip_result(stmt->result TSRMLS_CC); + } + } while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS); + } + DBG_INF(ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::send_long_data */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, send_long_data)(MYSQLND_STMT * const s, unsigned int param_no, + const char * const data, unsigned long length TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + enum_func_status ret = FAIL; + MYSQLND_CONN_DATA * conn; + zend_uchar * cmd_buf; + enum php_mysqlnd_server_command cmd = COM_STMT_SEND_LONG_DATA; + + DBG_ENTER("mysqlnd_stmt::send_long_data"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu param_no=%u data_len=%lu", stmt->stmt_id, param_no, length); + + conn = stmt->conn; + + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*stmt->conn->error_info); + + if (stmt->state < MYSQLND_STMT_PREPARED) { + SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared); + DBG_ERR("not prepared"); + DBG_RETURN(FAIL); + } + if (!stmt->param_bind) { + SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); + DBG_ERR("command out of sync"); + DBG_RETURN(FAIL); + } + if (param_no >= stmt->param_count) { + SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number"); + DBG_ERR("invalid param_no"); + DBG_RETURN(FAIL); + } + if (stmt->param_bind[param_no].type != MYSQL_TYPE_LONG_BLOB) { + SET_STMT_ERROR(stmt, CR_INVALID_BUFFER_USE, UNKNOWN_SQLSTATE, mysqlnd_not_bound_as_blob); + DBG_ERR("param_no is not of a blob type"); + DBG_RETURN(FAIL); + } + + /* + XXX: Unfortunately we have to allocate additional buffer to be able the + additional data, which is like a header inside the payload. + This should be optimised, but it will be a pervasive change, so + conn->m->simple_command() will accept not a buffer, but actually MYSQLND_STRING* + terminated by NULL, to send. If the strings are not big, we can collapse them + on the buffer every connection has, but otherwise we will just send them + one by one to the wire. + */ + + if (CONN_GET_STATE(conn) == CONN_READY) { + size_t packet_len; + cmd_buf = mnd_emalloc(packet_len = STMT_ID_LENGTH + 2 + length); + if (cmd_buf) { + stmt->param_bind[param_no].flags |= MYSQLND_PARAM_BIND_BLOB_USED; + + int4store(cmd_buf, stmt->stmt_id); + int2store(cmd_buf + STMT_ID_LENGTH, param_no); + memcpy(cmd_buf + STMT_ID_LENGTH + 2, data, length); + + /* COM_STMT_SEND_LONG_DATA doesn't send an OK packet*/ + ret = conn->m->simple_command(conn, cmd, cmd_buf, packet_len, PROT_LAST , FALSE, TRUE TSRMLS_CC); + mnd_efree(cmd_buf); + if (FAIL == ret) { + COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info); + } + } else { + ret = FAIL; + SET_OOM_ERROR(*stmt->error_info); + SET_OOM_ERROR(*conn->error_info); + } + /* + Cover protocol error: COM_STMT_SEND_LONG_DATA was designed to be quick and not + sent response packets. According to documentation the only way to get an error + is to have out-of-memory on the server-side. However, that's not true, as if + max_allowed_packet_size is smaller than the chunk being sent to the server, the + latter will complain with an error message. However, normally we don't expect + an error message, thus we continue. When sending the next command, which expects + response we will read the unexpected data and error message will look weird. + Therefore we do non-blocking read to clean the line, if there is a need. + Nevertheless, there is a built-in protection when sending a command packet, that + checks if the line is clear - useful for debug purposes and to be switched off + in release builds. + + Maybe we can make it automatic by checking what's the value of + max_allowed_packet_size on the server and resending the data. + */ +#ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND +#if HAVE_USLEEP && !defined(PHP_WIN32) + usleep(120000); +#endif + if ((packet_len = conn->net->m.consume_uneaten_data(conn->net, cmd TSRMLS_CC))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "There was an error " + "while sending long data. Probably max_allowed_packet_size " + "is smaller than the data. You have to increase it or send " + "smaller chunks of data. Answer was "MYSQLND_SZ_T_SPEC" bytes long.", packet_len); + SET_STMT_ERROR(stmt, CR_CONNECTION_ERROR, UNKNOWN_SQLSTATE, + "Server responded to COM_STMT_SEND_LONG_DATA."); + ret = FAIL; + } +#endif + } + + DBG_INF(ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::bind_parameters */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, bind_parameters)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * const param_bind TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt::bind_param"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu param_count=%u", stmt->stmt_id, stmt->param_count); + + if (stmt->state < MYSQLND_STMT_PREPARED) { + SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared); + DBG_ERR("not prepared"); + if (param_bind) { + s->m->free_parameter_bind(s, param_bind TSRMLS_CC); + } + DBG_RETURN(FAIL); + } + + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*stmt->conn->error_info); + + if (stmt->param_count) { + unsigned int i = 0; + + if (!param_bind) { + SET_STMT_ERROR(stmt, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, "Re-binding (still) not supported"); + DBG_ERR("Re-binding (still) not supported"); + DBG_RETURN(FAIL); + } else if (stmt->param_bind) { + DBG_INF("Binding"); + /* + There is already result bound. + Forbid for now re-binding!! + */ + for (i = 0; i < stmt->param_count; i++) { + /* + We may have the last reference, then call zval_ptr_dtor() or we may leak memory. + Switching from bind_one_parameter to bind_parameters may result in zv being NULL + */ + if (stmt->param_bind[i].zv) { + zval_ptr_dtor(&stmt->param_bind[i].zv); + } + } + if (stmt->param_bind != param_bind) { + s->m->free_parameter_bind(s, stmt->param_bind TSRMLS_CC); + } + } + + stmt->param_bind = param_bind; + for (i = 0; i < stmt->param_count; i++) { + /* The client will use stmt_send_long_data */ + DBG_INF_FMT("%u is of type %u", i, stmt->param_bind[i].type); + /* Prevent from freeing */ + /* Don't update is_ref, or we will leak during conversion */ + Z_ADDREF_P(stmt->param_bind[i].zv); + stmt->param_bind[i].flags = 0; + if (stmt->param_bind[i].type == MYSQL_TYPE_LONG_BLOB) { + stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED; + } + } + stmt->send_types_to_server = 1; + } + DBG_INF("PASS"); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::bind_one_parameter */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter)(MYSQLND_STMT * const s, unsigned int param_no, + zval * const zv, zend_uchar type TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt::bind_one_parameter"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu param_no=%u param_count=%u type=%u", stmt->stmt_id, param_no, stmt->param_count, type); + + if (stmt->state < MYSQLND_STMT_PREPARED) { + SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared); + DBG_ERR("not prepared"); + DBG_RETURN(FAIL); + } + + if (param_no >= stmt->param_count) { + SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number"); + DBG_ERR("invalid param_no"); + DBG_RETURN(FAIL); + } + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*stmt->conn->error_info); + + if (stmt->param_count) { + if (!stmt->param_bind) { + stmt->param_bind = mnd_ecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND)); + if (!stmt->param_bind) { + DBG_RETURN(FAIL); + } + } + + /* Prevent from freeing */ + /* Don't update is_ref, or we will leak during conversion */ + Z_ADDREF_P(zv); + DBG_INF("Binding"); + /* Release what we had, if we had */ + if (stmt->param_bind[param_no].zv) { + zval_ptr_dtor(&stmt->param_bind[param_no].zv); + } + if (type == MYSQL_TYPE_LONG_BLOB) { + /* The client will use stmt_send_long_data */ + stmt->param_bind[param_no].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED; + } + stmt->param_bind[param_no].zv = zv; + stmt->param_bind[param_no].type = type; + + stmt->send_types_to_server = 1; + } + DBG_INF("PASS"); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::refresh_bind_param */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param)(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt::refresh_bind_param"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu param_count=%u", stmt->stmt_id, stmt->param_count); + + if (stmt->state < MYSQLND_STMT_PREPARED) { + SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared); + DBG_ERR("not prepared"); + DBG_RETURN(FAIL); + } + + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*stmt->conn->error_info); + + if (stmt->param_count) { + stmt->send_types_to_server = 1; + } + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::bind_result */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, bind_result)(MYSQLND_STMT * const s, + MYSQLND_RESULT_BIND * const result_bind TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt::bind_result"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu field_count=%u", stmt->stmt_id, stmt->field_count); + + if (stmt->state < MYSQLND_STMT_PREPARED) { + SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared); + if (result_bind) { + s->m->free_result_bind(s, result_bind TSRMLS_CC); + } + DBG_ERR("not prepared"); + DBG_RETURN(FAIL); + } + + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*stmt->conn->error_info); + + if (stmt->field_count) { + unsigned int i = 0; + + if (!result_bind) { + DBG_ERR("no result bind passed"); + DBG_RETURN(FAIL); + } + + mysqlnd_stmt_separate_result_bind(s TSRMLS_CC); + stmt->result_zvals_separated_once = FALSE; + stmt->result_bind = result_bind; + for (i = 0; i < stmt->field_count; i++) { + /* Prevent from freeing */ + Z_ADDREF_P(stmt->result_bind[i].zv); + DBG_INF_FMT("ref of %p = %u", stmt->result_bind[i].zv, Z_REFCOUNT_P(stmt->result_bind[i].zv)); + /* + Don't update is_ref !!! it's not our job + Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt + will fail. + */ + stmt->result_bind[i].bound = TRUE; + } + } else if (result_bind) { + s->m->free_result_bind(s, result_bind TSRMLS_CC); + } + DBG_INF("PASS"); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::bind_result */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, bind_one_result)(MYSQLND_STMT * const s, unsigned int param_no TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt::bind_result"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu field_count=%u", stmt->stmt_id, stmt->field_count); + + if (stmt->state < MYSQLND_STMT_PREPARED) { + SET_STMT_ERROR(stmt, CR_NO_PREPARE_STMT, UNKNOWN_SQLSTATE, mysqlnd_stmt_not_prepared); + DBG_ERR("not prepared"); + DBG_RETURN(FAIL); + } + + if (param_no >= stmt->field_count) { + SET_STMT_ERROR(stmt, CR_INVALID_PARAMETER_NO, UNKNOWN_SQLSTATE, "Invalid parameter number"); + DBG_ERR("invalid param_no"); + DBG_RETURN(FAIL); + } + + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*stmt->conn->error_info); + + if (stmt->field_count) { + mysqlnd_stmt_separate_one_result_bind(s, param_no TSRMLS_CC); + /* Guaranteed is that stmt->result_bind is NULL */ + if (!stmt->result_bind) { + stmt->result_bind = mnd_pecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND), stmt->persistent); + } else { + stmt->result_bind = mnd_perealloc(stmt->result_bind, stmt->field_count * sizeof(MYSQLND_RESULT_BIND), stmt->persistent); + } + if (!stmt->result_bind) { + DBG_RETURN(FAIL); + } + ALLOC_INIT_ZVAL(stmt->result_bind[param_no].zv); + /* + Don't update is_ref !!! it's not our job + Otherwise either 009.phpt or mysqli_stmt_bind_result.phpt + will fail. + */ + stmt->result_bind[param_no].bound = TRUE; + } + DBG_INF("PASS"); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::insert_id */ +static uint64_t +MYSQLND_METHOD(mysqlnd_stmt, insert_id)(const MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + return stmt? stmt->upsert_status->last_insert_id : 0; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::affected_rows */ +static uint64_t +MYSQLND_METHOD(mysqlnd_stmt, affected_rows)(const MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + return stmt? stmt->upsert_status->affected_rows : 0; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::num_rows */ +static uint64_t +MYSQLND_METHOD(mysqlnd_stmt, num_rows)(const MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + return stmt && stmt->result? mysqlnd_num_rows(stmt->result):0; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::warning_count */ +static unsigned int +MYSQLND_METHOD(mysqlnd_stmt, warning_count)(const MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + return stmt? stmt->upsert_status->warning_count : 0; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::server_status */ +static unsigned int +MYSQLND_METHOD(mysqlnd_stmt, server_status)(const MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + return stmt? stmt->upsert_status->server_status : 0; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::field_count */ +static unsigned int +MYSQLND_METHOD(mysqlnd_stmt, field_count)(const MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + return stmt? stmt->field_count : 0; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::param_count */ +static unsigned int +MYSQLND_METHOD(mysqlnd_stmt, param_count)(const MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + return stmt? stmt->param_count : 0; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::errno */ +static unsigned int +MYSQLND_METHOD(mysqlnd_stmt, errno)(const MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + return stmt? stmt->error_info->error_no : 0; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::error */ +static const char * +MYSQLND_METHOD(mysqlnd_stmt, error)(const MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + return stmt? stmt->error_info->error : 0; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::sqlstate */ +static const char * +MYSQLND_METHOD(mysqlnd_stmt, sqlstate)(const MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + return stmt && stmt->error_info->sqlstate[0] ? stmt->error_info->sqlstate:MYSQLND_SQLSTATE_NULL; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::data_seek */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, data_seek)(const MYSQLND_STMT * const s, uint64_t row TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + return stmt && stmt->result? stmt->result->m.seek_data(stmt->result, row TSRMLS_CC) : FAIL; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::param_metadata */ +static MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_stmt, param_metadata)(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + if (!stmt || !stmt->param_count) { + return NULL; + } + return NULL; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::result_metadata */ +static MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + MYSQLND_RES *result; + + DBG_ENTER("mysqlnd_stmt::result_metadata"); + if (!stmt) { + DBG_RETURN(NULL); + } + DBG_INF_FMT("stmt=%u field_count=%u", stmt->stmt_id, stmt->field_count); + + if (!stmt->field_count || !stmt->conn || !stmt->result || !stmt->result->meta) { + DBG_INF("NULL"); + DBG_RETURN(NULL); + } + + if (stmt->update_max_length && stmt->result->stored_data) { + /* stored result, we have to update the max_length before we clone the meta data :( */ + stmt->result->m.initialize_result_set_rest(stmt->result TSRMLS_CC); + } + /* + TODO: This implementation is kind of a hack, + find a better way to do it. In different functions I have put + fuses to check for result->m.fetch_row() being NULL. This should + be handled in a better way. + + In the meantime we don't need a zval cache reference for this fake + result set, so we don't get one. + */ + do { + result = stmt->conn->m->result_init(stmt->field_count, stmt->persistent TSRMLS_CC); + if (!result) { + break; + } + result->type = MYSQLND_RES_NORMAL; + result->m.fetch_row = result->m.fetch_row_normal_unbuffered; + result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED)); + if (!result->unbuf) { + break; + } + result->unbuf->eof_reached = TRUE; + result->meta = stmt->result->meta->m->clone_metadata(stmt->result->meta, FALSE TSRMLS_CC); + if (!result->meta) { + break; + } + + DBG_INF_FMT("result=%p", result); + DBG_RETURN(result); + } while (0); + + SET_OOM_ERROR(*stmt->conn->error_info); + if (result) { + result->m.free_result(result, TRUE TSRMLS_CC); + } + DBG_RETURN(NULL); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::attr_set */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, attr_set)(MYSQLND_STMT * const s, + enum mysqlnd_stmt_attr attr_type, + const void * const value TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt::attr_set"); + if (!stmt) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu attr_type=%u", stmt->stmt_id, attr_type); + + switch (attr_type) { + case STMT_ATTR_UPDATE_MAX_LENGTH:{ + zend_uchar bval = *(zend_uchar *) value; + /* + XXX : libmysql uses my_bool, but mysqli uses ulong as storage on the stack + and mysqlnd won't be used out of the scope of PHP -> use ulong. + */ + stmt->update_max_length = bval? TRUE:FALSE; + break; + } + case STMT_ATTR_CURSOR_TYPE: { + unsigned int ival = *(unsigned int *) value; + if (ival > (unsigned long) CURSOR_TYPE_READ_ONLY) { + SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented"); + DBG_INF("FAIL"); + DBG_RETURN(FAIL); + } + stmt->flags = ival; + break; + } + case STMT_ATTR_PREFETCH_ROWS: { + unsigned int ival = *(unsigned int *) value; + if (ival == 0) { + ival = MYSQLND_DEFAULT_PREFETCH_ROWS; + } else if (ival > 1) { + SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented"); + DBG_INF("FAIL"); + DBG_RETURN(FAIL); + } + stmt->prefetch_rows = ival; + break; + } + default: + SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented"); + DBG_RETURN(FAIL); + } + DBG_INF("PASS"); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::attr_get */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, attr_get)(const MYSQLND_STMT * const s, + enum mysqlnd_stmt_attr attr_type, + void * const value TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt::attr_set"); + if (!stmt) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu attr_type=%u", stmt->stmt_id, attr_type); + + switch (attr_type) { + case STMT_ATTR_UPDATE_MAX_LENGTH: + *(zend_bool *) value= stmt->update_max_length; + break; + case STMT_ATTR_CURSOR_TYPE: + *(unsigned long *) value= stmt->flags; + break; + case STMT_ATTR_PREFETCH_ROWS: + *(unsigned long *) value= stmt->prefetch_rows; + break; + default: + DBG_RETURN(FAIL); + } + DBG_INF_FMT("value=%lu", value); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* free_result() doesn't actually free stmt->result but only the buffers */ +/* {{{ mysqlnd_stmt::free_result */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, free_result)(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt::free_result"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + if (!stmt->result) { + DBG_INF("no result"); + DBG_RETURN(PASS); + } + + /* + If right after execute() we have to call the appropriate + use_result() or store_result() and clean. + */ + if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) { + DBG_INF("fetching result set header"); + /* Do implicit use_result and then flush the result */ + stmt->default_rset_handler = s->m->use_result; + stmt->default_rset_handler(s TSRMLS_CC); + } + + if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE) { + DBG_INF("skipping result"); + /* Flush if anything is left and unbuffered set */ + stmt->result->m.skip_result(stmt->result TSRMLS_CC); + /* + Separate the bound variables, which point to the result set, then + destroy the set. + */ + mysqlnd_stmt_separate_result_bind(s TSRMLS_CC); + + /* Now we can destroy the result set */ + stmt->result->m.free_result_buffers(stmt->result TSRMLS_CC); + } + + if (stmt->state > MYSQLND_STMT_PREPARED) { + /* As the buffers have been freed, we should go back to PREPARED */ + stmt->state = MYSQLND_STMT_PREPARED; + } + + /* Line is free! */ + CONN_SET_STATE(stmt->conn, CONN_READY); + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_separate_result_bind */ +static void +mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + unsigned int i; + + DBG_ENTER("mysqlnd_stmt_separate_result_bind"); + if (!stmt) { + DBG_VOID_RETURN; + } + DBG_INF_FMT("stmt=%lu result_bind=%p field_count=%u", stmt->stmt_id, stmt->result_bind, stmt->field_count); + + if (!stmt->result_bind) { + DBG_VOID_RETURN; + } + + /* + Because only the bound variables can point to our internal buffers, then + separate or free only them. Free is possible because the user could have + lost reference. + */ + for (i = 0; i < stmt->field_count; i++) { + /* Let's try with no cache */ + if (stmt->result_bind[i].bound == TRUE) { + DBG_INF_FMT("%u has refcount=%u", i, Z_REFCOUNT_P(stmt->result_bind[i].zv)); + /* + We have to separate the actual zval value of the bound + variable from our allocated zvals or we will face double-free + */ + if (Z_REFCOUNT_P(stmt->result_bind[i].zv) > 1) { +#ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF + zval_copy_ctor(stmt->result_bind[i].zv); +#endif + zval_ptr_dtor(&stmt->result_bind[i].zv); + } else { + /* + If it is a string, what is pointed will be freed + later in free_result(). We need to remove the variable to + which the user has lost reference. + */ +#ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF + ZVAL_NULL(stmt->result_bind[i].zv); +#endif + zval_ptr_dtor(&stmt->result_bind[i].zv); + } + } + } + s->m->free_result_bind(s, stmt->result_bind TSRMLS_CC); + stmt->result_bind = NULL; + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_separate_one_result_bind */ +static void +mysqlnd_stmt_separate_one_result_bind(MYSQLND_STMT * const s, unsigned int param_no TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt_separate_one_result_bind"); + if (!stmt) { + DBG_VOID_RETURN; + } + DBG_INF_FMT("stmt=%lu result_bind=%p field_count=%u param_no=%u", stmt->stmt_id, stmt->result_bind, stmt->field_count, param_no); + + if (!stmt->result_bind) { + DBG_VOID_RETURN; + } + + /* + Because only the bound variables can point to our internal buffers, then + separate or free only them. Free is possible because the user could have + lost reference. + */ + /* Let's try with no cache */ + if (stmt->result_bind[param_no].bound == TRUE) { + DBG_INF_FMT("%u has refcount=%u", param_no, Z_REFCOUNT_P(stmt->result_bind[param_no].zv)); + /* + We have to separate the actual zval value of the bound + variable from our allocated zvals or we will face double-free + */ + if (Z_REFCOUNT_P(stmt->result_bind[param_no].zv) > 1) { +#ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF + zval_copy_ctor(stmt->result_bind[param_no].zv); +#endif + zval_ptr_dtor(&stmt->result_bind[param_no].zv); + } else { + /* + If it is a string, what is pointed will be freed + later in free_result(). We need to remove the variable to + which the user has lost reference. + */ +#ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF + ZVAL_NULL(stmt->result_bind[param_no].zv); +#endif + zval_ptr_dtor(&stmt->result_bind[param_no].zv); + } + } + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::free_stmt_content */ +static void +MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content)(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt::free_stmt_content"); + if (!stmt) { + DBG_VOID_RETURN; + } + DBG_INF_FMT("stmt=%lu param_bind=%p param_count=%u", stmt->stmt_id, stmt->param_bind, stmt->param_count); + + /* Destroy the input bind */ + if (stmt->param_bind) { + unsigned int i; + /* + Because only the bound variables can point to our internal buffers, then + separate or free only them. Free is possible because the user could have + lost reference. + */ + for (i = 0; i < stmt->param_count; i++) { + /* + If bind_one_parameter was used, but not everything was + bound and nothing was fetched, then some `zv` could be NULL + */ + if (stmt->param_bind[i].zv) { + zval_ptr_dtor(&stmt->param_bind[i].zv); + } + } + s->m->free_parameter_bind(s, stmt->param_bind TSRMLS_CC); + stmt->param_bind = NULL; + } + + /* + First separate the bound variables, which point to the result set, then + destroy the set. + */ + mysqlnd_stmt_separate_result_bind(s TSRMLS_CC); + /* Not every statement has a result set attached */ + if (stmt->result) { + stmt->result->m.free_result_internal(stmt->result TSRMLS_CC); + stmt->result = NULL; + } + if (stmt->error_info->error_list) { + zend_llist_clean(stmt->error_info->error_list); + mnd_pefree(stmt->error_info->error_list, s->persistent); + stmt->error_info->error_list = NULL; + } + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::net_close */ +static enum_func_status +MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, net_close)(MYSQLND_STMT * const s, zend_bool implicit TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + MYSQLND_CONN_DATA * conn; + zend_uchar cmd_buf[STMT_ID_LENGTH /* statement id */]; + enum_mysqlnd_collected_stats statistic = STAT_LAST; + + DBG_ENTER("mysqlnd_stmt::net_close"); + if (!stmt || !stmt->conn) { + DBG_RETURN(FAIL); + } + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + conn = stmt->conn; + + SET_EMPTY_ERROR(*stmt->error_info); + SET_EMPTY_ERROR(*stmt->conn->error_info); + + /* + If the user decided to close the statement right after execute() + We have to call the appropriate use_result() or store_result() and + clean. + */ + do { + if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) { + DBG_INF("fetching result set header"); + stmt->default_rset_handler(s TSRMLS_CC); + stmt->state = MYSQLND_STMT_USER_FETCHING; + } + + /* unbuffered set not fetched to the end ? Clean the line */ + if (stmt->result) { + DBG_INF("skipping result"); + stmt->result->m.skip_result(stmt->result TSRMLS_CC); + } + } while (mysqlnd_stmt_more_results(s) && mysqlnd_stmt_next_result(s) == PASS); + /* + After this point we are allowed to free the result set, + as we have cleaned the line + */ + if (stmt->stmt_id) { + MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE? STAT_FREE_RESULT_IMPLICIT: + STAT_FREE_RESULT_EXPLICIT); + + int4store(cmd_buf, stmt->stmt_id); + if (CONN_GET_STATE(conn) == CONN_READY && + FAIL == conn->m->simple_command(conn, COM_STMT_CLOSE, cmd_buf, sizeof(cmd_buf), + PROT_LAST /* COM_STMT_CLOSE doesn't send an OK packet*/, + FALSE, TRUE TSRMLS_CC)) { + COPY_CLIENT_ERROR(*stmt->error_info, *conn->error_info); + DBG_RETURN(FAIL); + } + } + switch (stmt->execute_count) { + case 0: + statistic = STAT_PS_PREPARED_NEVER_EXECUTED; + break; + case 1: + statistic = STAT_PS_PREPARED_ONCE_USED; + break; + default: + break; + } + if (statistic != STAT_LAST) { + MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic); + } + + if (stmt->execute_cmd_buffer.buffer) { + mnd_pefree(stmt->execute_cmd_buffer.buffer, stmt->persistent); + stmt->execute_cmd_buffer.buffer = NULL; + } + + s->m->free_stmt_content(s TSRMLS_CC); + + if (stmt->conn) { + stmt->conn->m->free_reference(stmt->conn TSRMLS_CC); + stmt->conn = NULL; + } + + DBG_RETURN(PASS); +} +/* }}} */ + +/* {{{ mysqlnd_stmt::dtor */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, dtor)(MYSQLND_STMT * const s, zend_bool implicit TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = (s != NULL) ? s->data:NULL; + enum_func_status ret = FAIL; + zend_bool persistent = (s != NULL) ? s->persistent : 0; + + DBG_ENTER("mysqlnd_stmt::dtor"); + if (stmt) { + DBG_INF_FMT("stmt=%p", stmt); + + MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE? STAT_STMT_CLOSE_IMPLICIT: + STAT_STMT_CLOSE_EXPLICIT); + + ret = s->m->net_close(s, implicit TSRMLS_CC); + mnd_pefree(stmt, persistent); + } + mnd_pefree(s, persistent); + + DBG_INF(ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::alloc_param_bind */ +static MYSQLND_PARAM_BIND * +MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind)(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt::alloc_param_bind"); + if (!stmt) { + DBG_RETURN(NULL); + } + DBG_RETURN(mnd_pecalloc(stmt->param_count, sizeof(MYSQLND_PARAM_BIND), stmt->persistent)); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::alloc_result_bind */ +static MYSQLND_RESULT_BIND * +MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind)(MYSQLND_STMT * const s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + DBG_ENTER("mysqlnd_stmt::alloc_result_bind"); + if (!stmt) { + DBG_RETURN(NULL); + } + DBG_RETURN(mnd_pecalloc(stmt->field_count, sizeof(MYSQLND_RESULT_BIND), stmt->persistent)); +} +/* }}} */ + + +/* {{{ param_bind::free_parameter_bind */ +PHPAPI void +MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind)(MYSQLND_STMT * const s, MYSQLND_PARAM_BIND * param_bind TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + if (stmt) { + mnd_pefree(param_bind, stmt->persistent); + } +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::free_result_bind */ +PHPAPI void +MYSQLND_METHOD(mysqlnd_stmt, free_result_bind)(MYSQLND_STMT * const s, MYSQLND_RESULT_BIND * result_bind TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s? s->data:NULL; + if (stmt) { + mnd_pefree(result_bind, stmt->persistent); + } +} +/* }}} */ + + + +MYSQLND_CLASS_METHODS_START(mysqlnd_stmt) + MYSQLND_METHOD(mysqlnd_stmt, prepare), + MYSQLND_METHOD(mysqlnd_stmt, execute), + MYSQLND_METHOD(mysqlnd_stmt, use_result), + MYSQLND_METHOD(mysqlnd_stmt, store_result), + MYSQLND_METHOD(mysqlnd_stmt, get_result), + MYSQLND_METHOD(mysqlnd_stmt, more_results), + MYSQLND_METHOD(mysqlnd_stmt, next_result), + MYSQLND_METHOD(mysqlnd_stmt, free_result), + MYSQLND_METHOD(mysqlnd_stmt, data_seek), + MYSQLND_METHOD(mysqlnd_stmt, reset), + MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, net_close), + MYSQLND_METHOD(mysqlnd_stmt, dtor), + + MYSQLND_METHOD(mysqlnd_stmt, fetch), + + MYSQLND_METHOD(mysqlnd_stmt, bind_parameters), + MYSQLND_METHOD(mysqlnd_stmt, bind_one_parameter), + MYSQLND_METHOD(mysqlnd_stmt, refresh_bind_param), + MYSQLND_METHOD(mysqlnd_stmt, bind_result), + MYSQLND_METHOD(mysqlnd_stmt, bind_one_result), + MYSQLND_METHOD(mysqlnd_stmt, send_long_data), + MYSQLND_METHOD(mysqlnd_stmt, param_metadata), + MYSQLND_METHOD(mysqlnd_stmt, result_metadata), + + MYSQLND_METHOD(mysqlnd_stmt, insert_id), + MYSQLND_METHOD(mysqlnd_stmt, affected_rows), + MYSQLND_METHOD(mysqlnd_stmt, num_rows), + + MYSQLND_METHOD(mysqlnd_stmt, param_count), + MYSQLND_METHOD(mysqlnd_stmt, field_count), + MYSQLND_METHOD(mysqlnd_stmt, warning_count), + + MYSQLND_METHOD(mysqlnd_stmt, errno), + MYSQLND_METHOD(mysqlnd_stmt, error), + MYSQLND_METHOD(mysqlnd_stmt, sqlstate), + + MYSQLND_METHOD(mysqlnd_stmt, attr_get), + MYSQLND_METHOD(mysqlnd_stmt, attr_set), + + + MYSQLND_METHOD(mysqlnd_stmt, alloc_param_bind), + MYSQLND_METHOD(mysqlnd_stmt, alloc_result_bind), + MYSQLND_METHOD(mysqlnd_stmt, free_parameter_bind), + MYSQLND_METHOD(mysqlnd_stmt, free_result_bind), + MYSQLND_METHOD(mysqlnd_stmt, server_status), + mysqlnd_stmt_execute_generate_request, + mysqlnd_stmt_execute_parse_response, + MYSQLND_METHOD(mysqlnd_stmt, free_stmt_content), + MYSQLND_METHOD(mysqlnd_stmt, flush) +MYSQLND_CLASS_METHODS_END; + + +/* {{{ _mysqlnd_stmt_init */ +MYSQLND_STMT * +_mysqlnd_stmt_init(MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + MYSQLND_STMT * ret; + DBG_ENTER("_mysqlnd_stmt_init"); + ret = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_prepared_statement(conn TSRMLS_CC); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ _mysqlnd_init_ps_subsystem */ +void _mysqlnd_init_ps_subsystem() +{ + mysqlnd_stmt_set_methods(&MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_stmt)); + _mysqlnd_init_ps_fetch_subsystem(); +} +/* }}} */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_ps_codec.c b/ext/mysqlnd/mysqlnd_ps_codec.c new file mode 100644 index 0000000..5ead1b0 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_ps_codec.c @@ -0,0 +1,952 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_debug.h" + +#define MYSQLND_SILENT + + +enum mysqlnd_timestamp_type +{ + MYSQLND_TIMESTAMP_NONE= -2, + MYSQLND_TIMESTAMP_ERROR= -1, + MYSQLND_TIMESTAMP_DATE= 0, + MYSQLND_TIMESTAMP_DATETIME= 1, + MYSQLND_TIMESTAMP_TIME= 2 +}; + + +struct st_mysqlnd_time +{ + unsigned int year, month, day, hour, minute, second; + unsigned long second_part; + zend_bool neg; + enum mysqlnd_timestamp_type time_type; +}; + + +struct st_mysqlnd_perm_bind mysqlnd_ps_fetch_functions[MYSQL_TYPE_LAST + 1]; + +#define MYSQLND_PS_SKIP_RESULT_W_LEN -1 +#define MYSQLND_PS_SKIP_RESULT_STR -2 + +/* {{{ ps_fetch_from_1_to_8_bytes */ +void ps_fetch_from_1_to_8_bytes(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, zend_bool as_unicode, + unsigned int byte_count TSRMLS_DC) +{ + char tmp[22]; + size_t tmp_len = 0; + zend_bool is_bit = field->type == MYSQL_TYPE_BIT; + DBG_ENTER("ps_fetch_from_1_to_8_bytes"); + DBG_INF_FMT("zv=%p byte_count=%u", zv, byte_count); + if (field->flags & UNSIGNED_FLAG) { + uint64_t uval = 0; + + switch (byte_count) { + case 8:uval = is_bit? (uint64_t) bit_uint8korr(*row):(uint64_t) uint8korr(*row);break; + case 7:uval = bit_uint7korr(*row);break; + case 6:uval = bit_uint6korr(*row);break; + case 5:uval = bit_uint5korr(*row);break; + case 4:uval = is_bit? (uint64_t) bit_uint4korr(*row):(uint64_t) uint4korr(*row);break; + case 3:uval = is_bit? (uint64_t) bit_uint3korr(*row):(uint64_t) uint3korr(*row);break; + case 2:uval = is_bit? (uint64_t) bit_uint2korr(*row):(uint64_t) uint2korr(*row);break; + case 1:uval = (uint64_t) uint1korr(*row);break; + } + +#if SIZEOF_LONG==4 + if (uval > INT_MAX) { + DBG_INF("stringify"); + tmp_len = sprintf((char *)&tmp, MYSQLND_LLU_SPEC, uval); + } else +#endif /* #if SIZEOF_LONG==4 */ + { + if (byte_count < 8 || uval <= L64(9223372036854775807)) { + ZVAL_LONG(zv, (long) uval); /* the cast is safe, we are in the range */ + } else { + DBG_INF("stringify"); + tmp_len = sprintf((char *)&tmp, MYSQLND_LLU_SPEC, uval); + } + } + } else { + /* SIGNED */ + int64_t lval = 0; + switch (byte_count) { + case 8:lval = (int64_t) sint8korr(*row);break; + /* + 7, 6 and 5 are not possible. + BIT is only unsigned, thus only uint5|6|7 macroses exist + */ + case 4:lval = (int64_t) sint4korr(*row);break; + case 3:lval = (int64_t) sint3korr(*row);break; + case 2:lval = (int64_t) sint2korr(*row);break; + case 1:lval = (int64_t) *(int8_t*)*row;break; + } + +#if SIZEOF_LONG==4 + if ((L64(2147483647) < (int64_t) lval) || (L64(-2147483648) > (int64_t) lval)) { + DBG_INF("stringify"); + tmp_len = sprintf((char *)&tmp, MYSQLND_LL_SPEC, lval); + } else +#endif /* SIZEOF */ + { + ZVAL_LONG(zv, (long) lval); /* the cast is safe, we are in the range */ + } + } + + if (tmp_len) { +#if MYSQLND_UNICODE + if (as_unicode) { + DBG_INF("stringify"); + ZVAL_UTF8_STRINGL(zv, tmp, tmp_len, ZSTR_DUPLICATE); + } else +#endif + { + DBG_INF("stringify"); + ZVAL_STRINGL(zv, tmp, tmp_len, 1); + } + } + (*row)+= byte_count; + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ ps_fetch_null */ +static +void ps_fetch_null(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + ZVAL_NULL(zv); +} +/* }}} */ + + +/* {{{ ps_fetch_int8 */ +static +void ps_fetch_int8(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, as_unicode, 1 TSRMLS_CC); +} +/* }}} */ + + +/* {{{ ps_fetch_int16 */ +static +void ps_fetch_int16(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, as_unicode, 2 TSRMLS_CC); +} +/* }}} */ + + +/* {{{ ps_fetch_int32 */ +static +void ps_fetch_int32(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, as_unicode, 4 TSRMLS_CC); +} +/* }}} */ + + +/* {{{ ps_fetch_int64 */ +static +void ps_fetch_int64(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, as_unicode, 8 TSRMLS_CC); +} +/* }}} */ + + +/* {{{ ps_fetch_float */ +static +void ps_fetch_float(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + float value; + DBG_ENTER("ps_fetch_float"); + float4get(value, *row); + ZVAL_DOUBLE(zv, value); + (*row)+= 4; + DBG_INF_FMT("value=%f", value); + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ ps_fetch_double */ +static +void ps_fetch_double(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + double value; + DBG_ENTER("ps_fetch_double"); + float8get(value, *row); + ZVAL_DOUBLE(zv, value); + (*row)+= 8; + DBG_INF_FMT("value=%f", value); + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ ps_fetch_time */ +static +void ps_fetch_time(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + struct st_mysqlnd_time t; + unsigned int length; /* First byte encodes the length*/ + char * value; + DBG_ENTER("ps_fetch_time"); + + if ((length = php_mysqlnd_net_field_length(row))) { + zend_uchar *to= *row; + + t.time_type = MYSQLND_TIMESTAMP_TIME; + t.neg = (zend_bool) to[0]; + + t.day = (unsigned long) sint4korr(to+1); + t.hour = (unsigned int) to[5]; + t.minute = (unsigned int) to[6]; + t.second = (unsigned int) to[7]; + t.second_part = (length > 8) ? (unsigned long) sint4korr(to+8) : 0; + t.year = t.month= 0; + if (t.day) { + /* Convert days to hours at once */ + t.hour += t.day*24; + t.day = 0; + } + + (*row) += length; + } else { + memset(&t, 0, sizeof(t)); + t.time_type = MYSQLND_TIMESTAMP_TIME; + } + + length = mnd_sprintf(&value, 0, "%s%02u:%02u:%02u", (t.neg ? "-" : ""), t.hour, t.minute, t.second); + + DBG_INF_FMT("%s", value); +#if MYSQLND_UNICODE + if (!as_unicode) { +#endif + ZVAL_STRINGL(zv, value, length, 1); + mnd_sprintf_free(value); +#if MYSQLND_UNICODE + } else { + ZVAL_UTF8_STRINGL(zv, value, length, ZSTR_AUTOFREE); + } +#endif + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ ps_fetch_date */ +static +void ps_fetch_date(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + struct st_mysqlnd_time t = {0}; + unsigned int length; /* First byte encodes the length*/ + char * value; + DBG_ENTER("ps_fetch_date"); + + if ((length = php_mysqlnd_net_field_length(row))) { + zend_uchar *to= *row; + + t.time_type= MYSQLND_TIMESTAMP_DATE; + t.neg= 0; + + t.second_part = t.hour = t.minute = t.second = 0; + + t.year = (unsigned int) sint2korr(to); + t.month = (unsigned int) to[2]; + t.day = (unsigned int) to[3]; + + (*row)+= length; + } else { + memset(&t, 0, sizeof(t)); + t.time_type = MYSQLND_TIMESTAMP_DATE; + } + + length = mnd_sprintf(&value, 0, "%04u-%02u-%02u", t.year, t.month, t.day); + + DBG_INF_FMT("%s", value); +#if MYSQLND_UNICODE + if (!as_unicode) { +#endif + ZVAL_STRINGL(zv, value, length, 1); + mnd_sprintf_free(value); +#if MYSQLND_UNICODE + } else { + ZVAL_UTF8_STRINGL(zv, value, length, ZSTR_AUTOFREE); + } +#endif + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ ps_fetch_datetime */ +static +void ps_fetch_datetime(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + struct st_mysqlnd_time t; + unsigned int length; /* First byte encodes the length*/ + char * value; + DBG_ENTER("ps_fetch_datetime"); + + if ((length = php_mysqlnd_net_field_length(row))) { + zend_uchar *to= *row; + + t.time_type = MYSQLND_TIMESTAMP_DATETIME; + t.neg = 0; + + t.year = (unsigned int) sint2korr(to); + t.month = (unsigned int) to[2]; + t.day = (unsigned int) to[3]; + + if (length > 4) { + t.hour = (unsigned int) to[4]; + t.minute = (unsigned int) to[5]; + t.second = (unsigned int) to[6]; + } else { + t.hour = t.minute = t.second= 0; + } + t.second_part = (length > 7) ? (unsigned long) sint4korr(to+7) : 0; + + (*row)+= length; + } else { + memset(&t, 0, sizeof(t)); + t.time_type = MYSQLND_TIMESTAMP_DATETIME; + } + + length = mnd_sprintf(&value, 0, "%04u-%02u-%02u %02u:%02u:%02u", t.year, t.month, t.day, t.hour, t.minute, t.second); + + DBG_INF_FMT("%s", value); +#if MYSQLND_UNICODE + if (!as_unicode) { +#endif + ZVAL_STRINGL(zv, value, length, 1); + mnd_sprintf_free(value); +#if MYSQLND_UNICODE + } else { + ZVAL_UTF8_STRINGL(zv, to, length, ZSTR_AUTOFREE); + } +#endif + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ ps_fetch_string */ +static +void ps_fetch_string(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + /* + For now just copy, before we make it possible + to write \0 to the row buffer + */ + unsigned long length = php_mysqlnd_net_field_length(row); + DBG_ENTER("ps_fetch_string"); + DBG_INF_FMT("len = %lu", length); +#if MYSQLND_UNICODE + if (field->charsetnr == MYSQLND_BINARY_CHARSET_NR) { + DBG_INF("Binary charset"); + ZVAL_STRINGL(zv, (char *)*row, length, 1); + } else { + DBG_INF_FMT("copying from the row buffer"); + ZVAL_UTF8_STRINGL(zv, (char*)*row, length, ZSTR_DUPLICATE); + } +#else + DBG_INF("copying from the row buffer"); + ZVAL_STRINGL(zv, (char *)*row, length, 1); +#endif + + (*row) += length; + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ ps_fetch_bit */ +static +void ps_fetch_bit(zval *zv, const MYSQLND_FIELD * const field, + unsigned int pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + unsigned long length= php_mysqlnd_net_field_length(row); + ps_fetch_from_1_to_8_bytes(zv, field, pack_len, row, as_unicode, length TSRMLS_CC); +} +/* }}} */ + + +/* {{{ _mysqlnd_init_ps_fetch_subsystem */ +void _mysqlnd_init_ps_fetch_subsystem() +{ + memset(mysqlnd_ps_fetch_functions, 0, sizeof(mysqlnd_ps_fetch_functions)); + mysqlnd_ps_fetch_functions[MYSQL_TYPE_NULL].func = ps_fetch_null; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_NULL].pack_len = 0; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_NULL].php_type = IS_NULL; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_NULL].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY].func = ps_fetch_int8; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY].pack_len = 1; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY].php_type = IS_LONG; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_SHORT].func = ps_fetch_int16; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_SHORT].pack_len = 2; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_SHORT].php_type = IS_LONG; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_SHORT].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_YEAR].func = ps_fetch_int16; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_YEAR].pack_len = 2; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_YEAR].php_type = IS_LONG; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_YEAR].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_INT24].func = ps_fetch_int32; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_INT24].pack_len = 4; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_INT24].php_type = IS_LONG; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_INT24].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG].func = ps_fetch_int32; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG].pack_len = 4; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG].php_type = IS_LONG; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONGLONG].func = ps_fetch_int64; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONGLONG].pack_len= 8; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONGLONG].php_type= IS_LONG; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONGLONG].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_FLOAT].func = ps_fetch_float; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_FLOAT].pack_len = 4; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_FLOAT].php_type = IS_DOUBLE; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_FLOAT].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DOUBLE].func = ps_fetch_double; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DOUBLE].pack_len = 8; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DOUBLE].php_type = IS_DOUBLE; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DOUBLE].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIME].func = ps_fetch_time; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIME].pack_len = MYSQLND_PS_SKIP_RESULT_W_LEN; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIME].php_type = IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIME].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATE].func = ps_fetch_date; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATE].pack_len = MYSQLND_PS_SKIP_RESULT_W_LEN; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATE].php_type = IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATE].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDATE].func = ps_fetch_string; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDATE].pack_len = MYSQLND_PS_SKIP_RESULT_W_LEN; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDATE].php_type = IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDATE].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATETIME].func = ps_fetch_datetime; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATETIME].pack_len= MYSQLND_PS_SKIP_RESULT_W_LEN; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATETIME].php_type= IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DATETIME].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].func = ps_fetch_datetime; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].pack_len= MYSQLND_PS_SKIP_RESULT_W_LEN; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].php_type= IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TIMESTAMP].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].func = ps_fetch_string; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].pack_len= MYSQLND_PS_SKIP_RESULT_STR; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].php_type = IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].is_possibly_blob = TRUE; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_TINY_BLOB].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].func = ps_fetch_string; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].pack_len = MYSQLND_PS_SKIP_RESULT_STR; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].php_type = IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].is_possibly_blob = TRUE; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_BLOB].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].func = ps_fetch_string; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].pack_len = MYSQLND_PS_SKIP_RESULT_STR; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].php_type = IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].is_possibly_blob = TRUE; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_MEDIUM_BLOB].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].func = ps_fetch_string; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].pack_len = MYSQLND_PS_SKIP_RESULT_STR; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].php_type = IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].is_possibly_blob = TRUE; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_LONG_BLOB].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].func = ps_fetch_bit; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].pack_len = 8; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].php_type = IS_LONG; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_BIT].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].func = ps_fetch_string; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].pack_len = MYSQLND_PS_SKIP_RESULT_STR; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].php_type = IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_VAR_STRING].is_possibly_blob = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].func = ps_fetch_string; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].pack_len = MYSQLND_PS_SKIP_RESULT_STR; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].php_type = IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_VARCHAR].is_possibly_blob = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].func = ps_fetch_string; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].pack_len = MYSQLND_PS_SKIP_RESULT_STR; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].php_type = IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_STRING].is_possibly_blob = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].func = ps_fetch_string; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].pack_len = MYSQLND_PS_SKIP_RESULT_STR; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].php_type = IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_DECIMAL].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].func = ps_fetch_string; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].pack_len = MYSQLND_PS_SKIP_RESULT_STR; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].php_type = IS_STRING; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_NEWDECIMAL].can_ret_as_str_in_uni = TRUE; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_ENUM].func = ps_fetch_string; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_ENUM].pack_len = MYSQLND_PS_SKIP_RESULT_STR; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_ENUM].php_type = IS_STRING; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_SET].func = ps_fetch_string; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_SET].pack_len = MYSQLND_PS_SKIP_RESULT_STR; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_SET].php_type = IS_STRING; + + mysqlnd_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].func = ps_fetch_string; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].pack_len= MYSQLND_PS_SKIP_RESULT_STR; + mysqlnd_ps_fetch_functions[MYSQL_TYPE_GEOMETRY].php_type= IS_STRING; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_copy_it */ +static enum_func_status +mysqlnd_stmt_copy_it(zval *** copies, zval *original, unsigned int param_count, unsigned int current TSRMLS_DC) +{ + if (!*copies) { + *copies = mnd_ecalloc(param_count, sizeof(zval *)); + } + if (*copies) { + MAKE_STD_ZVAL((*copies)[current]); + *(*copies)[current] = *original; + Z_SET_REFCOUNT_P((*copies)[current], 1); + zval_copy_ctor((*copies)[current]); + return PASS; + } + return FAIL; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_execute_store_params */ +static enum_func_status +mysqlnd_stmt_execute_store_params(MYSQLND_STMT * s, zend_uchar **buf, zend_uchar **p, size_t *buf_len TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s->data; + unsigned int i = 0; + zend_uchar * provided_buffer = *buf; + size_t left = (*buf_len - (*p - *buf)); + size_t data_size = 0; + zval **copies = NULL;/* if there are different types */ + enum_func_status ret = FAIL; + int resend_types_next_time = 0; + size_t null_byte_offset; + + DBG_ENTER("mysqlnd_stmt_execute_store_params"); + + { + unsigned int null_count = (stmt->param_count + 7) / 8; + /* give it some reserved space - 20 bytes */ + if (left < (null_count + 20)) { + unsigned int offset = *p - *buf; + zend_uchar *tmp_buf; + *buf_len = offset + null_count + 20; + tmp_buf = mnd_emalloc(*buf_len); + if (!tmp_buf) { + SET_OOM_ERROR(*stmt->error_info); + goto end; + } + memcpy(tmp_buf, *buf, offset); + if (*buf != provided_buffer) { + mnd_efree(*buf); + } + *buf = tmp_buf; + + /* Update our pos pointer */ + *p = *buf + offset; + } + /* put `null` bytes */ + null_byte_offset = *p - *buf; + memset(*p, 0, null_count); + *p += null_count; + } + +/* 1. Store type information */ + /* + check if need to send the types even if stmt->send_types_to_server is 0. This is because + if we send "i" (42) then the type will be int and the server will expect int. However, if next + time we try to send > LONG_MAX, the conversion to string will send a string and the server + won't expect it and interpret the value as 0. Thus we need to resend the types, if any such values + occur, and force resend for the next execution. + */ + for (i = 0; i < stmt->param_count; i++) { + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_NULL && + (stmt->param_bind[i].type == MYSQL_TYPE_LONG || stmt->param_bind[i].type == MYSQL_TYPE_LONGLONG)) + { + /* always copy the var, because we do many conversions */ + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_LONG && + PASS != mysqlnd_stmt_copy_it(&copies, stmt->param_bind[i].zv, stmt->param_count, i TSRMLS_CC)) + { + SET_OOM_ERROR(*stmt->error_info); + goto end; + } + /* + if it doesn't fit in a long send it as a string. + Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX + */ + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_LONG) { + zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + convert_to_double_ex(&tmp_data); + if (Z_DVAL_P(tmp_data) > LONG_MAX || Z_DVAL_P(tmp_data) < LONG_MIN) { + stmt->send_types_to_server = resend_types_next_time = 1; + } + } + } + } + + int1store(*p, stmt->send_types_to_server); + (*p)++; + + if (stmt->send_types_to_server) { + /* 2 bytes per type, and leave 20 bytes for future use */ + if (left < ((stmt->param_count * 2) + 20)) { + unsigned int offset = *p - *buf; + zend_uchar *tmp_buf; + *buf_len = offset + stmt->param_count * 2 + 20; + tmp_buf = mnd_emalloc(*buf_len); + if (!tmp_buf) { + SET_OOM_ERROR(*stmt->error_info); + goto end; + } + memcpy(tmp_buf, *buf, offset); + if (*buf != provided_buffer) { + mnd_efree(*buf); + } + *buf = tmp_buf; + + /* Update our pos pointer */ + *p = *buf + offset; + } + for (i = 0; i < stmt->param_count; i++) { + short current_type = stmt->param_bind[i].type; + /* our types are not unsigned */ +#if SIZEOF_LONG==8 + if (current_type == MYSQL_TYPE_LONG) { + current_type = MYSQL_TYPE_LONGLONG; + } +#endif + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_NULL && (current_type == MYSQL_TYPE_LONG || current_type == MYSQL_TYPE_LONGLONG)) { + /* + if it doesn't fit in a long send it as a string. + Check bug #52891 : Wrong data inserted with mysqli/mysqlnd when using bind_param, value > LONG_MAX + */ + if (Z_TYPE_P(stmt->param_bind[i].zv) != IS_LONG) { + zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + + convert_to_double_ex(&tmp_data); + if (Z_DVAL_P(tmp_data) > LONG_MAX || Z_DVAL_P(tmp_data) < LONG_MIN) { + convert_to_string_ex(&tmp_data); + current_type = MYSQL_TYPE_VAR_STRING; + /* + don't change stmt->param_bind[i].type to MYSQL_TYPE_VAR_STRING + we force convert_to_long_ex in all cases, thus the type will be right in the next switch. + if the type is however not long, then we will do a goto in the next switch. + We want to preserve the original bind type given by the user. Thus, we do these hacks. + */ + } else { + convert_to_long_ex(&tmp_data); + } + } + } + int2store(*p, current_type); + *p+= 2; + } + } + stmt->send_types_to_server = resend_types_next_time; + +/* 2. Store data */ + /* 2.1 Calculate how much space we need */ + for (i = 0; i < stmt->param_count; i++) { + unsigned int j; + zval *the_var = stmt->param_bind[i].zv; + + if (!the_var || (stmt->param_bind[i].type != MYSQL_TYPE_LONG_BLOB && Z_TYPE_P(the_var) == IS_NULL)) { + continue; + } + for (j = i + 1; j < stmt->param_count; j++) { + if (stmt->param_bind[j].zv == the_var) { + /* Double binding of the same zval, make a copy */ + if (!copies || !copies[i]) { + if (PASS != mysqlnd_stmt_copy_it(&copies, the_var, stmt->param_count, i TSRMLS_CC)) { + SET_OOM_ERROR(*stmt->error_info); + goto end; + } + } + break; + } + } + + switch (stmt->param_bind[i].type) { + case MYSQL_TYPE_DOUBLE: + data_size += 8; + if (Z_TYPE_P(the_var) != IS_DOUBLE) { + if (!copies || !copies[i]) { + if (PASS != mysqlnd_stmt_copy_it(&copies, the_var, stmt->param_count, i TSRMLS_CC)) { + SET_OOM_ERROR(*stmt->error_info); + goto end; + } + } + } + break; + case MYSQL_TYPE_LONGLONG: + { + zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + if (Z_TYPE_P(tmp_data) == IS_STRING) { + goto use_string; + } + convert_to_long_ex(&tmp_data); + } + data_size += 8; + break; + case MYSQL_TYPE_LONG: + { + zval *tmp_data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + if (Z_TYPE_P(tmp_data) == IS_STRING) { + goto use_string; + } + convert_to_long_ex(&tmp_data); + } + data_size += 4; + break; + case MYSQL_TYPE_LONG_BLOB: + if (!(stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED)) { + /* + User hasn't sent anything, we will send empty string. + Empty string has length of 0, encoded in 1 byte. No real + data will follows after it. + */ + data_size++; + } + break; + case MYSQL_TYPE_VAR_STRING: +use_string: + data_size += 8; /* max 8 bytes for size */ +#if MYSQLND_UNICODE + if (Z_TYPE_P(the_var) != IS_STRING || Z_TYPE_P(the_var) == IS_UNICODE) +#else + if (Z_TYPE_P(the_var) != IS_STRING) +#endif + { + if (!copies || !copies[i]) { + if (PASS != mysqlnd_stmt_copy_it(&copies, the_var, stmt->param_count, i TSRMLS_CC)) { + SET_OOM_ERROR(*stmt->error_info); + goto end; + } + } + the_var = copies[i]; +#if MYSQLND_UNICODE + if (Z_TYPE_P(the_var) == IS_UNICODE) { + zval_unicode_to_string_ex(the_var, UG(utf8_conv) TSRMLS_CC); + } +#endif + } + convert_to_string_ex(&the_var); + data_size += Z_STRLEN_P(the_var); + break; + } + } + + /* 2.2 Enlarge the buffer, if needed */ + left = (*buf_len - (*p - *buf)); + if (left < data_size) { + unsigned int offset = *p - *buf; + zend_uchar *tmp_buf; + *buf_len = offset + data_size + 10; /* Allocate + 10 for safety */ + tmp_buf = mnd_emalloc(*buf_len); + if (!tmp_buf) { + SET_OOM_ERROR(*stmt->error_info); + goto end; + } + memcpy(tmp_buf, *buf, offset); + /* + When too many columns the buffer provided to the function might not be sufficient. + In this case new buffer has been allocated above. When we allocate a buffer and then + allocate a bigger one here, we should free the first one. + */ + if (*buf != provided_buffer) { + mnd_efree(*buf); + } + *buf = tmp_buf; + /* Update our pos pointer */ + *p = *buf + offset; + } + + /* 2.3 Store the actual data */ + for (i = 0; i < stmt->param_count; i++) { + zval *data = (copies && copies[i])? copies[i]: stmt->param_bind[i].zv; + /* Handle long data */ + if (stmt->param_bind[i].zv && Z_TYPE_P(data) == IS_NULL) { + (*buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7)); + } else { + switch (stmt->param_bind[i].type) { + case MYSQL_TYPE_DOUBLE: + convert_to_double_ex(&data); + float8store(*p, Z_DVAL_P(data)); + (*p) += 8; + break; + case MYSQL_TYPE_LONGLONG: + if (Z_TYPE_P(data) == IS_STRING) { + goto send_string; + } + /* data has alreade been converted to long */ + int8store(*p, Z_LVAL_P(data)); + (*p) += 8; + break; + case MYSQL_TYPE_LONG: + if (Z_TYPE_P(data) == IS_STRING) { + goto send_string; + } + /* data has alreade been converted to long */ + int4store(*p, Z_LVAL_P(data)); + (*p) += 4; + break; + case MYSQL_TYPE_LONG_BLOB: + if (stmt->param_bind[i].flags & MYSQLND_PARAM_BIND_BLOB_USED) { + stmt->param_bind[i].flags &= ~MYSQLND_PARAM_BIND_BLOB_USED; + } else { + /* send_long_data() not called, send empty string */ + *p = php_mysqlnd_net_store_length(*p, 0); + } + break; + case MYSQL_TYPE_VAR_STRING: +send_string: + { + unsigned int len = Z_STRLEN_P(data); + /* to is after p. The latter hasn't been moved */ + *p = php_mysqlnd_net_store_length(*p, len); + memcpy(*p, Z_STRVAL_P(data), len); + (*p) += len; + } + break; + default: + /* Won't happen, but set to NULL */ + (*buf + null_byte_offset)[i/8] |= (zend_uchar) (1 << (i & 7)); + break; + } + } + } + ret = PASS; +end: + if (copies) { + for (i = 0; i < stmt->param_count; i++) { + if (copies[i]) { + zval_ptr_dtor(&copies[i]); + } + } + mnd_efree(copies); + } + + DBG_INF_FMT("ret=%s", ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_execute_generate_request */ +enum_func_status +mysqlnd_stmt_execute_generate_request(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s->data; + zend_uchar *p = stmt->execute_cmd_buffer.buffer, + *cmd_buffer = stmt->execute_cmd_buffer.buffer; + size_t cmd_buffer_length = stmt->execute_cmd_buffer.length; + enum_func_status ret; + + DBG_ENTER("mysqlnd_stmt_execute_generate_request"); + + int4store(p, stmt->stmt_id); + p += 4; + + /* flags is 4 bytes, we store just 1 */ + int1store(p, (zend_uchar) stmt->flags); + p++; + + /* Make it all zero */ + int4store(p, 0); + + int1store(p, 1); /* and send 1 for iteration count */ + p+= 4; + + ret = mysqlnd_stmt_execute_store_params(s, &cmd_buffer, &p, &cmd_buffer_length TSRMLS_CC); + + *free_buffer = (cmd_buffer != stmt->execute_cmd_buffer.buffer); + *request_len = (p - cmd_buffer); + *request = cmd_buffer; + DBG_INF_FMT("ret=%s", ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_result.c b/ext/mysqlnd/mysqlnd_result.c new file mode 100644 index 0000000..2e28906 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_result.c @@ -0,0 +1,1702 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_block_alloc.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_result.h" +#include "mysqlnd_result_meta.h" +#include "mysqlnd_statistics.h" +#include "mysqlnd_debug.h" +#include "mysqlnd_ext_plugin.h" + +#define MYSQLND_SILENT + + +/* {{{ mysqlnd_res::initialize_result_set_rest */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest)(MYSQLND_RES * const result TSRMLS_DC) +{ + unsigned int i; + zval **data_cursor = result->stored_data? result->stored_data->data:NULL; + zval **data_begin = result->stored_data? result->stored_data->data:NULL; + unsigned int field_count = result->meta? result->meta->field_count : 0; + uint64_t row_count = result->stored_data? result->stored_data->row_count:0; + enum_func_status ret = PASS; + DBG_ENTER("mysqlnd_res::initialize_result_set_rest"); + + if (!data_cursor || row_count == result->stored_data->initialized_rows) { + DBG_RETURN(ret); + } + while ((data_cursor - data_begin) < (int)(row_count * field_count)) { + if (NULL == data_cursor[0]) { + enum_func_status rc = result->m.row_decoder( + result->stored_data->row_buffers[(data_cursor - data_begin) / field_count], + data_cursor, + result->meta->field_count, + result->meta->fields, + result->conn->options->numeric_and_datetime_as_unicode, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); + if (rc != PASS) { + ret = FAIL; + break; + } + result->stored_data->initialized_rows++; + for (i = 0; i < result->field_count; i++) { + /* + NULL fields are 0 length, 0 is not more than 0 + String of zero size, definitely can't be the next max_length. + Thus for NULL and zero-length we are quite efficient. + */ + if (Z_TYPE_P(data_cursor[i]) >= IS_STRING) { + unsigned long len = Z_STRLEN_P(data_cursor[i]); + if (result->meta->fields[i].max_length < len) { + result->meta->fields[i].max_length = len; + } + } + } + } + data_cursor += field_count; + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_rset_zval_ptr_dtor */ +static void +mysqlnd_rset_zval_ptr_dtor(zval **zv, enum_mysqlnd_res_type type, zend_bool * copy_ctor_called TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_rset_zval_ptr_dtor"); + if (!zv || !*zv) { + *copy_ctor_called = FALSE; + DBG_ERR_FMT("zv was NULL"); + DBG_VOID_RETURN; + } + /* + This zval is not from the cache block. + Thus the refcount is -1 than of a zval from the cache, + because the zvals from the cache are owned by it. + */ + if (type == MYSQLND_RES_PS_BUF || type == MYSQLND_RES_PS_UNBUF) { + *copy_ctor_called = FALSE; + ; /* do nothing, zval_ptr_dtor will do the job*/ + } else if (Z_REFCOUNT_PP(zv) > 1) { + /* + Not a prepared statement, then we have to + call copy_ctor and then zval_ptr_dtor() + + In Unicode mode the destruction of the zvals should not call + zval_copy_ctor() because then we will leak. + I suppose we can use UG(unicode) in mysqlnd.c when freeing a result set + to check if we need to call copy_ctor(). + + If the type is IS_UNICODE, which can happen with PHP6, then we don't + need to copy_ctor, as the data doesn't point to our internal buffers. + If it's string (in PHP5 always) and in PHP6 if data is binary, then + it still points to internal buffers and has to be copied. + */ + if (Z_TYPE_PP(zv) == IS_STRING) { + zval_copy_ctor(*zv); + } + *copy_ctor_called = TRUE; + } else { + /* + noone but us point to this, so we can safely ZVAL_NULL the zval, + so Zend does not try to free what the zval points to - which is + in result set buffers + */ + *copy_ctor_called = FALSE; + if (Z_TYPE_PP(zv) == IS_STRING) { + ZVAL_NULL(*zv); + } + } + zval_ptr_dtor(zv); + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_res::unbuffered_free_last_data */ +static void +MYSQLND_METHOD(mysqlnd_res, unbuffered_free_last_data)(MYSQLND_RES * result TSRMLS_DC) +{ + MYSQLND_RES_UNBUFFERED *unbuf = result->unbuf; + + DBG_ENTER("mysqlnd_res::unbuffered_free_last_data"); + + if (!unbuf) { + DBG_VOID_RETURN; + } + + if (unbuf->last_row_data) { + unsigned int i, ctor_called_count = 0; + zend_bool copy_ctor_called; + MYSQLND_STATS *global_stats = result->conn? result->conn->stats:NULL; + + for (i = 0; i < result->field_count; i++) { + mysqlnd_rset_zval_ptr_dtor(&(unbuf->last_row_data[i]), result->type, ©_ctor_called TSRMLS_CC); + if (copy_ctor_called) { + ++ctor_called_count; + } + } + DBG_INF_FMT("copy_ctor_called_count=%u", ctor_called_count); + /* By using value3 macros we hold a mutex only once, there is no value2 */ + MYSQLND_INC_CONN_STATISTIC_W_VALUE2(global_stats, + STAT_COPY_ON_WRITE_PERFORMED, + ctor_called_count, + STAT_COPY_ON_WRITE_SAVED, + result->field_count - ctor_called_count); + /* Free last row's zvals */ + mnd_efree(unbuf->last_row_data); + unbuf->last_row_data = NULL; + } + if (unbuf->last_row_buffer) { + DBG_INF("Freeing last row buffer"); + /* Nothing points to this buffer now, free it */ + unbuf->last_row_buffer->free_chunk(unbuf->last_row_buffer TSRMLS_CC); + unbuf->last_row_buffer = NULL; + } + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_res::free_buffered_data */ +static void +MYSQLND_METHOD(mysqlnd_res, free_buffered_data)(MYSQLND_RES * result TSRMLS_DC) +{ + MYSQLND_RES_BUFFERED *set = result->stored_data; + unsigned int field_count = result->field_count; + int64_t row; + + DBG_ENTER("mysqlnd_res::free_buffered_data"); + DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", set->row_count); + + if (set->data) { + unsigned int copy_on_write_performed = 0; + unsigned int copy_on_write_saved = 0; + + for (row = set->row_count - 1; row >= 0; row--) { + zval **current_row = set->data + row * field_count; + MYSQLND_MEMORY_POOL_CHUNK *current_buffer = set->row_buffers[row]; + int64_t col; + + if (current_row != NULL) { + for (col = field_count - 1; col >= 0; --col) { + if (current_row[col]) { + zend_bool copy_ctor_called; + mysqlnd_rset_zval_ptr_dtor(&(current_row[col]), result->type, ©_ctor_called TSRMLS_CC); + if (copy_ctor_called) { + ++copy_on_write_performed; + } else { + ++copy_on_write_saved; + } + } + } + } + current_buffer->free_chunk(current_buffer TSRMLS_CC); + } + + MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_COPY_ON_WRITE_PERFORMED, copy_on_write_performed, + STAT_COPY_ON_WRITE_SAVED, copy_on_write_saved); + mnd_efree(set->data); + set->data = NULL; + } + + if (set->row_buffers) { + mnd_efree(set->row_buffers); + set->row_buffers = NULL; + } + set->data_cursor = NULL; + set->row_count = 0; + + mnd_efree(set); + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_res::free_result_buffers */ +static void +MYSQLND_METHOD(mysqlnd_res, free_result_buffers)(MYSQLND_RES * result TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res::free_result_buffers"); + DBG_INF_FMT("%s", result->unbuf? "unbuffered":(result->stored_data? "buffered":"unknown")); + + if (result->unbuf) { + result->m.unbuffered_free_last_data(result TSRMLS_CC); + mnd_efree(result->unbuf); + result->unbuf = NULL; + } else if (result->stored_data) { + result->m.free_buffered_data(result TSRMLS_CC); + result->stored_data = NULL; + } + + if (result->lengths) { + mnd_efree(result->lengths); + result->lengths = NULL; + } + + if (result->row_packet) { + PACKET_FREE(result->row_packet); + result->row_packet = NULL; + } + + if (result->result_set_memory_pool) { + mysqlnd_mempool_destroy(result->result_set_memory_pool TSRMLS_CC); + result->result_set_memory_pool = NULL; + } + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_internal_free_result_contents */ +static +void mysqlnd_internal_free_result_contents(MYSQLND_RES * result TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_internal_free_result_contents"); + + result->m.free_result_buffers(result TSRMLS_CC); + + if (result->meta) { + result->meta->m->free_metadata(result->meta TSRMLS_CC); + result->meta = NULL; + } + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_internal_free_result */ +static +void mysqlnd_internal_free_result(MYSQLND_RES * result TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_internal_free_result"); + result->m.free_result_contents(result TSRMLS_CC); + + if (result->conn) { + result->conn->m->free_reference(result->conn TSRMLS_CC); + result->conn = NULL; + } + + mnd_pefree(result, result->persistent); + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_res::read_result_metadata */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_res, read_result_metadata)(MYSQLND_RES * result, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res::read_result_metadata"); + + /* + Make it safe to call it repeatedly for PS - + better free and allocate a new because the number of field might change + (select *) with altered table. Also for statements which skip the PS + infrastructure! + */ + if (result->meta) { + result->meta->m->free_metadata(result->meta TSRMLS_CC); + result->meta = NULL; + } + + result->meta = result->m.result_meta_init(result->field_count, result->persistent TSRMLS_CC); + if (!result->meta) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(FAIL); + } + + /* 1. Read all fields metadata */ + + /* It's safe to reread without freeing */ + if (FAIL == result->meta->m->read_metadata(result->meta, conn TSRMLS_CC)) { + result->m.free_result_contents(result TSRMLS_CC); + DBG_RETURN(FAIL); + } + /* COM_FIELD_LIST is broken and has premature EOF, thus we need to hack here and in mysqlnd_res_meta.c */ + result->field_count = result->meta->field_count; + + /* + 2. Follows an EOF packet, which the client of mysqlnd_read_result_metadata() + should consume. + 3. If there is a result set, it follows. The last packet will have 'eof' set + If PS, then no result set follows. + */ + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_query_read_result_set_header */ +enum_func_status +mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s TSRMLS_DC) +{ + MYSQLND_STMT_DATA * stmt = s ? s->data:NULL; + enum_func_status ret; + MYSQLND_PACKET_RSET_HEADER * rset_header = NULL; + MYSQLND_PACKET_EOF * fields_eof = NULL; + + DBG_ENTER("mysqlnd_query_read_result_set_header"); + DBG_INF_FMT("stmt=%lu", stmt? stmt->stmt_id:0); + + ret = FAIL; + do { + rset_header = conn->protocol->m.get_rset_header_packet(conn->protocol, FALSE TSRMLS_CC); + if (!rset_header) { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + break; + } + + SET_ERROR_AFF_ROWS(conn); + + if (FAIL == (ret = PACKET_READ(rset_header, conn))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error reading result set's header"); + break; + } + + if (rset_header->error_info.error_no) { + /* + 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; + /* + This will copy the error code and the messages, as they + are buffers in the struct + */ + COPY_CLIENT_ERROR(*conn->error_info, rset_header->error_info); + ret = FAIL; + DBG_ERR_FMT("error=%s", rset_header->error_info.error); + /* Return back from CONN_QUERY_SENT */ + CONN_SET_STATE(conn, CONN_READY); + break; + } + conn->error_info->error_no = 0; + + switch (rset_header->field_count) { + case MYSQLND_NULL_LENGTH: { /* LOAD DATA LOCAL INFILE */ + zend_bool is_warning; + DBG_INF("LOAD DATA"); + conn->last_query_type = QUERY_LOAD_LOCAL; + conn->field_count = 0; /* overwrite previous value, or the last value could be used and lead to bug#53503 */ + CONN_SET_STATE(conn, CONN_SENDING_LOAD_DATA); + ret = mysqlnd_handle_local_infile(conn, rset_header->info_or_local_file, &is_warning TSRMLS_CC); + CONN_SET_STATE(conn, (ret == PASS || is_warning == TRUE)? CONN_READY:CONN_QUIT_SENT); + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY); + break; + } + case 0: /* UPSERT */ + DBG_INF("UPSERT"); + conn->last_query_type = QUERY_UPSERT; + conn->field_count = rset_header->field_count; + conn->upsert_status->warning_count = rset_header->warning_count; + conn->upsert_status->server_status = rset_header->server_status; + conn->upsert_status->affected_rows = rset_header->affected_rows; + conn->upsert_status->last_insert_id = rset_header->last_insert_id; + SET_NEW_MESSAGE(conn->last_message, conn->last_message_len, + rset_header->info_or_local_file, rset_header->info_or_local_file_len, + conn->persistent); + /* Result set can follow UPSERT statement, check server_status */ + if (conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) { + CONN_SET_STATE(conn, CONN_NEXT_RESULT_PENDING); + } else { + CONN_SET_STATE(conn, CONN_READY); + } + ret = PASS; + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_NON_RSET_QUERY); + break; + default: do { /* Result set */ + MYSQLND_RES * result; + enum_mysqlnd_collected_stats statistic = STAT_LAST; + + DBG_INF("Result set pending"); + SET_EMPTY_MESSAGE(conn->last_message, conn->last_message_len, conn->persistent); + + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_RSET_QUERY); + memset(conn->upsert_status, 0, sizeof(*conn->upsert_status)); + /* restore after zeroing */ + SET_ERROR_AFF_ROWS(conn); + + conn->last_query_type = QUERY_SELECT; + CONN_SET_STATE(conn, CONN_FETCHING_DATA); + /* PS has already allocated it */ + conn->field_count = rset_header->field_count; + if (!stmt) { + result = conn->current_result = conn->m->result_init(rset_header->field_count, conn->persistent TSRMLS_CC); + } else { + if (!stmt->result) { + DBG_INF("This is 'SHOW'/'EXPLAIN'-like query."); + /* + This is 'SHOW'/'EXPLAIN'-like query. Current implementation of + prepared statements can't send result set metadata for these queries + on prepare stage. Read it now. + */ + result = stmt->result = conn->m->result_init(rset_header->field_count, stmt->persistent TSRMLS_CC); + } else { + /* + Update result set metadata if it for some reason changed between + prepare and execute, i.e.: + - in case of 'SELECT ?' we don't know column type unless data was + supplied to mysql_stmt_execute, so updated column type is sent + now. + - if data dictionary changed between prepare and execute, for + example a table used in the query was altered. + Note, that now (4.1.3) we always send metadata in reply to + COM_STMT_EXECUTE (even if it is not necessary), so either this or + previous branch always works. + */ + } + result = stmt->result; + } + if (!result) { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + break; + } + + if (FAIL == (ret = result->m.read_result_metadata(result, conn TSRMLS_CC))) { + /* For PS, we leave them in Prepared state */ + if (!stmt && conn->current_result) { + mnd_efree(conn->current_result); + conn->current_result = NULL; + } + DBG_ERR("Error ocurred while reading metadata"); + break; + } + + /* Check for SERVER_STATUS_MORE_RESULTS if needed */ + fields_eof = conn->protocol->m.get_eof_packet(conn->protocol, FALSE TSRMLS_CC); + if (!fields_eof) { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + break; + } + if (FAIL == (ret = PACKET_READ(fields_eof, conn))) { + DBG_ERR("Error ocurred while reading the EOF packet"); + result->m.free_result_contents(result TSRMLS_CC); + mnd_efree(result); + if (!stmt) { + conn->current_result = NULL; + } else { + stmt->result = NULL; + memset(stmt, 0, sizeof(MYSQLND_STMT)); + stmt->state = MYSQLND_STMT_INITTED; + } + } else { + unsigned int to_log = MYSQLND_G(log_mask); + to_log &= fields_eof->server_status; + DBG_INF_FMT("warnings=%u server_status=%u", fields_eof->warning_count, fields_eof->server_status); + conn->upsert_status->warning_count = fields_eof->warning_count; + /* + If SERVER_MORE_RESULTS_EXISTS is set then this is either MULTI_QUERY or a CALL() + The first packet after sending the query/com_execute has the bit set only + in this cases. Not sure why it's a needed but it marks that the whole stream + will include many result sets. What actually matters are the bits set at the end + of every result set (the EOF packet). + */ + conn->upsert_status->server_status = fields_eof->server_status; + if (fields_eof->server_status & SERVER_QUERY_NO_GOOD_INDEX_USED) { + statistic = STAT_BAD_INDEX_USED; + } else if (fields_eof->server_status & SERVER_QUERY_NO_INDEX_USED) { + statistic = STAT_NO_INDEX_USED; + } else if (fields_eof->server_status & SERVER_QUERY_WAS_SLOW) { + statistic = STAT_QUERY_WAS_SLOW; + } + if (to_log) { +#if A0 + char *backtrace = mysqlnd_get_backtrace(TSRMLS_C); + php_log_err(backtrace TSRMLS_CC); + efree(backtrace); +#endif + } + MYSQLND_INC_CONN_STATISTIC(conn->stats, statistic); + } + } while (0); + PACKET_FREE(fields_eof); + break; /* switch break */ + } + } while (0); + PACKET_FREE(rset_header); + + DBG_INF(ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_fetch_lengths_buffered */ +/* + Do lazy initialization for buffered results. As PHP strings have + length inside, this function makes not much sense in the context + of PHP, to be called as separate function. But let's have it for + completeness. +*/ +static unsigned long * +mysqlnd_fetch_lengths_buffered(MYSQLND_RES * const result TSRMLS_DC) +{ + unsigned int i; + zval **previous_row; + MYSQLND_RES_BUFFERED *set = result->stored_data; + + /* + If: + - unbuffered result + - first row has not been read + - last_row has been read + */ + if (set->data_cursor == NULL || + set->data_cursor == set->data || + ((set->data_cursor - set->data) > (set->row_count * result->meta->field_count) )) + { + return NULL;/* No rows or no more rows */ + } + + previous_row = set->data_cursor - result->meta->field_count; + for (i = 0; i < result->meta->field_count; i++) { + result->lengths[i] = (Z_TYPE_P(previous_row[i]) == IS_NULL)? 0:Z_STRLEN_P(previous_row[i]); + } + + return result->lengths; +} +/* }}} */ + + +/* {{{ mysqlnd_fetch_lengths_unbuffered */ +static unsigned long * +mysqlnd_fetch_lengths_unbuffered(MYSQLND_RES * const result TSRMLS_DC) +{ + /* simulate output of libmysql */ + return (!result->unbuf || result->unbuf->last_row_data || result->unbuf->eof_reached)? result->lengths:NULL; +} +/* }}} */ + + +/* {{{ mysqlnd_res::fetch_lengths */ +PHPAPI unsigned long * _mysqlnd_fetch_lengths(MYSQLND_RES * const result TSRMLS_DC) +{ + return result->m.fetch_lengths? result->m.fetch_lengths(result TSRMLS_CC) : NULL; +} +/* }}} */ + + +/* {{{ mysqlnd_fetch_row_unbuffered_c */ +static MYSQLND_ROW_C +mysqlnd_fetch_row_unbuffered_c(MYSQLND_RES * result TSRMLS_DC) +{ + enum_func_status ret; + MYSQLND_ROW_C retrow = NULL; + unsigned int i, + field_count = result->field_count; + MYSQLND_PACKET_ROW *row_packet = result->row_packet; + unsigned long *lengths = result->lengths; + + DBG_ENTER("mysqlnd_fetch_row_unbuffered_c"); + + if (result->unbuf->eof_reached) { + /* No more rows obviously */ + DBG_RETURN(retrow); + } + if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) { + SET_CLIENT_ERROR(*result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, + UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); + DBG_RETURN(retrow); + } + if (!row_packet) { + /* Not fully initialized object that is being cleaned up */ + DBG_RETURN(retrow); + } + /* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */ + row_packet->skip_extraction = FALSE; + + /* + If we skip rows (row == NULL) we have to + result->m.unbuffered_free_last_data() before it. The function returns always true. + */ + if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) { + result->unbuf->row_count++; + + result->m.unbuffered_free_last_data(result TSRMLS_CC); + + result->unbuf->last_row_data = row_packet->fields; + result->unbuf->last_row_buffer = row_packet->row_buffer; + row_packet->fields = NULL; + row_packet->row_buffer = NULL; + + MYSQLND_INC_CONN_STATISTIC(result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF); + + if (!row_packet->skip_extraction) { + MYSQLND_FIELD *field = result->meta->fields; + struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys; + + enum_func_status rc = result->m.row_decoder(result->unbuf->last_row_buffer, + result->unbuf->last_row_data, + row_packet->field_count, + row_packet->fields_metadata, + result->conn->options->numeric_and_datetime_as_unicode, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); + if (PASS != rc) { + DBG_RETURN(retrow); + } + + retrow = mnd_malloc(result->field_count * sizeof(char *)); + if (retrow) { + for (i = 0; i < field_count; i++, field++, hash_key++) { + zval *data = result->unbuf->last_row_data[i]; + unsigned int len; + + if (Z_TYPE_P(data) != IS_NULL) { + convert_to_string(data); + retrow[i] = Z_STRVAL_P(data); + len = Z_STRLEN_P(data); + } else { + retrow[i] = NULL; + len = 0; + } + + if (lengths) { + lengths[i] = len; + } + + if (field->max_length < len) { + field->max_length = len; + } + } + } else { + SET_OOM_ERROR(*result->conn->error_info); + } + } + } else if (ret == FAIL) { + if (row_packet->error_info.error_no) { + COPY_CLIENT_ERROR(*result->conn->error_info, row_packet->error_info); + DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error); + } + CONN_SET_STATE(result->conn, CONN_READY); + result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */ + } else if (row_packet->eof) { + /* Mark the connection as usable again */ + DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status); + result->unbuf->eof_reached = TRUE; + result->conn->upsert_status->warning_count = row_packet->warning_count; + result->conn->upsert_status->server_status = row_packet->server_status; + /* + result->row_packet will be cleaned when + destroying the result object + */ + if (result->conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) { + CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING); + } else { + CONN_SET_STATE(result->conn, CONN_READY); + } + result->m.unbuffered_free_last_data(result TSRMLS_CC); + } + + DBG_RETURN(retrow); +} +/* }}} */ + + +/* {{{ mysqlnd_fetch_row_unbuffered */ +static enum_func_status +mysqlnd_fetch_row_unbuffered(MYSQLND_RES * result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +{ + enum_func_status ret; + zval *row = (zval *) param; + MYSQLND_PACKET_ROW *row_packet = result->row_packet; + + DBG_ENTER("mysqlnd_fetch_row_unbuffered"); + + *fetched_anything = FALSE; + if (result->unbuf->eof_reached) { + /* No more rows obviously */ + DBG_RETURN(PASS); + } + if (CONN_GET_STATE(result->conn) != CONN_FETCHING_DATA) { + SET_CLIENT_ERROR(*result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); + DBG_RETURN(FAIL); + } + if (!row_packet) { + /* Not fully initialized object that is being cleaned up */ + DBG_RETURN(FAIL); + } + /* Let the row packet fill our buffer and skip additional mnd_malloc + memcpy */ + row_packet->skip_extraction = row? FALSE:TRUE; + + /* + If we skip rows (row == NULL) we have to + result->m.unbuffered_free_last_data() before it. The function returns always true. + */ + if (PASS == (ret = PACKET_READ(row_packet, result->conn)) && !row_packet->eof) { + result->m.unbuffered_free_last_data(result TSRMLS_CC); + + result->unbuf->last_row_data = row_packet->fields; + result->unbuf->last_row_buffer = row_packet->row_buffer; + row_packet->fields = NULL; + row_packet->row_buffer = NULL; + + MYSQLND_INC_CONN_STATISTIC(result->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_UNBUF); + + if (!row_packet->skip_extraction) { + HashTable *row_ht = Z_ARRVAL_P(row); + MYSQLND_FIELD *field = result->meta->fields; + struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys; + unsigned int i, field_count = result->field_count; + unsigned long *lengths = result->lengths; + + enum_func_status rc = result->m.row_decoder(result->unbuf->last_row_buffer, + result->unbuf->last_row_data, + field_count, + row_packet->fields_metadata, + result->conn->options->numeric_and_datetime_as_unicode, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); + if (PASS != rc) { + DBG_RETURN(FAIL); + } + for (i = 0; i < field_count; i++, field++, hash_key++) { + zval *data = result->unbuf->last_row_data[i]; + unsigned int len = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data); + + if (lengths) { + lengths[i] = len; + } + + if (flags & MYSQLND_FETCH_NUM) { + Z_ADDREF_P(data); + zend_hash_next_index_insert(row_ht, &data, sizeof(zval *), NULL); + } + if (flags & MYSQLND_FETCH_ASSOC) { + /* zend_hash_quick_update needs length + trailing zero */ + /* QQ: Error handling ? */ + /* + zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether + the index is a numeric and convert it to it. This however means constant + hashing of the column name, which is not needed as it can be precomputed. + */ + Z_ADDREF_P(data); + if (hash_key->is_numeric == FALSE) { +#if MYSQLND_UNICODE + zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE, + hash_key->ustr, + hash_key->ulen + 1, + hash_key->key, + (void *) &data, sizeof(zval *), NULL); +#else + zend_hash_quick_update(Z_ARRVAL_P(row), + field->name, + field->name_length + 1, + hash_key->key, + (void *) &data, sizeof(zval *), NULL); +#endif + } else { + zend_hash_index_update(Z_ARRVAL_P(row), + hash_key->key, + (void *) &data, sizeof(zval *), NULL); + } + } + if (field->max_length < len) { + field->max_length = len; + } + } + } + *fetched_anything = TRUE; + result->unbuf->row_count++; + } else if (ret == FAIL) { + if (row_packet->error_info.error_no) { + COPY_CLIENT_ERROR(*result->conn->error_info, row_packet->error_info); + DBG_ERR_FMT("errorno=%u error=%s", row_packet->error_info.error_no, row_packet->error_info.error); + } + CONN_SET_STATE(result->conn, CONN_READY); + result->unbuf->eof_reached = TRUE; /* so next time we won't get an error */ + } else if (row_packet->eof) { + /* Mark the connection as usable again */ + DBG_INF_FMT("warnings=%u server_status=%u", row_packet->warning_count, row_packet->server_status); + result->unbuf->eof_reached = TRUE; + result->conn->upsert_status->warning_count = row_packet->warning_count; + result->conn->upsert_status->server_status = row_packet->server_status; + /* + result->row_packet will be cleaned when + destroying the result object + */ + if (result->conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) { + CONN_SET_STATE(result->conn, CONN_NEXT_RESULT_PENDING); + } else { + CONN_SET_STATE(result->conn, CONN_READY); + } + result->m.unbuffered_free_last_data(result TSRMLS_CC); + } + + DBG_INF_FMT("ret=%s fetched=%u", ret == PASS? "PASS":"FAIL", *fetched_anything); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_res::use_result */ +static MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_res, use_result)(MYSQLND_RES * const result, zend_bool ps TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res::use_result"); + + SET_EMPTY_ERROR(*result->conn->error_info); + + if (ps == FALSE) { + result->type = MYSQLND_RES_NORMAL; + result->m.fetch_row = result->m.fetch_row_normal_unbuffered; + result->m.fetch_lengths = mysqlnd_fetch_lengths_unbuffered; + result->m.row_decoder = php_mysqlnd_rowp_read_text_protocol; + result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long)); + if (!result->lengths) { + goto oom; + } + } else { + result->type = MYSQLND_RES_PS_UNBUF; + result->m.fetch_row = NULL; + /* result->m.fetch_row() will be set in mysqlnd_ps.c */ + result->m.fetch_lengths = NULL; /* makes no sense */ + result->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; + result->lengths = NULL; + } + + result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC); + result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED)); + if (!result->result_set_memory_pool || !result->unbuf) { + goto oom; + } + + /* + Will be freed in the mysqlnd_internal_free_result_contents() called + by the resource destructor. mysqlnd_fetch_row_unbuffered() expects + this to be not NULL. + */ + /* FALSE = non-persistent */ + result->row_packet = result->conn->protocol->m.get_row_packet(result->conn->protocol, FALSE TSRMLS_CC); + if (!result->row_packet) { + goto oom; + } + result->row_packet->result_set_memory_pool = result->result_set_memory_pool; + result->row_packet->field_count = result->field_count; + result->row_packet->binary_protocol = ps; + result->row_packet->fields_metadata = result->meta->fields; + result->row_packet->bit_fields_count = result->meta->bit_fields_count; + result->row_packet->bit_fields_total_len = result->meta->bit_fields_total_len; + + DBG_RETURN(result); +oom: + SET_OOM_ERROR(*result->conn->error_info); + DBG_RETURN(NULL); +} +/* }}} */ + + +/* {{{ mysqlnd_fetch_row_buffered_c */ +static MYSQLND_ROW_C +mysqlnd_fetch_row_buffered_c(MYSQLND_RES * result TSRMLS_DC) +{ + MYSQLND_ROW_C ret = NULL; + MYSQLND_RES_BUFFERED *set = result->stored_data; + + DBG_ENTER("mysqlnd_fetch_row_buffered_c"); + + /* If we haven't read everything */ + if (set->data_cursor && + (set->data_cursor - set->data) < (set->row_count * result->meta->field_count)) + { + zval **current_row = set->data_cursor; + MYSQLND_FIELD *field = result->meta->fields; + struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys; + unsigned int i; + + if (NULL == current_row[0]) { + uint64_t row_num = (set->data_cursor - set->data) / result->meta->field_count; + enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num], + current_row, + result->meta->field_count, + result->meta->fields, + result->conn->options->numeric_and_datetime_as_unicode, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); + if (rc != PASS) { + DBG_RETURN(ret); + } + set->initialized_rows++; + for (i = 0; i < result->field_count; i++) { + /* + NULL fields are 0 length, 0 is not more than 0 + String of zero size, definitely can't be the next max_length. + Thus for NULL and zero-length we are quite efficient. + */ + if (Z_TYPE_P(current_row[i]) >= IS_STRING) { + unsigned long len = Z_STRLEN_P(current_row[i]); + if (field->max_length < len) { + field->max_length = len; + } + } + } + } + + set->data_cursor += result->meta->field_count; + MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF); + + ret = mnd_malloc(result->field_count * sizeof(char *)); + if (ret) { + for (i = 0; i < result->field_count; i++, field++, hash_key++) { + zval *data = current_row[i]; + + if (Z_TYPE_P(data) != IS_NULL) { + convert_to_string(data); + ret[i] = Z_STRVAL_P(data); + } else { + ret[i] = NULL; + } + } + } + /* there is no conn handle in this function thus we can't set OOM in error_info */ + } else { + set->data_cursor = NULL; + DBG_INF("EOF reached"); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_fetch_row_buffered */ +static enum_func_status +mysqlnd_fetch_row_buffered(MYSQLND_RES * result, void *param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +{ + unsigned int i; + zval *row = (zval *) param; + MYSQLND_RES_BUFFERED *set = result->stored_data; + enum_func_status ret = FAIL; + + DBG_ENTER("mysqlnd_fetch_row_buffered"); + + /* If we haven't read everything */ + if (set->data_cursor && + (set->data_cursor - set->data) < (set->row_count * result->meta->field_count)) + { + zval **current_row = set->data_cursor; + MYSQLND_FIELD *field = result->meta->fields; + struct mysqlnd_field_hash_key * hash_key = result->meta->zend_hash_keys; + + if (NULL == current_row[0]) { + uint64_t row_num = (set->data_cursor - set->data) / result->meta->field_count; + enum_func_status rc = result->m.row_decoder(set->row_buffers[row_num], + current_row, + result->meta->field_count, + result->meta->fields, + result->conn->options->numeric_and_datetime_as_unicode, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); + if (rc != PASS) { + DBG_RETURN(FAIL); + } + set->initialized_rows++; + for (i = 0; i < result->field_count; i++) { + /* + NULL fields are 0 length, 0 is not more than 0 + String of zero size, definitely can't be the next max_length. + Thus for NULL and zero-length we are quite efficient. + */ + if (Z_TYPE_P(current_row[i]) >= IS_STRING) { + unsigned long len = Z_STRLEN_P(current_row[i]); + if (field->max_length < len) { + field->max_length = len; + } + } + } + } + + for (i = 0; i < result->field_count; i++, field++, hash_key++) { + zval *data = current_row[i]; + + if (flags & MYSQLND_FETCH_NUM) { + Z_ADDREF_P(data); + zend_hash_next_index_insert(Z_ARRVAL_P(row), &data, sizeof(zval *), NULL); + } + if (flags & MYSQLND_FETCH_ASSOC) { + /* zend_hash_quick_update needs length + trailing zero */ + /* QQ: Error handling ? */ + /* + zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether + the index is a numeric and convert it to it. This however means constant + hashing of the column name, which is not needed as it can be precomputed. + */ + Z_ADDREF_P(data); + if (hash_key->is_numeric == FALSE) { +#if MYSQLND_UNICODE + zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE, + hash_key->ustr, + hash_key->ulen + 1, + hash_key->key, + (void *) &data, sizeof(zval *), NULL); +#else + zend_hash_quick_update(Z_ARRVAL_P(row), + field->name, + field->name_length + 1, + hash_key->key, + (void *) &data, sizeof(zval *), NULL); +#endif + } else { + zend_hash_index_update(Z_ARRVAL_P(row), + hash_key->key, + (void *) &data, sizeof(zval *), NULL); + } + } + } + set->data_cursor += result->meta->field_count; + *fetched_anything = TRUE; + MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF); + ret = PASS; + } else { + set->data_cursor = NULL; + *fetched_anything = FALSE; + ret = PASS; + DBG_INF("EOF reached"); + } + DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything); + DBG_RETURN(ret); +} +/* }}} */ + + +#define STORE_RESULT_PREALLOCATED_SET_IF_NOT_EMPTY 2 + +/* {{{ mysqlnd_res::store_result_fetch_data */ +enum_func_status +MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result, + MYSQLND_RES_METADATA *meta, + zend_bool binary_protocol TSRMLS_DC) +{ + enum_func_status ret; + MYSQLND_PACKET_ROW *row_packet = NULL; + unsigned int next_extend = STORE_RESULT_PREALLOCATED_SET_IF_NOT_EMPTY, free_rows = 1; + MYSQLND_RES_BUFFERED *set; + + DBG_ENTER("mysqlnd_res::store_result_fetch_data"); + + result->stored_data = set = mnd_ecalloc(1, sizeof(MYSQLND_RES_BUFFERED)); + if (!set) { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + goto end; + } + if (free_rows) { + set->row_buffers = mnd_emalloc((size_t)(free_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *))); + if (!set->row_buffers) { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + goto end; + } + } + set->references = 1; + + /* non-persistent */ + row_packet = conn->protocol->m.get_row_packet(conn->protocol, FALSE TSRMLS_CC); + if (!row_packet) { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + goto end; + } + row_packet->result_set_memory_pool = result->result_set_memory_pool; + row_packet->field_count = meta->field_count; + row_packet->binary_protocol = binary_protocol; + row_packet->fields_metadata = meta->fields; + row_packet->bit_fields_count = meta->bit_fields_count; + row_packet->bit_fields_total_len = meta->bit_fields_total_len; + + row_packet->skip_extraction = TRUE; /* let php_mysqlnd_rowp_read() not allocate row_packet->fields, we will do it */ + + while (FAIL != (ret = PACKET_READ(row_packet, conn)) && !row_packet->eof) { + if (!free_rows) { + uint64_t total_allocated_rows = free_rows = next_extend = next_extend * 11 / 10; /* extend with 10% */ + MYSQLND_MEMORY_POOL_CHUNK ** new_row_buffers; + total_allocated_rows += set->row_count; + + /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */ + if (total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *) > SIZE_MAX) { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + goto end; + } + new_row_buffers = mnd_erealloc(set->row_buffers, (size_t)(total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *))); + if (!new_row_buffers) { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + goto end; + } + set->row_buffers = new_row_buffers; + } + free_rows--; + set->row_buffers[set->row_count] = row_packet->row_buffer; + + set->row_count++; + + /* So row_packet's destructor function won't efree() it */ + row_packet->fields = NULL; + row_packet->row_buffer = NULL; + + /* + No need to FREE_ALLOCA as we can reuse the + 'lengths' and 'fields' arrays. For lengths its absolutely safe. + 'fields' is reused because the ownership of the strings has been + transfered above. + */ + } + /* Overflow ? */ + if (set->row_count) { + /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */ + if (set->row_count * meta->field_count * sizeof(zval *) > SIZE_MAX) { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + goto end; + } + /* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */ + set->data = mnd_emalloc((size_t)(set->row_count * meta->field_count * sizeof(zval *))); + if (!set->data) { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + goto end; + } + memset(set->data, 0, (size_t)(set->row_count * meta->field_count * sizeof(zval *))); + } + + MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn->stats, + binary_protocol? STAT_ROWS_BUFFERED_FROM_CLIENT_PS: + STAT_ROWS_BUFFERED_FROM_CLIENT_NORMAL, + set->row_count); + + /* Finally clean */ + if (row_packet->eof) { + conn->upsert_status->warning_count = row_packet->warning_count; + conn->upsert_status->server_status = row_packet->server_status; + } + /* save some memory */ + if (free_rows) { + /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */ + if (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *) > SIZE_MAX) { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + goto end; + } + set->row_buffers = mnd_erealloc(set->row_buffers, (size_t) (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *))); + } + + if (conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) { + CONN_SET_STATE(conn, CONN_NEXT_RESULT_PENDING); + } else { + CONN_SET_STATE(conn, CONN_READY); + } + + if (ret == FAIL) { + COPY_CLIENT_ERROR(set->error_info, row_packet->error_info); + } else { + /* Position at the first row */ + set->data_cursor = set->data; + + /* libmysql's documentation says it should be so for SELECT statements */ + conn->upsert_status->affected_rows = set->row_count; + } + DBG_INF_FMT("ret=%s row_count=%u warnings=%u server_status=%u", + ret == PASS? "PASS":"FAIL", (uint) set->row_count, conn->upsert_status->warning_count, conn->upsert_status->server_status); +end: + PACKET_FREE(row_packet); + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_res::store_result */ +static MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result, + MYSQLND_CONN_DATA * const conn, + zend_bool ps_protocol TSRMLS_DC) +{ + enum_func_status ret; + + DBG_ENTER("mysqlnd_res::store_result"); + + /* We need the conn because we are doing lazy zval initialization in buffered_fetch_row */ + result->conn = conn->m->get_reference(conn TSRMLS_CC); + result->type = MYSQLND_RES_NORMAL; + result->m.fetch_row = result->m.fetch_row_normal_buffered; + result->m.fetch_lengths = mysqlnd_fetch_lengths_buffered; + result->m.row_decoder = ps_protocol? php_mysqlnd_rowp_read_binary_protocol: + php_mysqlnd_rowp_read_text_protocol; + + result->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC); + result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long)); + + if (!result->result_set_memory_pool || !result->lengths) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + + CONN_SET_STATE(conn, CONN_FETCHING_DATA); + + ret = result->m.store_result_fetch_data(conn, result, result->meta, ps_protocol TSRMLS_CC); + if (FAIL == ret) { + if (result->stored_data) { + COPY_CLIENT_ERROR(*conn->error_info, result->stored_data->error_info); + } else { + SET_OOM_ERROR(*conn->error_info); + } + DBG_RETURN(NULL); + } + /* libmysql's documentation says it should be so for SELECT statements */ + conn->upsert_status->affected_rows = result->stored_data->row_count; + + DBG_RETURN(result); +} +/* }}} */ + + +/* {{{ mysqlnd_res::skip_result */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_res, skip_result)(MYSQLND_RES * const result TSRMLS_DC) +{ + zend_bool fetched_anything; + + DBG_ENTER("mysqlnd_res::skip_result"); + /* + Unbuffered sets + A PS could be prepared - there is metadata and thus a stmt->result but the + fetch_row function isn't actually set (NULL), thus we have to skip these. + */ + if (!result->stored_data && result->unbuf && + !result->unbuf->eof_reached && result->m.fetch_row) + { + DBG_INF("skipping result"); + /* We have to fetch all data to clean the line */ + MYSQLND_INC_CONN_STATISTIC(result->conn->stats, + result->type == MYSQLND_RES_NORMAL? STAT_FLUSHED_NORMAL_SETS: + STAT_FLUSHED_PS_SETS); + + while ((PASS == result->m.fetch_row(result, NULL, 0, &fetched_anything TSRMLS_CC)) && fetched_anything == TRUE) { + /* do nothing */; + } + } + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_res::free_result */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_res, free_result)(MYSQLND_RES * result, zend_bool implicit TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res::free_result"); + + result->m.skip_result(result TSRMLS_CC); + MYSQLND_INC_CONN_STATISTIC(result->conn? result->conn->stats : NULL, + implicit == TRUE? STAT_FREE_RESULT_IMPLICIT: + STAT_FREE_RESULT_EXPLICIT); + + result->m.free_result_internal(result TSRMLS_CC); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_res::data_seek */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES * result, uint64_t row TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res::data_seek"); + DBG_INF_FMT("row=%lu", row); + + if (!result->stored_data) { + return FAIL; + } + + /* libmysql just moves to the end, it does traversing of a linked list */ + if (row >= result->stored_data->row_count) { + result->stored_data->data_cursor = NULL; + } else { + result->stored_data->data_cursor = result->stored_data->data + row * result->meta->field_count; + } + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_res::num_rows */ +static uint64_t +MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const result TSRMLS_DC) +{ + /* Be compatible with libmysql. We count row_count, but will return 0 */ + return result->stored_data? result->stored_data->row_count:(result->unbuf && result->unbuf->eof_reached? result->unbuf->row_count:0); +} +/* }}} */ + + +/* {{{ mysqlnd_res::num_fields */ +static unsigned int +MYSQLND_METHOD(mysqlnd_res, num_fields)(const MYSQLND_RES * const result TSRMLS_DC) +{ + return result->field_count; +} +/* }}} */ + + +/* {{{ mysqlnd_res::fetch_field */ +static const MYSQLND_FIELD * +MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res::fetch_field"); + do { + if (result->meta) { + /* + We optimize the result set, so we don't convert all the data from raw buffer format to + zval arrays during store. In the case someone doesn't read all the lines this will + save time. However, when a metadata call is done, we need to calculate max_length. + We don't have control whether max_length will be used, unfortunately. Otherwise we + could have been able to skip that step. + Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata, + then we can have max_length as dynamic property, which will be calculated during runtime and + not during mysqli_fetch_field() time. + */ + if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) { + DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request"); + /* we have to initialize the rest to get the updated max length */ + if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) { + break; + } + } + DBG_RETURN(result->meta->m->fetch_field(result->meta TSRMLS_CC)); + } + } while (0); + DBG_RETURN(NULL); +} +/* }}} */ + + +/* {{{ mysqlnd_res::fetch_field_direct */ +static const MYSQLND_FIELD * +MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res::fetch_field_direct"); + do { + if (result->meta) { + /* + We optimize the result set, so we don't convert all the data from raw buffer format to + zval arrays during store. In the case someone doesn't read all the lines this will + save time. However, when a metadata call is done, we need to calculate max_length. + We don't have control whether max_length will be used, unfortunately. Otherwise we + could have been able to skip that step. + Well, if the mysqli API switches from returning stdClass to class like mysqli_field_metadata, + then we can have max_length as dynamic property, which will be calculated during runtime and + not during mysqli_fetch_field_direct() time. + */ + if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) { + DBG_INF_FMT("We have decode the whole result set to be able to satisfy this meta request"); + /* we have to initialized the rest to get the updated max length */ + if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) { + break; + } + } + DBG_RETURN(result->meta->m->fetch_field_direct(result->meta, fieldnr TSRMLS_CC)); + } + } while (0); + + DBG_RETURN(NULL); +} +/* }}} */ + + +/* {{{ mysqlnd_res::fetch_field */ +static const MYSQLND_FIELD * +MYSQLND_METHOD(mysqlnd_res, fetch_fields)(MYSQLND_RES * const result TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res::fetch_fields"); + do { + if (result->meta) { + if (result->stored_data && (result->stored_data->initialized_rows < result->stored_data->row_count)) { + /* we have to initialize the rest to get the updated max length */ + if (PASS != result->m.initialize_result_set_rest(result TSRMLS_CC)) { + break; + } + } + DBG_RETURN(result->meta->m->fetch_fields(result->meta TSRMLS_CC)); + } + } while (0); + DBG_RETURN(NULL); +} +/* }}} */ + + + +/* {{{ mysqlnd_res::field_seek */ +static MYSQLND_FIELD_OFFSET +MYSQLND_METHOD(mysqlnd_res, field_seek)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET field_offset TSRMLS_DC) +{ + MYSQLND_FIELD_OFFSET return_value = 0; + if (result->meta) { + return_value = result->meta->current_field; + result->meta->current_field = field_offset; + } + return return_value; +} +/* }}} */ + + +/* {{{ mysqlnd_res::field_tell */ +static MYSQLND_FIELD_OFFSET +MYSQLND_METHOD(mysqlnd_res, field_tell)(const MYSQLND_RES * const result TSRMLS_DC) +{ + return result->meta? result->meta->m->field_tell(result->meta TSRMLS_CC) : 0; +} +/* }}} */ + + +/* {{{ mysqlnd_res::fetch_into */ +static void +MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, unsigned int flags, + zval *return_value, + enum_mysqlnd_extension extension TSRMLS_DC ZEND_FILE_LINE_DC) +{ + zend_bool fetched_anything; + + DBG_ENTER("mysqlnd_res::fetch_into"); + + if (!result->m.fetch_row) { + RETVAL_NULL(); + DBG_VOID_RETURN; + } + /* + Hint Zend how many elements we will have in the hash. Thus it won't + extend and rehash the hash constantly. + */ + mysqlnd_array_init(return_value, mysqlnd_num_fields(result) * 2); + if (FAIL == result->m.fetch_row(result, (void *)return_value, flags, &fetched_anything TSRMLS_CC)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading a row"); + RETVAL_FALSE; + } else if (fetched_anything == FALSE) { + zval_dtor(return_value); + switch (extension) { + case MYSQLND_MYSQLI: + RETVAL_NULL(); + break; + case MYSQLND_MYSQL: + RETVAL_FALSE; + break; + default:exit(0); + } + } + /* + return_value is IS_NULL for no more data and an array for data. Thus it's ok + to return here. + */ + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_res::fetch_row_c */ +static MYSQLND_ROW_C +MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result TSRMLS_DC) +{ + MYSQLND_ROW_C ret = NULL; + DBG_ENTER("mysqlnd_res::fetch_row_c"); + + if (result->m.fetch_row) { + if (result->m.fetch_row == result->m.fetch_row_normal_buffered) { + DBG_RETURN(mysqlnd_fetch_row_buffered_c(result TSRMLS_CC)); + } else if (result->m.fetch_row == result->m.fetch_row_normal_unbuffered) { + DBG_RETURN(mysqlnd_fetch_row_unbuffered_c(result TSRMLS_CC)); + } else { + php_error_docref(NULL TSRMLS_CC, E_ERROR, "result->m.fetch_row has invalid value. Report to the developers"); + } + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_res::fetch_all */ +static void +MYSQLND_METHOD(mysqlnd_res, fetch_all)(MYSQLND_RES * result, unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC) +{ + zval *row; + ulong i = 0; + MYSQLND_RES_BUFFERED *set = result->stored_data; + + DBG_ENTER("mysqlnd_res::fetch_all"); + + if ((!result->unbuf && !set)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "fetch_all can be used only with buffered sets"); + if (result->conn) { + SET_CLIENT_ERROR(*result->conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "fetch_all can be used only with buffered sets"); + } + RETVAL_NULL(); + DBG_VOID_RETURN; + } + + /* 4 is a magic value. The cast is safe, if larger then the array will be later extended - no big deal :) */ + mysqlnd_array_init(return_value, set? (unsigned int) set->row_count : 4); + + do { + MAKE_STD_ZVAL(row); + mysqlnd_fetch_into(result, flags, row, MYSQLND_MYSQLI); + if (Z_TYPE_P(row) != IS_ARRAY) { + zval_ptr_dtor(&row); + break; + } + add_index_zval(return_value, i++, row); + } while (1); + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_res::fetch_field_data */ +static void +MYSQLND_METHOD(mysqlnd_res, fetch_field_data)(MYSQLND_RES * result, unsigned int offset, zval *return_value TSRMLS_DC) +{ + zval row; + zval **entry; + unsigned int i = 0; + + DBG_ENTER("mysqlnd_res::fetch_field_data"); + DBG_INF_FMT("offset=%u", offset); + + if (!result->m.fetch_row) { + RETVAL_NULL(); + DBG_VOID_RETURN; + } + /* + Hint Zend how many elements we will have in the hash. Thus it won't + extend and rehash the hash constantly. + */ + INIT_PZVAL(&row); + mysqlnd_fetch_into(result, MYSQLND_FETCH_NUM, &row, MYSQLND_MYSQL); + if (Z_TYPE(row) != IS_ARRAY) { + zval_dtor(&row); + RETVAL_NULL(); + DBG_VOID_RETURN; + } + zend_hash_internal_pointer_reset(Z_ARRVAL(row)); + while (i++ < offset) { + zend_hash_move_forward(Z_ARRVAL(row)); + zend_hash_get_current_data(Z_ARRVAL(row), (void **)&entry); + } + + zend_hash_get_current_data(Z_ARRVAL(row), (void **)&entry); + + *return_value = **entry; + zval_copy_ctor(return_value); + Z_SET_REFCOUNT_P(return_value, 1); + zval_dtor(&row); + + DBG_VOID_RETURN; +} +/* }}} */ + + +MYSQLND_CLASS_METHODS_START(mysqlnd_res) + NULL, /* fetch_row */ + mysqlnd_fetch_row_buffered, + mysqlnd_fetch_row_unbuffered, + MYSQLND_METHOD(mysqlnd_res, use_result), + MYSQLND_METHOD(mysqlnd_res, store_result), + MYSQLND_METHOD(mysqlnd_res, fetch_into), + MYSQLND_METHOD(mysqlnd_res, fetch_row_c), + MYSQLND_METHOD(mysqlnd_res, fetch_all), + MYSQLND_METHOD(mysqlnd_res, fetch_field_data), + MYSQLND_METHOD(mysqlnd_res, num_rows), + MYSQLND_METHOD(mysqlnd_res, num_fields), + MYSQLND_METHOD(mysqlnd_res, skip_result), + MYSQLND_METHOD(mysqlnd_res, data_seek), + MYSQLND_METHOD(mysqlnd_res, field_seek), + MYSQLND_METHOD(mysqlnd_res, field_tell), + MYSQLND_METHOD(mysqlnd_res, fetch_field), + MYSQLND_METHOD(mysqlnd_res, fetch_field_direct), + MYSQLND_METHOD(mysqlnd_res, fetch_fields), + MYSQLND_METHOD(mysqlnd_res, read_result_metadata), + NULL, /* fetch_lengths */ + MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data), + MYSQLND_METHOD(mysqlnd_res, initialize_result_set_rest), + MYSQLND_METHOD(mysqlnd_res, free_result_buffers), + MYSQLND_METHOD(mysqlnd_res, free_result), + + mysqlnd_internal_free_result, /* free_result_internal */ + mysqlnd_internal_free_result_contents, /* free_result_contents */ + MYSQLND_METHOD(mysqlnd_res, free_buffered_data), + MYSQLND_METHOD(mysqlnd_res, unbuffered_free_last_data), + + NULL /* row_decoder */, + mysqlnd_result_meta_init +MYSQLND_CLASS_METHODS_END; + + +/* {{{ mysqlnd_result_init */ +PHPAPI MYSQLND_RES * +mysqlnd_result_init(unsigned int field_count, zend_bool persistent TSRMLS_DC) +{ + size_t alloc_size = sizeof(MYSQLND_RES) + mysqlnd_plugin_count() * sizeof(void *); + MYSQLND_RES *ret = mnd_pecalloc(1, alloc_size, persistent); + + DBG_ENTER("mysqlnd_result_init"); + + if (!ret) { + DBG_RETURN(NULL); + } + + ret->persistent = persistent; + ret->field_count = field_count; + ret->m = *mysqlnd_result_get_methods(); + + DBG_RETURN(ret); +} +/* }}} */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_result.h b/ext/mysqlnd/mysqlnd_result.h new file mode 100644 index 0000000..f821b91 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_result.h @@ -0,0 +1,39 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef MYSQLND_RESULT_H +#define MYSQLND_RESULT_H + +PHPAPI MYSQLND_RES * mysqlnd_result_init(unsigned int field_count, zend_bool persistent TSRMLS_DC); + +enum_func_status mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * stmt TSRMLS_DC); + +#endif /* MYSQLND_RESULT_H */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_result_meta.c b/ext/mysqlnd/mysqlnd_result_meta.c new file mode 100644 index 0000000..1d22e6c --- /dev/null +++ b/ext/mysqlnd/mysqlnd_result_meta.c @@ -0,0 +1,532 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_result.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_debug.h" +#include "ext/standard/basic_functions.h" + + +/* {{{ php_mysqlnd_free_field_metadata */ +static void +php_mysqlnd_free_field_metadata(MYSQLND_FIELD *meta, zend_bool persistent TSRMLS_DC) +{ + if (meta) { + if (meta->root) { + mnd_pefree(meta->root, persistent); + meta->root = NULL; + } + if (meta->def) { + mnd_pefree(meta->def, persistent); + meta->def = NULL; + } + } +} +/* }}} */ + + +/* {{{ mysqlnd_handle_numeric */ +/* + The following code is stolen from ZE - HANDLE_NUMERIC() macro from zend_hash.c + and modified for the needs of mysqlnd. +*/ +static zend_bool +mysqlnd_is_key_numeric(const char * key, size_t length, long *idx) +{ + register const char * tmp = key; + + if (*tmp=='-') { + tmp++; + } + if ((*tmp>='0' && *tmp<='9')) { + do { /* possibly a numeric index */ + const char *end=key+length-1; + + if (*tmp++=='0' && length>2) { /* don't accept numbers with leading zeros */ + break; + } + while (tmp<end) { + if (!(*tmp>='0' && *tmp<='9')) { + break; + } + tmp++; + } + if (tmp==end && *tmp=='\0') { /* a numeric index */ + if (*key=='-') { + *idx = strtol(key, NULL, 10); + if (*idx!=LONG_MIN) { + return TRUE; + } + } else { + *idx = strtol(key, NULL, 10); + if (*idx!=LONG_MAX) { + return TRUE; + } + } + } + } while (0); + } + return FALSE; +} +/* }}} */ + + +#if MYSQLND_UNICODE +/* {{{ mysqlnd_unicode_is_key_numeric */ +static zend_bool +mysqlnd_unicode_is_key_numeric(UChar *key, size_t length, long *idx) +{ + register UChar * tmp=key; + + if (*tmp==0x2D /*'-'*/) { + tmp++; + } + if ((*tmp>=0x30 /*'0'*/ && *tmp<=0x39 /*'9'*/)) { /* possibly a numeric index */ + do { + UChar *end=key+length-1; + + if (*tmp++==0x30 && length>2) { /* don't accept numbers with leading zeros */ + break; + } + while (tmp<end) { + if (!(*tmp>=0x30 /*'0'*/ && *tmp<=0x39 /*'9'*/)) { + break; + } + tmp++; + } + if (tmp==end && *tmp==0) { /* a numeric index */ + if (*key==0x2D /*'-'*/) { + *idx = zend_u_strtol(key, NULL, 10); + if (*idx!=LONG_MIN) { + return TRUE; + } + } else { + *idx = zend_u_strtol(key, NULL, 10); + if (*idx!=LONG_MAX) { + return TRUE; + } + } + } + } while (0); + } + return FALSE; +} +/* }}} */ +#endif + + +/* {{{ mysqlnd_res_meta::read_metadata */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_res_meta, read_metadata)(MYSQLND_RES_METADATA * const meta, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + unsigned int i = 0; + MYSQLND_PACKET_RES_FIELD * field_packet; +#if MYSQLND_UNICODE + UChar *ustr; + int ulen; +#endif + + DBG_ENTER("mysqlnd_res_meta::read_metadata"); + + field_packet = conn->protocol->m.get_result_field_packet(conn->protocol, FALSE TSRMLS_CC); + if (!field_packet) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(FAIL); + } + field_packet->persistent_alloc = meta->persistent; + for (;i < meta->field_count; i++) { + long idx; + + if (meta->fields[i].root) { + /* We re-read metadata for PS */ + mnd_pefree(meta->fields[i].root, meta->persistent); + meta->fields[i].root = NULL; + } + + field_packet->metadata = &(meta->fields[i]); + if (FAIL == PACKET_READ(field_packet, conn)) { + PACKET_FREE(field_packet); + DBG_RETURN(FAIL); + } + if (field_packet->error_info.error_no) { + COPY_CLIENT_ERROR(*conn->error_info, field_packet->error_info); + /* Return back from CONN_QUERY_SENT */ + PACKET_FREE(field_packet); + DBG_RETURN(FAIL); + } + + if (field_packet->stupid_list_fields_eof == TRUE) { + meta->field_count = i; + break; + } + + if (mysqlnd_ps_fetch_functions[meta->fields[i].type].func == NULL) { + DBG_ERR_FMT("Unknown type %u sent by the server. Please send a report to the developers", + meta->fields[i].type); + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Unknown type %u sent by the server. " + "Please send a report to the developers", + meta->fields[i].type); + PACKET_FREE(field_packet); + DBG_RETURN(FAIL); + } + if (meta->fields[i].type == MYSQL_TYPE_BIT) { + size_t field_len; + DBG_INF("BIT"); + ++meta->bit_fields_count; + /* .length is in bits */ + field_len = meta->fields[i].length / 8; + /* + If there is rest, add one byte : + 8 bits = 1 byte but 9 bits = 2 bytes + */ + if (meta->fields[i].length % 8) { + ++field_len; + } + switch (field_len) { + case 8: + case 7: + case 6: + case 5: + meta->bit_fields_total_len += 20;/* 21 digis, no sign*/ + break; + case 4: + meta->bit_fields_total_len += 10;/* 2 000 000 000*/ + break; + case 3: + meta->bit_fields_total_len += 8;/* 12 000 000*/ + break; + case 2: + meta->bit_fields_total_len += 5;/* 32 500 */ + break; + case 1: + meta->bit_fields_total_len += 3;/* 120 */ + break; + } + } + +#if MYSQLND_UNICODE + zend_string_to_unicode(UG(utf8_conv), &ustr, &ulen, + meta->fields[i].name, + meta->fields[i].name_length TSRMLS_CC); + if ((meta->zend_hash_keys[i].is_numeric = + mysqlnd_unicode_is_key_numeric(ustr, ulen + 1, &idx))) + { + meta->zend_hash_keys[i].key = idx; + mnd_efree(ustr); + } else { + meta->zend_hash_keys[i].ustr.u = ustr; + meta->zend_hash_keys[i].ulen = ulen; + meta->zend_hash_keys[i].key = zend_u_get_hash_value(IS_UNICODE, ZSTR(ustr), ulen + 1); + } +#else + /* For BC we have to check whether the key is numeric and use it like this */ + if ((meta->zend_hash_keys[i].is_numeric = + mysqlnd_is_key_numeric(field_packet->metadata->name, + field_packet->metadata->name_length + 1, + &idx))) + { + meta->zend_hash_keys[i].key = idx; + } else { + meta->zend_hash_keys[i].key = + zend_get_hash_value(field_packet->metadata->name, + field_packet->metadata->name_length + 1); + } +#endif + } + PACKET_FREE(field_packet); + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_res_meta::free */ +static void +MYSQLND_METHOD(mysqlnd_res_meta, free)(MYSQLND_RES_METADATA * meta TSRMLS_DC) +{ + int i; + MYSQLND_FIELD *fields; + DBG_ENTER("mysqlnd_res_meta::free"); + DBG_INF_FMT("persistent=%u", meta->persistent); + + if ((fields = meta->fields)) { + DBG_INF("Freeing fields metadata"); + i = meta->field_count; + while (i--) { + php_mysqlnd_free_field_metadata(fields++, meta->persistent TSRMLS_CC); + } + mnd_pefree(meta->fields, meta->persistent); + meta->fields = NULL; + } + + if (meta->zend_hash_keys) { + DBG_INF("Freeing zend_hash_keys"); +#if MYSQLND_UNICODE + if (UG(unicode)) { + for (i = 0; i < meta->field_count; i++) { + if (meta->zend_hash_keys[i].ustr.v) { + mnd_pefree(meta->zend_hash_keys[i].ustr.v, meta->persistent); + } + } + } +#endif + mnd_pefree(meta->zend_hash_keys, meta->persistent); + meta->zend_hash_keys = NULL; + } + DBG_INF("Freeing metadata structure"); + mnd_pefree(meta, meta->persistent); + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_res::clone_metadata */ +static MYSQLND_RES_METADATA * +MYSQLND_METHOD(mysqlnd_res_meta, clone_metadata)(const MYSQLND_RES_METADATA * const meta, zend_bool persistent TSRMLS_DC) +{ + unsigned int i; + /* +1 is to have empty marker at the end */ + MYSQLND_RES_METADATA * new_meta = NULL; + MYSQLND_FIELD * new_fields; + MYSQLND_FIELD * orig_fields = meta->fields; + size_t len = meta->field_count * sizeof(struct mysqlnd_field_hash_key); + + DBG_ENTER("mysqlnd_res_meta::clone_metadata"); + DBG_INF_FMT("persistent=%u", persistent); + + new_meta = mnd_pecalloc(1, sizeof(MYSQLND_RES_METADATA), persistent); + if (!new_meta) { + goto oom; + } + new_meta->persistent = persistent; + new_meta->m = meta->m; + + new_fields = mnd_pecalloc(meta->field_count + 1, sizeof(MYSQLND_FIELD), persistent); + if (!new_fields) { + goto oom; + } + + new_meta->zend_hash_keys = mnd_pemalloc(len, persistent); + if (!new_meta->zend_hash_keys) { + goto oom; + } + memcpy(new_meta->zend_hash_keys, meta->zend_hash_keys, len); + + /* + This will copy also the strings and the root, which we will have + to adjust in the loop + */ + memcpy(new_fields, orig_fields, (meta->field_count) * sizeof(MYSQLND_FIELD)); + for (i = 0; i < meta->field_count; i++) { + /* First copy the root, then field by field adjust the pointers */ + new_fields[i].root = mnd_pemalloc(orig_fields[i].root_len, persistent); + if (!new_fields[i].root) { + goto oom; + } + memcpy(new_fields[i].root, orig_fields[i].root, new_fields[i].root_len); + + if (orig_fields[i].name && orig_fields[i].name != mysqlnd_empty_string) { + new_fields[i].name = new_fields[i].root + + (orig_fields[i].name - orig_fields[i].root); + } + if (orig_fields[i].org_name && orig_fields[i].org_name != mysqlnd_empty_string) { + new_fields[i].org_name = new_fields[i].root + + (orig_fields[i].org_name - orig_fields[i].root); + } + if (orig_fields[i].table && orig_fields[i].table != mysqlnd_empty_string) { + new_fields[i].table = new_fields[i].root + + (orig_fields[i].table - orig_fields[i].root); + } + if (orig_fields[i].org_table && orig_fields[i].org_table != mysqlnd_empty_string) { + new_fields[i].org_table = new_fields[i].root + + (orig_fields[i].org_table - orig_fields[i].root); + } + if (orig_fields[i].db && orig_fields[i].db != mysqlnd_empty_string) { + new_fields[i].db = new_fields[i].root + (orig_fields[i].db - orig_fields[i].root); + } + if (orig_fields[i].catalog && orig_fields[i].catalog != mysqlnd_empty_string) { + new_fields[i].catalog = new_fields[i].root + (orig_fields[i].catalog - orig_fields[i].root); + } + /* def is not on the root, if allocated at all */ + if (orig_fields[i].def) { + new_fields[i].def = mnd_pemalloc(orig_fields[i].def_length + 1, persistent); + if (!new_fields[i].def) { + goto oom; + } + /* copy the trailing \0 too */ + memcpy(new_fields[i].def, orig_fields[i].def, orig_fields[i].def_length + 1); + } +#if MYSQLND_UNICODE + if (new_meta->zend_hash_keys[i].ustr.u) { + new_meta->zend_hash_keys[i].ustr.u = + eustrndup(new_meta->zend_hash_keys[i].ustr.u, new_meta->zend_hash_keys[i].ulen); + if (!new_meta->zend_hash_keys[i].ustr.u) { + goto oom; + } + } +#endif + } + new_meta->current_field = 0; + new_meta->field_count = meta->field_count; + + new_meta->fields = new_fields; + + DBG_RETURN(new_meta); +oom: + if (new_meta) { + new_meta->m->free_metadata(new_meta TSRMLS_CC); + new_meta = NULL; + } + DBG_RETURN(NULL); +} +/* }}} */ + +/* {{{ mysqlnd_res_meta::fetch_field */ +static const MYSQLND_FIELD * +MYSQLND_METHOD(mysqlnd_res_meta, fetch_field)(MYSQLND_RES_METADATA * const meta TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res_meta::fetch_field"); + if (meta->current_field >= meta->field_count) { + DBG_INF("no more fields"); + DBG_RETURN(NULL); + } + DBG_INF_FMT("name=%s max_length=%u", + meta->fields[meta->current_field].name? meta->fields[meta->current_field].name:"", + meta->fields[meta->current_field].max_length); + DBG_RETURN(&meta->fields[meta->current_field++]); +} +/* }}} */ + + +/* {{{ mysqlnd_res_meta::fetch_field_direct */ +static const MYSQLND_FIELD * +MYSQLND_METHOD(mysqlnd_res_meta, fetch_field_direct)(const MYSQLND_RES_METADATA * const meta, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res_meta::fetch_field_direct"); + DBG_INF_FMT("fieldnr=%u", fieldnr); + DBG_INF_FMT("name=%s max_length=%u", + meta->fields[meta->current_field].name? meta->fields[meta->current_field].name:"", + meta->fields[meta->current_field].max_length); + DBG_RETURN(&meta->fields[fieldnr]); +} +/* }}} */ + + +/* {{{ mysqlnd_res_meta::fetch_fields */ +static const MYSQLND_FIELD * +MYSQLND_METHOD(mysqlnd_res_meta, fetch_fields)(MYSQLND_RES_METADATA * const meta TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res_meta::fetch_fields"); + DBG_RETURN(meta->fields); +} +/* }}} */ + + +/* {{{ mysqlnd_res_meta::field_tell */ +static MYSQLND_FIELD_OFFSET +MYSQLND_METHOD(mysqlnd_res_meta, field_tell)(const MYSQLND_RES_METADATA * const meta TSRMLS_DC) +{ + return meta->current_field; +} +/* }}} */ + + +static +MYSQLND_CLASS_METHODS_START(mysqlnd_res_meta) + MYSQLND_METHOD(mysqlnd_res_meta, fetch_field), + MYSQLND_METHOD(mysqlnd_res_meta, fetch_field_direct), + MYSQLND_METHOD(mysqlnd_res_meta, fetch_fields), + MYSQLND_METHOD(mysqlnd_res_meta, field_tell), + MYSQLND_METHOD(mysqlnd_res_meta, read_metadata), + MYSQLND_METHOD(mysqlnd_res_meta, clone_metadata), + MYSQLND_METHOD(mysqlnd_res_meta, free), +MYSQLND_CLASS_METHODS_END; + + +/* {{{ mysqlnd_result_meta_init */ +PHPAPI MYSQLND_RES_METADATA * +mysqlnd_result_meta_init(unsigned int field_count, zend_bool persistent TSRMLS_DC) +{ + size_t alloc_size = sizeof(MYSQLND_RES_METADATA) + mysqlnd_plugin_count() * sizeof(void *); + MYSQLND_RES_METADATA *ret = mnd_pecalloc(1, alloc_size, persistent); + DBG_ENTER("mysqlnd_result_meta_init"); + DBG_INF_FMT("persistent=%u", persistent); + + do { + if (!ret) { + break; + } + ret->m = & mysqlnd_mysqlnd_res_meta_methods; + + ret->persistent = persistent; + ret->field_count = field_count; + /* +1 is to have empty marker at the end */ + ret->fields = mnd_pecalloc(field_count + 1, sizeof(MYSQLND_FIELD), ret->persistent); + ret->zend_hash_keys = mnd_pecalloc(field_count, sizeof(struct mysqlnd_field_hash_key), ret->persistent); + if (!ret->fields || !ret->zend_hash_keys) { + break; + } + DBG_INF_FMT("meta=%p", ret); + DBG_RETURN(ret); + } while (0); + if (ret) { + ret->m->free_metadata(ret TSRMLS_CC); + } + DBG_RETURN(NULL); +} +/* }}} */ + + +/* {{{ mysqlnd_res_meta_get_methods */ +PHPAPI struct st_mysqlnd_res_meta_methods * +mysqlnd_result_metadata_get_methods() +{ + return &mysqlnd_mysqlnd_res_meta_methods; +} +/* }}} */ + + +/* {{{ _mysqlnd_plugin_get_plugin_result_metadata_data */ +PHPAPI void ** +_mysqlnd_plugin_get_plugin_result_metadata_data(const MYSQLND_RES_METADATA * meta, unsigned int plugin_id TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_plugin_get_plugin_result_metadata_data"); + DBG_INF_FMT("plugin_id=%u", plugin_id); + if (!meta || plugin_id >= mysqlnd_plugin_count()) { + return NULL; + } + DBG_RETURN((void *)((char *)meta + sizeof(MYSQLND_RES_METADATA) + plugin_id * sizeof(void *))); +} +/* }}} */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_result_meta.h b/ext/mysqlnd/mysqlnd_result_meta.h new file mode 100644 index 0000000..8fa9989 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_result_meta.h @@ -0,0 +1,39 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef MYSQLND_RESULT_META_H +#define MYSQLND_RESULT_META_H + +PHPAPI MYSQLND_RES_METADATA * mysqlnd_result_meta_init(unsigned int field_count, zend_bool persistent TSRMLS_DC); +PHPAPI struct st_mysqlnd_res_meta_methods * mysqlnd_result_metadata_get_methods(); +PHPAPI void ** _mysqlnd_plugin_get_plugin_result_metadata_data(const MYSQLND_RES_METADATA * meta, unsigned int plugin_id TSRMLS_DC); + +#endif /* MYSQLND_RESULT_META_H */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_reverse_api.c b/ext/mysqlnd/mysqlnd_reverse_api.c new file mode 100644 index 0000000..daa43ec --- /dev/null +++ b/ext/mysqlnd/mysqlnd_reverse_api.c @@ -0,0 +1,100 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id: mysqlnd.c 317989 2011-10-10 20:49:28Z andrey $ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_debug.h" +#include "mysqlnd_reverse_api.h" + + +static HashTable mysqlnd_api_ext_ht; + + +/* {{{ mysqlnd_reverse_api_init */ +PHPAPI void +mysqlnd_reverse_api_init(TSRMLS_D) +{ + zend_hash_init(&mysqlnd_api_ext_ht, 3, NULL, NULL, 1); +} +/* }}} */ + + +/* {{{ mysqlnd_reverse_api_end */ +PHPAPI void +mysqlnd_reverse_api_end(TSRMLS_D) +{ + zend_hash_destroy(&mysqlnd_api_ext_ht); +} +/* }}} */ + + +/* {{{ myslqnd_get_api_extensions */ +PHPAPI HashTable * +mysqlnd_reverse_api_get_api_list(TSRMLS_D) +{ + return &mysqlnd_api_ext_ht; +} +/* }}} */ + + +/* {{{ mysqlnd_reverse_api_register_api */ +PHPAPI void +mysqlnd_reverse_api_register_api(MYSQLND_REVERSE_API * apiext TSRMLS_DC) +{ + zend_hash_add(&mysqlnd_api_ext_ht, apiext->module->name, strlen(apiext->module->name) + 1, &apiext, + sizeof(MYSQLND_REVERSE_API), NULL); +} +/* }}} */ + + +/* {{{ zval_to_mysqlnd */ +PHPAPI MYSQLND * +zval_to_mysqlnd(zval * zv TSRMLS_DC) +{ + MYSQLND * retval; + MYSQLND_REVERSE_API ** elem; + + for (zend_hash_internal_pointer_reset(&mysqlnd_api_ext_ht); + zend_hash_get_current_data(&mysqlnd_api_ext_ht, (void **)&elem) == SUCCESS; + zend_hash_move_forward(&mysqlnd_api_ext_ht)) + { + if ((*elem)->conversion_cb) { + retval = (*elem)->conversion_cb(zv TSRMLS_CC); + if (retval) { + return retval; + } + } + } + + return NULL; +} +/* }}} */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_reverse_api.h b/ext/mysqlnd/mysqlnd_reverse_api.h new file mode 100644 index 0000000..dd23555 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_reverse_api.h @@ -0,0 +1,50 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ +/* $Id: mysqlnd.h 318051 2011-10-12 16:18:02Z andrey $ */ + +#ifndef MYSQLND_REVERSE_API_H +#define MYSQLND_REVERSE_API_H +typedef struct st_mysqlnd_reverse_api +{ + zend_module_entry * module; + MYSQLND *(*conversion_cb)(zval * zv TSRMLS_DC); +} MYSQLND_REVERSE_API; + + +PHPAPI void mysqlnd_reverse_api_init(TSRMLS_D); +PHPAPI void mysqlnd_reverse_api_end(TSRMLS_D); + +PHPAPI HashTable * mysqlnd_reverse_api_get_api_list(TSRMLS_D); + +PHPAPI void mysqlnd_reverse_api_register_api(MYSQLND_REVERSE_API * apiext TSRMLS_DC); + +PHPAPI MYSQLND * zval_to_mysqlnd(zval * zv TSRMLS_DC); + +#endif /* MYSQLND_REVERSE_API_H */ + +/* + * 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 + */ + diff --git a/ext/mysqlnd/mysqlnd_statistics.c b/ext/mysqlnd/mysqlnd_statistics.c new file mode 100644 index 0000000..271cb29 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_statistics.c @@ -0,0 +1,317 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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_priv.h" +#include "mysqlnd_statistics.h" +#include "mysqlnd_debug.h" + + +/* {{{ mysqlnd_stats_values_names + */ + +const MYSQLND_STRING mysqlnd_stats_values_names[STAT_LAST] = +{ + { MYSQLND_STR_W_LEN("bytes_sent") }, + { MYSQLND_STR_W_LEN("bytes_received") }, + { MYSQLND_STR_W_LEN("packets_sent") }, + { MYSQLND_STR_W_LEN("packets_received") }, + { MYSQLND_STR_W_LEN("protocol_overhead_in") }, + { MYSQLND_STR_W_LEN("protocol_overhead_out") }, + { MYSQLND_STR_W_LEN("bytes_received_ok_packet") }, + { MYSQLND_STR_W_LEN("bytes_received_eof_packet") }, + { MYSQLND_STR_W_LEN("bytes_received_rset_header_packet") }, + { MYSQLND_STR_W_LEN("bytes_received_rset_field_meta_packet") }, + { MYSQLND_STR_W_LEN("bytes_received_rset_row_packet") }, + { MYSQLND_STR_W_LEN("bytes_received_prepare_response_packet") }, + { MYSQLND_STR_W_LEN("bytes_received_change_user_packet") }, + { MYSQLND_STR_W_LEN("packets_sent_command") }, + { MYSQLND_STR_W_LEN("packets_received_ok") }, + { MYSQLND_STR_W_LEN("packets_received_eof") }, + { MYSQLND_STR_W_LEN("packets_received_rset_header") }, + { MYSQLND_STR_W_LEN("packets_received_rset_field_meta") }, + { MYSQLND_STR_W_LEN("packets_received_rset_row") }, + { MYSQLND_STR_W_LEN("packets_received_prepare_response") }, + { MYSQLND_STR_W_LEN("packets_received_change_user") }, + { MYSQLND_STR_W_LEN("result_set_queries") }, + { MYSQLND_STR_W_LEN("non_result_set_queries") }, + { MYSQLND_STR_W_LEN("no_index_used") }, + { MYSQLND_STR_W_LEN("bad_index_used") }, + { MYSQLND_STR_W_LEN("slow_queries") }, + { MYSQLND_STR_W_LEN("buffered_sets") }, + { MYSQLND_STR_W_LEN("unbuffered_sets") }, + { MYSQLND_STR_W_LEN("ps_buffered_sets") }, + { MYSQLND_STR_W_LEN("ps_unbuffered_sets") }, + { MYSQLND_STR_W_LEN("flushed_normal_sets") }, + { MYSQLND_STR_W_LEN("flushed_ps_sets") }, + { MYSQLND_STR_W_LEN("ps_prepared_never_executed") }, + { MYSQLND_STR_W_LEN("ps_prepared_once_executed") }, + { MYSQLND_STR_W_LEN("rows_fetched_from_server_normal") }, + { MYSQLND_STR_W_LEN("rows_fetched_from_server_ps") }, + { MYSQLND_STR_W_LEN("rows_buffered_from_client_normal") }, + { MYSQLND_STR_W_LEN("rows_buffered_from_client_ps") }, + { MYSQLND_STR_W_LEN("rows_fetched_from_client_normal_buffered") }, + { MYSQLND_STR_W_LEN("rows_fetched_from_client_normal_unbuffered") }, + { MYSQLND_STR_W_LEN("rows_fetched_from_client_ps_buffered") }, + { MYSQLND_STR_W_LEN("rows_fetched_from_client_ps_unbuffered") }, + { MYSQLND_STR_W_LEN("rows_fetched_from_client_ps_cursor") }, + { MYSQLND_STR_W_LEN("rows_affected_normal") }, + { MYSQLND_STR_W_LEN("rows_affected_ps") }, + { MYSQLND_STR_W_LEN("rows_skipped_normal") }, + { MYSQLND_STR_W_LEN("rows_skipped_ps") }, + { MYSQLND_STR_W_LEN("copy_on_write_saved") }, + { MYSQLND_STR_W_LEN("copy_on_write_performed") }, + { MYSQLND_STR_W_LEN("command_buffer_too_small") }, + { MYSQLND_STR_W_LEN("connect_success") }, + { MYSQLND_STR_W_LEN("connect_failure") }, + { MYSQLND_STR_W_LEN("connection_reused") }, + { MYSQLND_STR_W_LEN("reconnect") }, + { MYSQLND_STR_W_LEN("pconnect_success") }, + { MYSQLND_STR_W_LEN("active_connections") }, + { MYSQLND_STR_W_LEN("active_persistent_connections") }, + { MYSQLND_STR_W_LEN("explicit_close") }, + { MYSQLND_STR_W_LEN("implicit_close") }, + { MYSQLND_STR_W_LEN("disconnect_close") }, + { MYSQLND_STR_W_LEN("in_middle_of_command_close") }, + { MYSQLND_STR_W_LEN("explicit_free_result") }, + { MYSQLND_STR_W_LEN("implicit_free_result") }, + { MYSQLND_STR_W_LEN("explicit_stmt_close") }, + { MYSQLND_STR_W_LEN("implicit_stmt_close") }, + { MYSQLND_STR_W_LEN("mem_emalloc_count") }, + { MYSQLND_STR_W_LEN("mem_emalloc_amount") }, + { MYSQLND_STR_W_LEN("mem_ecalloc_count") }, + { MYSQLND_STR_W_LEN("mem_ecalloc_amount") }, + { MYSQLND_STR_W_LEN("mem_erealloc_count") }, + { MYSQLND_STR_W_LEN("mem_erealloc_amount") }, + { MYSQLND_STR_W_LEN("mem_efree_count") }, + { MYSQLND_STR_W_LEN("mem_efree_amount") }, + { MYSQLND_STR_W_LEN("mem_malloc_count") }, + { MYSQLND_STR_W_LEN("mem_malloc_amount") }, + { MYSQLND_STR_W_LEN("mem_calloc_count") }, + { MYSQLND_STR_W_LEN("mem_calloc_amount") }, + { MYSQLND_STR_W_LEN("mem_realloc_count") }, + { MYSQLND_STR_W_LEN("mem_realloc_amount") }, + { MYSQLND_STR_W_LEN("mem_free_count") }, + { MYSQLND_STR_W_LEN("mem_free_amount") }, + { MYSQLND_STR_W_LEN("mem_estrndup_count") }, + { MYSQLND_STR_W_LEN("mem_strndup_count") }, + { MYSQLND_STR_W_LEN("mem_estndup_count") }, + { MYSQLND_STR_W_LEN("mem_strdup_count") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_null") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_bit") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_tinyint") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_short") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_int24") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_int") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_bigint") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_decimal") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_float") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_double") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_date") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_year") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_time") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_datetime") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_timestamp") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_string") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_blob") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_enum") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_set") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_geometry") }, + { MYSQLND_STR_W_LEN("proto_text_fetched_other") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_null") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_bit") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_tinyint") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_short") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_int24") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_int") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_bigint") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_decimal") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_float") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_double") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_date") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_year") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_time") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_datetime") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_timestamp") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_string") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_blob") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_enum") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_set") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_geometry") }, + { MYSQLND_STR_W_LEN("proto_binary_fetched_other") }, + { MYSQLND_STR_W_LEN("init_command_executed_count") }, + { MYSQLND_STR_W_LEN("init_command_failed_count") }, + { MYSQLND_STR_W_LEN("com_quit") }, + { MYSQLND_STR_W_LEN("com_init_db") }, + { MYSQLND_STR_W_LEN("com_query") }, + { MYSQLND_STR_W_LEN("com_field_list") }, + { MYSQLND_STR_W_LEN("com_create_db") }, + { MYSQLND_STR_W_LEN("com_drop_db") }, + { MYSQLND_STR_W_LEN("com_refresh") }, + { MYSQLND_STR_W_LEN("com_shutdown") }, + { MYSQLND_STR_W_LEN("com_statistics") }, + { MYSQLND_STR_W_LEN("com_process_info") }, + { MYSQLND_STR_W_LEN("com_connect") }, + { MYSQLND_STR_W_LEN("com_process_kill") }, + { MYSQLND_STR_W_LEN("com_debug") }, + { MYSQLND_STR_W_LEN("com_ping") }, + { MYSQLND_STR_W_LEN("com_time") }, + { MYSQLND_STR_W_LEN("com_delayed_insert") }, + { MYSQLND_STR_W_LEN("com_change_user") }, + { MYSQLND_STR_W_LEN("com_binlog_dump") }, + { MYSQLND_STR_W_LEN("com_table_dump") }, + { MYSQLND_STR_W_LEN("com_connect_out") }, + { MYSQLND_STR_W_LEN("com_register_slave") }, + { MYSQLND_STR_W_LEN("com_stmt_prepare") }, + { MYSQLND_STR_W_LEN("com_stmt_execute") }, + { MYSQLND_STR_W_LEN("com_stmt_send_long_data") }, + { MYSQLND_STR_W_LEN("com_stmt_close") }, + { MYSQLND_STR_W_LEN("com_stmt_reset") }, + { MYSQLND_STR_W_LEN("com_stmt_set_option") }, + { MYSQLND_STR_W_LEN("com_stmt_fetch") }, + { MYSQLND_STR_W_LEN("com_deamon") }, + { MYSQLND_STR_W_LEN("bytes_received_real_data_normal") }, + { MYSQLND_STR_W_LEN("bytes_received_real_data_ps") } +}; +/* }}} */ + + +/* {{{ mysqlnd_fill_stats_hash */ +PHPAPI void +mysqlnd_fill_stats_hash(const MYSQLND_STATS * const stats, const MYSQLND_STRING * names, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC) +{ + unsigned int i; + + mysqlnd_array_init(return_value, stats->count); + for (i = 0; i < stats->count; i++) { +#if MYSQLND_UNICODE + UChar *ustr, *tstr; + int ulen, tlen; +#endif + char tmp[25]; + + sprintf((char *)&tmp, MYSQLND_LLU_SPEC, stats->values[i]); +#if MYSQLND_UNICODE + zend_string_to_unicode(UG(utf8_conv), &ustr, &ulen, names[i].s, names[i].l + 1 TSRMLS_CC); + zend_string_to_unicode(UG(utf8_conv), &tstr, &tlen, tmp, strlen(tmp) + 1 TSRMLS_CC); + add_u_assoc_unicode_ex(return_value, IS_UNICODE, ZSTR(ustr), ulen, tstr, 1); + efree(ustr); + efree(tstr); +#else + add_assoc_string_ex(return_value, names[i].s, names[i].l + 1, tmp, 1); +#endif + } +} +/* }}} */ + + +/* {{{ _mysqlnd_get_client_stats */ +PHPAPI void +_mysqlnd_get_client_stats(zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC) +{ + MYSQLND_STATS stats, *stats_ptr = mysqlnd_global_stats; + DBG_ENTER("_mysqlnd_get_client_stats"); + if (!stats_ptr) { + memset(&stats, 0, sizeof(stats)); + stats_ptr = &stats; + } + mysqlnd_fill_stats_hash(stats_ptr, mysqlnd_stats_values_names, return_value TSRMLS_CC ZEND_FILE_LINE_CC); + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_stats_init */ +PHPAPI void +mysqlnd_stats_init(MYSQLND_STATS ** stats, size_t statistic_count) +{ + *stats = calloc(1, sizeof(MYSQLND_STATS)); + if (*stats == NULL) { + return; + } + (*stats)->values = calloc(statistic_count, sizeof(uint64_t)); + (*stats)->triggers = calloc(statistic_count, sizeof(mysqlnd_stat_trigger)); + (*stats)->in_trigger = FALSE; + (*stats)->count = statistic_count; +#ifdef ZTS + (*stats)->LOCK_access = tsrm_mutex_alloc(); +#endif +} +/* }}} */ + + +/* {{{ mysqlnd_stats_end */ +PHPAPI void +mysqlnd_stats_end(MYSQLND_STATS * stats) +{ +#ifdef ZTS + tsrm_mutex_free(stats->LOCK_access); +#endif + free(stats->triggers); + free(stats->values); + /* mnd_free will reference LOCK_access and crash...*/ + free(stats); +} +/* }}} */ + + +/* {{{ mysqlnd_stats_set_trigger */ +PHPAPI mysqlnd_stat_trigger +mysqlnd_stats_set_trigger(MYSQLND_STATS * const stats, enum_mysqlnd_collected_stats statistic, mysqlnd_stat_trigger trigger TSRMLS_DC) +{ + mysqlnd_stat_trigger ret = NULL; + DBG_ENTER("mysqlnd_stats_set_trigger"); + if (stats) { + MYSQLND_STATS_LOCK(stats); + ret = stats->triggers[statistic]; + stats->triggers[statistic] = trigger; + MYSQLND_STATS_UNLOCK(stats); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stats_set_handler */ +PHPAPI mysqlnd_stat_trigger +mysqlnd_stats_reset_triggers(MYSQLND_STATS * const stats TSRMLS_DC) +{ + mysqlnd_stat_trigger ret = NULL; + DBG_ENTER("mysqlnd_stats_reset_trigger"); + if (stats) { + MYSQLND_STATS_LOCK(stats); + memset(stats->triggers, 0, stats->count * sizeof(mysqlnd_stat_trigger)); + MYSQLND_STATS_UNLOCK(stats); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_statistics.h b/ext/mysqlnd/mysqlnd_statistics.h new file mode 100644 index 0000000..d1fd03b --- /dev/null +++ b/ext/mysqlnd/mysqlnd_statistics.h @@ -0,0 +1,177 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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$ */ + +#ifndef MYSQLND_STATISTICS_H +#define MYSQLND_STATISTICS_H + + +PHPAPI extern MYSQLND_STATS * mysqlnd_global_stats; + +extern const MYSQLND_STRING mysqlnd_stats_values_names[]; + +#ifdef ZTS +#define MYSQLND_STATS_LOCK(stats) tsrm_mutex_lock((stats)->LOCK_access) +#define MYSQLND_STATS_UNLOCK(stats) tsrm_mutex_unlock((stats)->LOCK_access) +#else +#define MYSQLND_STATS_LOCK(stats) +#define MYSQLND_STATS_UNLOCK(stats) +#endif + +#ifndef MYSQLND_CORE_STATISTICS_TRIGGERS_DISABLED +#define MYSQLND_STAT_CALL_TRIGGER(s_array, statistic, val) \ + if ((s_array)->triggers[(statistic)] && (s_array)->in_trigger == FALSE) { \ + (s_array)->in_trigger = TRUE; \ + MYSQLND_STATS_UNLOCK((s_array)); \ + \ + (s_array)->triggers[(statistic)]((s_array), (statistic), (val) TSRMLS_CC); \ + \ + MYSQLND_STATS_LOCK((s_array)); \ + (s_array)->in_trigger = FALSE; \ + } +#else +#define MYSQLND_STAT_CALL_TRIGGER(s_array, statistic, val) +#endif /* MYSQLND_CORE_STATISTICS_TRIGGERS_DISABLED */ + +#define MYSQLND_UPDATE_VALUE_AND_CALL_TRIGGER(stats, statistic, value) \ + { \ + MYSQLND_STATS_LOCK(stats); \ + (stats)->values[(statistic)] += (value); \ + MYSQLND_STAT_CALL_TRIGGER((stats), (statistic), (value)); \ + MYSQLND_STATS_UNLOCK(_p_s); \ + } + +#define MYSQLND_DEC_STATISTIC(enabler, stats, statistic) \ + { \ + enum_mysqlnd_collected_stats _s = (statistic);\ + MYSQLND_STATS * _p_s = (MYSQLND_STATS *) (stats); \ + if ((enabler) && _p_s && _s != _p_s->count) { \ + MYSQLND_UPDATE_VALUE_AND_CALL_TRIGGER(_p_s, _s, -1); \ + }\ + } + +#define MYSQLND_INC_STATISTIC(enabler, stats, statistic) \ + { \ + enum_mysqlnd_collected_stats _s = (statistic);\ + MYSQLND_STATS * _p_s = (MYSQLND_STATS *) (stats); \ + if ((enabler) && _p_s && _s != _p_s->count) { \ + MYSQLND_UPDATE_VALUE_AND_CALL_TRIGGER(_p_s, _s, 1); \ + }\ + } + +#define MYSQLND_INC_STATISTIC_W_VALUE(enabler, stats, statistic, value) \ + { \ + enum_mysqlnd_collected_stats _s = (statistic);\ + MYSQLND_STATS * _p_s = (MYSQLND_STATS *) (stats); \ + if ((enabler) && _p_s && _s != _p_s->count) { \ + uint64_t v = (uint64_t) (value); \ + MYSQLND_UPDATE_VALUE_AND_CALL_TRIGGER(_p_s, _s, v); \ + }\ + } + +#define MYSQLND_INC_STATISTIC_W_VALUE2(enabler, stats, statistic1, value1, statistic2, value2) \ + { \ + MYSQLND_STATS * _p_s = (MYSQLND_STATS *) (stats); \ + if ((enabler) && _p_s) { \ + uint64_t v1 = (uint64_t) (value1); \ + uint64_t v2 = (uint64_t) (value2); \ + enum_mysqlnd_collected_stats _s1 = (statistic1);\ + enum_mysqlnd_collected_stats _s2 = (statistic2);\ + if (_s1 != _p_s->count) MYSQLND_UPDATE_VALUE_AND_CALL_TRIGGER(_p_s, _s1, v1); \ + if (_s2 != _p_s->count) MYSQLND_UPDATE_VALUE_AND_CALL_TRIGGER(_p_s, _s2, v2); \ + }\ + } + +#define MYSQLND_INC_STATISTIC_W_VALUE3(enabler, stats, statistic1, value1, statistic2, value2, statistic3, value3) \ + { \ + MYSQLND_STATS * _p_s = (MYSQLND_STATS *) (stats); \ + if ((enabler) && _p_s) { \ + uint64_t v1 = (uint64_t) (value1); \ + uint64_t v2 = (uint64_t) (value2); \ + uint64_t v3 = (uint64_t) (value3); \ + enum_mysqlnd_collected_stats _s1 = (statistic1);\ + enum_mysqlnd_collected_stats _s2 = (statistic2);\ + enum_mysqlnd_collected_stats _s3 = (statistic3);\ + if (_s1 != _p_s->count) MYSQLND_UPDATE_VALUE_AND_CALL_TRIGGER(_p_s, _s1, v1); \ + if (_s2 != _p_s->count) MYSQLND_UPDATE_VALUE_AND_CALL_TRIGGER(_p_s, _s2, v2); \ + if (_s3 != _p_s->count) MYSQLND_UPDATE_VALUE_AND_CALL_TRIGGER(_p_s, _s3, v3); \ + }\ + } + + + +#ifndef MYSQLND_CORE_STATISTICS_DISABLED + +#define MYSQLND_INC_GLOBAL_STATISTIC(statistic) \ + MYSQLND_INC_STATISTIC(MYSQLND_G(collect_statistics), mysqlnd_global_stats, (statistic)) + +#define MYSQLND_DEC_CONN_STATISTIC(conn_stats, statistic) \ + MYSQLND_DEC_STATISTIC(MYSQLND_G(collect_statistics), mysqlnd_global_stats, (statistic)) + +#define MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(statistic1, value1, statistic2, value2) \ + MYSQLND_INC_STATISTIC_W_VALUE2(MYSQLND_G(collect_statistics), mysqlnd_global_stats, (statistic1), (value1), (statistic2), (value2)) + +#define MYSQLND_INC_CONN_STATISTIC(conn_stats, statistic) \ + MYSQLND_INC_STATISTIC(MYSQLND_G(collect_statistics), mysqlnd_global_stats, (statistic)); \ + MYSQLND_INC_STATISTIC(MYSQLND_G(collect_statistics), (conn_stats), (statistic)); + +#define MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn_stats, statistic, value) \ + MYSQLND_INC_STATISTIC_W_VALUE(MYSQLND_G(collect_statistics), mysqlnd_global_stats, (statistic), (value)); \ + MYSQLND_INC_STATISTIC_W_VALUE(MYSQLND_G(collect_statistics), (conn_stats), (statistic), (value)); + +#define MYSQLND_INC_CONN_STATISTIC_W_VALUE2(conn_stats, statistic1, value1, statistic2, value2) \ + MYSQLND_INC_STATISTIC_W_VALUE2(MYSQLND_G(collect_statistics), mysqlnd_global_stats, (statistic1), (value1), (statistic2), (value2)); \ + MYSQLND_INC_STATISTIC_W_VALUE2(MYSQLND_G(collect_statistics), (conn_stats), (statistic1), (value1), (statistic2), (value2)); + +#define MYSQLND_INC_CONN_STATISTIC_W_VALUE3(conn_stats, statistic1, value1, statistic2, value2, statistic3, value3) \ + MYSQLND_INC_STATISTIC_W_VALUE3(MYSQLND_G(collect_statistics), mysqlnd_global_stats, (statistic1), (value1), (statistic2), (value2), (statistic3), (value3)); \ + MYSQLND_INC_STATISTIC_W_VALUE3(MYSQLND_G(collect_statistics), (conn_stats), (statistic1), (value1), (statistic2), (value2), (statistic3), (value3)); + +#else + +#define MYSQLND_INC_GLOBAL_STATISTIC(statistic) +#define MYSQLND_DEC_CONN_STATISTIC(conn_stats, statistic) +#define MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(statistic1, value1, statistic2, value2) +#define MYSQLND_INC_CONN_STATISTIC(conn_stats, statistic) +#define MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn_stats, statistic, value) +#define MYSQLND_INC_CONN_STATISTIC_W_VALUE2(conn_stats, statistic1, value1, statistic2, value2) +#define MYSQLND_INC_CONN_STATISTIC_W_VALUE3(conn_stats, statistic1, value1, statistic2, value2, statistic3, value3) + +#endif /* MYSQLND_CORE_STATISTICS_DISABLED */ + +PHPAPI void mysqlnd_fill_stats_hash(const MYSQLND_STATS * const stats, const MYSQLND_STRING * names, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC); + +PHPAPI void mysqlnd_stats_init(MYSQLND_STATS ** stats, size_t statistic_count); +PHPAPI void mysqlnd_stats_end(MYSQLND_STATS * stats); + +PHPAPI mysqlnd_stat_trigger mysqlnd_stats_set_trigger(MYSQLND_STATS * const stats, enum_mysqlnd_collected_stats stat, mysqlnd_stat_trigger trigger TSRMLS_DC); +PHPAPI mysqlnd_stat_trigger mysqlnd_stats_reset_triggers(MYSQLND_STATS * const stats TSRMLS_DC); + +#endif /* MYSQLND_STATISTICS_H */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_structs.h b/ext/mysqlnd/mysqlnd_structs.h new file mode 100644 index 0000000..5c07a3c --- /dev/null +++ b/ext/mysqlnd/mysqlnd_structs.h @@ -0,0 +1,1085 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef MYSQLND_STRUCTS_H +#define MYSQLND_STRUCTS_H + +#define MYSQLND_TYPEDEFED_METHODS + +#define MYSQLND_CLASS_METHOD_TABLE_NAME(class) mysqlnd_##class##_methods +#define MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(class) struct st_##class##_methods MYSQLND_CLASS_METHOD_TABLE_NAME(class) + +#define MYSQLND_CLASS_METHODS_START(class) MYSQLND_CLASS_METHOD_TABLE_NAME_FORWARD(class) = { +#define MYSQLND_CLASS_METHODS_END } + +typedef struct st_mysqlnd_memory_pool MYSQLND_MEMORY_POOL; +typedef struct st_mysqlnd_memory_pool_chunk MYSQLND_MEMORY_POOL_CHUNK; +typedef struct st_mysqlnd_memory_pool_chunk_llist MYSQLND_MEMORY_POOL_CHUNK_LLIST; + + +#define MYSQLND_MEMORY_POOL_CHUNK_LIST_SIZE 100 + +struct st_mysqlnd_memory_pool +{ + zend_uchar *arena; + unsigned int refcount; + unsigned int arena_size; + unsigned int free_size; + + MYSQLND_MEMORY_POOL_CHUNK* (*get_chunk)(MYSQLND_MEMORY_POOL * pool, unsigned int size TSRMLS_DC); +}; + +struct st_mysqlnd_memory_pool_chunk +{ + size_t app; + MYSQLND_MEMORY_POOL *pool; + zend_uchar *ptr; + unsigned int size; + enum_func_status (*resize_chunk)(MYSQLND_MEMORY_POOL_CHUNK * chunk, unsigned int size TSRMLS_DC); + void (*free_chunk)(MYSQLND_MEMORY_POOL_CHUNK * chunk TSRMLS_DC); + zend_bool from_pool; +}; + + +typedef struct st_mysqlnd_cmd_buffer +{ + zend_uchar *buffer; + size_t length; +} MYSQLND_CMD_BUFFER; + + +typedef struct st_mysqlnd_field +{ + const char *name; /* Name of column */ + const char *org_name; /* Original column name, if an alias */ + const char *table; /* Table of column if column was a field */ + const char *org_table; /* Org table name, if table was an alias */ + const char *db; /* Database for table */ + const char *catalog; /* Catalog for table */ + char *def; /* Default value (set by mysql_list_fields) */ + unsigned long length; /* Width of column (create length) */ + unsigned long max_length; /* Max width for selected set */ + unsigned int name_length; + unsigned int org_name_length; + unsigned int table_length; + unsigned int org_table_length; + unsigned int db_length; + unsigned int catalog_length; + unsigned int def_length; + unsigned int flags; /* Diverse flags */ + unsigned int decimals; /* Number of decimals in field */ + unsigned int charsetnr; /* Character set */ + enum mysqlnd_field_types type; /* Type of field. See mysql_com.h for types */ + char *root; + size_t root_len; +} MYSQLND_FIELD; + + +typedef struct st_mysqlnd_upsert_result +{ + unsigned int warning_count; + unsigned int server_status; + uint64_t affected_rows; + uint64_t last_insert_id; +} MYSQLND_UPSERT_STATUS; + + +typedef struct st_mysqlnd_error_info +{ + char error[MYSQLND_ERRMSG_SIZE+1]; + char sqlstate[MYSQLND_SQLSTATE_LENGTH + 1]; + unsigned int error_no; + zend_llist * error_list; +} MYSQLND_ERROR_INFO; + + +typedef struct st_mysqlnd_error_list_element +{ + char * error; + char sqlstate[MYSQLND_SQLSTATE_LENGTH + 1]; + unsigned int error_no; +} MYSQLND_ERROR_LIST_ELEMENT; + + +typedef struct st_mysqlnd_infile_info +{ + php_stream *fd; + int error_no; + char error_msg[MYSQLND_ERRMSG_SIZE + 1]; + const char *filename; +} MYSQLND_INFILE_INFO; + + +/* character set information */ +typedef struct st_mysqlnd_charset +{ + unsigned int nr; + const char *name; + const char *collation; + unsigned int char_minlen; + unsigned int char_maxlen; + const char *comment; + unsigned int (*mb_charlen)(unsigned int c); + unsigned int (*mb_valid)(const char *start, const char *end); +} MYSQLND_CHARSET; + + +/* local infile handler */ +typedef struct st_mysqlnd_infile +{ + int (*local_infile_init)(void **ptr, char *filename, void **userdata TSRMLS_DC); + int (*local_infile_read)(void *ptr, zend_uchar * buf, unsigned int buf_len TSRMLS_DC); + int (*local_infile_error)(void *ptr, char *error_msg, unsigned int error_msg_len TSRMLS_DC); + void (*local_infile_end)(void *ptr TSRMLS_DC); + zval *callback; + void *userdata; +} MYSQLND_INFILE; + +typedef struct st_mysqlnd_options +{ + ulong flags; + + /* init commands - we need to send them to server directly after connect */ + unsigned int num_commands; + char **init_commands; + + /* configuration file information */ + char *cfg_file; + char *cfg_section; + + char *auth_protocol; + /* + We need to keep these because otherwise st_mysqlnd_conn will be changed. + The ABI will be broken and the methods structure will be somewhere else + in the memory which can crash external code. Feel free to reuse these. + */ + char * unused2; + char * unused3; + char * unused4; + char * unused5; + + enum_mysqlnd_protocol_type protocol; + + char *charset_name; + /* maximum allowed packet size for communication */ + ulong max_allowed_packet; + + zend_bool numeric_and_datetime_as_unicode; +#ifdef MYSQLND_STRING_TO_INT_CONVERSION + zend_bool int_and_float_native; +#endif +} MYSQLND_OPTIONS; + +typedef struct st_mysqlnd_net_options +{ + /* timeouts */ + unsigned int timeout_connect; + unsigned int timeout_read; + unsigned int timeout_write; + + unsigned int net_read_buffer_size; + + /* SSL information */ + char *ssl_key; + char *ssl_cert; + char *ssl_ca; + char *ssl_capath; + char *ssl_cipher; + char *ssl_passphrase; + zend_bool ssl_verify_peer; + uint64_t flags; +} MYSQLND_NET_OPTIONS; + + +typedef struct st_mysqlnd_connection MYSQLND; +typedef struct st_mysqlnd_connection_data MYSQLND_CONN_DATA; +typedef struct st_mysqlnd_net MYSQLND_NET; +typedef struct st_mysqlnd_protocol MYSQLND_PROTOCOL; +typedef struct st_mysqlnd_res MYSQLND_RES; +typedef char** MYSQLND_ROW_C; /* return data as array of strings */ +typedef struct st_mysqlnd_stmt_data MYSQLND_STMT_DATA; +typedef struct st_mysqlnd_stmt MYSQLND_STMT; +typedef unsigned int MYSQLND_FIELD_OFFSET; + +typedef struct st_mysqlnd_param_bind MYSQLND_PARAM_BIND; + +typedef struct st_mysqlnd_result_bind MYSQLND_RESULT_BIND; + +typedef struct st_mysqlnd_result_metadata MYSQLND_RES_METADATA; +typedef struct st_mysqlnd_buffered_result MYSQLND_RES_BUFFERED; +typedef struct st_mysqlnd_unbuffered_result MYSQLND_RES_UNBUFFERED; + +typedef struct st_mysqlnd_debug MYSQLND_DEBUG; + + +typedef MYSQLND_RES* (*mysqlnd_stmt_use_or_store_func)(MYSQLND_STMT * const TSRMLS_DC); +typedef enum_func_status (*mysqlnd_fetch_row_func)(MYSQLND_RES *result, + void *param, + unsigned int flags, + zend_bool *fetched_anything + TSRMLS_DC); + +typedef struct st_mysqlnd_stats MYSQLND_STATS; + +typedef void (*mysqlnd_stat_trigger)(MYSQLND_STATS * stats, enum_mysqlnd_collected_stats stat, int64_t change TSRMLS_DC); + +struct st_mysqlnd_stats +{ + uint64_t *values; + mysqlnd_stat_trigger *triggers; + size_t count; + zend_bool in_trigger; +#ifdef ZTS + MUTEX_T LOCK_access; +#endif +}; + + +typedef struct st_mysqlnd_read_buffer { + zend_uchar * data; + size_t offset; + size_t size; + size_t len; + zend_bool (*is_empty)(struct st_mysqlnd_read_buffer *); + void (*read)(struct st_mysqlnd_read_buffer *, size_t count, zend_uchar * dest); + size_t (*bytes_left)(struct st_mysqlnd_read_buffer *); + void (*free_buffer)(struct st_mysqlnd_read_buffer ** TSRMLS_DC); +} MYSQLND_READ_BUFFER; + + + +typedef enum_func_status (*func_mysqlnd_net__set_client_option)(MYSQLND_NET * const net, enum_mysqlnd_option option, const char * const value TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_net__decode)(zend_uchar * uncompressed_data, const size_t uncompressed_data_len, const zend_uchar * const compressed_data, const size_t compressed_data_len TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_net__encode)(zend_uchar * compress_buffer, size_t * compress_buffer_len, const zend_uchar * const uncompressed_data, const size_t uncompressed_data_len TSRMLS_DC); +typedef size_t (*func_mysqlnd_net__consume_uneaten_data)(MYSQLND_NET * const net, enum php_mysqlnd_server_command cmd TSRMLS_DC); +typedef void (*func_mysqlnd_net__free_contents)(MYSQLND_NET * net TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_net__enable_ssl)(MYSQLND_NET * const net TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_net__disable_ssl)(MYSQLND_NET * const net TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_net__network_read_ex)(MYSQLND_NET * const net, zend_uchar * const buffer, const size_t count, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC); +typedef size_t (*func_mysqlnd_net__network_write_ex)(MYSQLND_NET * const net, const zend_uchar * const buf, const size_t count, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC); +typedef size_t (*func_mysqlnd_net__send_ex)(MYSQLND_NET * const net, zend_uchar * const buffer, const size_t count, MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_net__receive_ex)(MYSQLND_NET * const net, zend_uchar * const buffer, const size_t count, MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_net__init)(MYSQLND_NET * const net, MYSQLND_STATS * const stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC); +typedef void (*func_mysqlnd_net__dtor)(MYSQLND_NET * const net, MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_net__connect_ex)(MYSQLND_NET * const net, const char * const scheme, const size_t scheme_len, const zend_bool persistent, MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC); +typedef void (*func_mysqlnd_net__close_stream)(MYSQLND_NET * const net, MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_net__open_stream)(MYSQLND_NET * const net, const char * const scheme, const size_t scheme_len, const zend_bool persistent, MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC); +typedef void (*func_mysqlnd_net__post_connect_set_opt)(MYSQLND_NET * const net, const char * const scheme, const size_t scheme_len, MYSQLND_STATS * const conn_stats, MYSQLND_ERROR_INFO * const error_info TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_net__read_compressed_packet_from_stream_and_fill_read_buffer)(MYSQLND_NET * net, size_t net_payload_size, MYSQLND_STATS * conn_stats, MYSQLND_ERROR_INFO * error_info TSRMLS_DC); + +struct st_mysqlnd_net_methods +{ + func_mysqlnd_net__init init; + func_mysqlnd_net__dtor dtor; + func_mysqlnd_net__connect_ex connect_ex; + func_mysqlnd_net__close_stream close_stream; + func_mysqlnd_net__open_stream open_pipe; + func_mysqlnd_net__open_stream open_tcp_or_unix; + + void * unused1; + void * unused2; + func_mysqlnd_net__post_connect_set_opt post_connect_set_opt; + + func_mysqlnd_net__set_client_option set_client_option; + func_mysqlnd_net__decode decode; + func_mysqlnd_net__encode encode; + func_mysqlnd_net__consume_uneaten_data consume_uneaten_data; + func_mysqlnd_net__free_contents free_contents; + func_mysqlnd_net__enable_ssl enable_ssl; + func_mysqlnd_net__disable_ssl disable_ssl; + + func_mysqlnd_net__network_read_ex network_read_ex; + func_mysqlnd_net__network_write_ex network_write_ex; + func_mysqlnd_net__send_ex send_ex; + func_mysqlnd_net__receive_ex receive_ex; + + func_mysqlnd_net__read_compressed_packet_from_stream_and_fill_read_buffer read_compressed_packet_from_stream_and_fill_read_buffer; + + void * unused3; + void * unused4; + void * unused5; + void * unused6; + void * unused7; +}; + + +struct st_mysqlnd_packet_greet; +struct st_mysqlnd_packet_greet; +struct st_mysqlnd_packet_auth; +struct st_mysqlnd_packet_ok; +struct st_mysqlnd_packet_command; +struct st_mysqlnd_packet_eof; +struct st_mysqlnd_packet_rset_header; +struct st_mysqlnd_packet_res_field; +struct st_mysqlnd_packet_row; +struct st_mysqlnd_packet_stats; +struct st_mysqlnd_packet_prepare_response; +struct st_mysqlnd_packet_chg_user_resp; +struct st_mysqlnd_packet_auth_pam; + +typedef struct st_mysqlnd_packet_greet * (*func_mysqlnd_protocol__get_greet_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_auth * (*func_mysqlnd_protocol__get_auth_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_auth_response *(*func_mysqlnd_protocol__get_auth_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_change_auth_response * (*func_mysqlnd_protocol__get_change_auth_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_ok * (*func_mysqlnd_protocol__get_ok_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_command * (*func_mysqlnd_protocol__get_command_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_eof * (*func_mysqlnd_protocol__get_eof_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_rset_header * (*func_mysqlnd_protocol__get_rset_header_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_res_field * (*func_mysqlnd_protocol__get_result_field_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_row * (*func_mysqlnd_protocol__get_row_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_stats * (*func_mysqlnd_protocol__get_stats_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_prepare_response *(*func_mysqlnd_protocol__get_prepare_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); +typedef struct st_mysqlnd_packet_chg_user_resp*(*func_mysqlnd_protocol__get_change_user_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC); + +struct st_mysqlnd_protocol_methods +{ + func_mysqlnd_protocol__get_greet_packet get_greet_packet; + func_mysqlnd_protocol__get_auth_packet get_auth_packet; + func_mysqlnd_protocol__get_auth_response_packet get_auth_response_packet; + func_mysqlnd_protocol__get_change_auth_response_packet get_change_auth_response_packet; + func_mysqlnd_protocol__get_ok_packet get_ok_packet; + func_mysqlnd_protocol__get_command_packet get_command_packet; + func_mysqlnd_protocol__get_eof_packet get_eof_packet; + func_mysqlnd_protocol__get_rset_header_packet get_rset_header_packet; + func_mysqlnd_protocol__get_result_field_packet get_result_field_packet; + func_mysqlnd_protocol__get_row_packet get_row_packet; + func_mysqlnd_protocol__get_stats_packet get_stats_packet; + func_mysqlnd_protocol__get_prepare_response_packet get_prepare_response_packet; + func_mysqlnd_protocol__get_change_user_response_packet get_change_user_response_packet; + + void * unused1; + void * unused2; + void * unused3; + void * unused4; + void * unused5; +}; + + +typedef MYSQLND * (*func_mysqlnd_object_factory__get_connection)(zend_bool persistent TSRMLS_DC); +typedef MYSQLND * (*func_mysqlnd_object_factory__clone_connection_object)(MYSQLND * conn TSRMLS_DC); +typedef MYSQLND_STMT * (*func_mysqlnd_object_factory__get_prepared_statement)(MYSQLND_CONN_DATA * conn TSRMLS_DC); +typedef MYSQLND_NET * (*func_mysqlnd_object_factory__get_io_channel)(zend_bool persistent, MYSQLND_STATS * stats, MYSQLND_ERROR_INFO * error_info TSRMLS_DC); +typedef MYSQLND_PROTOCOL * (*func_mysqlnd_object_factory__get_protocol_decoder)(zend_bool persistent TSRMLS_DC); + + +struct st_mysqlnd_object_factory_methods +{ + func_mysqlnd_object_factory__get_connection get_connection; + func_mysqlnd_object_factory__clone_connection_object clone_connection_object; + func_mysqlnd_object_factory__get_prepared_statement get_prepared_statement; + func_mysqlnd_object_factory__get_io_channel get_io_channel; + func_mysqlnd_object_factory__get_protocol_decoder get_protocol_decoder; +}; + + +typedef enum_func_status (*func_mysqlnd_conn_data__init)(MYSQLND_CONN_DATA * conn TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__connect)(MYSQLND_CONN_DATA * conn, const char * host, const char * user, const char * passwd, unsigned int passwd_len, const char * db, unsigned int db_len, unsigned int port, const char * socket_or_pipe, unsigned int mysql_flags TSRMLS_DC); +typedef ulong (*func_mysqlnd_conn_data__escape_string)(MYSQLND_CONN_DATA * const conn, char *newstr, const char *escapestr, size_t escapestr_len TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__set_charset)(MYSQLND_CONN_DATA * const conn, const char * const charset TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__query)(MYSQLND_CONN_DATA * conn, const char * query, unsigned int query_len TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__send_query)(MYSQLND_CONN_DATA * conn, const char *query, unsigned int query_len TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__reap_query)(MYSQLND_CONN_DATA * conn TSRMLS_DC); +typedef MYSQLND_RES * (*func_mysqlnd_conn_data__use_result)(MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef MYSQLND_RES * (*func_mysqlnd_conn_data__store_result)(MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__next_result)(MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef zend_bool (*func_mysqlnd_conn_data__more_results)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); + +typedef MYSQLND_STMT * (*func_mysqlnd_conn_data__stmt_init)(MYSQLND_CONN_DATA * const conn TSRMLS_DC); + +typedef enum_func_status (*func_mysqlnd_conn_data__shutdown_server)(MYSQLND_CONN_DATA * const conn, uint8_t level TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__refresh_server)(MYSQLND_CONN_DATA * const conn, uint8_t options TSRMLS_DC); + +typedef enum_func_status (*func_mysqlnd_conn_data__ping)(MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__kill_connection)(MYSQLND_CONN_DATA * conn, unsigned int pid TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__select_db)(MYSQLND_CONN_DATA * const conn, const char * const db, unsigned int db_len TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__server_dump_debug_information)(MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__change_user)(MYSQLND_CONN_DATA * const conn, const char * user, const char * passwd, const char * db, zend_bool silent, size_t passwd_len TSRMLS_DC); + +typedef unsigned int (*func_mysqlnd_conn_data__get_error_no)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef const char * (*func_mysqlnd_conn_data__get_error_str)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef const char * (*func_mysqlnd_conn_data__get_sqlstate)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef uint64_t (*func_mysqlnd_conn_data__get_thread_id)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef void (*func_mysqlnd_conn_data__get_statistics)(const MYSQLND_CONN_DATA * const conn, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC); + +typedef unsigned long (*func_mysqlnd_conn_data__get_server_version)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef const char * (*func_mysqlnd_conn_data__get_server_information)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__get_server_statistics)(MYSQLND_CONN_DATA * conn, char **message, unsigned int * message_len TSRMLS_DC); +typedef const char * (*func_mysqlnd_conn_data__get_host_information)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef unsigned int (*func_mysqlnd_conn_data__get_protocol_information)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef const char * (*func_mysqlnd_conn_data__get_last_message)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef const char * (*func_mysqlnd_conn_data__charset_name)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef MYSQLND_RES * (*func_mysqlnd_conn_data__list_fields)(MYSQLND_CONN_DATA * conn, const char * table, const char * achtung_wild TSRMLS_DC); +typedef MYSQLND_RES * (*func_mysqlnd_conn_data__list_method)(MYSQLND_CONN_DATA * conn, const char * query, const char * achtung_wild, char *par1 TSRMLS_DC); + +typedef uint64_t (*func_mysqlnd_conn_data__get_last_insert_id)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef uint64_t (*func_mysqlnd_conn_data__get_affected_rows)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef unsigned int (*func_mysqlnd_conn_data__get_warning_count)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); + +typedef unsigned int (*func_mysqlnd_conn_data__get_field_count)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); + +typedef unsigned int (*func_mysqlnd_conn_data__get_server_status)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__set_server_option)(MYSQLND_CONN_DATA * const conn, enum_mysqlnd_server_option option TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__set_client_option)(MYSQLND_CONN_DATA * const conn, enum_mysqlnd_option option, const char * const value TSRMLS_DC); +typedef void (*func_mysqlnd_conn_data__free_contents)(MYSQLND_CONN_DATA * conn TSRMLS_DC);/* private */ +typedef void (*func_mysqlnd_conn_data__free_options)(MYSQLND_CONN_DATA * conn TSRMLS_DC); /* private */ +typedef void (*func_mysqlnd_conn_data__dtor)(MYSQLND_CONN_DATA * conn TSRMLS_DC); /* private */ + +typedef enum_func_status (*func_mysqlnd_conn_data__query_read_result_set_header)(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * stmt TSRMLS_DC); + +typedef MYSQLND_CONN_DATA * (*func_mysqlnd_conn_data__get_reference)(MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__free_reference)(MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef enum mysqlnd_connection_state (*func_mysqlnd_conn_data__get_state)(MYSQLND_CONN_DATA * const conn TSRMLS_DC); +typedef void (*func_mysqlnd_conn_data__set_state)(MYSQLND_CONN_DATA * const conn, enum mysqlnd_connection_state new_state TSRMLS_DC); + +typedef enum_func_status (*func_mysqlnd_conn_data__simple_command)(MYSQLND_CONN_DATA * conn, enum php_mysqlnd_server_command command, const zend_uchar * const arg, size_t arg_len, enum mysqlnd_packet_type ok_packet, zend_bool silent, zend_bool ignore_upsert_status TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__simple_command_handle_response)(MYSQLND_CONN_DATA * conn, enum mysqlnd_packet_type ok_packet, zend_bool silent, enum php_mysqlnd_server_command command, zend_bool ignore_upsert_status TSRMLS_DC); + +typedef enum_func_status (*func_mysqlnd_conn_data__restart_psession)(MYSQLND_CONN_DATA * conn TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__end_psession)(MYSQLND_CONN_DATA * conn TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__send_close)(MYSQLND_CONN_DATA * conn TSRMLS_DC); + +typedef enum_func_status (*func_mysqlnd_conn_data__ssl_set)(MYSQLND_CONN_DATA * const conn, const char * key, const char * const cert, const char * const ca, const char * const capath, const char * const cipher TSRMLS_DC); + +typedef MYSQLND_RES * (*func_mysqlnd_conn_data__result_init)(unsigned int field_count, zend_bool persistent TSRMLS_DC); + +typedef enum_func_status (*func_mysqlnd_conn_data__set_autocommit)(MYSQLND_CONN_DATA * conn, unsigned int mode TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__tx_commit)(MYSQLND_CONN_DATA * conn TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__tx_rollback)(MYSQLND_CONN_DATA * conn TSRMLS_DC); + +typedef enum_func_status (*func_mysqlnd_conn_data__local_tx_start)(MYSQLND_CONN_DATA * conn, size_t this_func TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn_data__local_tx_end)(MYSQLND_CONN_DATA * conn, size_t this_func, enum_func_status status TSRMLS_DC); + + +struct st_mysqlnd_conn_data_methods +{ + func_mysqlnd_conn_data__init init; + func_mysqlnd_conn_data__connect connect; + func_mysqlnd_conn_data__escape_string escape_string; + func_mysqlnd_conn_data__set_charset set_charset; + func_mysqlnd_conn_data__query query; + func_mysqlnd_conn_data__send_query send_query; + func_mysqlnd_conn_data__reap_query reap_query; + func_mysqlnd_conn_data__use_result use_result; + func_mysqlnd_conn_data__store_result store_result; + func_mysqlnd_conn_data__next_result next_result; + func_mysqlnd_conn_data__more_results more_results; + + func_mysqlnd_conn_data__stmt_init stmt_init; + + func_mysqlnd_conn_data__shutdown_server shutdown_server; + func_mysqlnd_conn_data__refresh_server refresh_server; + + func_mysqlnd_conn_data__ping ping; + func_mysqlnd_conn_data__kill_connection kill_connection; + func_mysqlnd_conn_data__select_db select_db; + func_mysqlnd_conn_data__server_dump_debug_information server_dump_debug_information; + func_mysqlnd_conn_data__change_user change_user; + + func_mysqlnd_conn_data__get_error_no get_error_no; + func_mysqlnd_conn_data__get_error_str get_error_str; + func_mysqlnd_conn_data__get_sqlstate get_sqlstate; + func_mysqlnd_conn_data__get_thread_id get_thread_id; + func_mysqlnd_conn_data__get_statistics get_statistics; + + func_mysqlnd_conn_data__get_server_version get_server_version; + func_mysqlnd_conn_data__get_server_information get_server_information; + func_mysqlnd_conn_data__get_server_statistics get_server_statistics; + func_mysqlnd_conn_data__get_host_information get_host_information; + func_mysqlnd_conn_data__get_protocol_information get_protocol_information; + func_mysqlnd_conn_data__get_last_message get_last_message; + func_mysqlnd_conn_data__charset_name charset_name; + func_mysqlnd_conn_data__list_fields list_fields; + func_mysqlnd_conn_data__list_method list_method; + + func_mysqlnd_conn_data__get_last_insert_id get_last_insert_id; + func_mysqlnd_conn_data__get_affected_rows get_affected_rows; + func_mysqlnd_conn_data__get_warning_count get_warning_count; + + func_mysqlnd_conn_data__get_field_count get_field_count; + + func_mysqlnd_conn_data__get_server_status get_server_status; + + func_mysqlnd_conn_data__set_server_option set_server_option; + func_mysqlnd_conn_data__set_client_option set_client_option; + func_mysqlnd_conn_data__free_contents free_contents; + func_mysqlnd_conn_data__free_options free_options; + func_mysqlnd_conn_data__dtor dtor; + + func_mysqlnd_conn_data__query_read_result_set_header query_read_result_set_header; + + func_mysqlnd_conn_data__get_reference get_reference; + func_mysqlnd_conn_data__free_reference free_reference; + func_mysqlnd_conn_data__get_state get_state; + func_mysqlnd_conn_data__set_state set_state; + + func_mysqlnd_conn_data__simple_command simple_command; + func_mysqlnd_conn_data__simple_command_handle_response simple_command_handle_response; + + func_mysqlnd_conn_data__restart_psession restart_psession; + func_mysqlnd_conn_data__end_psession end_psession; + func_mysqlnd_conn_data__send_close send_close; + + func_mysqlnd_conn_data__ssl_set ssl_set; + + func_mysqlnd_conn_data__result_init result_init; + func_mysqlnd_conn_data__set_autocommit set_autocommit; + func_mysqlnd_conn_data__tx_commit tx_commit; + func_mysqlnd_conn_data__tx_rollback tx_rollback; + + func_mysqlnd_conn_data__local_tx_start local_tx_start; + func_mysqlnd_conn_data__local_tx_end local_tx_end; +}; + + +typedef enum_func_status (*func_mysqlnd_data__connect)(MYSQLND * conn, const char * host, const char * user, const char * passwd, unsigned int passwd_len, const char * db, unsigned int db_len, unsigned int port, const char * socket_or_pipe, unsigned int mysql_flags TSRMLS_DC); +typedef MYSQLND * (*func_mysqlnd_conn__clone_object)(MYSQLND * const conn TSRMLS_DC); +typedef void (*func_mysqlnd_conn__dtor)(MYSQLND * conn TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_conn__close)(MYSQLND * conn, enum_connection_close_type close_type TSRMLS_DC); + +struct st_mysqlnd_conn_methods +{ + func_mysqlnd_data__connect connect; + func_mysqlnd_conn__clone_object clone_object; + func_mysqlnd_conn__dtor dtor; + func_mysqlnd_conn__close close; +}; + + +typedef mysqlnd_fetch_row_func fetch_row; +typedef mysqlnd_fetch_row_func fetch_row_normal_buffered; /* private */ +typedef mysqlnd_fetch_row_func fetch_row_normal_unbuffered; /* private */ + +typedef MYSQLND_RES * (*func_mysqlnd_res__use_result)(MYSQLND_RES * const result, zend_bool ps_protocol TSRMLS_DC); +typedef MYSQLND_RES * (*func_mysqlnd_res__store_result)(MYSQLND_RES * result, MYSQLND_CONN_DATA * const conn, zend_bool ps TSRMLS_DC); +typedef void (*func_mysqlnd_res__fetch_into)(MYSQLND_RES *result, unsigned int flags, zval *return_value, enum_mysqlnd_extension ext TSRMLS_DC ZEND_FILE_LINE_DC); +typedef MYSQLND_ROW_C (*func_mysqlnd_res__fetch_row_c)(MYSQLND_RES *result TSRMLS_DC); +typedef void (*func_mysqlnd_res__fetch_all)(MYSQLND_RES *result, unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC); +typedef void (*func_mysqlnd_res__fetch_field_data)(MYSQLND_RES *result, unsigned int offset, zval *return_value TSRMLS_DC); +typedef uint64_t (*func_mysqlnd_res__num_rows)(const MYSQLND_RES * const result TSRMLS_DC); +typedef unsigned int (*func_mysqlnd_res__num_fields)(const MYSQLND_RES * const result TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_res__skip_result)(MYSQLND_RES * const result TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_res__seek_data)(MYSQLND_RES * result, uint64_t row TSRMLS_DC); +typedef MYSQLND_FIELD_OFFSET (*func_mysqlnd_res__seek_field)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET field_offset TSRMLS_DC); +typedef MYSQLND_FIELD_OFFSET (*func_mysqlnd_res__field_tell)(const MYSQLND_RES * const result TSRMLS_DC); +typedef const MYSQLND_FIELD *(*func_mysqlnd_res__fetch_field)(MYSQLND_RES * const result TSRMLS_DC); +typedef const MYSQLND_FIELD *(*func_mysqlnd_res__fetch_field_direct)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC); +typedef const MYSQLND_FIELD *(*func_mysqlnd_res__fetch_fields)(MYSQLND_RES * const result TSRMLS_DC); + +typedef enum_func_status (*func_mysqlnd_res__read_result_metadata)(MYSQLND_RES * result, MYSQLND_CONN_DATA * conn TSRMLS_DC); +typedef unsigned long * (*func_mysqlnd_res__fetch_lengths)(MYSQLND_RES * const result TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_res__store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result, MYSQLND_RES_METADATA *meta, zend_bool binary_protocol TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_res__initialize_result_set_rest)(MYSQLND_RES * const result TSRMLS_DC); + +typedef void (*func_mysqlnd_res__free_result_buffers)(MYSQLND_RES * result TSRMLS_DC); /* private */ +typedef enum_func_status (*func_mysqlnd_res__free_result)(MYSQLND_RES * result, zend_bool implicit TSRMLS_DC); +typedef void (*func_mysqlnd_res__free_result_internal)(MYSQLND_RES *result TSRMLS_DC); +typedef void (*func_mysqlnd_res__free_result_contents)(MYSQLND_RES *result TSRMLS_DC); +typedef void (*func_mysqlnd_res__free_buffered_data)(MYSQLND_RES *result TSRMLS_DC); +typedef void (*func_mysqlnd_res__unbuffered_free_last_data)(MYSQLND_RES *result TSRMLS_DC); + + /* for decoding - binary or text protocol */ +typedef enum_func_status (*func_mysqlnd_res__row_decoder)(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, + unsigned int field_count, MYSQLND_FIELD *fields_metadata, + zend_bool as_unicode, zend_bool as_int_or_float, + MYSQLND_STATS * stats TSRMLS_DC); + +typedef MYSQLND_RES_METADATA * (*func_mysqlnd_res__result_meta_init)(unsigned int field_count, zend_bool persistent TSRMLS_DC); + +struct st_mysqlnd_res_methods +{ + mysqlnd_fetch_row_func fetch_row; + mysqlnd_fetch_row_func fetch_row_normal_buffered; /* private */ + mysqlnd_fetch_row_func fetch_row_normal_unbuffered; /* private */ + + func_mysqlnd_res__use_result use_result; + func_mysqlnd_res__store_result store_result; + func_mysqlnd_res__fetch_into fetch_into; + func_mysqlnd_res__fetch_row_c fetch_row_c; + func_mysqlnd_res__fetch_all fetch_all; + func_mysqlnd_res__fetch_field_data fetch_field_data; + func_mysqlnd_res__num_rows num_rows; + func_mysqlnd_res__num_fields num_fields; + func_mysqlnd_res__skip_result skip_result; + func_mysqlnd_res__seek_data seek_data; + func_mysqlnd_res__seek_field seek_field; + func_mysqlnd_res__field_tell field_tell; + func_mysqlnd_res__fetch_field fetch_field; + func_mysqlnd_res__fetch_field_direct fetch_field_direct; + func_mysqlnd_res__fetch_fields fetch_fields; + func_mysqlnd_res__read_result_metadata read_result_metadata; + func_mysqlnd_res__fetch_lengths fetch_lengths; + func_mysqlnd_res__store_result_fetch_data store_result_fetch_data; + func_mysqlnd_res__initialize_result_set_rest initialize_result_set_rest; + func_mysqlnd_res__free_result_buffers free_result_buffers; + func_mysqlnd_res__free_result free_result; + func_mysqlnd_res__free_result_internal free_result_internal; + func_mysqlnd_res__free_result_contents free_result_contents; + func_mysqlnd_res__free_buffered_data free_buffered_data; + func_mysqlnd_res__unbuffered_free_last_data unbuffered_free_last_data; + + /* for decoding - binary or text protocol */ + func_mysqlnd_res__row_decoder row_decoder; + + func_mysqlnd_res__result_meta_init result_meta_init; + + void * unused1; + void * unused2; + void * unused3; + void * unused4; + void * unused5; +}; + + +typedef const MYSQLND_FIELD * (*func_mysqlnd_res_meta__fetch_field)(MYSQLND_RES_METADATA * const meta TSRMLS_DC); +typedef const MYSQLND_FIELD * (*func_mysqlnd_res_meta__fetch_field_direct)(const MYSQLND_RES_METADATA * const meta, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC); +typedef const MYSQLND_FIELD * (*func_mysqlnd_res_meta__fetch_fields)(MYSQLND_RES_METADATA * const meta TSRMLS_DC); +typedef MYSQLND_FIELD_OFFSET (*func_mysqlnd_res_meta__field_tell)(const MYSQLND_RES_METADATA * const meta TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_res_meta__read_metadata)(MYSQLND_RES_METADATA * const meta, MYSQLND_CONN_DATA * conn TSRMLS_DC); +typedef MYSQLND_RES_METADATA * (*func_mysqlnd_res_meta__clone_metadata)(const MYSQLND_RES_METADATA * const meta, zend_bool persistent TSRMLS_DC); +typedef void (*func_mysqlnd_res_meta__free_metadata)(MYSQLND_RES_METADATA * meta TSRMLS_DC); + +struct st_mysqlnd_res_meta_methods +{ + func_mysqlnd_res_meta__fetch_field fetch_field; + func_mysqlnd_res_meta__fetch_field_direct fetch_field_direct; + func_mysqlnd_res_meta__fetch_fields fetch_fields; + func_mysqlnd_res_meta__field_tell field_tell; + func_mysqlnd_res_meta__read_metadata read_metadata; + func_mysqlnd_res_meta__clone_metadata clone_metadata; + func_mysqlnd_res_meta__free_metadata free_metadata; +}; + + +typedef enum_func_status (*func_mysqlnd_stmt__prepare)(MYSQLND_STMT * const stmt, const char * const query, unsigned int query_len TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__execute)(MYSQLND_STMT * const stmt TSRMLS_DC); +typedef MYSQLND_RES * (*func_mysqlnd_stmt__use_result)(MYSQLND_STMT * const stmt TSRMLS_DC); +typedef MYSQLND_RES * (*func_mysqlnd_stmt__store_result)(MYSQLND_STMT * const stmt TSRMLS_DC); +typedef MYSQLND_RES * (*func_mysqlnd_stmt__get_result)(MYSQLND_STMT * const stmt TSRMLS_DC); +typedef zend_bool (*func_mysqlnd_stmt__more_results)(const MYSQLND_STMT * const stmt TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__next_result)(MYSQLND_STMT * const stmt TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__free_result)(MYSQLND_STMT * const stmt TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__seek_data)(const MYSQLND_STMT * const stmt, uint64_t row TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__reset)(MYSQLND_STMT * const stmt TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__net_close)(MYSQLND_STMT * const stmt, zend_bool implicit TSRMLS_DC); /* private */ +typedef enum_func_status (*func_mysqlnd_stmt__dtor)(MYSQLND_STMT * const stmt, zend_bool implicit TSRMLS_DC); /* use this for mysqlnd_stmt_close */ +typedef enum_func_status (*func_mysqlnd_stmt__fetch)(MYSQLND_STMT * const stmt, zend_bool * const fetched_anything TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__bind_parameters)(MYSQLND_STMT * const stmt, MYSQLND_PARAM_BIND * const param_bind TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__bind_one_parameter)(MYSQLND_STMT * const stmt, unsigned int param_no, zval * const zv, zend_uchar type TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__refresh_bind_param)(MYSQLND_STMT * const stmt TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__bind_result)(MYSQLND_STMT * const stmt, MYSQLND_RESULT_BIND * const result_bind TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__bind_one_result)(MYSQLND_STMT * const stmt, unsigned int param_no TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__send_long_data)(MYSQLND_STMT * const stmt, unsigned int param_num, const char * const data, unsigned long length TSRMLS_DC); +typedef MYSQLND_RES * (*func_mysqlnd_stmt__get_parameter_metadata)(MYSQLND_STMT * const stmt TSRMLS_DC); +typedef MYSQLND_RES * (*func_mysqlnd_stmt__get_result_metadata)(MYSQLND_STMT * const stmt TSRMLS_DC); +typedef uint64_t (*func_mysqlnd_stmt__get_last_insert_id)(const MYSQLND_STMT * const stmt TSRMLS_DC); +typedef uint64_t (*func_mysqlnd_stmt__get_affected_rows)(const MYSQLND_STMT * const stmt TSRMLS_DC); +typedef uint64_t (*func_mysqlnd_stmt__get_num_rows)(const MYSQLND_STMT * const stmt TSRMLS_DC); +typedef unsigned int (*func_mysqlnd_stmt__get_param_count)(const MYSQLND_STMT * const stmt TSRMLS_DC); +typedef unsigned int (*func_mysqlnd_stmt__get_field_count)(const MYSQLND_STMT * const stmt TSRMLS_DC); +typedef unsigned int (*func_mysqlnd_stmt__get_warning_count)(const MYSQLND_STMT * const stmt TSRMLS_DC); +typedef unsigned int (*func_mysqlnd_stmt__get_error_no)(const MYSQLND_STMT * const stmt TSRMLS_DC); +typedef const char * (*func_mysqlnd_stmt__get_error_str)(const MYSQLND_STMT * const stmt TSRMLS_DC); +typedef const char * (*func_mysqlnd_stmt__get_sqlstate)(const MYSQLND_STMT * const stmt TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__get_attribute)(const MYSQLND_STMT * const stmt, enum mysqlnd_stmt_attr attr_type, void * const value TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__set_attribute)(MYSQLND_STMT * const stmt, enum mysqlnd_stmt_attr attr_type, const void * const value TSRMLS_DC); +typedef MYSQLND_PARAM_BIND *(*func_mysqlnd_stmt__alloc_param_bind)(MYSQLND_STMT * const stmt TSRMLS_DC); +typedef MYSQLND_RESULT_BIND*(*func_mysqlnd_stmt__alloc_result_bind)(MYSQLND_STMT * const stmt TSRMLS_DC); +typedef void (*func_mysqlnd_stmt__free_parameter_bind)(MYSQLND_STMT * const stmt, MYSQLND_PARAM_BIND * TSRMLS_DC); +typedef void (*func_mysqlnd_stmt__free_result_bind)(MYSQLND_STMT * const stmt, MYSQLND_RESULT_BIND * TSRMLS_DC); +typedef unsigned int (*func_mysqlnd_stmt__server_status)(const MYSQLND_STMT * const stmt TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__generate_execute_request)(MYSQLND_STMT * const s, zend_uchar ** request, size_t *request_len, zend_bool * free_buffer TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__parse_execute_response)(MYSQLND_STMT * const s TSRMLS_DC); +typedef void (*func_mysqlnd_stmt__free_stmt_content)(MYSQLND_STMT * const s TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_stmt__flush)(MYSQLND_STMT * const stmt TSRMLS_DC); + +struct st_mysqlnd_stmt_methods +{ + func_mysqlnd_stmt__prepare prepare; + func_mysqlnd_stmt__execute execute; + func_mysqlnd_stmt__use_result use_result; + func_mysqlnd_stmt__store_result store_result; + func_mysqlnd_stmt__get_result get_result; + func_mysqlnd_stmt__more_results more_results; + func_mysqlnd_stmt__next_result next_result; + func_mysqlnd_stmt__free_result free_result; + func_mysqlnd_stmt__seek_data seek_data; + func_mysqlnd_stmt__reset reset; + func_mysqlnd_stmt__net_close net_close; + func_mysqlnd_stmt__dtor dtor; + func_mysqlnd_stmt__fetch fetch; + + func_mysqlnd_stmt__bind_parameters bind_parameters; + func_mysqlnd_stmt__bind_one_parameter bind_one_parameter; + func_mysqlnd_stmt__refresh_bind_param refresh_bind_param; + func_mysqlnd_stmt__bind_result bind_result; + func_mysqlnd_stmt__bind_one_result bind_one_result; + func_mysqlnd_stmt__send_long_data send_long_data; + func_mysqlnd_stmt__get_parameter_metadata get_parameter_metadata; + func_mysqlnd_stmt__get_result_metadata get_result_metadata; + + func_mysqlnd_stmt__get_last_insert_id get_last_insert_id; + func_mysqlnd_stmt__get_affected_rows get_affected_rows; + func_mysqlnd_stmt__get_num_rows get_num_rows; + + func_mysqlnd_stmt__get_param_count get_param_count; + func_mysqlnd_stmt__get_field_count get_field_count; + func_mysqlnd_stmt__get_warning_count get_warning_count; + + func_mysqlnd_stmt__get_error_no get_error_no; + func_mysqlnd_stmt__get_error_str get_error_str; + func_mysqlnd_stmt__get_sqlstate get_sqlstate; + + func_mysqlnd_stmt__get_attribute get_attribute; + func_mysqlnd_stmt__set_attribute set_attribute; + + func_mysqlnd_stmt__alloc_param_bind alloc_parameter_bind; + func_mysqlnd_stmt__alloc_result_bind alloc_result_bind; + + func_mysqlnd_stmt__free_parameter_bind free_parameter_bind; + func_mysqlnd_stmt__free_result_bind free_result_bind; + + func_mysqlnd_stmt__server_status get_server_status; + + func_mysqlnd_stmt__generate_execute_request generate_execute_request; + func_mysqlnd_stmt__parse_execute_response parse_execute_response; + + func_mysqlnd_stmt__free_stmt_content free_stmt_content; + + func_mysqlnd_stmt__flush flush; +}; + + +struct st_mysqlnd_net +{ + php_stream *stream; + /* sequence for simple checking of correct packets */ + zend_uchar packet_no; + zend_bool compressed; + zend_uchar compressed_envelope_packet_no; +#ifdef MYSQLND_COMPRESSION_ENABLED + MYSQLND_READ_BUFFER * uncompressed_data; +#else + void * unused_pad1; +#endif +#ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND + zend_uchar last_command; +#else + zend_uchar unused_pad2; +#endif + /* cmd buffer */ + MYSQLND_CMD_BUFFER cmd_buffer; + + MYSQLND_NET_OPTIONS options; + + zend_bool persistent; + + struct st_mysqlnd_net_methods m; +}; + + +struct st_mysqlnd_protocol +{ + zend_bool persistent; + struct st_mysqlnd_protocol_methods m; +}; + + +struct st_mysqlnd_connection_data +{ +/* Operation related */ + MYSQLND_NET * net; + MYSQLND_PROTOCOL * protocol; + +/* Information related */ + char *host; + unsigned int host_len; + char *unix_socket; + unsigned int unix_socket_len; + char *user; + unsigned int user_len; + char *passwd; + unsigned int passwd_len; + char *scheme; + unsigned int scheme_len; + uint64_t thread_id; + char *server_version; + char *host_info; + zend_uchar *auth_plugin_data; + size_t auth_plugin_data_len; + const MYSQLND_CHARSET *charset; + const MYSQLND_CHARSET *greet_charset; + char *connect_or_select_db; + unsigned int connect_or_select_db_len; + MYSQLND_INFILE infile; + unsigned int protocol_version; + unsigned long max_packet_size; + unsigned int port; + unsigned long client_flag; + unsigned long server_capabilities; + + /* For UPSERT queries */ + MYSQLND_UPSERT_STATUS * upsert_status; + MYSQLND_UPSERT_STATUS upsert_status_impl; + char *last_message; + unsigned int last_message_len; + + /* If error packet, we use these */ + MYSQLND_ERROR_INFO * error_info; + MYSQLND_ERROR_INFO error_info_impl; + + /* + To prevent queries during unbuffered fetches. Also to + mark the connection as destroyed for garbage collection. + */ + enum mysqlnd_connection_state state; + enum_mysqlnd_query_type last_query_type; + /* Temporary storage between query and (use|store)_result() call */ + MYSQLND_RES *current_result; + + /* + How many result sets reference this connection. + It won't be freed until this number reaches 0. + The last one, please close the door! :-) + The result set objects can determine by inspecting + 'quit_sent' whether the connection is still valid. + */ + unsigned int refcount; + + /* Temporal storage for mysql_query */ + unsigned int field_count; + + /* persistent connection */ + zend_bool persistent; + + /* options */ + MYSQLND_OPTIONS * options; + MYSQLND_OPTIONS options_impl; + + /* stats */ + MYSQLND_STATS * stats; + + struct st_mysqlnd_conn_data_methods * m; +}; + + +struct st_mysqlnd_connection +{ + MYSQLND_CONN_DATA * data; + zend_bool persistent; + struct st_mysqlnd_conn_methods * m; +}; + + +struct mysqlnd_field_hash_key +{ + zend_bool is_numeric; + unsigned long key; +#if MYSQLND_UNICODE + zstr ustr; + unsigned int ulen; +#endif +}; + + +struct st_mysqlnd_result_metadata +{ + MYSQLND_FIELD *fields; + struct mysqlnd_field_hash_key *zend_hash_keys; + unsigned int current_field; + unsigned int field_count; + /* We need this to make fast allocs in rowp_read */ + unsigned int bit_fields_count; + size_t bit_fields_total_len; /* trailing \0 not counted */ + zend_bool persistent; + + struct st_mysqlnd_res_meta_methods *m; +}; + + +struct st_mysqlnd_buffered_result +{ + zval **data; + zval **data_cursor; + MYSQLND_MEMORY_POOL_CHUNK **row_buffers; + uint64_t row_count; + uint64_t initialized_rows; + + unsigned int references; + + MYSQLND_ERROR_INFO error_info; +}; + + +struct st_mysqlnd_unbuffered_result +{ + /* For unbuffered (both normal and PS) */ + zval **last_row_data; + MYSQLND_MEMORY_POOL_CHUNK *last_row_buffer; + + uint64_t row_count; + zend_bool eof_reached; +}; + + +struct st_mysqlnd_res +{ + MYSQLND_CONN_DATA *conn; + enum_mysqlnd_res_type type; + unsigned int field_count; + + /* For metadata functions */ + MYSQLND_RES_METADATA *meta; + + /* To be used with store_result() - both normal and PS */ + MYSQLND_RES_BUFFERED *stored_data; + MYSQLND_RES_UNBUFFERED *unbuf; + + /* + Column lengths of current row - both buffered and unbuffered. + For buffered results it duplicates the data found in **data + */ + unsigned long *lengths; + + struct st_mysqlnd_packet_row * row_packet; + + MYSQLND_MEMORY_POOL * result_set_memory_pool; + zend_bool persistent; + + struct st_mysqlnd_res_methods m; +}; + + +struct st_mysqlnd_param_bind +{ + zval *zv; + zend_uchar type; + enum_param_bind_flags flags; +}; + +struct st_mysqlnd_result_bind +{ + zval *zv; + zend_bool bound; +}; + + +struct st_mysqlnd_stmt_data +{ + MYSQLND_CONN_DATA *conn; + unsigned long stmt_id; + unsigned long flags;/* cursor is set here */ + enum_mysqlnd_stmt_state state; + unsigned int warning_count; + MYSQLND_RES *result; + unsigned int field_count; + unsigned int param_count; + unsigned char send_types_to_server; + MYSQLND_PARAM_BIND *param_bind; + MYSQLND_RESULT_BIND *result_bind; + zend_bool result_zvals_separated_once; + zend_bool persistent; + + MYSQLND_UPSERT_STATUS * upsert_status; + MYSQLND_UPSERT_STATUS upsert_status_impl; + + MYSQLND_ERROR_INFO * error_info; + MYSQLND_ERROR_INFO error_info_impl; + + zend_bool update_max_length; + unsigned long prefetch_rows; + + zend_bool cursor_exists; + mysqlnd_stmt_use_or_store_func default_rset_handler; + + MYSQLND_CMD_BUFFER execute_cmd_buffer; + unsigned int execute_count;/* count how many times the stmt was executed */ +}; + + +struct st_mysqlnd_stmt +{ + MYSQLND_STMT_DATA * data; + struct st_mysqlnd_stmt_methods *m; + zend_bool persistent; +}; + + +typedef struct st_mysqlnd_string +{ + char *s; + size_t l; +} MYSQLND_STRING; + + +struct st_mysqlnd_plugin_header +{ + unsigned int plugin_api_version; + const char * plugin_name; + unsigned long plugin_version; + const char * plugin_string_version; + const char * plugin_license; + const char * plugin_author; + struct + { + MYSQLND_STATS * values; + const MYSQLND_STRING * names; + } plugin_stats; + + struct + { + enum_func_status (*plugin_shutdown)(void * plugin TSRMLS_DC); + } m; +}; + + +struct st_mysqlnd_plugin_core +{ + struct st_mysqlnd_plugin_header plugin_header; +}; + + +struct st_mysqlnd_typeii_plugin_example +{ + struct st_mysqlnd_plugin_header plugin_header; + void * methods; + unsigned int counter; +}; + +struct st_mysqlnd_authentication_plugin; + +typedef zend_uchar * (*func_auth_plugin__get_auth_data)(struct st_mysqlnd_authentication_plugin * self, + size_t * auth_data_len, + MYSQLND_CONN_DATA * conn, const char * const user, const char * const passwd, + const size_t passwd_len, zend_uchar * auth_plugin_data, size_t auth_plugin_data_len, + const MYSQLND_OPTIONS * const options, unsigned long mysql_flags + TSRMLS_DC); + +struct st_mysqlnd_authentication_plugin +{ + struct st_mysqlnd_plugin_header plugin_header; + struct { + func_auth_plugin__get_auth_data get_auth_data; + } methods; +}; + + +#endif /* MYSQLND_STRUCTS_H */ diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c new file mode 100644 index 0000000..d0ab9fe --- /dev/null +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -0,0 +1,2416 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "php_globals.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_statistics.h" +#include "mysqlnd_debug.h" +#include "zend_ini.h" + +#define MYSQLND_SILENT 1 + +#define MYSQLND_DUMP_HEADER_N_BODY + +#define PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_size, packet_type_as_text, packet_type) \ + { \ + DBG_INF_FMT("buf=%p size=%u", (buf), (buf_size)); \ + if (FAIL == mysqlnd_read_header((conn)->net, &((packet)->header), (conn)->stats, ((conn)->error_info) TSRMLS_CC)) {\ + CONN_SET_STATE(conn, CONN_QUIT_SENT); \ + SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);\ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", mysqlnd_server_gone); \ + DBG_ERR_FMT("Can't read %s's header", (packet_type_as_text)); \ + DBG_RETURN(FAIL);\ + }\ + if ((buf_size) < (packet)->header.size) { \ + DBG_ERR_FMT("Packet buffer %u wasn't big enough %u, %u bytes will be unread", \ + (buf_size), (packet)->header.size, (packet)->header.size - (buf_size)); \ + DBG_RETURN(FAIL); \ + }\ + if (FAIL == conn->net->m.receive_ex((conn)->net, (buf), (packet)->header.size, (conn)->stats, ((conn)->error_info) TSRMLS_CC)) { \ + CONN_SET_STATE(conn, CONN_QUIT_SENT); \ + SET_CLIENT_ERROR(*conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone);\ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", mysqlnd_server_gone); \ + DBG_ERR_FMT("Empty '%s' packet body", (packet_type_as_text)); \ + DBG_RETURN(FAIL);\ + } \ + MYSQLND_INC_CONN_STATISTIC_W_VALUE2(conn->stats, packet_type_to_statistic_byte_count[packet_type], \ + MYSQLND_HEADER_SIZE + (packet)->header.size, \ + packet_type_to_statistic_packet_count[packet_type], \ + 1); \ + } + + +#define BAIL_IF_NO_MORE_DATA \ + if ((size_t)(p - begin) > packet->header.size) { \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Premature end of data (mysqlnd_wireprotocol.c:%u)", __LINE__); \ + goto premature_end; \ + } \ + + +static const char *unknown_sqlstate= "HY000"; + +const char * const mysqlnd_empty_string = ""; + +/* Used in mysqlnd_debug.c */ +const char mysqlnd_read_header_name[] = "mysqlnd_read_header"; +const char mysqlnd_read_body_name[] = "mysqlnd_read_body"; + + +#define ERROR_MARKER 0xFF +#define EODATA_MARKER 0xFE + +/* {{{ mysqlnd_command_to_text + */ +const char * const mysqlnd_command_to_text[COM_END] = +{ + "SLEEP", "QUIT", "INIT_DB", "QUERY", "FIELD_LIST", + "CREATE_DB", "DROP_DB", "REFRESH", "SHUTDOWN", "STATISTICS", + "PROCESS_INFO", "CONNECT", "PROCESS_KILL", "DEBUG", "PING", + "TIME", "DELAYED_INSERT", "CHANGE_USER", "BINLOG_DUMP", + "TABLE_DUMP", "CONNECT_OUT", "REGISTER_SLAVE", + "STMT_PREPARE", "STMT_EXECUTE", "STMT_SEND_LONG_DATA", "STMT_CLOSE", + "STMT_RESET", "SET_OPTION", "STMT_FETCH", "DAEMON" +}; +/* }}} */ + + + +static enum_mysqlnd_collected_stats packet_type_to_statistic_byte_count[PROT_LAST] = +{ + STAT_LAST, + STAT_LAST, + STAT_BYTES_RECEIVED_OK, + STAT_BYTES_RECEIVED_EOF, + STAT_LAST, + STAT_BYTES_RECEIVED_RSET_HEADER, + STAT_BYTES_RECEIVED_RSET_FIELD_META, + STAT_BYTES_RECEIVED_RSET_ROW, + STAT_BYTES_RECEIVED_PREPARE_RESPONSE, + STAT_BYTES_RECEIVED_CHANGE_USER, +}; + +static enum_mysqlnd_collected_stats packet_type_to_statistic_packet_count[PROT_LAST] = +{ + STAT_LAST, + STAT_LAST, + STAT_PACKETS_RECEIVED_OK, + STAT_PACKETS_RECEIVED_EOF, + STAT_LAST, + STAT_PACKETS_RECEIVED_RSET_HEADER, + STAT_PACKETS_RECEIVED_RSET_FIELD_META, + STAT_PACKETS_RECEIVED_RSET_ROW, + STAT_PACKETS_RECEIVED_PREPARE_RESPONSE, + STAT_PACKETS_RECEIVED_CHANGE_USER, +}; + + +/* {{{ php_mysqlnd_net_field_length + Get next field's length */ +unsigned long +php_mysqlnd_net_field_length(zend_uchar **packet) +{ + register zend_uchar *p= (zend_uchar *)*packet; + + if (*p < 251) { + (*packet)++; + return (unsigned long) *p; + } + + switch (*p) { + case 251: + (*packet)++; + return MYSQLND_NULL_LENGTH; + case 252: + (*packet) += 3; + return (unsigned long) uint2korr(p+1); + case 253: + (*packet) += 4; + return (unsigned long) uint3korr(p+1); + default: + (*packet) += 9; + return (unsigned long) uint4korr(p+1); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_net_field_length_ll + Get next field's length */ +uint64_t +php_mysqlnd_net_field_length_ll(zend_uchar **packet) +{ + register zend_uchar *p= (zend_uchar *)*packet; + + if (*p < 251) { + (*packet)++; + return (uint64_t) *p; + } + + switch (*p) { + case 251: + (*packet)++; + return (uint64_t) MYSQLND_NULL_LENGTH; + case 252: + (*packet) += 3; + return (uint64_t) uint2korr(p + 1); + case 253: + (*packet) += 4; + return (uint64_t) uint3korr(p + 1); + default: + (*packet) += 9; + return (uint64_t) uint8korr(p + 1); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_net_store_length */ +zend_uchar * +php_mysqlnd_net_store_length(zend_uchar *packet, uint64_t length) +{ + if (length < (uint64_t) L64(251)) { + *packet = (zend_uchar) length; + return packet + 1; + } + + if (length < (uint64_t) L64(65536)) { + *packet++ = 252; + int2store(packet,(unsigned int) length); + return packet + 2; + } + + if (length < (uint64_t) L64(16777216)) { + *packet++ = 253; + int3store(packet,(ulong) length); + return packet + 3; + } + *packet++ = 254; + int8store(packet, length); + return packet + 8; +} +/* }}} */ + + +/* {{{ php_mysqlnd_read_error_from_line */ +static enum_func_status +php_mysqlnd_read_error_from_line(zend_uchar *buf, size_t buf_len, + char *error, int error_buf_len, + unsigned int *error_no, char *sqlstate TSRMLS_DC) +{ + zend_uchar *p = buf; + int error_msg_len= 0; + + DBG_ENTER("php_mysqlnd_read_error_from_line"); + + *error_no = CR_UNKNOWN_ERROR; + memcpy(sqlstate, unknown_sqlstate, MYSQLND_SQLSTATE_LENGTH); + + if (buf_len > 2) { + *error_no = uint2korr(p); + p+= 2; + /* + sqlstate is following. No need to check for buf_left_len as we checked > 2 above, + if it was >=2 then we would need a check + */ + if (*p == '#') { + ++p; + if ((buf_len - (p - buf)) >= MYSQLND_SQLSTATE_LENGTH) { + memcpy(sqlstate, p, MYSQLND_SQLSTATE_LENGTH); + p+= MYSQLND_SQLSTATE_LENGTH; + } else { + goto end; + } + } + if ((buf_len - (p - buf)) > 0) { + error_msg_len = MIN((int)((buf_len - (p - buf))), (int) (error_buf_len - 1)); + memcpy(error, p, error_msg_len); + } + } +end: + sqlstate[MYSQLND_SQLSTATE_LENGTH] = '\0'; + error[error_msg_len]= '\0'; + + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ mysqlnd_read_header */ +static enum_func_status +mysqlnd_read_header(MYSQLND_NET * net, MYSQLND_PACKET_HEADER * header, + MYSQLND_STATS * conn_stats, MYSQLND_ERROR_INFO * error_info TSRMLS_DC) +{ + zend_uchar buffer[MYSQLND_HEADER_SIZE]; + + DBG_ENTER(mysqlnd_read_header_name); + DBG_INF_FMT("compressed=%u", net->compressed); + if (FAIL == net->m.receive_ex(net, buffer, MYSQLND_HEADER_SIZE, conn_stats, error_info TSRMLS_CC)) { + DBG_RETURN(FAIL); + } + + header->size = uint3korr(buffer); + header->packet_no = uint1korr(buffer + 3); + +#ifdef MYSQLND_DUMP_HEADER_N_BODY + DBG_INF_FMT("HEADER: prot_packet_no=%u size=%3u", header->packet_no, header->size); +#endif + MYSQLND_INC_CONN_STATISTIC_W_VALUE2(conn_stats, + STAT_PROTOCOL_OVERHEAD_IN, MYSQLND_HEADER_SIZE, + STAT_PACKETS_RECEIVED, 1); + + if (net->compressed || net->packet_no == header->packet_no) { + /* + Have to increase the number, so we can send correct number back. It will + round at 255 as this is unsigned char. The server needs this for simple + flow control checking. + */ + net->packet_no++; + DBG_RETURN(PASS); + } + + DBG_ERR_FMT("Logical link: packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC, + net->packet_no, header->packet_no, header->size); + + php_error(E_WARNING, "Packets out of order. Expected %u received %u. Packet size="MYSQLND_SZ_T_SPEC, + net->packet_no, header->packet_no, header->size); + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ php_mysqlnd_greet_read */ +static enum_func_status +php_mysqlnd_greet_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + zend_uchar buf[2048]; + zend_uchar *p = buf; + zend_uchar *begin = buf; + zend_uchar *pad_start = NULL; + MYSQLND_PACKET_GREET *packet= (MYSQLND_PACKET_GREET *) _packet; + + DBG_ENTER("php_mysqlnd_greet_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, sizeof(buf), "greeting", PROT_GREET_PACKET); + BAIL_IF_NO_MORE_DATA; + + packet->auth_plugin_data = packet->intern_auth_plugin_data; + packet->auth_plugin_data_len = sizeof(packet->intern_auth_plugin_data); + + if (packet->header.size < sizeof(buf)) { + /* + Null-terminate the string, so strdup can work even if the packets have a string at the end, + which is not ASCIIZ + */ + buf[packet->header.size] = '\0'; + } + + packet->protocol_version = uint1korr(p); + p++; + BAIL_IF_NO_MORE_DATA; + + if (ERROR_MARKER == packet->protocol_version) { + php_mysqlnd_read_error_from_line(p, packet->header.size - 1, + packet->error, sizeof(packet->error), + &packet->error_no, packet->sqlstate + TSRMLS_CC); + /* + The server doesn't send sqlstate in the greet packet. + It's a bug#26426 , so we have to set it correctly ourselves. + It's probably "Too many connections, which has SQL state 08004". + */ + if (packet->error_no == 1040) { + memcpy(packet->sqlstate, "08004", MYSQLND_SQLSTATE_LENGTH); + } + DBG_RETURN(PASS); + } + + packet->server_version = estrdup((char *)p); + p+= strlen(packet->server_version) + 1; /* eat the '\0' */ + BAIL_IF_NO_MORE_DATA; + + packet->thread_id = uint4korr(p); + p+=4; + BAIL_IF_NO_MORE_DATA; + + memcpy(packet->auth_plugin_data, p, SCRAMBLE_LENGTH_323); + p+= SCRAMBLE_LENGTH_323; + BAIL_IF_NO_MORE_DATA; + + /* pad1 */ + p++; + BAIL_IF_NO_MORE_DATA; + + packet->server_capabilities = uint2korr(p); + p+= 2; + BAIL_IF_NO_MORE_DATA; + + packet->charset_no = uint1korr(p); + p++; + BAIL_IF_NO_MORE_DATA; + + packet->server_status = uint2korr(p); + p+= 2; + BAIL_IF_NO_MORE_DATA; + + /* pad2 */ + pad_start = p; + p+= 13; + BAIL_IF_NO_MORE_DATA; + + if ((size_t) (p - buf) < packet->header.size) { + /* auth_plugin_data is split into two parts */ + memcpy(packet->auth_plugin_data + SCRAMBLE_LENGTH_323, p, SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323); + p+= SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323; + p++; /* 0x0 at the end of the scramble and thus last byte in the packet in 5.1 and previous */ + } else { + packet->pre41 = TRUE; + } + + /* Is this a 5.5+ server ? */ + if ((size_t) (p - buf) < packet->header.size) { + /* backtrack one byte, the 0x0 at the end of the scramble in 5.1 and previous */ + p--; + + /* Additional 16 bits for server capabilities */ + packet->server_capabilities |= uint2korr(pad_start) << 16; + /* And a length of the server scramble in one byte */ + packet->auth_plugin_data_len = uint1korr(pad_start + 2); + if (packet->auth_plugin_data_len > SCRAMBLE_LENGTH) { + /* more data*/ + zend_uchar * new_auth_plugin_data = emalloc(packet->auth_plugin_data_len); + if (!new_auth_plugin_data) { + goto premature_end; + } + /* copy what we already have */ + memcpy(new_auth_plugin_data, packet->auth_plugin_data, SCRAMBLE_LENGTH); + /* add additional scramble data 5.5+ sent us */ + memcpy(new_auth_plugin_data + SCRAMBLE_LENGTH, p, packet->auth_plugin_data_len - SCRAMBLE_LENGTH); + p+= (packet->auth_plugin_data_len - SCRAMBLE_LENGTH); + packet->auth_plugin_data = new_auth_plugin_data; + } + } + + if (packet->server_capabilities & CLIENT_PLUGIN_AUTH) { + BAIL_IF_NO_MORE_DATA; + /* The server is 5.5.x and supports authentication plugins */ + packet->auth_protocol = estrdup((char *)p); + p+= strlen(packet->auth_protocol) + 1; /* eat the '\0' */ + } + + DBG_INF_FMT("proto=%u server=%s thread_id=%u", + packet->protocol_version, packet->server_version, packet->thread_id); + + DBG_INF_FMT("server_capabilities=%u charset_no=%u server_status=%i auth_protocol=%s scramble_length=%u", + packet->server_capabilities, packet->charset_no, packet->server_status, + packet->auth_protocol? packet->auth_protocol:"n/a", packet->auth_plugin_data_len); + + DBG_RETURN(PASS); +premature_end: + DBG_ERR_FMT("GREET packet %d bytes shorter than expected", p - begin - packet->header.size); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "GREET packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected", + p - begin - packet->header.size); + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ php_mysqlnd_greet_free_mem */ +static +void php_mysqlnd_greet_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC) +{ + MYSQLND_PACKET_GREET *p= (MYSQLND_PACKET_GREET *) _packet; + if (p->server_version) { + efree(p->server_version); + p->server_version = NULL; + } + if (p->auth_plugin_data && p->auth_plugin_data != p->intern_auth_plugin_data) { + efree(p->auth_plugin_data); + p->auth_plugin_data = NULL; + } + if (p->auth_protocol) { + efree(p->auth_protocol); + p->auth_protocol = NULL; + } + if (!stack_allocation) { + mnd_pefree(p, p->header.persistent); + } +} +/* }}} */ + + +#define AUTH_WRITE_BUFFER_LEN (MYSQLND_HEADER_SIZE + MYSQLND_MAX_ALLOWED_USER_LEN + SCRAMBLE_LENGTH + MYSQLND_MAX_ALLOWED_DB_LEN + 1 + 1024) + +/* {{{ php_mysqlnd_auth_write */ +static +size_t php_mysqlnd_auth_write(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + zend_uchar buffer[AUTH_WRITE_BUFFER_LEN]; + zend_uchar *p = buffer + MYSQLND_HEADER_SIZE; /* start after the header */ + int len; + MYSQLND_PACKET_AUTH * packet= (MYSQLND_PACKET_AUTH *) _packet; + + DBG_ENTER("php_mysqlnd_auth_write"); + + if (!packet->is_change_user_packet) { + int4store(p, packet->client_flags); + p+= 4; + + int4store(p, packet->max_packet_size); + p+= 4; + + int1store(p, packet->charset_no); + p++; + + memset(p, 0, 23); /* filler */ + p+= 23; + } + + if (packet->send_auth_data || packet->is_change_user_packet) { + len = MIN(strlen(packet->user), MYSQLND_MAX_ALLOWED_USER_LEN); + memcpy(p, packet->user, len); + p+= len; + *p++ = '\0'; + + /* defensive coding */ + if (packet->auth_data == NULL) { + packet->auth_data_len = 0; + } + if (packet->auth_data_len > 0xFF) { + const char * const msg = "Authentication data too long. " + "Won't fit into the buffer and will be truncated. Authentication will thus fail"; + SET_CLIENT_ERROR(*conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, msg); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", msg); + DBG_RETURN(0); + } + + int1store(p, packet->auth_data_len); + ++p; +/*!!!!! is the buffer big enough ??? */ + if ((sizeof(buffer) - (p - buffer)) < packet->auth_data_len) { + DBG_ERR("the stack buffer was not enough!!"); + DBG_RETURN(0); + } + if (packet->auth_data_len) { + memcpy(p, packet->auth_data, packet->auth_data_len); + p+= packet->auth_data_len; + } + + if (packet->db) { + /* CLIENT_CONNECT_WITH_DB should have been set */ + size_t real_db_len = MIN(MYSQLND_MAX_ALLOWED_DB_LEN, packet->db_len); + memcpy(p, packet->db, real_db_len); + p+= real_db_len; + *p++= '\0'; + } else if (packet->is_change_user_packet) { + *p++= '\0'; + } + /* no \0 for no DB */ + + if (packet->is_change_user_packet) { + if (packet->charset_no) { + int2store(p, packet->charset_no); + p+= 2; + } + } + + if (packet->auth_plugin_name) { + size_t len = MIN(strlen(packet->auth_plugin_name), sizeof(buffer) - (p - buffer) - 1); + memcpy(p, packet->auth_plugin_name, len); + p+= len; + *p++= '\0'; + } + } + if (packet->is_change_user_packet) { + if (PASS != conn->m->simple_command(conn, COM_CHANGE_USER, buffer + MYSQLND_HEADER_SIZE, p - buffer - MYSQLND_HEADER_SIZE, + PROT_LAST /* the caller will handle the OK packet */, + packet->silent, TRUE TSRMLS_CC)) { + DBG_RETURN(0); + } + DBG_RETURN(p - buffer - MYSQLND_HEADER_SIZE); + } else { + size_t sent = conn->net->m.send_ex(conn->net, buffer, p - buffer - MYSQLND_HEADER_SIZE, conn->stats, conn->error_info TSRMLS_CC); + if (!sent) { + CONN_SET_STATE(conn, CONN_QUIT_SENT); + } + DBG_RETURN(sent); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_auth_free_mem */ +static +void php_mysqlnd_auth_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC) +{ + if (!stack_allocation) { + MYSQLND_PACKET_AUTH * p = (MYSQLND_PACKET_AUTH *) _packet; + mnd_pefree(p, p->header.persistent); + } +} +/* }}} */ + + +#define AUTH_RESP_BUFFER_SIZE 2048 + +/* {{{ php_mysqlnd_auth_response_read */ +static enum_func_status +php_mysqlnd_auth_response_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + zend_uchar local_buf[AUTH_RESP_BUFFER_SIZE]; + size_t buf_len = conn->net->cmd_buffer.buffer? conn->net->cmd_buffer.length: AUTH_RESP_BUFFER_SIZE; + zend_uchar *buf = conn->net->cmd_buffer.buffer? (zend_uchar *) conn->net->cmd_buffer.buffer : local_buf; + zend_uchar *p = buf; + zend_uchar *begin = buf; + unsigned long i; + register MYSQLND_PACKET_AUTH_RESPONSE * packet= (MYSQLND_PACKET_AUTH_RESPONSE *) _packet; + + DBG_ENTER("php_mysqlnd_auth_response_read"); + + /* leave space for terminating safety \0 */ + buf_len--; + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "OK", PROT_OK_PACKET); + BAIL_IF_NO_MORE_DATA; + + /* + zero-terminate the buffer for safety. We are sure there is place for the \0 + because buf_len is -1 the size of the buffer pointed + */ + buf[packet->header.size] = '\0'; + + /* Should be always 0x0 or ERROR_MARKER for error */ + packet->response_code = uint1korr(p); + p++; + BAIL_IF_NO_MORE_DATA; + + if (ERROR_MARKER == packet->response_code) { + php_mysqlnd_read_error_from_line(p, packet->header.size - 1, + packet->error, sizeof(packet->error), + &packet->error_no, packet->sqlstate + TSRMLS_CC); + DBG_RETURN(PASS); + } + if (0xFE == packet->response_code) { + /* Authentication Switch Response */ + if (packet->header.size > (size_t) (p - buf)) { + packet->new_auth_protocol = mnd_pestrdup((char *)p, FALSE); + packet->new_auth_protocol_len = strlen(packet->new_auth_protocol); + p+= packet->new_auth_protocol_len + 1; /* +1 for the \0 */ + + packet->new_auth_protocol_data_len = packet->header.size - (size_t) (p - buf); + if (packet->new_auth_protocol_data_len) { + packet->new_auth_protocol_data = mnd_emalloc(packet->new_auth_protocol_data_len); + memcpy(packet->new_auth_protocol_data, p, packet->new_auth_protocol_data_len); + } + DBG_INF_FMT("The server requested switching auth plugin to : %s", packet->new_auth_protocol); + DBG_INF_FMT("Server salt : [%*s]", packet->new_auth_protocol_data_len, packet->new_auth_protocol_data); + } + } else { + /* Everything was fine! */ + packet->affected_rows = php_mysqlnd_net_field_length_ll(&p); + BAIL_IF_NO_MORE_DATA; + + packet->last_insert_id = php_mysqlnd_net_field_length_ll(&p); + BAIL_IF_NO_MORE_DATA; + + packet->server_status = uint2korr(p); + p+= 2; + BAIL_IF_NO_MORE_DATA; + + packet->warning_count = uint2korr(p); + p+= 2; + BAIL_IF_NO_MORE_DATA; + + /* There is a message */ + if (packet->header.size > (size_t) (p - buf) && (i = php_mysqlnd_net_field_length(&p))) { + packet->message_len = MIN(i, buf_len - (p - begin)); + packet->message = mnd_pestrndup((char *)p, packet->message_len, FALSE); + } else { + packet->message = NULL; + packet->message_len = 0; + } + + DBG_INF_FMT("OK packet: aff_rows=%lld last_ins_id=%ld server_status=%u warnings=%u", + packet->affected_rows, packet->last_insert_id, packet->server_status, + packet->warning_count); + } + + DBG_RETURN(PASS); +premature_end: + DBG_ERR_FMT("OK packet %d bytes shorter than expected", p - begin - packet->header.size); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "AUTH_RESPONSE packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected", + p - begin - packet->header.size); + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ php_mysqlnd_auth_response_free_mem */ +static void +php_mysqlnd_auth_response_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC) +{ + MYSQLND_PACKET_AUTH_RESPONSE * p = (MYSQLND_PACKET_AUTH_RESPONSE *) _packet; + if (p->message) { + mnd_efree(p->message); + p->message = NULL; + } + if (p->new_auth_protocol) { + mnd_efree(p->new_auth_protocol); + p->new_auth_protocol = NULL; + } + p->new_auth_protocol_len = 0; + + if (p->new_auth_protocol_data) { + mnd_efree(p->new_auth_protocol_data); + p->new_auth_protocol_data = NULL; + } + p->new_auth_protocol_data_len = 0; + + if (!stack_allocation) { + mnd_pefree(p, p->header.persistent); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_change_auth_response_write */ +static size_t +php_mysqlnd_change_auth_response_write(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + MYSQLND_PACKET_CHANGE_AUTH_RESPONSE *packet= (MYSQLND_PACKET_CHANGE_AUTH_RESPONSE *) _packet; + zend_uchar * buffer = conn->net->cmd_buffer.length >= packet->auth_data_len? conn->net->cmd_buffer.buffer : mnd_emalloc(packet->auth_data_len); + zend_uchar *p = buffer + MYSQLND_HEADER_SIZE; /* start after the header */ + + DBG_ENTER("php_mysqlnd_change_auth_response_write"); + + if (packet->auth_data_len) { + memcpy(p, packet->auth_data, packet->auth_data_len); + p+= packet->auth_data_len; + } + + { + size_t sent = conn->net->m.send_ex(conn->net, buffer, p - buffer - MYSQLND_HEADER_SIZE, conn->stats, conn->error_info TSRMLS_CC); + if (buffer != conn->net->cmd_buffer.buffer) { + mnd_efree(buffer); + } + if (!sent) { + CONN_SET_STATE(conn, CONN_QUIT_SENT); + } + DBG_RETURN(sent); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_change_auth_response_free_mem */ +static void +php_mysqlnd_change_auth_response_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC) +{ + if (!stack_allocation) { + MYSQLND_PACKET_CHANGE_AUTH_RESPONSE * p = (MYSQLND_PACKET_CHANGE_AUTH_RESPONSE *) _packet; + mnd_pefree(p, p->header.persistent); + } +} +/* }}} */ + + +#define OK_BUFFER_SIZE 2048 + +/* {{{ php_mysqlnd_ok_read */ +static enum_func_status +php_mysqlnd_ok_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + zend_uchar local_buf[OK_BUFFER_SIZE]; + size_t buf_len = conn->net->cmd_buffer.buffer? conn->net->cmd_buffer.length : OK_BUFFER_SIZE; + zend_uchar *buf = conn->net->cmd_buffer.buffer? (zend_uchar *) conn->net->cmd_buffer.buffer : local_buf; + zend_uchar *p = buf; + zend_uchar *begin = buf; + unsigned long i; + register MYSQLND_PACKET_OK *packet= (MYSQLND_PACKET_OK *) _packet; + + DBG_ENTER("php_mysqlnd_ok_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "OK", PROT_OK_PACKET); + BAIL_IF_NO_MORE_DATA; + + /* Should be always 0x0 or ERROR_MARKER for error */ + packet->field_count = uint1korr(p); + p++; + BAIL_IF_NO_MORE_DATA; + + if (ERROR_MARKER == packet->field_count) { + php_mysqlnd_read_error_from_line(p, packet->header.size - 1, + packet->error, sizeof(packet->error), + &packet->error_no, packet->sqlstate + TSRMLS_CC); + DBG_RETURN(PASS); + } + /* Everything was fine! */ + packet->affected_rows = php_mysqlnd_net_field_length_ll(&p); + BAIL_IF_NO_MORE_DATA; + + packet->last_insert_id = php_mysqlnd_net_field_length_ll(&p); + BAIL_IF_NO_MORE_DATA; + + packet->server_status = uint2korr(p); + p+= 2; + BAIL_IF_NO_MORE_DATA; + + packet->warning_count = uint2korr(p); + p+= 2; + BAIL_IF_NO_MORE_DATA; + + /* There is a message */ + if (packet->header.size > (size_t) (p - buf) && (i = php_mysqlnd_net_field_length(&p))) { + packet->message_len = MIN(i, buf_len - (p - begin)); + packet->message = mnd_pestrndup((char *)p, packet->message_len, FALSE); + } else { + packet->message = NULL; + packet->message_len = 0; + } + + DBG_INF_FMT("OK packet: aff_rows=%lld last_ins_id=%ld server_status=%u warnings=%u", + packet->affected_rows, packet->last_insert_id, packet->server_status, + packet->warning_count); + + BAIL_IF_NO_MORE_DATA; + + DBG_RETURN(PASS); +premature_end: + DBG_ERR_FMT("OK packet %d bytes shorter than expected", p - begin - packet->header.size); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "OK packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected", + p - begin - packet->header.size); + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ php_mysqlnd_ok_free_mem */ +static void +php_mysqlnd_ok_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC) +{ + MYSQLND_PACKET_OK *p= (MYSQLND_PACKET_OK *) _packet; + if (p->message) { + mnd_efree(p->message); + p->message = NULL; + } + if (!stack_allocation) { + mnd_pefree(p, p->header.persistent); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_eof_read */ +static enum_func_status +php_mysqlnd_eof_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + /* + EOF packet is since 4.1 five bytes long, + but we can get also an error, make it bigger. + + Error : error_code + '#' + sqlstate + MYSQLND_ERRMSG_SIZE + */ + MYSQLND_PACKET_EOF *packet= (MYSQLND_PACKET_EOF *) _packet; + size_t buf_len = conn->net->cmd_buffer.length; + zend_uchar *buf = (zend_uchar *) conn->net->cmd_buffer.buffer; + zend_uchar *p = buf; + zend_uchar *begin = buf; + + DBG_ENTER("php_mysqlnd_eof_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "EOF", PROT_EOF_PACKET); + BAIL_IF_NO_MORE_DATA; + + /* Should be always EODATA_MARKER */ + packet->field_count = uint1korr(p); + p++; + BAIL_IF_NO_MORE_DATA; + + if (ERROR_MARKER == packet->field_count) { + php_mysqlnd_read_error_from_line(p, packet->header.size - 1, + packet->error, sizeof(packet->error), + &packet->error_no, packet->sqlstate + TSRMLS_CC); + DBG_RETURN(PASS); + } + + /* + 4.1 sends 1 byte EOF packet after metadata of + PREPARE/EXECUTE but 5 bytes after the result. This is not + according to the Docs@Forge!!! + */ + if (packet->header.size > 1) { + packet->warning_count = uint2korr(p); + p+= 2; + BAIL_IF_NO_MORE_DATA; + + packet->server_status = uint2korr(p); + p+= 2; + BAIL_IF_NO_MORE_DATA; + } else { + packet->warning_count = 0; + packet->server_status = 0; + } + + BAIL_IF_NO_MORE_DATA; + + DBG_INF_FMT("EOF packet: fields=%u status=%u warnings=%u", + packet->field_count, packet->server_status, packet->warning_count); + + DBG_RETURN(PASS); +premature_end: + DBG_ERR_FMT("EOF packet %d bytes shorter than expected", p - begin - packet->header.size); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "EOF packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected", + p - begin - packet->header.size); + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ php_mysqlnd_eof_free_mem */ +static +void php_mysqlnd_eof_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC) +{ + if (!stack_allocation) { + mnd_pefree(_packet, ((MYSQLND_PACKET_EOF *)_packet)->header.persistent); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_cmd_write */ +size_t php_mysqlnd_cmd_write(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + /* Let's have some space, which we can use, if not enough, we will allocate new buffer */ + MYSQLND_PACKET_COMMAND * packet= (MYSQLND_PACKET_COMMAND *) _packet; + MYSQLND_NET * net = conn->net; + unsigned int error_reporting = EG(error_reporting); + size_t sent = 0; + + DBG_ENTER("php_mysqlnd_cmd_write"); + /* + Reset packet_no, or we will get bad handshake! + Every command starts a new TX and packet numbers are reset to 0. + */ + net->packet_no = 0; + net->compressed_envelope_packet_no = 0; /* this is for the response */ + + if (error_reporting) { + EG(error_reporting) = 0; + } + + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_PACKETS_SENT_CMD); + +#ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND + net->m.consume_uneaten_data(net, packet->command TSRMLS_CC); +#endif + + if (!packet->argument || !packet->arg_len) { + zend_uchar buffer[MYSQLND_HEADER_SIZE + 1]; + + int1store(buffer + MYSQLND_HEADER_SIZE, packet->command); + sent = net->m.send_ex(net, buffer, 1, conn->stats, conn->error_info TSRMLS_CC); + } else { + size_t tmp_len = packet->arg_len + 1 + MYSQLND_HEADER_SIZE; + zend_uchar *tmp, *p; + tmp = (tmp_len > net->cmd_buffer.length)? mnd_emalloc(tmp_len):net->cmd_buffer.buffer; + if (!tmp) { + goto end; + } + p = tmp + MYSQLND_HEADER_SIZE; /* skip the header */ + + int1store(p, packet->command); + p++; + + memcpy(p, packet->argument, packet->arg_len); + + sent = net->m.send_ex(net, tmp, tmp_len - MYSQLND_HEADER_SIZE, conn->stats, conn->error_info TSRMLS_CC); + if (tmp != net->cmd_buffer.buffer) { + MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_CMD_BUFFER_TOO_SMALL); + mnd_efree(tmp); + } + } +end: + if (error_reporting) { + /* restore error reporting */ + EG(error_reporting) = error_reporting; + } + if (!sent) { + CONN_SET_STATE(conn, CONN_QUIT_SENT); + } + DBG_RETURN(sent); +} +/* }}} */ + + +/* {{{ php_mysqlnd_cmd_free_mem */ +static +void php_mysqlnd_cmd_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC) +{ + if (!stack_allocation) { + MYSQLND_PACKET_COMMAND * p = (MYSQLND_PACKET_COMMAND *) _packet; + mnd_pefree(p, p->header.persistent); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_rset_header_read */ +static enum_func_status +php_mysqlnd_rset_header_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + enum_func_status ret = PASS; + size_t buf_len = conn->net->cmd_buffer.length; + zend_uchar *buf = (zend_uchar *) conn->net->cmd_buffer.buffer; + zend_uchar *p = buf; + zend_uchar *begin = buf; + size_t len; + MYSQLND_PACKET_RSET_HEADER *packet= (MYSQLND_PACKET_RSET_HEADER *) _packet; + + DBG_ENTER("php_mysqlnd_rset_header_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "resultset header", PROT_RSET_HEADER_PACKET); + BAIL_IF_NO_MORE_DATA; + + /* + Don't increment. First byte is ERROR_MARKER on error, but otherwise is starting byte + of encoded sequence for length. + */ + if (ERROR_MARKER == *p) { + /* Error */ + p++; + BAIL_IF_NO_MORE_DATA; + php_mysqlnd_read_error_from_line(p, packet->header.size - 1, + packet->error_info.error, sizeof(packet->error_info.error), + &packet->error_info.error_no, packet->error_info.sqlstate + TSRMLS_CC); + DBG_RETURN(PASS); + } + + packet->field_count = php_mysqlnd_net_field_length(&p); + BAIL_IF_NO_MORE_DATA; + + switch (packet->field_count) { + case MYSQLND_NULL_LENGTH: + DBG_INF("LOAD LOCAL"); + /* + First byte in the packet is the field count. + Thus, the name is size - 1. And we add 1 for a trailing \0. + Because we have BAIL_IF_NO_MORE_DATA before the switch, we are guaranteed + that packet->header.size is > 0. Which means that len can't underflow, that + would lead to 0 byte allocation but 2^32 or 2^64 bytes copied. + */ + len = packet->header.size - 1; + packet->info_or_local_file = mnd_emalloc(len + 1); + if (packet->info_or_local_file) { + memcpy(packet->info_or_local_file, p, len); + packet->info_or_local_file[len] = '\0'; + packet->info_or_local_file_len = len; + } else { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + } + break; + case 0x00: + DBG_INF("UPSERT"); + packet->affected_rows = php_mysqlnd_net_field_length_ll(&p); + BAIL_IF_NO_MORE_DATA; + + packet->last_insert_id = php_mysqlnd_net_field_length_ll(&p); + BAIL_IF_NO_MORE_DATA; + + packet->server_status = uint2korr(p); + p+=2; + BAIL_IF_NO_MORE_DATA; + + packet->warning_count = uint2korr(p); + p+=2; + BAIL_IF_NO_MORE_DATA; + /* Check for additional textual data */ + if (packet->header.size > (size_t) (p - buf) && (len = php_mysqlnd_net_field_length(&p))) { + packet->info_or_local_file = mnd_emalloc(len + 1); + if (packet->info_or_local_file) { + memcpy(packet->info_or_local_file, p, len); + packet->info_or_local_file[len] = '\0'; + packet->info_or_local_file_len = len; + } else { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + } + } + DBG_INF_FMT("affected_rows=%llu last_insert_id=%llu server_status=%u warning_count=%u", + packet->affected_rows, packet->last_insert_id, + packet->server_status, packet->warning_count); + break; + default: + DBG_INF("SELECT"); + /* Result set */ + break; + } + BAIL_IF_NO_MORE_DATA; + + DBG_RETURN(ret); +premature_end: + DBG_ERR_FMT("RSET_HEADER packet %d bytes shorter than expected", p - begin - packet->header.size); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "RSET_HEADER packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected", + p - begin - packet->header.size); + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ php_mysqlnd_rset_header_free_mem */ +static +void php_mysqlnd_rset_header_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC) +{ + MYSQLND_PACKET_RSET_HEADER *p= (MYSQLND_PACKET_RSET_HEADER *) _packet; + DBG_ENTER("php_mysqlnd_rset_header_free_mem"); + if (p->info_or_local_file) { + mnd_efree(p->info_or_local_file); + p->info_or_local_file = NULL; + } + if (!stack_allocation) { + mnd_pefree(p, p->header.persistent); + } + DBG_VOID_RETURN; +} +/* }}} */ + +static size_t rset_field_offsets[] = +{ + STRUCT_OFFSET(MYSQLND_FIELD, catalog), + STRUCT_OFFSET(MYSQLND_FIELD, catalog_length), + STRUCT_OFFSET(MYSQLND_FIELD, db), + STRUCT_OFFSET(MYSQLND_FIELD, db_length), + STRUCT_OFFSET(MYSQLND_FIELD, table), + STRUCT_OFFSET(MYSQLND_FIELD, table_length), + STRUCT_OFFSET(MYSQLND_FIELD, org_table), + STRUCT_OFFSET(MYSQLND_FIELD, org_table_length), + STRUCT_OFFSET(MYSQLND_FIELD, name), + STRUCT_OFFSET(MYSQLND_FIELD, name_length), + STRUCT_OFFSET(MYSQLND_FIELD, org_name), + STRUCT_OFFSET(MYSQLND_FIELD, org_name_length) +}; + + +/* {{{ php_mysqlnd_rset_field_read */ +static enum_func_status +php_mysqlnd_rset_field_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + /* Should be enough for the metadata of a single row */ + MYSQLND_PACKET_RES_FIELD *packet= (MYSQLND_PACKET_RES_FIELD *) _packet; + size_t buf_len = conn->net->cmd_buffer.length, total_len = 0; + zend_uchar *buf = (zend_uchar *) conn->net->cmd_buffer.buffer; + zend_uchar *p = buf; + zend_uchar *begin = buf; + char *root_ptr; + unsigned long len; + MYSQLND_FIELD *meta; + unsigned int i, field_count = sizeof(rset_field_offsets)/sizeof(size_t); + + DBG_ENTER("php_mysqlnd_rset_field_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "field", PROT_RSET_FLD_PACKET); + + if (packet->skip_parsing) { + DBG_RETURN(PASS); + } + + BAIL_IF_NO_MORE_DATA; + if (ERROR_MARKER == *p) { + /* Error */ + p++; + BAIL_IF_NO_MORE_DATA; + php_mysqlnd_read_error_from_line(p, packet->header.size - 1, + packet->error_info.error, sizeof(packet->error_info.error), + &packet->error_info.error_no, packet->error_info.sqlstate + TSRMLS_CC); + DBG_ERR_FMT("Server error : (%u) %s", packet->error_info.error_no, packet->error_info.error); + DBG_RETURN(PASS); + } else if (EODATA_MARKER == *p && packet->header.size < 8) { + /* Premature EOF. That should be COM_FIELD_LIST */ + DBG_INF("Premature EOF. That should be COM_FIELD_LIST"); + packet->stupid_list_fields_eof = TRUE; + DBG_RETURN(PASS); + } + + meta = packet->metadata; + + for (i = 0; i < field_count; i += 2) { + len = php_mysqlnd_net_field_length(&p); + BAIL_IF_NO_MORE_DATA; + switch ((len)) { + case 0: + *(const char **)(((char*)meta) + rset_field_offsets[i]) = mysqlnd_empty_string; + *(unsigned int *)(((char*)meta) + rset_field_offsets[i+1]) = 0; + break; + case MYSQLND_NULL_LENGTH: + goto faulty_or_fake; + default: + *(const char **)(((char *)meta) + rset_field_offsets[i]) = (const char *)p; + *(unsigned int *)(((char*)meta) + rset_field_offsets[i+1]) = len; + p += len; + total_len += len + 1; + break; + } + BAIL_IF_NO_MORE_DATA; + } + + /* 1 byte length */ + if (12 != *p) { + DBG_ERR_FMT("Protocol error. Server sent false length. Expected 12 got %d", (int) *p); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Protocol error. Server sent false length. Expected 12"); + } + p++; + BAIL_IF_NO_MORE_DATA; + + meta->charsetnr = uint2korr(p); + p += 2; + BAIL_IF_NO_MORE_DATA; + + meta->length = uint4korr(p); + p += 4; + BAIL_IF_NO_MORE_DATA; + + meta->type = uint1korr(p); + p += 1; + BAIL_IF_NO_MORE_DATA; + + meta->flags = uint2korr(p); + p += 2; + BAIL_IF_NO_MORE_DATA; + + meta->decimals = uint1korr(p); + p += 1; + BAIL_IF_NO_MORE_DATA; + + /* 2 byte filler */ + p +=2; + BAIL_IF_NO_MORE_DATA; + + /* Should we set NUM_FLAG (libmysql does it) ? */ + if ( + (meta->type <= MYSQL_TYPE_INT24 && + (meta->type != MYSQL_TYPE_TIMESTAMP || meta->length == 14 || meta->length == 8) + ) || meta->type == MYSQL_TYPE_YEAR) + { + meta->flags |= NUM_FLAG; + } + + + /* + def could be empty, thus don't allocate on the root. + NULL_LENGTH (0xFB) comes from COM_FIELD_LIST when the default value is NULL. + Otherwise the string is length encoded. + */ + if (packet->header.size > (size_t) (p - buf) && + (len = php_mysqlnd_net_field_length(&p)) && + len != MYSQLND_NULL_LENGTH) + { + BAIL_IF_NO_MORE_DATA; + DBG_INF_FMT("Def found, length %lu, persistent=%u", len, packet->persistent_alloc); + meta->def = mnd_pemalloc(len + 1, packet->persistent_alloc); + if (!meta->def) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(FAIL); + } + memcpy(meta->def, p, len); + meta->def[len] = '\0'; + meta->def_length = len; + p += len; + } + + DBG_INF_FMT("allocing root. persistent=%u", packet->persistent_alloc); + root_ptr = meta->root = mnd_pemalloc(total_len, packet->persistent_alloc); + if (!root_ptr) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(FAIL); + } + + meta->root_len = total_len; + /* Now do allocs */ + if (meta->catalog && meta->catalog != mysqlnd_empty_string) { + len = meta->catalog_length; + meta->catalog = memcpy(root_ptr, meta->catalog, len); + *(root_ptr +=len) = '\0'; + root_ptr++; + } + + if (meta->db && meta->db != mysqlnd_empty_string) { + len = meta->db_length; + meta->db = memcpy(root_ptr, meta->db, len); + *(root_ptr +=len) = '\0'; + root_ptr++; + } + + if (meta->table && meta->table != mysqlnd_empty_string) { + len = meta->table_length; + meta->table = memcpy(root_ptr, meta->table, len); + *(root_ptr +=len) = '\0'; + root_ptr++; + } + + if (meta->org_table && meta->org_table != mysqlnd_empty_string) { + len = meta->org_table_length; + meta->org_table = memcpy(root_ptr, meta->org_table, len); + *(root_ptr +=len) = '\0'; + root_ptr++; + } + + if (meta->name && meta->name != mysqlnd_empty_string) { + len = meta->name_length; + meta->name = memcpy(root_ptr, meta->name, len); + *(root_ptr +=len) = '\0'; + root_ptr++; + } + + if (meta->org_name && meta->org_name != mysqlnd_empty_string) { + len = meta->org_name_length; + meta->org_name = memcpy(root_ptr, meta->org_name, len); + *(root_ptr +=len) = '\0'; + root_ptr++; + } + + DBG_INF_FMT("FIELD=[%s.%s.%s]", meta->db? meta->db:"*NA*", meta->table? meta->table:"*NA*", + meta->name? meta->name:"*NA*"); + + DBG_RETURN(PASS); + +faulty_or_fake: + DBG_ERR_FMT("Protocol error. Server sent NULL_LENGTH. The server is faulty"); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Protocol error. Server sent NULL_LENGTH." + " The server is faulty"); + DBG_RETURN(FAIL); +premature_end: + DBG_ERR_FMT("RSET field packet %d bytes shorter than expected", p - begin - packet->header.size); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Result set field packet "MYSQLND_SZ_T_SPEC" bytes " + "shorter than expected", p - begin - packet->header.size); + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ php_mysqlnd_rset_field_free_mem */ +static +void php_mysqlnd_rset_field_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC) +{ + MYSQLND_PACKET_RES_FIELD *p= (MYSQLND_PACKET_RES_FIELD *) _packet; + /* p->metadata was passed to us as temporal buffer */ + if (!stack_allocation) { + mnd_pefree(p, p->header.persistent); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_read_row_ex */ +static enum_func_status +php_mysqlnd_read_row_ex(MYSQLND_CONN_DATA * conn, MYSQLND_MEMORY_POOL * result_set_memory_pool, + MYSQLND_MEMORY_POOL_CHUNK ** buffer, + size_t * data_size, zend_bool persistent_alloc, + unsigned int prealloc_more_bytes TSRMLS_DC) +{ + enum_func_status ret = PASS; + MYSQLND_PACKET_HEADER header; + zend_uchar * p = NULL; + zend_bool first_iteration = TRUE; + + DBG_ENTER("php_mysqlnd_read_row_ex"); + + /* + To ease the process the server splits everything in packets up to 2^24 - 1. + Even in the case the payload is evenly divisible by this value, the last + packet will be empty, namely 0 bytes. Thus, we can read every packet and ask + for next one if they have 2^24 - 1 sizes. But just read the header of a + zero-length byte, don't read the body, there is no such. + */ + + *data_size = prealloc_more_bytes; + while (1) { + if (FAIL == mysqlnd_read_header(conn->net, &header, conn->stats, conn->error_info TSRMLS_CC)) { + ret = FAIL; + break; + } + + *data_size += header.size; + + if (first_iteration) { + first_iteration = FALSE; + /* + We need a trailing \0 for the last string, in case of text-mode, + to be able to implement read-only variables. Thus, we add + 1. + */ + *buffer = result_set_memory_pool->get_chunk(result_set_memory_pool, *data_size + 1 TSRMLS_CC); + if (!*buffer) { + ret = FAIL; + break; + } + p = (*buffer)->ptr; + } else if (!first_iteration) { + /* Empty packet after MYSQLND_MAX_PACKET_SIZE packet. That's ok, break */ + if (!header.size) { + break; + } + + /* + We have to realloc the buffer. + + We need a trailing \0 for the last string, in case of text-mode, + to be able to implement read-only variables. + */ + if (FAIL == (*buffer)->resize_chunk((*buffer), *data_size + 1 TSRMLS_CC)) { + SET_OOM_ERROR(*conn->error_info); + ret = FAIL; + break; + } + /* The position could have changed, recalculate */ + p = (*buffer)->ptr + (*data_size - header.size); + } + + if (PASS != (ret = conn->net->m.receive_ex(conn->net, p, header.size, conn->stats, conn->error_info TSRMLS_CC))) { + DBG_ERR("Empty row packet body"); + php_error(E_WARNING, "Empty row packet body"); + break; + } + + if (header.size < MYSQLND_MAX_PACKET_SIZE) { + break; + } + } + if (ret == FAIL && *buffer) { + (*buffer)->free_chunk((*buffer) TSRMLS_CC); + *buffer = NULL; + } + *data_size -= prealloc_more_bytes; + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ php_mysqlnd_rowp_read_binary_protocol */ +enum_func_status +php_mysqlnd_rowp_read_binary_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, + unsigned int field_count, MYSQLND_FIELD *fields_metadata, + zend_bool as_unicode, zend_bool as_int_or_float, + MYSQLND_STATS * stats TSRMLS_DC) +{ + unsigned int i; + zend_uchar * p = row_buffer->ptr; + zend_uchar * null_ptr, bit; + zval **current_field, **end_field, **start_field; + + DBG_ENTER("php_mysqlnd_rowp_read_binary_protocol"); + + if (!fields) { + DBG_RETURN(FAIL); + } + + end_field = (start_field = fields) + field_count; + + /* skip the first byte, not EODATA_MARKER -> 0x0, status */ + p++; + null_ptr= p; + p += (field_count + 9)/8; /* skip null bits */ + bit = 4; /* first 2 bits are reserved */ + + for (i = 0, current_field = start_field; current_field < end_field; current_field++, i++) { + DBG_INF("Directly creating zval"); + MAKE_STD_ZVAL(*current_field); + if (!*current_field) { + DBG_RETURN(FAIL); + } + } + + for (i = 0, current_field = start_field; current_field < end_field; current_field++, i++) { + enum_mysqlnd_collected_stats statistic; + zend_uchar * orig_p = p; + + DBG_INF_FMT("Into zval=%p decoding column %u [%s.%s.%s] type=%u field->flags&unsigned=%u flags=%u is_bit=%u as_unicode=%u", + *current_field, i, + fields_metadata[i].db, fields_metadata[i].table, fields_metadata[i].name, fields_metadata[i].type, + fields_metadata[i].flags & UNSIGNED_FLAG, fields_metadata[i].flags, fields_metadata[i].type == MYSQL_TYPE_BIT, as_unicode); + if (*null_ptr & bit) { + DBG_INF("It's null"); + ZVAL_NULL(*current_field); + statistic = STAT_BINARY_TYPE_FETCHED_NULL; + } else { + enum_mysqlnd_field_types type = fields_metadata[i].type; + mysqlnd_ps_fetch_functions[type].func(*current_field, &fields_metadata[i], 0, &p, as_unicode TSRMLS_CC); + + if (MYSQLND_G(collect_statistics)) { + switch (fields_metadata[i].type) { + case MYSQL_TYPE_DECIMAL: statistic = STAT_BINARY_TYPE_FETCHED_DECIMAL; break; + case MYSQL_TYPE_TINY: statistic = STAT_BINARY_TYPE_FETCHED_INT8; break; + case MYSQL_TYPE_SHORT: statistic = STAT_BINARY_TYPE_FETCHED_INT16; break; + case MYSQL_TYPE_LONG: statistic = STAT_BINARY_TYPE_FETCHED_INT32; break; + case MYSQL_TYPE_FLOAT: statistic = STAT_BINARY_TYPE_FETCHED_FLOAT; break; + case MYSQL_TYPE_DOUBLE: statistic = STAT_BINARY_TYPE_FETCHED_DOUBLE; break; + case MYSQL_TYPE_NULL: statistic = STAT_BINARY_TYPE_FETCHED_NULL; break; + case MYSQL_TYPE_TIMESTAMP: statistic = STAT_BINARY_TYPE_FETCHED_TIMESTAMP; break; + case MYSQL_TYPE_LONGLONG: statistic = STAT_BINARY_TYPE_FETCHED_INT64; break; + case MYSQL_TYPE_INT24: statistic = STAT_BINARY_TYPE_FETCHED_INT24; break; + case MYSQL_TYPE_DATE: statistic = STAT_BINARY_TYPE_FETCHED_DATE; break; + case MYSQL_TYPE_TIME: statistic = STAT_BINARY_TYPE_FETCHED_TIME; break; + case MYSQL_TYPE_DATETIME: statistic = STAT_BINARY_TYPE_FETCHED_DATETIME; break; + case MYSQL_TYPE_YEAR: statistic = STAT_BINARY_TYPE_FETCHED_YEAR; break; + case MYSQL_TYPE_NEWDATE: statistic = STAT_BINARY_TYPE_FETCHED_DATE; break; + case MYSQL_TYPE_VARCHAR: statistic = STAT_BINARY_TYPE_FETCHED_STRING; break; + case MYSQL_TYPE_BIT: statistic = STAT_BINARY_TYPE_FETCHED_BIT; break; + case MYSQL_TYPE_NEWDECIMAL: statistic = STAT_BINARY_TYPE_FETCHED_DECIMAL; break; + case MYSQL_TYPE_ENUM: statistic = STAT_BINARY_TYPE_FETCHED_ENUM; break; + case MYSQL_TYPE_SET: statistic = STAT_BINARY_TYPE_FETCHED_SET; break; + case MYSQL_TYPE_TINY_BLOB: statistic = STAT_BINARY_TYPE_FETCHED_BLOB; break; + case MYSQL_TYPE_MEDIUM_BLOB:statistic = STAT_BINARY_TYPE_FETCHED_BLOB; break; + case MYSQL_TYPE_LONG_BLOB: statistic = STAT_BINARY_TYPE_FETCHED_BLOB; break; + case MYSQL_TYPE_BLOB: statistic = STAT_BINARY_TYPE_FETCHED_BLOB; break; + case MYSQL_TYPE_VAR_STRING: statistic = STAT_BINARY_TYPE_FETCHED_STRING; break; + case MYSQL_TYPE_STRING: statistic = STAT_BINARY_TYPE_FETCHED_STRING; break; + case MYSQL_TYPE_GEOMETRY: statistic = STAT_BINARY_TYPE_FETCHED_GEOMETRY; break; + default: statistic = STAT_BINARY_TYPE_FETCHED_OTHER; break; + } + } + } + MYSQLND_INC_CONN_STATISTIC_W_VALUE2(stats, statistic, 1, + STAT_BYTES_RECEIVED_PURE_DATA_PS, + (Z_TYPE_PP(current_field) == IS_STRING)? + Z_STRLEN_PP(current_field) : (p - orig_p)); + + if (!((bit<<=1) & 255)) { + bit = 1; /* to the following byte */ + null_ptr++; + } + } + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ php_mysqlnd_rowp_read_text_protocol */ +enum_func_status +php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, + unsigned int field_count, MYSQLND_FIELD *fields_metadata, + zend_bool as_unicode, zend_bool as_int_or_float, + MYSQLND_STATS * stats TSRMLS_DC) +{ + unsigned int i; + zend_bool last_field_was_string = FALSE; + zval **current_field, **end_field, **start_field; + zend_uchar * p = row_buffer->ptr; + size_t data_size = row_buffer->app; + zend_uchar * bit_area = (zend_uchar*) row_buffer->ptr + data_size + 1; /* we allocate from here */ + + DBG_ENTER("php_mysqlnd_rowp_read_text_protocol"); + + if (!fields) { + DBG_RETURN(FAIL); + } + + end_field = (start_field = fields) + field_count; + + for (i = 0, current_field = start_field; current_field < end_field; current_field++, i++) { + DBG_INF("Directly creating zval"); + MAKE_STD_ZVAL(*current_field); + if (!*current_field) { + DBG_RETURN(FAIL); + } + } + + for (i = 0, current_field = start_field; current_field < end_field; current_field++, i++) { + /* Don't reverse the order. It is significant!*/ + zend_uchar *this_field_len_pos = p; + /* php_mysqlnd_net_field_length() call should be after *this_field_len_pos = p; */ + unsigned long len = php_mysqlnd_net_field_length(&p); + + if (current_field > start_field && last_field_was_string) { + /* + Normal queries: + We have to put \0 now to the end of the previous field, if it was + a string. IS_NULL doesn't matter. Because we have already read our + length, then we can overwrite it in the row buffer. + This statement terminates the previous field, not the current one. + + NULL_LENGTH is encoded in one byte, so we can stick a \0 there. + Any string's length is encoded in at least one byte, so we can stick + a \0 there. + */ + + *this_field_len_pos = '\0'; + } + + /* NULL or NOT NULL, this is the question! */ + if (len == MYSQLND_NULL_LENGTH) { + ZVAL_NULL(*current_field); + last_field_was_string = FALSE; + } else { +#if MYSQLND_UNICODE || defined(MYSQLND_STRING_TO_INT_CONVERSION) + struct st_mysqlnd_perm_bind perm_bind = + mysqlnd_ps_fetch_functions[fields_metadata[i].type]; +#endif + if (MYSQLND_G(collect_statistics)) { + enum_mysqlnd_collected_stats statistic; + switch (fields_metadata[i].type) { + case MYSQL_TYPE_DECIMAL: statistic = STAT_TEXT_TYPE_FETCHED_DECIMAL; break; + case MYSQL_TYPE_TINY: statistic = STAT_TEXT_TYPE_FETCHED_INT8; break; + case MYSQL_TYPE_SHORT: statistic = STAT_TEXT_TYPE_FETCHED_INT16; break; + case MYSQL_TYPE_LONG: statistic = STAT_TEXT_TYPE_FETCHED_INT32; break; + case MYSQL_TYPE_FLOAT: statistic = STAT_TEXT_TYPE_FETCHED_FLOAT; break; + case MYSQL_TYPE_DOUBLE: statistic = STAT_TEXT_TYPE_FETCHED_DOUBLE; break; + case MYSQL_TYPE_NULL: statistic = STAT_TEXT_TYPE_FETCHED_NULL; break; + case MYSQL_TYPE_TIMESTAMP: statistic = STAT_TEXT_TYPE_FETCHED_TIMESTAMP; break; + case MYSQL_TYPE_LONGLONG: statistic = STAT_TEXT_TYPE_FETCHED_INT64; break; + case MYSQL_TYPE_INT24: statistic = STAT_TEXT_TYPE_FETCHED_INT24; break; + case MYSQL_TYPE_DATE: statistic = STAT_TEXT_TYPE_FETCHED_DATE; break; + case MYSQL_TYPE_TIME: statistic = STAT_TEXT_TYPE_FETCHED_TIME; break; + case MYSQL_TYPE_DATETIME: statistic = STAT_TEXT_TYPE_FETCHED_DATETIME; break; + case MYSQL_TYPE_YEAR: statistic = STAT_TEXT_TYPE_FETCHED_YEAR; break; + case MYSQL_TYPE_NEWDATE: statistic = STAT_TEXT_TYPE_FETCHED_DATE; break; + case MYSQL_TYPE_VARCHAR: statistic = STAT_TEXT_TYPE_FETCHED_STRING; break; + case MYSQL_TYPE_BIT: statistic = STAT_TEXT_TYPE_FETCHED_BIT; break; + case MYSQL_TYPE_NEWDECIMAL: statistic = STAT_TEXT_TYPE_FETCHED_DECIMAL; break; + case MYSQL_TYPE_ENUM: statistic = STAT_TEXT_TYPE_FETCHED_ENUM; break; + case MYSQL_TYPE_SET: statistic = STAT_TEXT_TYPE_FETCHED_SET; break; + case MYSQL_TYPE_TINY_BLOB: statistic = STAT_TEXT_TYPE_FETCHED_BLOB; break; + case MYSQL_TYPE_MEDIUM_BLOB:statistic = STAT_TEXT_TYPE_FETCHED_BLOB; break; + case MYSQL_TYPE_LONG_BLOB: statistic = STAT_TEXT_TYPE_FETCHED_BLOB; break; + case MYSQL_TYPE_BLOB: statistic = STAT_TEXT_TYPE_FETCHED_BLOB; break; + case MYSQL_TYPE_VAR_STRING: statistic = STAT_TEXT_TYPE_FETCHED_STRING; break; + case MYSQL_TYPE_STRING: statistic = STAT_TEXT_TYPE_FETCHED_STRING; break; + case MYSQL_TYPE_GEOMETRY: statistic = STAT_TEXT_TYPE_FETCHED_GEOMETRY; break; + default: statistic = STAT_TEXT_TYPE_FETCHED_OTHER; break; + } + MYSQLND_INC_CONN_STATISTIC_W_VALUE2(stats, statistic, 1, STAT_BYTES_RECEIVED_PURE_DATA_TEXT, len); + } +#ifdef MYSQLND_STRING_TO_INT_CONVERSION + if (as_int_or_float && perm_bind.php_type == IS_LONG) { + zend_uchar save = *(p + len); + /* We have to make it ASCIIZ temporarily */ + *(p + len) = '\0'; + if (perm_bind.pack_len < SIZEOF_LONG) { + /* direct conversion */ + int64_t v = +#ifndef PHP_WIN32 + atoll((char *) p); +#else + _atoi64((char *) p); +#endif + ZVAL_LONG(*current_field, (long) v); /* the cast is safe */ + } else { + uint64_t v = +#ifndef PHP_WIN32 + (uint64_t) atoll((char *) p); +#else + (uint64_t) _atoi64((char *) p); +#endif + zend_bool uns = fields_metadata[i].flags & UNSIGNED_FLAG? TRUE:FALSE; + /* We have to make it ASCIIZ temporarily */ +#if SIZEOF_LONG==8 + if (uns == TRUE && v > 9223372036854775807L) +#elif SIZEOF_LONG==4 + if ((uns == TRUE && v > L64(2147483647)) || + (uns == FALSE && (( L64(2147483647) < (int64_t) v) || + (L64(-2147483648) > (int64_t) v)))) +#else +#error Need fix for this architecture +#endif /* SIZEOF */ + { + ZVAL_STRINGL(*current_field, (char *)p, len, 0); + } else { + ZVAL_LONG(*current_field, (long) v); /* the cast is safe */ + } + } + *(p + len) = save; + } else if (as_int_or_float && perm_bind.php_type == IS_DOUBLE) { + zend_uchar save = *(p + len); + /* We have to make it ASCIIZ temporarily */ + *(p + len) = '\0'; + ZVAL_DOUBLE(*current_field, atof((char *) p)); + *(p + len) = save; + } else +#endif /* MYSQLND_STRING_TO_INT_CONVERSION */ + if (fields_metadata[i].type == MYSQL_TYPE_BIT) { + /* + BIT fields are specially handled. As they come as bit mask, we have + to convert it to human-readable representation. As the bits take + less space in the protocol than the numbers they represent, we don't + have enough space in the packet buffer to overwrite inside. + Thus, a bit more space is pre-allocated at the end of the buffer, + see php_mysqlnd_rowp_read(). And we add the strings at the end. + Definitely not nice, _hackish_ :(, but works. + */ + zend_uchar *start = bit_area; + ps_fetch_from_1_to_8_bytes(*current_field, &(fields_metadata[i]), 0, &p, as_unicode, len TSRMLS_CC); + /* + We have advanced in ps_fetch_from_1_to_8_bytes. We should go back because + later in this function there will be an advancement. + */ + p -= len; + if (Z_TYPE_PP(current_field) == IS_LONG) { + bit_area += 1 + sprintf((char *)start, "%ld", Z_LVAL_PP(current_field)); +#if MYSQLND_UNICODE + if (as_unicode) { + ZVAL_UTF8_STRINGL(*current_field, start, bit_area - start - 1, 0); + } else +#endif + { + ZVAL_STRINGL(*current_field, (char *) start, bit_area - start - 1, 0); + } + } else if (Z_TYPE_PP(current_field) == IS_STRING){ + memcpy(bit_area, Z_STRVAL_PP(current_field), Z_STRLEN_PP(current_field)); + bit_area += Z_STRLEN_PP(current_field); + *bit_area++ = '\0'; + zval_dtor(*current_field); +#if MYSQLND_UNICODE + if (as_unicode) { + ZVAL_UTF8_STRINGL(*current_field, start, bit_area - start - 1, 0); + } else +#endif + { + ZVAL_STRINGL(*current_field, (char *) start, bit_area - start - 1, 0); + } + } + /* + IS_UNICODE should not be specially handled. In unicode mode + the buffers are not referenced - everything is copied. + */ + } else +#if MYSQLND_UNICODE == 0 + { + ZVAL_STRINGL(*current_field, (char *)p, len, 0); + } +#else + /* + Here we have to convert to UTF16, which means not reusing the buffer. + Which in turn means that we can free the buffers once we have + stored the result set, if we use store_result(). + + Also the destruction of the zvals should not call zval_copy_ctor() + because then we will leak. + + XXX: Keep in mind that up there there is an open `else` in + #ifdef MYSQLND_STRING_TO_INT_CONVERSION + which will make with this `if` an `else if`. + */ + if ((perm_bind.is_possibly_blob == TRUE && + fields_metadata[i].charsetnr == MYSQLND_BINARY_CHARSET_NR) || + (!as_unicode && perm_bind.can_ret_as_str_in_uni == TRUE)) + { + /* BLOB - no conversion please */ + ZVAL_STRINGL(*current_field, (char *)p, len, 0); + } else { + ZVAL_UTF8_STRINGL(*current_field, (char *)p, len, 0); + } +#endif + p += len; + last_field_was_string = TRUE; + } + } + if (last_field_was_string) { + /* Normal queries: The buffer has one more byte at the end, because we need it */ + row_buffer->ptr[data_size] = '\0'; + } + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ php_mysqlnd_rowp_read */ +/* + if normal statements => packet->fields is created by this function, + if PS => packet->fields is passed from outside +*/ +static enum_func_status +php_mysqlnd_rowp_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + MYSQLND_NET * net = conn->net; + zend_uchar *p; + enum_func_status ret = PASS; + size_t old_chunk_size = net->stream->chunk_size; + MYSQLND_PACKET_ROW *packet= (MYSQLND_PACKET_ROW *) _packet; + size_t post_alloc_for_bit_fields = 0; + size_t data_size = 0; + + DBG_ENTER("php_mysqlnd_rowp_read"); + + if (!packet->binary_protocol && packet->bit_fields_count) { + /* For every field we need terminating \0 */ + post_alloc_for_bit_fields = packet->bit_fields_total_len + packet->bit_fields_count; + } + + ret = php_mysqlnd_read_row_ex(conn, packet->result_set_memory_pool, &packet->row_buffer, &data_size, + packet->persistent_alloc, post_alloc_for_bit_fields + TSRMLS_CC); + if (FAIL == ret) { + goto end; + } + MYSQLND_INC_CONN_STATISTIC_W_VALUE2(conn->stats, packet_type_to_statistic_byte_count[PROT_ROW_PACKET], + MYSQLND_HEADER_SIZE + packet->header.size, + packet_type_to_statistic_packet_count[PROT_ROW_PACKET], + 1); + + /* packet->row_buffer->ptr is of size 'data_size + 1' */ + packet->header.size = data_size; + packet->row_buffer->app = data_size; + + if (ERROR_MARKER == (*(p = packet->row_buffer->ptr))) { + /* + Error message as part of the result set, + not good but we should not hang. See: + Bug #27876 : SF with cyrillic variable name fails during execution + */ + ret = FAIL; + php_mysqlnd_read_error_from_line(p + 1, data_size - 1, + packet->error_info.error, + sizeof(packet->error_info.error), + &packet->error_info.error_no, + packet->error_info.sqlstate + TSRMLS_CC); + } else if (EODATA_MARKER == *p && data_size < 8) { /* EOF */ + packet->eof = TRUE; + p++; + if (data_size > 1) { + packet->warning_count = uint2korr(p); + p += 2; + packet->server_status = uint2korr(p); + /* Seems we have 3 bytes reserved for future use */ + DBG_INF_FMT("server_status=%u warning_count=%u", packet->server_status, packet->warning_count); + } + } else { + MYSQLND_INC_CONN_STATISTIC(conn->stats, + packet->binary_protocol? STAT_ROWS_FETCHED_FROM_SERVER_PS: + STAT_ROWS_FETCHED_FROM_SERVER_NORMAL); + + packet->eof = FALSE; + /* packet->field_count is set by the user of the packet */ + + if (!packet->skip_extraction) { + if (!packet->fields) { + DBG_INF("Allocating packet->fields"); + /* + old-API will probably set packet->fields to NULL every time, though for + unbuffered sets it makes not much sense as the zvals in this buffer matter, + not the buffer. Constantly allocating and deallocating brings nothing. + + For PS - if stmt_store() is performed, thus we don't have a cursor, it will + behave just like old-API buffered. Cursors will behave like a bit different, + but mostly like old-API unbuffered and thus will populate this array with + value. + */ + packet->fields = (zval **) mnd_pecalloc(packet->field_count, sizeof(zval *), + packet->persistent_alloc); + } + } else { + MYSQLND_INC_CONN_STATISTIC(conn->stats, + packet->binary_protocol? STAT_ROWS_SKIPPED_PS: + STAT_ROWS_SKIPPED_NORMAL); + } + } + +end: + net->stream->chunk_size = old_chunk_size; + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ php_mysqlnd_rowp_free_mem */ +static void +php_mysqlnd_rowp_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC) +{ + MYSQLND_PACKET_ROW *p; + + DBG_ENTER("php_mysqlnd_rowp_free_mem"); + p = (MYSQLND_PACKET_ROW *) _packet; + if (p->row_buffer) { + p->row_buffer->free_chunk(p->row_buffer TSRMLS_CC); + p->row_buffer = NULL; + } + DBG_INF_FMT("stack_allocation=%u persistent=%u", (int)stack_allocation, (int)p->header.persistent); + /* + Don't free packet->fields : + - normal queries -> store_result() | fetch_row_unbuffered() will transfer + the ownership and NULL it. + - PS will pass in it the bound variables, we have to use them! and of course + not free the array. As it is passed to us, we should not clean it ourselves. + */ + if (!stack_allocation) { + mnd_pefree(p, p->header.persistent); + } + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ php_mysqlnd_stats_read */ +static enum_func_status +php_mysqlnd_stats_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + MYSQLND_PACKET_STATS *packet= (MYSQLND_PACKET_STATS *) _packet; + size_t buf_len = conn->net->cmd_buffer.length; + zend_uchar *buf = (zend_uchar *) conn->net->cmd_buffer.buffer; + + DBG_ENTER("php_mysqlnd_stats_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "statistics", PROT_STATS_PACKET); + + packet->message = mnd_emalloc(packet->header.size + 1); + memcpy(packet->message, buf, packet->header.size); + packet->message[packet->header.size] = '\0'; + packet->message_len = packet->header.size; + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ php_mysqlnd_stats_free_mem */ +static +void php_mysqlnd_stats_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC) +{ + MYSQLND_PACKET_STATS *p= (MYSQLND_PACKET_STATS *) _packet; + if (p->message) { + mnd_efree(p->message); + p->message = NULL; + } + if (!stack_allocation) { + mnd_pefree(p, p->header.persistent); + } +} +/* }}} */ + + +/* 1 + 4 (id) + 2 (field_c) + 2 (param_c) + 1 (filler) + 2 (warnings ) */ +#define PREPARE_RESPONSE_SIZE_41 9 +#define PREPARE_RESPONSE_SIZE_50 12 + +/* {{{ php_mysqlnd_prepare_read */ +static enum_func_status +php_mysqlnd_prepare_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + /* In case of an error, we should have place to put it */ + size_t buf_len = conn->net->cmd_buffer.length; + zend_uchar *buf = (zend_uchar *) conn->net->cmd_buffer.buffer; + zend_uchar *p = buf; + zend_uchar *begin = buf; + unsigned int data_size; + MYSQLND_PACKET_PREPARE_RESPONSE *packet= (MYSQLND_PACKET_PREPARE_RESPONSE *) _packet; + + DBG_ENTER("php_mysqlnd_prepare_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "prepare", PROT_PREPARE_RESP_PACKET); + BAIL_IF_NO_MORE_DATA; + + data_size = packet->header.size; + packet->error_code = uint1korr(p); + p++; + BAIL_IF_NO_MORE_DATA; + + if (ERROR_MARKER == packet->error_code) { + php_mysqlnd_read_error_from_line(p, data_size - 1, + packet->error_info.error, + sizeof(packet->error_info.error), + &packet->error_info.error_no, + packet->error_info.sqlstate + TSRMLS_CC); + DBG_RETURN(PASS); + } + + if (data_size != PREPARE_RESPONSE_SIZE_41 && + data_size != PREPARE_RESPONSE_SIZE_50 && + !(data_size > PREPARE_RESPONSE_SIZE_50)) { + DBG_ERR_FMT("Wrong COM_STMT_PREPARE response size. Received %u", data_size); + php_error(E_WARNING, "Wrong COM_STMT_PREPARE response size. Received %u", data_size); + DBG_RETURN(FAIL); + } + + packet->stmt_id = uint4korr(p); + p += 4; + BAIL_IF_NO_MORE_DATA; + + /* Number of columns in result set */ + packet->field_count = uint2korr(p); + p += 2; + BAIL_IF_NO_MORE_DATA; + + packet->param_count = uint2korr(p); + p += 2; + BAIL_IF_NO_MORE_DATA; + + if (data_size > 9) { + /* 0x0 filler sent by the server for 5.0+ clients */ + p++; + BAIL_IF_NO_MORE_DATA; + + packet->warning_count = uint2korr(p); + } + + DBG_INF_FMT("Prepare packet read: stmt_id=%u fields=%u params=%u", + packet->stmt_id, packet->field_count, packet->param_count); + + BAIL_IF_NO_MORE_DATA; + + DBG_RETURN(PASS); +premature_end: + DBG_ERR_FMT("PREPARE packet %d bytes shorter than expected", p - begin - packet->header.size); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "PREPARE packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected", + p - begin - packet->header.size); + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ php_mysqlnd_prepare_free_mem */ +static void +php_mysqlnd_prepare_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC) +{ + MYSQLND_PACKET_PREPARE_RESPONSE *p= (MYSQLND_PACKET_PREPARE_RESPONSE *) _packet; + if (!stack_allocation) { + mnd_pefree(p, p->header.persistent); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_chg_user_read */ +static enum_func_status +php_mysqlnd_chg_user_read(void * _packet, MYSQLND_CONN_DATA * conn TSRMLS_DC) +{ + /* There could be an error message */ + size_t buf_len = conn->net->cmd_buffer.length; + zend_uchar *buf = (zend_uchar *) conn->net->cmd_buffer.buffer; + zend_uchar *p = buf; + zend_uchar *begin = buf; + MYSQLND_PACKET_CHG_USER_RESPONSE *packet= (MYSQLND_PACKET_CHG_USER_RESPONSE *) _packet; + + DBG_ENTER("php_mysqlnd_chg_user_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_len, "change user response", PROT_CHG_USER_RESP_PACKET); + BAIL_IF_NO_MORE_DATA; + + /* + Don't increment. First byte is ERROR_MARKER on error, but otherwise is starting byte + of encoded sequence for length. + */ + + /* Should be always 0x0 or ERROR_MARKER for error */ + packet->response_code = uint1korr(p); + p++; + + if (packet->header.size == 1 && buf[0] == EODATA_MARKER && packet->server_capabilities & CLIENT_SECURE_CONNECTION) { + /* We don't handle 3.23 authentication */ + packet->server_asked_323_auth = TRUE; + DBG_RETURN(FAIL); + } + + if (ERROR_MARKER == packet->response_code) { + php_mysqlnd_read_error_from_line(p, packet->header.size - 1, + packet->error_info.error, + sizeof(packet->error_info.error), + &packet->error_info.error_no, + packet->error_info.sqlstate + TSRMLS_CC); + } + BAIL_IF_NO_MORE_DATA; + if (packet->response_code == 0xFE && packet->header.size > (size_t) (p - buf)) { + packet->new_auth_protocol = mnd_pestrdup((char *)p, FALSE); + packet->new_auth_protocol_len = strlen(packet->new_auth_protocol); + p+= packet->new_auth_protocol_len + 1; /* +1 for the \0 */ + packet->new_auth_protocol_data_len = packet->header.size - (size_t) (p - buf); + if (packet->new_auth_protocol_data_len) { + packet->new_auth_protocol_data = mnd_emalloc(packet->new_auth_protocol_data_len); + memcpy(packet->new_auth_protocol_data, p, packet->new_auth_protocol_data_len); + } + DBG_INF_FMT("The server requested switching auth plugin to : %s", packet->new_auth_protocol); + DBG_INF_FMT("Server salt : [%*s]", packet->new_auth_protocol_data_len, packet->new_auth_protocol_data); + } + + DBG_RETURN(PASS); +premature_end: + DBG_ERR_FMT("CHANGE_USER packet %d bytes shorter than expected", p - begin - packet->header.size); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "CHANGE_USER packet "MYSQLND_SZ_T_SPEC" bytes shorter than expected", + p - begin - packet->header.size); + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ php_mysqlnd_chg_user_free_mem */ +static void +php_mysqlnd_chg_user_free_mem(void * _packet, zend_bool stack_allocation TSRMLS_DC) +{ + MYSQLND_PACKET_CHG_USER_RESPONSE * p = (MYSQLND_PACKET_CHG_USER_RESPONSE *) _packet; + + if (p->new_auth_protocol) { + mnd_efree(p->new_auth_protocol); + p->new_auth_protocol = NULL; + } + p->new_auth_protocol_len = 0; + + if (p->new_auth_protocol_data) { + mnd_efree(p->new_auth_protocol_data); + p->new_auth_protocol_data = NULL; + } + p->new_auth_protocol_data_len = 0; + + if (!stack_allocation) { + mnd_pefree(p, p->header.persistent); + } +} +/* }}} */ + + +/* {{{ packet_methods */ +static +mysqlnd_packet_methods packet_methods[PROT_LAST] = +{ + { + sizeof(MYSQLND_PACKET_GREET), + php_mysqlnd_greet_read, + NULL, /* write */ + php_mysqlnd_greet_free_mem, + }, /* PROT_GREET_PACKET */ + { + sizeof(MYSQLND_PACKET_AUTH), + NULL, /* read */ + php_mysqlnd_auth_write, + php_mysqlnd_auth_free_mem, + }, /* PROT_AUTH_PACKET */ + { + sizeof(MYSQLND_PACKET_AUTH_RESPONSE), + php_mysqlnd_auth_response_read, /* read */ + NULL, /* write */ + php_mysqlnd_auth_response_free_mem, + }, /* PROT_AUTH_RESP_PACKET */ + { + sizeof(MYSQLND_PACKET_CHANGE_AUTH_RESPONSE), + NULL, /* read */ + php_mysqlnd_change_auth_response_write, /* write */ + php_mysqlnd_change_auth_response_free_mem, + }, /* PROT_CHANGE_AUTH_RESP_PACKET */ + { + sizeof(MYSQLND_PACKET_OK), + php_mysqlnd_ok_read, /* read */ + NULL, /* write */ + php_mysqlnd_ok_free_mem, + }, /* PROT_OK_PACKET */ + { + sizeof(MYSQLND_PACKET_EOF), + php_mysqlnd_eof_read, /* read */ + NULL, /* write */ + php_mysqlnd_eof_free_mem, + }, /* PROT_EOF_PACKET */ + { + sizeof(MYSQLND_PACKET_COMMAND), + NULL, /* read */ + php_mysqlnd_cmd_write, /* write */ + php_mysqlnd_cmd_free_mem, + }, /* PROT_CMD_PACKET */ + { + sizeof(MYSQLND_PACKET_RSET_HEADER), + php_mysqlnd_rset_header_read, /* read */ + NULL, /* write */ + php_mysqlnd_rset_header_free_mem, + }, /* PROT_RSET_HEADER_PACKET */ + { + sizeof(MYSQLND_PACKET_RES_FIELD), + php_mysqlnd_rset_field_read, /* read */ + NULL, /* write */ + php_mysqlnd_rset_field_free_mem, + }, /* PROT_RSET_FLD_PACKET */ + { + sizeof(MYSQLND_PACKET_ROW), + php_mysqlnd_rowp_read, /* read */ + NULL, /* write */ + php_mysqlnd_rowp_free_mem, + }, /* PROT_ROW_PACKET */ + { + sizeof(MYSQLND_PACKET_STATS), + php_mysqlnd_stats_read, /* read */ + NULL, /* write */ + php_mysqlnd_stats_free_mem, + }, /* PROT_STATS_PACKET */ + { + sizeof(MYSQLND_PACKET_PREPARE_RESPONSE), + php_mysqlnd_prepare_read, /* read */ + NULL, /* write */ + php_mysqlnd_prepare_free_mem, + }, /* PROT_PREPARE_RESP_PACKET */ + { + sizeof(MYSQLND_PACKET_CHG_USER_RESPONSE), + php_mysqlnd_chg_user_read, /* read */ + NULL, /* write */ + php_mysqlnd_chg_user_free_mem, + } /* PROT_CHG_USER_RESP_PACKET */ +}; +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_greet_packet */ +static struct st_mysqlnd_packet_greet * +MYSQLND_METHOD(mysqlnd_protocol, get_greet_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_greet * packet = mnd_pecalloc(1, packet_methods[PROT_GREET_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_greet_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_GREET_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_auth_packet */ +static struct st_mysqlnd_packet_auth * +MYSQLND_METHOD(mysqlnd_protocol, get_auth_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_auth * packet = mnd_pecalloc(1, packet_methods[PROT_AUTH_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_auth_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_AUTH_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_auth_response_packet */ +static struct st_mysqlnd_packet_auth_response * +MYSQLND_METHOD(mysqlnd_protocol, get_auth_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_auth_response * packet = mnd_pecalloc(1, packet_methods[PROT_AUTH_RESP_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_auth_response_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_AUTH_RESP_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_change_auth_response_packet */ +static struct st_mysqlnd_packet_change_auth_response * +MYSQLND_METHOD(mysqlnd_protocol, get_change_auth_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_change_auth_response * packet = mnd_pecalloc(1, packet_methods[PROT_CHANGE_AUTH_RESP_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_change_auth_response_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_CHANGE_AUTH_RESP_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_ok_packet */ +static struct st_mysqlnd_packet_ok * +MYSQLND_METHOD(mysqlnd_protocol, get_ok_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_ok * packet = mnd_pecalloc(1, packet_methods[PROT_OK_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_ok_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_OK_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_eof_packet */ +static struct st_mysqlnd_packet_eof * +MYSQLND_METHOD(mysqlnd_protocol, get_eof_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_eof * packet = mnd_pecalloc(1, packet_methods[PROT_EOF_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_eof_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_EOF_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_command_packet */ +static struct st_mysqlnd_packet_command * +MYSQLND_METHOD(mysqlnd_protocol, get_command_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_command * packet = mnd_pecalloc(1, packet_methods[PROT_CMD_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_command_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_CMD_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_rset_packet */ +static struct st_mysqlnd_packet_rset_header * +MYSQLND_METHOD(mysqlnd_protocol, get_rset_header_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_rset_header * packet = mnd_pecalloc(1, packet_methods[PROT_RSET_HEADER_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_rset_header_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_RSET_HEADER_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_result_field_packet */ +static struct st_mysqlnd_packet_res_field * +MYSQLND_METHOD(mysqlnd_protocol, get_result_field_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_res_field * packet = mnd_pecalloc(1, packet_methods[PROT_RSET_FLD_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_result_field_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_RSET_FLD_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_row_packet */ +static struct st_mysqlnd_packet_row * +MYSQLND_METHOD(mysqlnd_protocol, get_row_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_row * packet = mnd_pecalloc(1, packet_methods[PROT_ROW_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_row_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_ROW_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_stats_packet */ +static struct st_mysqlnd_packet_stats * +MYSQLND_METHOD(mysqlnd_protocol, get_stats_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_stats * packet = mnd_pecalloc(1, packet_methods[PROT_STATS_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_stats_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_STATS_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_prepare_response_packet */ +static struct st_mysqlnd_packet_prepare_response * +MYSQLND_METHOD(mysqlnd_protocol, get_prepare_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_prepare_response * packet = mnd_pecalloc(1, packet_methods[PROT_PREPARE_RESP_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_prepare_response_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_PREPARE_RESP_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol::get_change_user_response_packet */ +static struct st_mysqlnd_packet_chg_user_resp* +MYSQLND_METHOD(mysqlnd_protocol, get_change_user_response_packet)(MYSQLND_PROTOCOL * const protocol, zend_bool persistent TSRMLS_DC) +{ + struct st_mysqlnd_packet_chg_user_resp * packet = mnd_pecalloc(1, packet_methods[PROT_CHG_USER_RESP_PACKET].struct_size, persistent); + DBG_ENTER("mysqlnd_protocol::get_change_user_response_packet"); + if (packet) { + packet->header.m = &packet_methods[PROT_CHG_USER_RESP_PACKET]; + packet->header.persistent = persistent; + } + DBG_RETURN(packet); +} +/* }}} */ + + +MYSQLND_CLASS_METHODS_START(mysqlnd_protocol) + MYSQLND_METHOD(mysqlnd_protocol, get_greet_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_auth_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_auth_response_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_change_auth_response_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_ok_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_command_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_eof_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_rset_header_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_result_field_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_row_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_stats_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_prepare_response_packet), + MYSQLND_METHOD(mysqlnd_protocol, get_change_user_response_packet) +MYSQLND_CLASS_METHODS_END; + + +/* {{{ mysqlnd_protocol_init */ +PHPAPI MYSQLND_PROTOCOL * +mysqlnd_protocol_init(zend_bool persistent TSRMLS_DC) +{ + MYSQLND_PROTOCOL * ret; + DBG_ENTER("mysqlnd_protocol_init"); + ret = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_protocol_decoder(persistent TSRMLS_CC); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_protocol_free */ +PHPAPI void +mysqlnd_protocol_free(MYSQLND_PROTOCOL * const protocol TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_protocol_free"); + + if (protocol) { + zend_bool pers = protocol->persistent; + mnd_pefree(protocol, pers); + } + DBG_VOID_RETURN; +} +/* }}} */ + + +/* + * 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 + */ diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.h b/ext/mysqlnd/mysqlnd_wireprotocol.h new file mode 100644 index 0000000..253784c --- /dev/null +++ b/ext/mysqlnd/mysqlnd_wireprotocol.h @@ -0,0 +1,321 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef MYSQLND_WIREPROTOCOL_H +#define MYSQLND_WIREPROTOCOL_H + +#include "mysqlnd_net.h" + +#define MYSQLND_HEADER_SIZE 4 +#define COMPRESSED_HEADER_SIZE 3 + +#define MYSQLND_NULL_LENGTH (unsigned long) ~0 + +/* Used in mysqlnd_debug.c */ +PHPAPI extern const char mysqlnd_read_header_name[]; +PHPAPI extern const char mysqlnd_read_body_name[]; + + +/* Packet handling */ +#define PACKET_WRITE(packet, conn) ((packet)->header.m->write_to_net((packet), (conn) TSRMLS_CC)) +#define PACKET_READ(packet, conn) ((packet)->header.m->read_from_net((packet), (conn) TSRMLS_CC)) +#define PACKET_FREE(packet) \ + do { \ + DBG_INF_FMT("PACKET_FREE(%p)", packet); \ + if ((packet)) { \ + ((packet)->header.m->free_mem((packet), FALSE TSRMLS_CC)); \ + } \ + } while (0); + +PHPAPI extern const char * const mysqlnd_command_to_text[COM_END]; + +/* Low-level extraction functionality */ +typedef struct st_mysqlnd_packet_methods { + size_t struct_size; + enum_func_status (*read_from_net)(void * packet, MYSQLND_CONN_DATA * conn TSRMLS_DC); + size_t (*write_to_net)(void * packet, MYSQLND_CONN_DATA * conn TSRMLS_DC); + void (*free_mem)(void *packet, zend_bool stack_allocation TSRMLS_DC); +} mysqlnd_packet_methods; + + +typedef struct st_mysqlnd_packet_header { + size_t size; + mysqlnd_packet_methods *m; + zend_uchar packet_no; + zend_bool persistent; +} MYSQLND_PACKET_HEADER; + +/* Server greets the client */ +typedef struct st_mysqlnd_packet_greet { + MYSQLND_PACKET_HEADER header; + uint8_t protocol_version; + char *server_version; + uint32_t thread_id; + zend_uchar intern_auth_plugin_data[SCRAMBLE_LENGTH]; + zend_uchar * auth_plugin_data; + size_t auth_plugin_data_len; + /* 1 byte pad */ + uint32_t server_capabilities; + uint8_t charset_no; + uint16_t server_status; + /* 13 byte pad, in 5.5 first 2 bytes are more capabilities followed by 1 byte scramble_length */ + zend_bool pre41; + /* If error packet, we use these */ + char error[MYSQLND_ERRMSG_SIZE+1]; + char sqlstate[MYSQLND_SQLSTATE_LENGTH + 1]; + unsigned int error_no; + char *auth_protocol; +} MYSQLND_PACKET_GREET; + + +/* Client authenticates */ +typedef struct st_mysqlnd_packet_auth { + MYSQLND_PACKET_HEADER header; + uint32_t client_flags; + uint32_t max_packet_size; + uint8_t charset_no; + const char *user; + const zend_uchar *auth_data; + size_t auth_data_len; + const char *db; + const char *auth_plugin_name; + /* Here the packet ends. This is user supplied data */ + size_t db_len; + zend_bool send_auth_data; + zend_bool is_change_user_packet; + zend_bool silent; + +} MYSQLND_PACKET_AUTH; + +/* Auth response packet */ +typedef struct st_mysqlnd_packet_auth_response { + MYSQLND_PACKET_HEADER header; + uint8_t response_code; + uint64_t affected_rows; + uint64_t last_insert_id; + uint16_t server_status; + uint16_t warning_count; + char *message; + size_t message_len; + /* If error packet, we use these */ + char error[MYSQLND_ERRMSG_SIZE+1]; + char sqlstate[MYSQLND_SQLSTATE_LENGTH + 1]; + unsigned int error_no; + + char *new_auth_protocol; + size_t new_auth_protocol_len; + zend_uchar *new_auth_protocol_data; + size_t new_auth_protocol_data_len; +} MYSQLND_PACKET_AUTH_RESPONSE; + + +/* Auth response packet */ +typedef struct st_mysqlnd_packet_change_auth_response { + MYSQLND_PACKET_HEADER header; + const zend_uchar *auth_data; + size_t auth_data_len; +} MYSQLND_PACKET_CHANGE_AUTH_RESPONSE; + + +/* OK packet */ +typedef struct st_mysqlnd_packet_ok { + MYSQLND_PACKET_HEADER header; + uint8_t field_count; /* always 0x0 */ + uint64_t affected_rows; + uint64_t last_insert_id; + uint16_t server_status; + uint16_t warning_count; + char *message; + size_t message_len; + /* If error packet, we use these */ + char error[MYSQLND_ERRMSG_SIZE+1]; + char sqlstate[MYSQLND_SQLSTATE_LENGTH + 1]; + unsigned int error_no; +} MYSQLND_PACKET_OK; + + +/* Command packet */ +typedef struct st_mysqlnd_packet_command { + MYSQLND_PACKET_HEADER header; + enum php_mysqlnd_server_command command; + const zend_uchar *argument; + size_t arg_len; +} MYSQLND_PACKET_COMMAND; + + +/* EOF packet */ +typedef struct st_mysqlnd_packet_eof { + MYSQLND_PACKET_HEADER header; + uint8_t field_count; /* 0xFE */ + uint16_t warning_count; + uint16_t server_status; + /* If error packet, we use these */ + char error[MYSQLND_ERRMSG_SIZE+1]; + char sqlstate[MYSQLND_SQLSTATE_LENGTH + 1]; + unsigned int error_no; +} MYSQLND_PACKET_EOF; +/* EOF packet */ + + +/* Result Set header*/ +typedef struct st_mysqlnd_packet_rset_header { + MYSQLND_PACKET_HEADER header; + /* + 0x00 => ok + ~0 => LOAD DATA LOCAL + error_no != 0 => error + others => result set -> Read res_field packets up to field_count + */ + unsigned long field_count; + /* + These are filled if no SELECT query. For SELECT warning_count + and server status are in the last row packet, the EOF packet. + */ + uint16_t warning_count; + uint16_t server_status; + uint64_t affected_rows; + uint64_t last_insert_id; + /* This is for both LOAD DATA or info, when no result set */ + char *info_or_local_file; + size_t info_or_local_file_len; + /* If error packet, we use these */ + MYSQLND_ERROR_INFO error_info; +} MYSQLND_PACKET_RSET_HEADER; + + +/* Result set field packet */ +typedef struct st_mysqlnd_packet_res_field { + MYSQLND_PACKET_HEADER header; + MYSQLND_FIELD *metadata; + /* For table definitions, empty for result sets */ + zend_bool skip_parsing; + zend_bool stupid_list_fields_eof; + zend_bool persistent_alloc; + + MYSQLND_ERROR_INFO error_info; +} MYSQLND_PACKET_RES_FIELD; + + +/* Row packet */ +typedef struct st_mysqlnd_packet_row { + MYSQLND_PACKET_HEADER header; + zval **fields; + uint32_t field_count; + zend_bool eof; + /* + These are, of course, only for SELECT in the EOF packet, + which is detected by this packet + */ + uint16_t warning_count; + uint16_t server_status; + + struct st_mysqlnd_memory_pool_chunk *row_buffer; + MYSQLND_MEMORY_POOL * result_set_memory_pool; + + zend_bool skip_extraction; + zend_bool binary_protocol; + zend_bool persistent_alloc; + MYSQLND_FIELD *fields_metadata; + /* We need this to alloc bigger bufs in non-PS mode */ + unsigned int bit_fields_count; + size_t bit_fields_total_len; /* trailing \0 not counted */ + + /* If error packet, we use these */ + MYSQLND_ERROR_INFO error_info; +} MYSQLND_PACKET_ROW; + + +/* Statistics packet */ +typedef struct st_mysqlnd_packet_stats { + MYSQLND_PACKET_HEADER header; + char *message; + /* message_len is not part of the packet*/ + size_t message_len; +} MYSQLND_PACKET_STATS; + + +/* COM_PREPARE response packet */ +typedef struct st_mysqlnd_packet_prepare_response { + MYSQLND_PACKET_HEADER header; + /* also known as field_count 0x00=OK , 0xFF=error */ + unsigned char error_code; + unsigned long stmt_id; + unsigned int field_count; + unsigned int param_count; + unsigned int warning_count; + + /* present in case of error */ + MYSQLND_ERROR_INFO error_info; +} MYSQLND_PACKET_PREPARE_RESPONSE; + + +/* Statistics packet */ +typedef struct st_mysqlnd_packet_chg_user_resp { + MYSQLND_PACKET_HEADER header; + uint32_t response_code; + + /* message_len is not part of the packet*/ + uint16_t server_capabilities; + /* If error packet, we use these */ + MYSQLND_ERROR_INFO error_info; + zend_bool server_asked_323_auth; + + char *new_auth_protocol; + size_t new_auth_protocol_len; + zend_uchar *new_auth_protocol_data; + size_t new_auth_protocol_data_len; +} MYSQLND_PACKET_CHG_USER_RESPONSE; + + +PHPAPI void php_mysqlnd_scramble(zend_uchar * const buffer, const zend_uchar * const scramble, const zend_uchar * const pass, size_t pass_len); + +unsigned long php_mysqlnd_net_field_length(zend_uchar **packet); +zend_uchar * php_mysqlnd_net_store_length(zend_uchar *packet, uint64_t length); + +PHPAPI const extern char * const mysqlnd_empty_string; + + +enum_func_status php_mysqlnd_rowp_read_binary_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, + unsigned int field_count, MYSQLND_FIELD *fields_metadata, + zend_bool as_unicode, zend_bool as_int_or_float, + MYSQLND_STATS * stats TSRMLS_DC); + + +enum_func_status php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, + unsigned int field_count, MYSQLND_FIELD *fields_metadata, + zend_bool as_unicode, zend_bool as_int_or_float, + MYSQLND_STATS * stats TSRMLS_DC); + + +PHPAPI MYSQLND_PROTOCOL * mysqlnd_protocol_init(zend_bool persistent TSRMLS_DC); +PHPAPI void mysqlnd_protocol_free(MYSQLND_PROTOCOL * const protocol TSRMLS_DC); + +#endif /* MYSQLND_WIREPROTOCOL_H */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/php_mysqlnd.c b/ext/mysqlnd/php_mysqlnd.c new file mode 100644 index 0000000..0021355 --- /dev/null +++ b/ext/mysqlnd/php_mysqlnd.c @@ -0,0 +1,389 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "php_ini.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_debug.h" +#include "mysqlnd_statistics.h" +#include "mysqlnd_reverse_api.h" +#include "ext/standard/info.h" +#include "ext/standard/php_smart_str.h" + +/* {{{ mysqlnd_functions[] + * + * Every user visible function must have an entry in mysqlnd_functions[]. + */ +static zend_function_entry mysqlnd_functions[] = { + PHP_FE_END +}; +/* }}} */ + + +/* {{{ mysqlnd_minfo_print_hash */ +#if MYSQLND_UNICODE +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) { + zstr string_key; + uint string_key_len; + ulong num_key; + int s_len; + char *s = NULL; + + TSRMLS_FETCH(); + 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 (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); + } + + zend_hash_move_forward_ex(Z_ARRVAL_P(values), &pos_values); + } +} +#else +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) { + 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 +/* }}} */ + + +/* {{{ mysqlnd_minfo_dump_plugin_stats */ +static int +mysqlnd_minfo_dump_plugin_stats(void *pDest, void * argument TSRMLS_DC) +{ + struct st_mysqlnd_plugin_header * plugin_header = *(struct st_mysqlnd_plugin_header **) pDest; + if (plugin_header->plugin_stats.values) { + char buf[64]; + zval values; + snprintf(buf, sizeof(buf), "%s statistics", plugin_header->plugin_name); + + mysqlnd_fill_stats_hash(plugin_header->plugin_stats.values, plugin_header->plugin_stats.names, &values TSRMLS_CC ZEND_FILE_LINE_CC); + + php_info_print_table_start(); + php_info_print_table_header(2, buf, ""); + mysqlnd_minfo_print_hash(&values); + php_info_print_table_end(); + zval_dtor(&values); + } + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + + +/* {{{ mysqlnd_minfo_dump_loaded_plugins */ +static int +mysqlnd_minfo_dump_loaded_plugins(void *pDest, void * buf TSRMLS_DC) +{ + smart_str * buffer = (smart_str *) buf; + struct st_mysqlnd_plugin_header * plugin_header = *(struct st_mysqlnd_plugin_header **) pDest; + if (plugin_header->plugin_name) { + if (buffer->len) { + smart_str_appendc(buffer, ','); + } + smart_str_appends(buffer, plugin_header->plugin_name); + } + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +/* {{{ mysqlnd_minfo_dump_api_plugins */ +static void +mysqlnd_minfo_dump_api_plugins(smart_str * buffer TSRMLS_DC) +{ + HashTable *ht = mysqlnd_reverse_api_get_api_list(TSRMLS_C); + Bucket *p; + + p = ht->pListHead; + while(p != NULL) { + MYSQLND_REVERSE_API * ext = *(MYSQLND_REVERSE_API **) p->pData; + if (buffer->len) { + smart_str_appendc(buffer, ','); + } + smart_str_appends(buffer, ext->module->name); + + p = p->pListNext; + } +} +/* }}} */ + + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(mysqlnd) +{ + char buf[32]; + + php_info_print_table_start(); + php_info_print_table_header(2, "mysqlnd", "enabled"); + php_info_print_table_row(2, "Version", mysqlnd_get_client_info()); + php_info_print_table_row(2, "Compression", +#ifdef MYSQLND_COMPRESSION_ENABLED + "supported"); +#else + "not supported"); +#endif + php_info_print_table_row(2, "SSL", +#ifdef MYSQLND_SSL_SUPPORTED + "supported"); +#else + "not supported"); +#endif + 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); + snprintf(buf, sizeof(buf), "%ld", MYSQLND_G(net_read_timeout)); + php_info_print_table_row(2, "Read timeout", buf); + 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"); + + php_info_print_table_row(2, "Tracing", MYSQLND_G(debug)? MYSQLND_G(debug):"n/a"); + + /* loaded plugins */ + { + smart_str tmp_str = {0, 0, 0}; + mysqlnd_plugin_apply_with_argument(mysqlnd_minfo_dump_loaded_plugins, &tmp_str); + smart_str_0(&tmp_str); + php_info_print_table_row(2, "Loaded plugins", tmp_str.c); + smart_str_free(&tmp_str); + + mysqlnd_minfo_dump_api_plugins(&tmp_str TSRMLS_CC); + smart_str_0(&tmp_str); + php_info_print_table_row(2, "API Extensions", tmp_str.c); + smart_str_free(&tmp_str); + } + + php_info_print_table_end(); + + + /* Print client stats */ + mysqlnd_plugin_apply_with_argument(mysqlnd_minfo_dump_plugin_stats, NULL); +} +/* }}} */ + + +PHPAPI 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 = MYSQLND_NET_CMD_BUFFER_MIN_SIZE; + mysqlnd_globals->net_read_buffer_size = 32768; + mysqlnd_globals->net_read_timeout = 31536000; + mysqlnd_globals->log_mask = 0; + mysqlnd_globals->mempool_default_size = 16000; + mysqlnd_globals->debug_emalloc_fail_threshold = -1; + mysqlnd_globals->debug_ecalloc_fail_threshold = -1; + mysqlnd_globals->debug_erealloc_fail_threshold = -1; + mysqlnd_globals->debug_malloc_fail_threshold = -1; + mysqlnd_globals->debug_calloc_fail_threshold = -1; + mysqlnd_globals->debug_realloc_fail_threshold = -1; +} +/* }}} */ + + +static PHP_INI_MH(OnUpdateNetCmdBufferSize) +{ + long long_value = atol(new_value); + if (long_value < MYSQLND_NET_CMD_BUFFER_MIN_SIZE) { + return FAILURE; + } + MYSQLND_G(net_cmd_buffer_size) = long_value; + + return SUCCESS; +} + +/* {{{ 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", MYSQLND_NET_CMD_BUFFER_MIN_SIZE_STR, PHP_INI_ALL, OnUpdateNetCmdBufferSize, 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) + STD_PHP_INI_ENTRY("mysqlnd.net_read_timeout", "31536000", PHP_INI_SYSTEM, OnUpdateLong, net_read_timeout, zend_mysqlnd_globals, mysqlnd_globals) + STD_PHP_INI_ENTRY("mysqlnd.log_mask", "0", PHP_INI_ALL, OnUpdateLong, log_mask, zend_mysqlnd_globals, mysqlnd_globals) + STD_PHP_INI_ENTRY("mysqlnd.mempool_default_size","16000", PHP_INI_ALL, OnUpdateLong, mempool_default_size, zend_mysqlnd_globals, mysqlnd_globals) + +#if PHP_DEBUG + STD_PHP_INI_ENTRY("mysqlnd.debug_emalloc_fail_threshold","-1", PHP_INI_SYSTEM, OnUpdateLong, debug_emalloc_fail_threshold, zend_mysqlnd_globals, mysqlnd_globals) + STD_PHP_INI_ENTRY("mysqlnd.debug_ecalloc_fail_threshold","-1", PHP_INI_SYSTEM, OnUpdateLong, debug_ecalloc_fail_threshold, zend_mysqlnd_globals, mysqlnd_globals) + STD_PHP_INI_ENTRY("mysqlnd.debug_erealloc_fail_threshold","-1", PHP_INI_SYSTEM, OnUpdateLong, debug_erealloc_fail_threshold, zend_mysqlnd_globals, mysqlnd_globals) + + STD_PHP_INI_ENTRY("mysqlnd.debug_malloc_fail_threshold","-1", PHP_INI_SYSTEM, OnUpdateLong, debug_malloc_fail_threshold, zend_mysqlnd_globals, mysqlnd_globals) + STD_PHP_INI_ENTRY("mysqlnd.debug_calloc_fail_threshold","-1", PHP_INI_SYSTEM, OnUpdateLong, debug_calloc_fail_threshold, zend_mysqlnd_globals, mysqlnd_globals) + STD_PHP_INI_ENTRY("mysqlnd.debug_realloc_fail_threshold","-1", PHP_INI_SYSTEM, OnUpdateLong, debug_realloc_fail_threshold, zend_mysqlnd_globals, mysqlnd_globals) +#endif +PHP_INI_END() +/* }}} */ + + +/* {{{ PHP_MINIT_FUNCTION + */ +static PHP_MINIT_FUNCTION(mysqlnd) +{ + REGISTER_INI_ENTRIES(); + + mysqlnd_library_init(TSRMLS_C); + return SUCCESS; +} +/* }}} */ + + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +static PHP_MSHUTDOWN_FUNCTION(mysqlnd) +{ + mysqlnd_library_end(TSRMLS_C); + + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + + +#if PHP_DEBUG +/* {{{ PHP_RINIT_FUNCTION + */ +static PHP_RINIT_FUNCTION(mysqlnd) +{ + if (MYSQLND_G(debug)) { + struct st_mysqlnd_plugin_trace_log * trace_log_plugin = mysqlnd_plugin_find("debug_trace"); + MYSQLND_G(dbg) = NULL; + if (trace_log_plugin) { + MYSQLND_DEBUG * dbg = trace_log_plugin->methods.trace_instance_init(mysqlnd_debug_std_no_trace_funcs TSRMLS_CC); + if (!dbg) { + return FAILURE; + } + dbg->m->set_mode(dbg, MYSQLND_G(debug)); + MYSQLND_G(dbg) = dbg; + } + } + return SUCCESS; +} +/* }}} */ +#endif + + +#if PHP_DEBUG +/* {{{ PHP_RSHUTDOWN_FUNCTION + */ +static PHP_RSHUTDOWN_FUNCTION(mysqlnd) +{ + MYSQLND_DEBUG *dbg = MYSQLND_G(dbg); + DBG_ENTER("RSHUTDOWN"); + if (dbg) { + dbg->m->close(dbg); + dbg->m->free_handle(dbg); + MYSQLND_G(dbg) = NULL; + } + return SUCCESS; +} +/* }}} */ +#endif + + + +static const zend_module_dep mysqlnd_deps[] = { + ZEND_MOD_REQUIRED("standard") + ZEND_MOD_END +}; + +/* {{{ mysqlnd_module_entry + */ +zend_module_entry mysqlnd_module_entry = { + STANDARD_MODULE_HEADER_EX, + NULL, + mysqlnd_deps, + "mysqlnd", + mysqlnd_functions, + PHP_MINIT(mysqlnd), + PHP_MSHUTDOWN(mysqlnd), +#if PHP_DEBUG + PHP_RINIT(mysqlnd), +#else + NULL, +#endif +#if PHP_DEBUG + PHP_RSHUTDOWN(mysqlnd), +#else + NULL, +#endif + PHP_MINFO(mysqlnd), + MYSQLND_VERSION, + PHP_MODULE_GLOBALS(mysqlnd), + PHP_GINIT(mysqlnd), + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + +/* {{{ COMPILE_DL_MYSQLND */ +#ifdef COMPILE_DL_MYSQLND +ZEND_GET_MODULE(mysqlnd) +#endif +/* }}} */ + +/* + * 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 + */ diff --git a/ext/mysqlnd/php_mysqlnd.h b/ext/mysqlnd/php_mysqlnd.h new file mode 100644 index 0000000..14ad234 --- /dev/null +++ b/ext/mysqlnd/php_mysqlnd.h @@ -0,0 +1,38 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2013 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: Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + | Georg Richter <georg@mysql.com> | + +----------------------------------------------------------------------+ + + $Id$ +*/ + +#ifndef PHP_MYSQLND_H +#define PHP_MYSQLND_H + +#define phpext_mysqlnd_ptr &mysqlnd_module_entry +extern zend_module_entry mysqlnd_module_entry; + +#endif /* PHP_MYSQLND_H */ + +/* + * 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 + */ |