diff options
Diffstat (limited to 'ext')
53 files changed, 17238 insertions, 1225 deletions
diff --git a/ext/mysql/config.m4 b/ext/mysql/config.m4 index 5870be02e1..f5ed71c6c5 100644 --- a/ext/mysql/config.m4 +++ b/ext/mysql/config.m4 @@ -40,7 +40,8 @@ AC_DEFUN([PHP_MYSQL_SOCKET_SEARCH], [ PHP_ARG_WITH(mysql, for MySQL support, -[ --with-mysql[=DIR] Include MySQL support. DIR is the MySQL base directory]) +[ --with-mysql[=DIR] Include MySQL support. DIR is the MySQL base directory. + If mysqlnd is passed as DIR, the MySQL native driver will be used]) PHP_ARG_WITH(mysql-sock, for specified location of the MySQL UNIX socket, [ --with-mysql-sock[=DIR] MySQL: Location of the MySQL unix socket pointer. @@ -51,9 +52,11 @@ if test -z "$PHP_ZLIB_DIR"; then [ --with-zlib-dir[=DIR] MySQL: Set the path to libz install prefix], no, no) fi +if test "$PHP_MYSQL" = "mysqlnd"; then + dnl enables build of mysqnd library + PHP_MYSQLND_ENABLED=yes -if test "$PHP_MYSQL" != "no"; then - AC_DEFINE(HAVE_MYSQL, 1, [Whether you have MySQL]) +elif test "$PHP_MYSQL" != "no"; then AC_MSG_CHECKING([for MySQL UNIX socket location]) if test "$PHP_MYSQL_SOCK" != "no" && test "$PHP_MYSQL_SOCK" != "yes"; then @@ -137,14 +140,22 @@ Note that the MySQL client library is not bundled anymore!]) PHP_ADD_LIBRARY_WITH_PATH($MYSQL_LIBNAME, $MYSQL_LIB_DIR, MYSQL_SHARED_LIBADD) PHP_ADD_INCLUDE($MYSQL_INC_DIR) - PHP_NEW_EXTENSION(mysql, php_mysql.c, $ext_shared) - MYSQL_MODULE_TYPE=external MYSQL_LIBS="-L$MYSQL_LIB_DIR -l$MYSQL_LIBNAME $MYSQL_LIBS" MYSQL_INCLUDE=-I$MYSQL_INC_DIR - PHP_SUBST(MYSQL_SHARED_LIBADD) PHP_SUBST_OLD(MYSQL_MODULE_TYPE) PHP_SUBST_OLD(MYSQL_LIBS) PHP_SUBST_OLD(MYSQL_INCLUDE) fi + +dnl Enable extension +if test "$PHP_MYSQL" != "no"; then + AC_DEFINE(HAVE_MYSQL, 1, [Whether you have MySQL]) + PHP_NEW_EXTENSION(mysql, php_mysql.c, $ext_shared) + PHP_SUBST(MYSQL_SHARED_LIBADD) + + if test "$PHP_MYSQLI" = "mysqlnd"; then + PHP_ADD_EXTENSION_DEP(mysqli, mysqlnd) + fi +fi diff --git a/ext/mysql/config.w32 b/ext/mysql/config.w32 index 95f689e1d6..45fc13c8d1 100644 --- a/ext/mysql/config.w32 +++ b/ext/mysql/config.w32 @@ -4,12 +4,17 @@ ARG_WITH("mysql", "MySQL support", "no"); if (PHP_MYSQL != "no") { - if (CHECK_LIB("libmysql.lib", "mysql", PHP_MYSQL) && + if (PHP_MYSQLI != "mysqlnd") { + if (CHECK_LIB("libmysql.lib", "mysql", PHP_MYSQL) && CHECK_HEADER_ADD_INCLUDE("mysql.h", "CFLAGS_MYSQL", - PHP_MYSQL + "\\include;" + PHP_PHP_BUILD + "\\include\\mysql;" + PHP_MYSQL)) { - EXTENSION("mysql", "php_mysql.c"); - AC_DEFINE('HAVE_MYSQL', 1, 'Have MySQL library'); + PHP_MYSQL + "\\include;" + PHP_PHP_BUILD + "\\include\\mysql;" + PHP_MYSQL)) { + } else { + WARNING("mysql not enabled; libraries and headers not found"); + } } else { - WARNING("mysql not enabled; libraries and headers not found"); + AC_DEFINE('HAVE_MYSQLND', 1, 'MySQL native driver support enabled'); + ADD_EXTENSION_DEP('mysql', 'mysqlnd', true); } + EXTENSION("mysql", "php_mysql.c"); + AC_DEFINE('HAVE_MYSQL', 1, 'Have MySQL library'); } diff --git a/ext/mysql/mysql_mysqlnd.h b/ext/mysql/mysql_mysqlnd.h new file mode 100644 index 0000000000..cc8d162a90 --- /dev/null +++ b/ext/mysql/mysql_mysqlnd.h @@ -0,0 +1,41 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ + +*/ + +#ifndef MYSQL_MYSQLND_H +#define MYSQL_MYSQLND_H + +#include "ext/mysqlnd/mysqlnd_libmysql_compat.h" + +/* Here comes non-libmysql API to have less ifdefs in mysqli*/ +#define MYSQLI_CLOSE_EXPLICIT MYSQLND_CLOSE_EXPLICIT +#define MYSQLI_CLOSE_IMPLICIT MYSQLND_CLOSE_IMPLICIT +#define MYSQLI_CLOSE_DISCONNECTED MYSQLND_CLOSE_DISCONNECTED + +#define mysqli_result_is_unbuffered(r) ((r)->unbuf) +#define mysqli_server_status(c) (c)->upsert_status.server_status +#define mysqli_stmt_warning_count(s) mysqlnd_stmt_warning_count((s)) +#define mysqli_stmt_server_status(s) (s)->upsert_status.server_status +#define mysqli_stmt_get_connection(s) (s)->conn +#define mysqli_close(c, how) mysqlnd_close((c), (how)) +#define mysqli_stmt_close(c, implicit) mysqlnd_stmt_close((c), (implicit)) +#define mysqli_free_result(r, implicit) mysqlnd_free_result((r), (implicit)) + +#endif diff --git a/ext/mysql/php_mysql.c b/ext/mysql/php_mysql.c index 3edf6b9cb5..41a90c622d 100644 --- a/ext/mysql/php_mysql.c +++ b/ext/mysql/php_mysql.c @@ -33,6 +33,7 @@ #include "php_globals.h" #include "ext/standard/info.h" #include "ext/standard/php_string.h" +#include "ext/standard/basic_functions.h" #ifdef ZEND_ENGINE_2 # include "zend_exceptions.h" @@ -64,7 +65,6 @@ # endif #endif -#include <mysql.h> #include "php_ini.h" #include "php_mysql.h" @@ -79,7 +79,7 @@ static int le_result, le_link, le_plink; #define SAFE_STRING(s) ((s)?(s):"") -#if MYSQL_VERSION_ID > 32199 +#if MYSQL_VERSION_ID > 32199 || defined(HAVE_MYSQLND) # define mysql_row_length_type unsigned long # define HAVE_MYSQL_ERRNO #else @@ -89,7 +89,7 @@ static int le_result, le_link, le_plink; # endif #endif -#if MYSQL_VERSION_ID >= 32032 +#if MYSQL_VERSION_ID >= 32032 || defined(HAVE_MYSQLND) #define HAVE_GETINFO_FUNCS #endif @@ -101,10 +101,6 @@ static int le_result, le_link, le_plink; #define MYSQL_HAS_YEAR #endif -#if (MYSQL_VERSION_ID >= 40113 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50007 -#define MYSQL_HAS_SET_CHARSET -#endif - #define MYSQL_ASSOC 1<<0 #define MYSQL_NUM 1<<1 #define MYSQL_BOTH (MYSQL_ASSOC|MYSQL_NUM) @@ -124,13 +120,24 @@ ZEND_DECLARE_MODULE_GLOBALS(mysql) static PHP_GINIT_FUNCTION(mysql); typedef struct _php_mysql_conn { - MYSQL conn; + MYSQL *conn; int active_result_id; + int multi_query; } php_mysql_conn; +#ifdef HAVE_MYSQLND +static MYSQLND_ZVAL_PCACHE *mysql_mysqlnd_zval_cache; +static MYSQLND_QCACHE *mysql_mysqlnd_qcache; +#endif + +#define MYSQL_DISABLE_MQ if (mysql->multi_query) { \ + mysql_set_server_option(mysql->conn, MYSQL_OPTION_MULTI_STATEMENTS_OFF); \ + mysql->multi_query = 0; \ +} + /* {{{ mysql_functions[] */ -const zend_function_entry mysql_functions[] = { +static const zend_function_entry mysql_functions[] = { PHP_FE(mysql_connect, NULL) PHP_FE(mysql_pconnect, NULL) PHP_FE(mysql_close, NULL) @@ -184,10 +191,8 @@ const zend_function_entry mysql_functions[] = { PHP_FE(mysql_get_server_info, NULL) #endif - PHP_FE(mysql_info, NULL) -#ifdef MYSQL_HAS_SET_CHARSET - PHP_FE(mysql_set_charset, NULL) -#endif + PHP_FE(mysql_info, NULL) + /* for downwards compatability */ PHP_FALIAS(mysql, mysql_db_query, NULL) PHP_FALIAS(mysql_fieldname, mysql_field_name, NULL) @@ -216,10 +221,23 @@ const zend_function_entry mysql_functions[] = { }; /* }}} */ +/* Dependancies */ +static const zend_module_dep mysql_deps[] = { +#if defined(HAVE_MYSQLND) + ZEND_MOD_REQUIRED("mysqlnd") +#endif + {NULL, NULL, NULL} +}; + /* {{{ mysql_module_entry */ zend_module_entry mysql_module_entry = { - STANDARD_MODULE_HEADER, +#if ZEND_MODULE_API_NO >= 20050922 + STANDARD_MODULE_HEADER_EX, NULL, + mysql_deps, +#elif ZEND_MODULE_API_NO >= 20010901 + STANDARD_MODULE_HEADER, +#endif "mysql", mysql_functions, ZEND_MODULE_STARTUP_N(mysql), @@ -244,6 +262,26 @@ void timeout(int sig); #define CHECK_LINK(link) { if (link==-1) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "A link to the server could not be established"); RETURN_FALSE; } } +#if defined(HAVE_MYSQLND) +#define PHPMY_UNBUFFERED_QUERY_CHECK() \ +{\ + if (mysql->active_result_id) { \ + do { \ + int type; \ + MYSQL_RES *mysql_result; \ + \ + mysql_result = (MYSQL_RES *) zend_list_find(mysql->active_result_id, &type); \ + if (mysql_result && type==le_result) { \ + if (mysqli_result_is_unbuffered(mysql_result) && !mysql_eof(mysql_result)) { \ + php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Function called without first fetching all rows from a previous unbuffered query"); \ + } \ + zend_list_delete(mysql->active_result_id); \ + mysql->active_result_id = 0; \ + } \ + } while(0); \ + }\ +} +#else #define PHPMY_UNBUFFERED_QUERY_CHECK() \ { \ if (mysql->active_result_id) { \ @@ -262,7 +300,8 @@ void timeout(int sig); } \ } while(0); \ } \ -} \ +} +#endif /* {{{ _free_mysql_result * This wrapper is required since mysql_free_result() returns an integer, and @@ -295,7 +334,7 @@ static int php_mysql_select_db(php_mysql_conn *mysql, char *db TSRMLS_DC) { PHPMY_UNBUFFERED_QUERY_CHECK(); - if (mysql_select_db(&mysql->conn, db) != 0) { + if (mysql_select_db(mysql->conn, db) != 0) { return 0; } else { return 1; @@ -311,7 +350,7 @@ static void _close_mysql_link(zend_rsrc_list_entry *rsrc TSRMLS_DC) void (*handler) (int); handler = signal(SIGPIPE, SIG_IGN); - mysql_close(&link->conn); + mysql_close(link->conn); signal(SIGPIPE, handler); efree(link); MySG(num_links)--; @@ -326,7 +365,7 @@ static void _close_mysql_plink(zend_rsrc_list_entry *rsrc TSRMLS_DC) void (*handler) (int); handler = signal(SIGPIPE, SIG_IGN); - mysql_close(&link->conn); + mysql_close(link->conn); signal(SIGPIPE, handler); free(link); @@ -361,6 +400,10 @@ PHP_INI_BEGIN() STD_PHP_INI_ENTRY("mysql.default_socket", NULL, PHP_INI_ALL, OnUpdateStringUnempty, default_socket, zend_mysql_globals, mysql_globals) STD_PHP_INI_ENTRY("mysql.connect_timeout", "60", PHP_INI_ALL, OnUpdateLong, connect_timeout, zend_mysql_globals, mysql_globals) STD_PHP_INI_BOOLEAN("mysql.trace_mode", "0", PHP_INI_ALL, OnUpdateLong, trace_mode, zend_mysql_globals, mysql_globals) + STD_PHP_INI_BOOLEAN("mysql.allow_local_infile", "1", PHP_INI_SYSTEM, OnUpdateLong, allow_local_infile, zend_mysql_globals, mysql_globals) +#ifdef HAVE_MYSQLND + STD_PHP_INI_ENTRY("mysql.cache_size", "2000", PHP_INI_SYSTEM, OnUpdateLong, cache_size, zend_mysql_globals, mysql_globals) +#endif PHP_INI_END() /* }}} */ @@ -377,7 +420,12 @@ static PHP_GINIT_FUNCTION(mysql) mysql_globals->connect_error = NULL; mysql_globals->connect_timeout = 0; mysql_globals->trace_mode = 0; + mysql_globals->allow_local_infile = 1; mysql_globals->result_allocated = 0; +#ifdef HAVE_MYSQLND + mysql_globals->cache_size = 0; + mysql_globals->mysqlnd_thd_zval_cache = NULL; +#endif } /* }}} */ @@ -401,11 +449,16 @@ ZEND_MODULE_STARTUP_D(mysql) REGISTER_LONG_CONSTANT("MYSQL_CLIENT_INTERACTIVE", CLIENT_INTERACTIVE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MYSQL_CLIENT_IGNORE_SPACE", CLIENT_IGNORE_SPACE, CONST_CS | CONST_PERSISTENT); +#ifndef HAVE_MYSQLND #if MYSQL_VERSION_ID >= 40000 if (mysql_server_init(0, NULL, NULL)) { return FAILURE; } #endif +#else + mysql_mysqlnd_zval_cache = mysqlnd_palloc_init_cache(MySG(cache_size)); + mysql_mysqlnd_qcache = mysqlnd_qcache_init_cache(); +#endif return SUCCESS; } @@ -415,17 +468,25 @@ ZEND_MODULE_STARTUP_D(mysql) */ PHP_MSHUTDOWN_FUNCTION(mysql) { +#ifndef HAVE_MYSQLND #if MYSQL_VERSION_ID >= 40000 #ifdef PHP_WIN32 unsigned long client_ver = mysql_get_client_version(); - /* Can't call mysql_server_end() multiple times prior to 5.0.42 on Windows */ - if ((client_ver > 50042 && client_ver < 50100) || client_ver > 50122) { + /* + Can't call mysql_server_end() multiple times prior to 5.0.42 on Windows. + PHP bug#41350 MySQL bug#25621 + */ + if ((client_ver >= 50042 && client_ver < 50100) || client_ver > 50122) { mysql_server_end(); } #else mysql_server_end(); #endif #endif +#else + mysqlnd_palloc_free_cache(mysql_mysqlnd_zval_cache); + mysqlnd_qcache_free_cache_reference(&mysql_mysqlnd_qcache); +#endif UNREGISTER_INI_ENTRIES(); return SUCCESS; @@ -436,7 +497,7 @@ PHP_MSHUTDOWN_FUNCTION(mysql) */ PHP_RINIT_FUNCTION(mysql) { -#if defined(ZTS) && MYSQL_VERSION_ID >= 40000 +#if !defined(HAVE_MYSQLND) && defined(ZTS) && MYSQL_VERSION_ID >= 40000 if (mysql_thread_init()) { return FAILURE; } @@ -448,6 +509,10 @@ PHP_RINIT_FUNCTION(mysql) MySG(connect_errno) =0; MySG(result_allocated) = 0; +#ifdef HAVE_MYSQLND + MySG(mysqlnd_thd_zval_cache) = mysqlnd_palloc_rinit(mysql_mysqlnd_zval_cache); +#endif + return SUCCESS; } /* }}} */ @@ -456,7 +521,7 @@ PHP_RINIT_FUNCTION(mysql) */ PHP_RSHUTDOWN_FUNCTION(mysql) { -#if defined(ZTS) && MYSQL_VERSION_ID >= 40000 +#if !defined(HAVE_MYSQLND) && defined(ZTS) && MYSQL_VERSION_ID >= 40000 mysql_thread_end(); #endif @@ -469,6 +534,10 @@ PHP_RSHUTDOWN_FUNCTION(mysql) if (MySG(connect_error)!=NULL) { efree(MySG(connect_error)); } +#ifdef HAVE_MYSQLND + mysqlnd_palloc_rshutdown(MySG(mysqlnd_thd_zval_cache)); +#endif + return SUCCESS; } /* }}} */ @@ -486,12 +555,26 @@ PHP_MINFO_FUNCTION(mysql) snprintf(buf, sizeof(buf), "%ld", MySG(num_links)); php_info_print_table_row(2, "Active Links", buf); php_info_print_table_row(2, "Client API version", mysql_get_client_info()); -#if !defined (PHP_WIN32) && !defined (NETWARE) +#if !defined (PHP_WIN32) && !defined (NETWARE) && !defined(HAVE_MYSQLND) php_info_print_table_row(2, "MYSQL_MODULE_TYPE", PHP_MYSQL_TYPE); php_info_print_table_row(2, "MYSQL_SOCKET", MYSQL_UNIX_ADDR); php_info_print_table_row(2, "MYSQL_INCLUDE", PHP_MYSQL_INCLUDE); php_info_print_table_row(2, "MYSQL_LIBS", PHP_MYSQL_LIBS); #endif +#if defined(HAVE_MYSQLND) + { + zval values; + + php_info_print_table_header(2, "Persistent cache", mysql_mysqlnd_zval_cache? "enabled":"disabled"); + + if (mysql_mysqlnd_zval_cache) { + /* Now report cache status */ + mysqlnd_palloc_stats(mysql_mysqlnd_zval_cache, &values); + mysqlnd_minfo_print_hash(&values); + zval_dtor(&values); + } + } +#endif php_info_print_table_end(); @@ -511,9 +594,14 @@ PHP_MINFO_FUNCTION(mysql) MYSQL_DO_CONNECT_CLEANUP(); \ RETURN_FALSE; +#ifdef HAVE_MYSQLND +#define MYSQL_PORT 0 +#endif + static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) { char *user=NULL, *passwd=NULL, *host_and_port=NULL, *socket=NULL, *tmp=NULL, *host=NULL; + int user_len, passwd_len, host_len; char *hashed_details=NULL; int hashed_details_length, port = MYSQL_PORT; int client_flags = 0; @@ -521,7 +609,6 @@ static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) #if MYSQL_VERSION_ID <= 32230 void (*handler) (int); #endif - zval **z_host=NULL, **z_user=NULL, **z_passwd=NULL, **z_new_link=NULL, **z_client_flags=NULL; zend_bool free_host=0, new_link=0; long connect_timeout; @@ -560,93 +647,20 @@ static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) user = MySG(default_user); passwd = MySG(default_password); - switch(ZEND_NUM_ARGS()) { - case 0: /* defaults */ - break; - case 1: { - if (zend_get_parameters_ex(1, &z_host)==FAILURE) { - MYSQL_DO_CONNECT_RETURN_FALSE(); - } - } - break; - case 2: { - if (zend_get_parameters_ex(2, &z_host, &z_user)==FAILURE) { - MYSQL_DO_CONNECT_RETURN_FALSE(); - } - convert_to_string_ex(z_user); - user = Z_STRVAL_PP(z_user); - } - break; - case 3: { - if (zend_get_parameters_ex(3, &z_host, &z_user, &z_passwd) == FAILURE) { - MYSQL_DO_CONNECT_RETURN_FALSE(); - } - convert_to_string_ex(z_user); - convert_to_string_ex(z_passwd); - user = Z_STRVAL_PP(z_user); - passwd = Z_STRVAL_PP(z_passwd); - } - break; - case 4: { - if (!persistent) { - if (zend_get_parameters_ex(4, &z_host, &z_user, &z_passwd, &z_new_link) == FAILURE) { - MYSQL_DO_CONNECT_RETURN_FALSE(); - } - convert_to_string_ex(z_user); - convert_to_string_ex(z_passwd); - convert_to_boolean_ex(z_new_link); - user = Z_STRVAL_PP(z_user); - passwd = Z_STRVAL_PP(z_passwd); - new_link = Z_BVAL_PP(z_new_link); - } - else { - if (zend_get_parameters_ex(4, &z_host, &z_user, &z_passwd, &z_client_flags) == FAILURE) { - MYSQL_DO_CONNECT_RETURN_FALSE(); - } - convert_to_string_ex(z_user); - convert_to_string_ex(z_passwd); - convert_to_long_ex(z_client_flags); - user = Z_STRVAL_PP(z_user); - passwd = Z_STRVAL_PP(z_passwd); - client_flags = Z_LVAL_PP(z_client_flags); - } - } - break; - case 5: { - if (zend_get_parameters_ex(5, &z_host, &z_user, &z_passwd, &z_new_link, &z_client_flags) == FAILURE) { - MYSQL_DO_CONNECT_RETURN_FALSE(); - } - convert_to_string_ex(z_user); - convert_to_string_ex(z_passwd); - convert_to_boolean_ex(z_new_link); - convert_to_long_ex(z_client_flags); - user = Z_STRVAL_PP(z_user); - passwd = Z_STRVAL_PP(z_passwd); - new_link = Z_BVAL_PP(z_new_link); - client_flags = Z_LVAL_PP(z_client_flags); - } - break; - default: - WRONG_PARAM_COUNT; - break; + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sssll", &host_and_port, &host_len, + &user, &user_len, &passwd, &passwd_len, + &new_link, &client_flags)==FAILURE) { + WRONG_PARAM_COUNT; } - /* disable local infile option for open_basedir */ - if (((PG(open_basedir) && PG(open_basedir)[0] != '\0') || PG(safe_mode)) && (client_flags & CLIENT_LOCAL_FILES)) { - client_flags ^= CLIENT_LOCAL_FILES; + + /* mysql_pconnect does not support new_link parameter */ + if (persistent) { + client_flags= new_link; } - if (z_host) { - SEPARATE_ZVAL(z_host); /* We may modify z_host if it contains a port, separate */ - convert_to_string_ex(z_host); - host_and_port = Z_STRVAL_PP(z_host); - if (z_user) { - convert_to_string_ex(z_user); - user = Z_STRVAL_PP(z_user); - if (z_passwd) { - convert_to_string_ex(z_passwd); - passwd = Z_STRVAL_PP(z_passwd); - } - } + /* disable local infile option for open_basedir */ + if (((PG(open_basedir) && PG(open_basedir)[0] != '\0') || PG(safe_mode)) && (client_flags & CLIENT_LOCAL_FILES)) { + client_flags ^= CLIENT_LOCAL_FILES; } hashed_details_length = spprintf(&hashed_details, 0, "mysql_%s_%s_%s_%d", SAFE_STRING(host_and_port), SAFE_STRING(user), SAFE_STRING(passwd), client_flags); @@ -687,12 +701,12 @@ static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_length+1, (void **) &le)==FAILURE) { /* we don't */ zend_rsrc_list_entry new_le; - if (MySG(max_links)!=-1 && MySG(num_links)>=MySG(max_links)) { + if (MySG(max_links) != -1 && MySG(num_links) >= MySG(max_links)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Too many open links (%ld)", MySG(num_links)); efree(hashed_details); MYSQL_DO_CONNECT_RETURN_FALSE(); } - if (MySG(max_persistent)!=-1 && MySG(num_persistent)>=MySG(max_persistent)) { + if (MySG(max_persistent) != -1 && MySG(num_persistent) >= MySG(max_persistent)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Too many open persistent links (%ld)", MySG(num_persistent)); efree(hashed_details); MYSQL_DO_CONNECT_RETURN_FALSE(); @@ -700,28 +714,36 @@ static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) /* create the link */ mysql = (php_mysql_conn *) malloc(sizeof(php_mysql_conn)); mysql->active_result_id = 0; -#if MYSQL_VERSION_ID > 32199 /* this lets us set the port number */ - mysql_init(&mysql->conn); - - if (connect_timeout != -1) { - mysql_options(&mysql->conn, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&connect_timeout); - } + mysql->multi_query = 1; +#ifndef HAVE_MYSQLND + mysql->conn = mysql_init(NULL); +#else + mysql->conn = mysql_init(persistent); +#endif - if (mysql_real_connect(&mysql->conn, host, user, passwd, NULL, port, socket, client_flags)==NULL) { + if (connect_timeout != -1) + mysql_options(mysql->conn, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&connect_timeout); +#ifndef HAVE_MYSQLND + if (mysql_real_connect(mysql->conn, host, user, passwd, NULL, port, socket, client_flags)==NULL) #else - if (mysql_connect(&mysql->conn, host, user, passwd)==NULL) { + if (mysqlnd_connect(mysql->conn, host, user, passwd, 0, NULL, 0, + port, socket, client_flags, MySG(mysqlnd_thd_zval_cache) TSRMLS_CC) == NULL) #endif + { /* Populate connect error globals so that the error functions can read them */ - if (MySG(connect_error)!=NULL) efree(MySG(connect_error)); - MySG(connect_error)=estrdup(mysql_error(&mysql->conn)); + if (MySG(connect_error) != NULL) { + efree(MySG(connect_error)); + } + MySG(connect_error) = estrdup(mysql_error(mysql->conn)); php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", MySG(connect_error)); #if defined(HAVE_MYSQL_ERRNO) - MySG(connect_errno)=mysql_errno(&mysql->conn); + MySG(connect_errno) = mysql_errno(mysql->conn); #endif free(mysql); efree(hashed_details); MYSQL_DO_CONNECT_RETURN_FALSE(); } + mysql_options(mysql->conn, MYSQL_OPT_LOCAL_INFILE, (char *)&MySG(allow_local_infile)); /* hash it up */ Z_TYPE(new_le) = le_plink; @@ -737,36 +759,31 @@ static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) if (Z_TYPE_P(le) != le_plink) { MYSQL_DO_CONNECT_RETURN_FALSE(); } + mysql = (php_mysql_conn *) le->ptr; + mysql->active_result_id = 0; + mysql->multi_query = 1; /* ensure that the link did not die */ -#if MYSQL_VERSION_ID > 32230 /* Use mysql_ping to ensure link is alive (and to reconnect if needed) */ - if (mysql_ping(le->ptr)) { -#else /* Use mysql_stat() to check if server is alive */ - handler=signal(SIGPIPE, SIG_IGN); -#if defined(HAVE_MYSQL_ERRNO) && defined(CR_SERVER_GONE_ERROR) - mysql_stat(le->ptr); - if (mysql_errno(&((php_mysql_conn *) le->ptr)->conn) == CR_SERVER_GONE_ERROR) { -#else - if (!strcasecmp(mysql_stat(le->ptr), "mysql server has gone away")) { /* the link died */ -#endif - signal(SIGPIPE, handler); -#endif /* end mysql_ping */ -#if MYSQL_VERSION_ID > 32199 /* this lets us set the port number */ - if (mysql_real_connect(le->ptr, host, user, passwd, NULL, port, socket, client_flags)==NULL) { + if (mysql_ping(mysql->conn)) { + if (mysql_errno(mysql->conn) == 2006) { +#ifndef HAVE_MYSQLND + if (mysql_real_connect(mysql->conn, host, user, passwd, NULL, port, socket, client_flags)==NULL) #else - if (mysql_connect(le->ptr, host, user, passwd)==NULL) { + if (mysqlnd_connect(mysql->conn, host, user, passwd, 0, NULL, 0, + port, socket, client_flags, MySG(mysqlnd_thd_zval_cache) TSRMLS_CC) == NULL) #endif - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Link to server lost, unable to reconnect"); - zend_hash_del(&EG(persistent_list), hashed_details, hashed_details_length+1); - efree(hashed_details); - MYSQL_DO_CONNECT_RETURN_FALSE(); + { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Link to server lost, unable to reconnect"); + zend_hash_del(&EG(persistent_list), hashed_details, hashed_details_length+1); + efree(hashed_details); + MYSQL_DO_CONNECT_RETURN_FALSE(); + } + mysql_options(mysql->conn, MYSQL_OPT_LOCAL_INFILE, (char *)&MySG(allow_local_infile)); } - } -#if MYSQL_VERSION_ID < 32231 - signal(SIGPIPE, handler); + } else { +#ifdef HAVE_MYSQLND + mysqlnd_restart_psession(mysql->conn); #endif - - mysql = (php_mysql_conn *) le->ptr; - mysql->active_result_id = 0; + } } ZEND_REGISTER_RESOURCE(return_value, mysql, le_plink); } else { /* non persistent */ @@ -799,7 +816,7 @@ static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) zend_hash_del(&EG(regular_list), hashed_details, hashed_details_length+1); } } - if (MySG(max_links)!=-1 && MySG(num_links)>=MySG(max_links)) { + if (MySG(max_links) != -1 && MySG(num_links) >= MySG(max_links)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Too many open links (%ld)", MySG(num_links)); efree(hashed_details); MYSQL_DO_CONNECT_RETURN_FALSE(); @@ -807,28 +824,41 @@ static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) mysql = (php_mysql_conn *) emalloc(sizeof(php_mysql_conn)); mysql->active_result_id = 0; -#if MYSQL_VERSION_ID > 32199 /* this lets us set the port number */ - mysql_init(&mysql->conn); + mysql->multi_query = 1; +#ifndef HAVE_MYSQLND + mysql->conn = mysql_init(NULL); +#else + mysql->conn = mysql_init(persistent); +#endif - if (connect_timeout != -1) { - mysql_options(&mysql->conn, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&connect_timeout); - } + if (connect_timeout != -1) + mysql_options(mysql->conn, MYSQL_OPT_CONNECT_TIMEOUT, (const char *)&connect_timeout); - if (mysql_real_connect(&mysql->conn, host, user, passwd, NULL, port, socket, client_flags)==NULL) { +#ifndef HAVE_MYSQLND + if (mysql_real_connect(mysql->conn, host, user, passwd, NULL, port, socket, client_flags)==NULL) #else - if (mysql_connect(&mysql->conn, host, user, passwd)==NULL) { + if (mysqlnd_connect(mysql->conn, host, user, passwd, 0, NULL, 0, + port, socket, client_flags, MySG(mysqlnd_thd_zval_cache) TSRMLS_CC) == NULL) #endif + { /* Populate connect error globals so that the error functions can read them */ - if (MySG(connect_error)!=NULL) efree(MySG(connect_error)); - MySG(connect_error)=estrdup(mysql_error(&mysql->conn)); + if (MySG(connect_error) != NULL) { + efree(MySG(connect_error)); + } + MySG(connect_error) = estrdup(mysql_error(mysql->conn)); php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", MySG(connect_error)); #if defined(HAVE_MYSQL_ERRNO) - MySG(connect_errno)=mysql_errno(&mysql->conn); + MySG(connect_errno) = mysql_errno(mysql->conn); +#endif + /* free mysql structure */ +#ifdef HAVE_MYSQLND + mysqli_close(mysql->conn, MYSQLI_CLOSE_DISCONNECTED); #endif efree(hashed_details); efree(mysql); MYSQL_DO_CONNECT_RETURN_FALSE(); } + mysql_options(mysql->conn, MYSQL_OPT_LOCAL_INFILE, (char *)&MySG(allow_local_infile)); /* add it to the list */ ZEND_REGISTER_RESOURCE(return_value, mysql, le_link); @@ -997,7 +1027,7 @@ PHP_FUNCTION(mysql_get_host_info) ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, mysql_link, id, "MySQL-Link", le_link, le_plink); - RETURN_STRING((char *)mysql_get_host_info(&mysql->conn),1); + RETURN_STRING((char *)mysql_get_host_info(mysql->conn),1); } /* }}} */ @@ -1027,7 +1057,7 @@ PHP_FUNCTION(mysql_get_proto_info) ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, mysql_link, id, "MySQL-Link", le_link, le_plink); - RETURN_LONG(mysql_get_proto_info(&mysql->conn)); + RETURN_LONG(mysql_get_proto_info(mysql->conn)); } /* }}} */ @@ -1057,7 +1087,7 @@ PHP_FUNCTION(mysql_get_server_info) ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, mysql_link, id, "MySQL-Link", le_link, le_plink); - RETURN_STRING((char *)mysql_get_server_info(&mysql->conn),1); + RETURN_STRING((char *)mysql_get_server_info(mysql->conn),1); } /* }}} */ @@ -1081,7 +1111,7 @@ PHP_FUNCTION(mysql_info) ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, &mysql_link, id, "MySQL-Link", le_link, le_plink); - if ((str = (char *)mysql_info(&mysql->conn))) { + if ((str = (char *)mysql_info(mysql->conn))) { RETURN_STRING(str,1); } else { RETURN_FALSE; @@ -1107,7 +1137,7 @@ PHP_FUNCTION(mysql_thread_id) } ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, &mysql_link, id, "MySQL-Link", le_link, le_plink); - RETURN_LONG(mysql_thread_id(&mysql->conn)); + RETURN_LONG(mysql_thread_id(mysql->conn)); } /* }}} */ @@ -1118,6 +1148,10 @@ PHP_FUNCTION(mysql_stat) zval *mysql_link = NULL; int id = -1; php_mysql_conn *mysql; + char *stat; +#ifdef HAVE_MYSQLND + uint stat_len; +#endif if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|r", &mysql_link) == FAILURE) { return; @@ -1130,8 +1164,16 @@ PHP_FUNCTION(mysql_stat) ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, &mysql_link, id, "MySQL-Link", le_link, le_plink); PHPMY_UNBUFFERED_QUERY_CHECK(); - - RETURN_STRING((char *)mysql_stat(&mysql->conn), 1); +#ifndef HAVE_MYSQLND + if ((stat = (char *)mysql_stat(mysql->conn))) { + RETURN_STRING(stat, 1); +#else + if (mysqlnd_stat(mysql->conn, &stat, &stat_len) == PASS) { + RETURN_STRINGL(stat, stat_len, 0); +#endif + } else { + RETURN_FALSE; + } } /* }}} */ @@ -1153,38 +1195,7 @@ PHP_FUNCTION(mysql_client_encoding) } ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, &mysql_link, id, "MySQL-Link", le_link, le_plink); - - RETURN_STRING((char *)mysql_character_set_name(&mysql->conn), 1); -} -/* }}} */ -#endif - -#ifdef MYSQL_HAS_SET_CHARSET -/* {{{ proto bool mysql_set_charset(string csname [, int link_identifier]) - sets client character set */ -PHP_FUNCTION(mysql_set_charset) -{ - zval *mysql_link = NULL; - char *csname; - int id = -1, csname_len; - php_mysql_conn *mysql; - - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|r", &csname, &csname_len, &mysql_link) == FAILURE) { - return; - } - - if (ZEND_NUM_ARGS() == 1) { - id = php_mysql_get_default_link(INTERNAL_FUNCTION_PARAM_PASSTHRU); - CHECK_LINK(id); - } - - ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, &mysql_link, id, "MySQL-Link", le_link, le_plink); - - if (!mysql_set_character_set(&mysql->conn, csname)) { - RETURN_TRUE; - } else { - RETURN_FALSE; - } + RETURN_STRING((char *)mysql_character_set_name(mysql->conn), 1); } /* }}} */ #endif @@ -1224,7 +1235,7 @@ PHP_FUNCTION(mysql_create_db) convert_to_string_ex(db); - if (mysql_create_db(&mysql->conn, Z_STRVAL_PP(db))==0) { + if (mysql_create_db(mysql->conn, Z_STRVAL_PP(db))==0) { RETURN_TRUE; } else { RETURN_FALSE; @@ -1263,7 +1274,7 @@ PHP_FUNCTION(mysql_drop_db) convert_to_string_ex(db); - if (mysql_drop_db(&mysql->conn, Z_STRVAL_PP(db))==0) { + if (mysql_drop_db(mysql->conn, Z_STRVAL_PP(db))==0) { RETURN_TRUE; } else { RETURN_FALSE; @@ -1291,8 +1302,11 @@ static void php_mysql_do_query_general(zval **query, zval **mysql_link, int link PHPMY_UNBUFFERED_QUERY_CHECK(); + MYSQL_DISABLE_MQ; + convert_to_string_ex(query); +#ifndef HAVE_MYSQLND /* check explain */ if (MySG(trace_mode)) { if (!strncasecmp("select", Z_STRVAL_PP(query), 6)){ @@ -1300,14 +1314,14 @@ static void php_mysql_do_query_general(zval **query, zval **mysql_link, int link char *newquery; int newql = spprintf (&newquery, 0, "EXPLAIN %s", Z_STRVAL_PP(query)); - mysql_real_query(&mysql->conn, newquery, newql); + mysql_real_query(mysql->conn, newquery, newql); efree (newquery); - if (mysql_errno(&mysql->conn)) { - php_error_docref("http://www.mysql.com/doc" TSRMLS_CC, E_WARNING, "%s", mysql_error(&mysql->conn)); + if (mysql_errno(mysql->conn)) { + php_error_docref("http://www.mysql.com/doc" TSRMLS_CC, E_WARNING, "%s", mysql_error(mysql->conn)); RETURN_FALSE; } else { - mysql_result = mysql_use_result(&mysql->conn); + mysql_result = mysql_use_result(mysql->conn); while ((row = mysql_fetch_row(mysql_result))) { if (!strcmp("ALL", row[1])) { php_error_docref("http://www.mysql.com/doc" TSRMLS_CC, E_WARNING, "Your query requires a full tablescan (table %s, %s rows affected). Use EXPLAIN to optimize your query.", row[0], row[6]); @@ -1319,36 +1333,37 @@ static void php_mysql_do_query_general(zval **query, zval **mysql_link, int link } } } /* end explain */ +#endif /* mysql_query is binary unsafe, use mysql_real_query */ #if MYSQL_VERSION_ID > 32199 - if (mysql_real_query(&mysql->conn, Z_STRVAL_PP(query), Z_STRLEN_PP(query))!=0) { + if (mysql_real_query(mysql->conn, Z_STRVAL_PP(query), Z_STRLEN_PP(query))!=0) { /* check possible error */ if (MySG(trace_mode)){ - if (mysql_errno(&mysql->conn)){ - php_error_docref("http://www.mysql.com/doc" TSRMLS_CC, E_WARNING, "%s", mysql_error(&mysql->conn)); + if (mysql_errno(mysql->conn)){ + php_error_docref("http://www.mysql.com/doc" TSRMLS_CC, E_WARNING, "%s", mysql_error(mysql->conn)); } } RETURN_FALSE; } #else - if (mysql_query(&mysql->conn, Z_STRVAL_PP(query))!=0) { + if (mysql_query(mysql->conn, Z_STRVAL_PP(query))!=0) { /* check possible error */ if (MySG(trace_mode)){ - if (mysql_errno(&mysql->conn)){ - php_error_docref("http://www.mysql.com/doc" TSRMLS_CC, E_WARNING, "%s", mysql_error(&mysql->conn)); + if (mysql_errno(mysql->conn)){ + php_error_docref("http://www.mysql.com/doc" TSRMLS_CC, E_WARNING, "%s", mysql_error(mysql->conn)); } } RETURN_FALSE; } #endif if(use_store == MYSQL_USE_RESULT) { - mysql_result=mysql_use_result(&mysql->conn); + mysql_result=mysql_use_result(mysql->conn); } else { - mysql_result=mysql_store_result(&mysql->conn); + mysql_result=mysql_store_result(mysql->conn); } if (!mysql_result) { - if (PHP_MYSQL_VALID_RESULT(&mysql->conn)) { /* query should have returned rows */ + if (PHP_MYSQL_VALID_RESULT(mysql->conn)) { /* query should have returned rows */ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to save result set"); RETURN_FALSE; } else { @@ -1474,7 +1489,7 @@ PHP_FUNCTION(mysql_list_dbs) PHPMY_UNBUFFERED_QUERY_CHECK(); - if ((mysql_result=mysql_list_dbs(&mysql->conn, NULL))==NULL) { + if ((mysql_result=mysql_list_dbs(mysql->conn, NULL))==NULL) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to save MySQL query result"); RETURN_FALSE; } @@ -1519,7 +1534,7 @@ PHP_FUNCTION(mysql_list_tables) PHPMY_UNBUFFERED_QUERY_CHECK(); - if ((mysql_result=mysql_list_tables(&mysql->conn, NULL))==NULL) { + if ((mysql_result=mysql_list_tables(mysql->conn, NULL))==NULL) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to save MySQL query result"); RETURN_FALSE; } @@ -1566,7 +1581,7 @@ PHP_FUNCTION(mysql_list_fields) PHPMY_UNBUFFERED_QUERY_CHECK(); convert_to_string_ex(table); - if ((mysql_result=mysql_list_fields(&mysql->conn, Z_STRVAL_PP(table), NULL))==NULL) { + if ((mysql_result=mysql_list_fields(mysql->conn, Z_STRVAL_PP(table), NULL))==NULL) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to save MySQL query result"); RETURN_FALSE; } @@ -1596,7 +1611,7 @@ PHP_FUNCTION(mysql_list_processes) PHPMY_UNBUFFERED_QUERY_CHECK(); - mysql_result = mysql_list_processes(&mysql->conn); + mysql_result = mysql_list_processes(mysql->conn); if (mysql_result == NULL) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to save MySQL query result"); RETURN_FALSE; @@ -1639,7 +1654,7 @@ PHP_FUNCTION(mysql_error) ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, mysql_link, id, "MySQL-Link", le_link, le_plink); - RETURN_STRING((char *)mysql_error(&mysql->conn), 1); + RETURN_STRING((char *)mysql_error(mysql->conn), 1); } /* }}} */ @@ -1677,7 +1692,7 @@ PHP_FUNCTION(mysql_errno) ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, mysql_link, id, "MySQL-Link", le_link, le_plink); - RETURN_LONG(mysql_errno(&mysql->conn)); + RETURN_LONG(mysql_errno(mysql->conn)); } #endif /* }}} */ @@ -1710,7 +1725,7 @@ PHP_FUNCTION(mysql_affected_rows) ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, mysql_link, id, "MySQL-Link", le_link, le_plink); /* conversion from int64 to long happing here */ - Z_LVAL_P(return_value) = (long) mysql_affected_rows(&mysql->conn); + Z_LVAL_P(return_value) = (long) mysql_affected_rows(mysql->conn); Z_TYPE_P(return_value) = IS_LONG; } /* }}} */ @@ -1722,8 +1737,9 @@ PHP_FUNCTION(mysql_escape_string) { zval **str; + if (ZEND_NUM_ARGS()!=1 || zend_get_parameters_ex(1, &str) == FAILURE) { - ZEND_WRONG_PARAM_COUNT(); + WRONG_PARAM_COUNT; } convert_to_string_ex(str); /* assume worst case situation, which is 2x of the original string. @@ -1738,7 +1754,6 @@ PHP_FUNCTION(mysql_escape_string) if (MySG(trace_mode)){ php_error_docref("function.mysql-real-escape-string" TSRMLS_CC, E_WARNING, "This function is deprecated; use mysql_real_escape_string() instead."); } - } /* }}} */ @@ -1765,7 +1780,7 @@ PHP_FUNCTION(mysql_real_escape_string) ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, &mysql_link, id, "MySQL-Link", le_link, le_plink); new_str = safe_emalloc(str_len, 2, 1); - new_str_len = mysql_real_escape_string(&mysql->conn, new_str, str, str_len); + new_str_len = mysql_real_escape_string(mysql->conn, new_str, str, str_len); new_str = erealloc(new_str, new_str_len + 1); RETURN_STRINGL(new_str, new_str_len, 0); @@ -1799,7 +1814,7 @@ PHP_FUNCTION(mysql_insert_id) ZEND_FETCH_RESOURCE2(mysql, php_mysql_conn *, mysql_link, id, "MySQL-Link", le_link, le_plink); /* conversion from int64 to long happing here */ - Z_LVAL_P(return_value) = (long) mysql_insert_id(&mysql->conn); + Z_LVAL_P(return_value) = (long) mysql_insert_id(mysql->conn); Z_TYPE_P(return_value) = IS_LONG; } /* }}} */ @@ -1811,8 +1826,10 @@ PHP_FUNCTION(mysql_result) { zval **result, **row, **field=NULL; MYSQL_RES *mysql_result; +#ifndef HAVE_MYSQLND MYSQL_ROW sql_row; mysql_row_length_type *sql_row_lengths; +#endif int field_offset=0; switch (ZEND_NUM_ARGS()) { @@ -1839,10 +1856,6 @@ PHP_FUNCTION(mysql_result) RETURN_FALSE; } mysql_data_seek(mysql_result, Z_LVAL_PP(row)); - if ((sql_row=mysql_fetch_row(mysql_result))==NULL - || (sql_row_lengths=mysql_fetch_lengths(mysql_result))==NULL) { /* shouldn't happen? */ - RETURN_FALSE; - } if (field) { switch(Z_TYPE_PP(field)) { @@ -1892,6 +1905,11 @@ PHP_FUNCTION(mysql_result) } } +#ifndef HAVE_MYSQLND + if ((sql_row=mysql_fetch_row(mysql_result))==NULL + || (sql_row_lengths=mysql_fetch_lengths(mysql_result))==NULL) { /* shouldn't happen? */ + RETURN_FALSE; + } if (sql_row[field_offset]) { Z_TYPE_P(return_value) = IS_STRING; @@ -1904,6 +1922,9 @@ PHP_FUNCTION(mysql_result) } else { Z_TYPE_P(return_value) = IS_NULL; } +#else + mysqlnd_result_fetch_field_data(mysql_result, field_offset, return_value); +#endif } /* }}} */ @@ -1951,12 +1972,14 @@ static void php_mysql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int result_type, { zval **result, **arg2; MYSQL_RES *mysql_result; - MYSQL_ROW mysql_row; - MYSQL_FIELD *mysql_field; - mysql_row_length_type *mysql_row_lengths; - int i; zval *res, *ctor_params = NULL; zend_class_entry *ce = NULL; +#ifndef HAVE_MYSQLND + int i; + MYSQL_FIELD *mysql_field; + MYSQL_ROW mysql_row; + mysql_row_length_type *mysql_row_lengths; +#endif #ifdef ZEND_ENGINE_2 if (into_object) { @@ -2007,20 +2030,24 @@ static void php_mysql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int result_type, } if ((result_type & MYSQL_BOTH) == 0) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "The result type should be either MYSQL_NUM, MYSQL_ASSOC or MYSQL_BOTH."); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "The result type should be either MYSQL_NUM, MYSQL_ASSOC or MYSQL_BOTH"); } ZEND_FETCH_RESOURCE(mysql_result, MYSQL_RES *, result, -1, "MySQL result", le_result); - if ((mysql_row=mysql_fetch_row(mysql_result))==NULL - || (mysql_row_lengths=mysql_fetch_lengths(mysql_result))==NULL) { +#ifndef HAVE_MYSQLND + if ((mysql_row = mysql_fetch_row(mysql_result)) == NULL || + (mysql_row_lengths = mysql_fetch_lengths(mysql_result)) == NULL) { RETURN_FALSE; } array_init(return_value); mysql_field_seek(mysql_result, 0); - for (mysql_field=mysql_fetch_field(mysql_result), i=0; mysql_field; mysql_field=mysql_fetch_field(mysql_result), i++) { + for (mysql_field = mysql_fetch_field(mysql_result), i = 0; + mysql_field; + mysql_field = mysql_fetch_field(mysql_result), i++) + { if (mysql_row[i]) { zval *data; @@ -2053,9 +2080,13 @@ static void php_mysql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int result_type, } } } +#else + mysqlnd_fetch_into(mysql_result, MYSQLND_FETCH_ASSOC, return_value, MYSQLND_MYSQL); +#endif #ifdef ZEND_ENGINE_2 - if (into_object) { + /* mysqlnd might return FALSE if no more rows */ + if (into_object && Z_TYPE_P(return_value) != IS_BOOL) { zval dataset = *return_value; zend_fcall_info fci; zend_fcall_info_cache fcc; @@ -2127,7 +2158,19 @@ static void php_mysql_fetch_hash(INTERNAL_FUNCTION_PARAMETERS, int result_type, Gets a result row as an enumerated array */ PHP_FUNCTION(mysql_fetch_row) { +#ifdef HAVE_MYSQLND + MYSQL_RES *result; + zval *mysql_result; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &mysql_result) == FAILURE) { + return; + } + ZEND_FETCH_RESOURCE(result, MYSQL_RES *, &mysql_result, -1, "MySQL result", le_result); + + mysqlnd_fetch_into(result, MYSQLND_FETCH_NUM, return_value, MYSQLND_MYSQL); +#else php_mysql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQL_NUM, 1, 0); +#endif } /* }}} */ @@ -2149,7 +2192,20 @@ PHP_FUNCTION(mysql_fetch_object) Fetch a result row as an array (associative, numeric or both) */ PHP_FUNCTION(mysql_fetch_array) { +#ifndef HAVE_MYSQLND php_mysql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 2, 0); +#else + MYSQL_RES *result; + zval *mysql_result; + long mode = MYSQLND_FETCH_BOTH; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l", &mysql_result, &mode) == FAILURE) { + return; + } + ZEND_FETCH_RESOURCE(result, MYSQL_RES *, &mysql_result, -1, "MySQL result", le_result); + + mysqlnd_fetch_into(result, mode, return_value, MYSQLND_MYSQL); +#endif } /* }}} */ @@ -2158,7 +2214,19 @@ PHP_FUNCTION(mysql_fetch_array) Fetch a result row as an associative array */ PHP_FUNCTION(mysql_fetch_assoc) { +#ifndef HAVE_MYSQLND php_mysql_fetch_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQL_ASSOC, 1, 0); +#else + MYSQL_RES *result; + zval *mysql_result; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &mysql_result) == FAILURE) { + return; + } + ZEND_FETCH_RESOURCE(result, MYSQL_RES *, &mysql_result, -1, "MySQL result", le_result); + + mysqlnd_fetch_into(result, MYSQLND_FETCH_ASSOC, return_value, MYSQLND_MYSQL); +#endif } /* }}} */ @@ -2223,6 +2291,9 @@ static char *php_mysql_get_field_name(int field_type) case FIELD_TYPE_VAR_STRING: return "string"; break; +#if MYSQL_VERSION_ID > 50002 || defined(HAVE_MYSQLND) + case MYSQL_TYPE_BIT: +#endif #ifdef MYSQL_HAS_TINY case FIELD_TYPE_TINY: #endif @@ -2585,7 +2656,7 @@ PHP_FUNCTION(mysql_ping) PHPMY_UNBUFFERED_QUERY_CHECK(); - RETURN_BOOL(! mysql_ping(&mysql->conn)); + RETURN_BOOL(! mysql_ping(mysql->conn)); } /* }}} */ diff --git a/ext/mysql/php_mysql.h b/ext/mysql/php_mysql.h index a088208a4b..fe21af15c5 100644 --- a/ext/mysql/php_mysql.h +++ b/ext/mysql/php_mysql.h @@ -34,6 +34,25 @@ #include "TSRM.h" #endif +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#if defined(HAVE_MYSQLND) +#include "ext/mysqlnd/mysqlnd.h" +#include "ext/mysql/mysql_mysqlnd.h" +#else +#include <mysql.h> +#endif + +#if (MYSQL_VERSION_ID >= 40113 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50007 || HAVE_MYSQLND +#define MYSQL_HAS_SET_CHARSET +#endif + extern zend_module_entry mysql_module_entry; #define mysql_module_ptr &mysql_module_entry @@ -91,9 +110,6 @@ PHP_FUNCTION(mysql_stat); PHP_FUNCTION(mysql_thread_id); PHP_FUNCTION(mysql_client_encoding); PHP_FUNCTION(mysql_ping); -#if (MYSQL_VERSION_ID >= 40113 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID >= 50007 -PHP_FUNCTION(mysql_set_charset); -#endif ZEND_BEGIN_MODULE_GLOBALS(mysql) long default_link; @@ -108,6 +124,12 @@ ZEND_BEGIN_MODULE_GLOBALS(mysql) long connect_timeout; long result_allocated; long trace_mode; + long allow_local_infile; +#ifdef HAVE_MYSQLND + MYSQLND_THD_ZVAL_PCACHE *mysqlnd_thd_zval_cache; + MYSQLND_QCACHE *mysqlnd_qcache; + long cache_size; +#endif ZEND_END_MODULE_GLOBALS(mysql) #ifdef ZTS diff --git a/ext/mysqli/config.m4 b/ext/mysqli/config.m4 index 19bc252bb0..7302e66c42 100644 --- a/ext/mysqli/config.m4 +++ b/ext/mysqli/config.m4 @@ -3,18 +3,17 @@ dnl $Id$ dnl config.m4 for extension mysqli PHP_ARG_WITH(mysqli, for MySQLi support, -[ --with-mysqli[=FILE] Include MySQLi support. FILE is the optional pathname - to mysql_config [mysql_config]]) +[ --with-mysqli[=FILE] Include MySQLi support. FILE is the optional pathname to mysql_config [mysql_config]. + If mysqlnd is passed as FILE, the MySQL native driver will be used]) PHP_ARG_ENABLE(embedded_mysqli, whether to enable embedded MySQLi support, [ --enable-embedded-mysqli MYSQLi: Enable embedded support], no, no) -if test "$PHP_MYSQLI" != "no"; then +if test "$PHP_MYSQLI" = "mysqlnd"; then + dnl This needs to be set in any extension which wishes to use mysqlnd + PHP_MYSQLND_ENABLED=yes -dnl there are no mysql libs currently bundled with PHP.. --Jani -dnl if test "$PHP_MYSQL" = "yes"; then -dnl AC_MSG_ERROR([--with-mysql (using bundled libs) can not be used together with --with-mysqli.]) -dnl fi +elif test "$PHP_MYSQLI" != "no"; then if test "$PHP_MYSQLI" = "yes"; then MYSQL_CONFIG=`$php_shtool path mysql_config` @@ -26,6 +25,8 @@ dnl fi if test "$PHP_EMBEDDED_MYSQLI" = "yes"; then AC_DEFINE(HAVE_EMBEDDED_MYSQLI, 1, [embedded MySQL support enabled]) MYSQL_LIB_CFG='--libmysqld-libs' + dnl mysqlnd doesn't support embedded, so we have to add some extra stuff + mysqli_extra_sources="mysqli_embedded.c" elif test "$enable_maintainer_zts" = "yes"; then MYSQL_LIB_CFG='--libs_r' MYSQL_LIB_NAME='mysqlclient_r' @@ -48,17 +49,29 @@ dnl fi [ PHP_EVAL_INCLINE($MYSQLI_INCLINE) PHP_EVAL_LIBLINE($MYSQLI_LIBLINE, MYSQLI_SHARED_LIBADD) - AC_DEFINE(HAVE_MYSQLILIB,1,[ ]) - PHP_CHECK_LIBRARY($MYSQL_LIB_NAME, mysql_stmt_field_count, + AC_DEFINE(HAVE_MYSQLILIB, 1, [ ]) + PHP_CHECK_LIBRARY($MYSQL_LIB_NAME, mysql_set_character_set, [ ],[ - AC_MSG_ERROR([MySQLI doesn't support versions < 4.1.3 (for MySQL 4.1.x) and < 5.0.1 for (MySQL 5.0.x) anymore. Please update your libraries.]) - ],[$MYSQLI_LIBLINE]) + AC_MSG_ERROR([MySQLI doesn't support versions < 4.1.13 (for MySQL 4.1.x) and < 5.0.7 for (MySQL 5.0.x) anymore. Please update your libraries.]) + ],[$MYSQLI_LIBLINE]) ],[ AC_MSG_ERROR([wrong mysql library version or lib not found. Check config.log for more information.]) ],[ $MYSQLI_LIBLINE ]) - PHP_NEW_EXTENSION(mysqli, mysqli.c mysqli_api.c mysqli_prop.c mysqli_nonapi.c mysqli_fe.c mysqli_report.c mysqli_repl.c mysqli_driver.c mysqli_warning.c mysqli_exception.c mysqli_embedded.c, $ext_shared) + mysqli_extra_sources="$mysqli_extra_sources mysqli_repl.c" +fi + +dnl Build extension +if test "$PHP_MYSQLI" != "no"; then + mysqli_sources="mysqli.c mysqli_api.c mysqli_prop.c mysqli_nonapi.c \ + mysqli_fe.c mysqli_report.c mysqli_driver.c mysqli_warning.c \ + mysqli_exception.c $mysqli_extra_sources" + PHP_NEW_EXTENSION(mysqli, $mysqli_sources, $ext_shared) PHP_SUBST(MYSQLI_SHARED_LIBADD) + + if test "$PHP_MYSQLI" = "mysqlnd"; then + PHP_ADD_EXTENSION_DEP(mysqli, mysqlnd) + fi fi diff --git a/ext/mysqli/config.w32 b/ext/mysqli/config.w32 index 789112ea14..0f418f0cba 100644 --- a/ext/mysqli/config.w32 +++ b/ext/mysqli/config.w32 @@ -1,14 +1,42 @@ // $Id$ // vim:ft=javascript +// Note: The extension name is "mysqli", you enable it with "--with-mysqli". +// Passing value "mysqlnd" to it enables the bundled +// client library to connect to the MySQL server, i.e. no external MySQL +// client library is needed to perform the build. + ARG_WITH("mysqli", "MySQLi support", "no"); if (PHP_MYSQLI != "no") { - if (CHECK_LIB("libmysql.lib", "mysqli", PHP_MYSQLI) && - CHECK_HEADER_ADD_INCLUDE("mysql.h", "CFLAGS_MYSQLI", PHP_MYSQLI + "\\include;" + PHP_PHP_BUILD + "\\include\\mysql;" + PHP_MYSQLI)) { - EXTENSION("mysqli", "mysqli.c mysqli_api.c mysqli_prop.c mysqli_nonapi.c mysqli_fe.c mysqli_report.c mysqli_repl.c mysqli_driver.c mysqli_warning.c mysqli_exception.c mysqli_embedded.c"); - AC_DEFINE('HAVE_MYSQLILIB', 1, 'Have MySQLi library'); + mysqli_source = + "mysqli.c " + + "mysqli_api.c " + + "mysqli_driver.c " + + "mysqli_embedded.c " + + "mysqli_exception.c " + + "mysqli_fe.c " + + "mysqli_nonapi.c " + + "mysqli_prop.c " + + "mysqli_report.c " + + "mysqli_warning.c"; + + if (PHP_MYSQLI != "mysqlnd") { + if (CHECK_LIB("libmysql.lib", "mysqli", PHP_MYSQLI) && + CHECK_HEADER_ADD_INCLUDE("mysql.h", "CFLAGS_MYSQLI", PHP_MYSQLI + + "\\include;" + PHP_PHP_BUILD + + "\\include\\mysql;" + PHP_MYSQLI)) { + // No "mysqli_repl.c" when using "mysqlnd" + mysqli_extra_sources = "mysqli_repl.c"; + EXTENSION("mysqli", mysqli_source + " " + mysqli_extra_sources); + AC_DEFINE('HAVE_MYSQLILIB', 1, 'Have MySQLi library'); + } else { + WARNING("mysqli not enabled; libraries and headers not found"); + } } else { - WARNING("mysqli not enabled; libraries and headers not found"); + EXTENSION("mysqli", mysqli_source); + AC_DEFINE('HAVE_MYSQLND', 1, 'MySQLi with native driver support enabled'); + AC_DEFINE('HAVE_MYSQLILIB', 1, 'Have MySQLi library'); + ADD_EXTENSION_DEP('mysqli', 'mysqlnd', true); } } diff --git a/ext/mysqli/mysqli.c b/ext/mysqli/mysqli.c index ff4f206b7a..8de80f5e05 100644 --- a/ext/mysqli/mysqli.c +++ b/ext/mysqli/mysqli.c @@ -28,7 +28,7 @@ #include "php_ini.h" #include "ext/standard/info.h" #include "ext/standard/php_string.h" -#include "php_mysqli.h" +#include "php_mysqli_structs.h" #include "zend_exceptions.h" #define MYSQLI_STORE_RESULT 0 @@ -52,6 +52,12 @@ zend_class_entry *mysqli_driver_class_entry; zend_class_entry *mysqli_warning_class_entry; zend_class_entry *mysqli_exception_class_entry; +#ifdef HAVE_MYSQLND +MYSQLND_ZVAL_PCACHE *mysqli_mysqlnd_zval_cache; +MYSQLND_QCACHE *mysqli_mysqlnd_qcache; +#endif + + extern void php_mysqli_connect(INTERNAL_FUNCTION_PARAMETERS); typedef int (*mysqli_read_t)(mysqli_object *obj, zval **retval TSRMLS_DC); @@ -62,6 +68,63 @@ typedef struct _mysqli_prop_handler { mysqli_write_t write_func; } mysqli_prop_handler; +static int le_pmysqli; + +static int php_mysqli_persistent_on_rshut(zend_rsrc_list_entry *le TSRMLS_DC) +{ + if (le->type == le_pmysqli) { + mysqli_plist_entry *plist = (mysqli_plist_entry *) le->ptr; + HashPosition pos; + MYSQL **mysql; + ulong idx; + dtor_func_t pDestructor = plist->used_links.pDestructor; + plist->used_links.pDestructor = NULL; /* Don't call pDestructor now */ + + zend_hash_internal_pointer_reset_ex(&plist->used_links, &pos); + while (SUCCESS == zend_hash_get_current_data_ex(&plist->used_links, (void **)&mysql, &pos)) { + zend_hash_get_current_key_ex(&plist->used_links, NULL, NULL, &idx, FALSE, &pos); + /* Make it free */ + zend_hash_next_index_insert(&plist->free_links, mysql, sizeof(MYSQL *), NULL); + /* First move forward */ + zend_hash_move_forward_ex(&plist->used_links, &pos); + /* The delete, because del will free memory, but we need it's ->nextItem */ + zend_hash_index_del(&plist->used_links, idx); + } + + /* restore pDestructor, which should be php_mysqli_dtor_p_elements() */ + plist->used_links.pDestructor = pDestructor; + } + return ZEND_HASH_APPLY_KEEP; +} + +/* Destructor for mysqli entries in free_links/used_links */ +void php_mysqli_dtor_p_elements(void *data) +{ + MYSQL **mysql = (MYSQL **) data; + TSRMLS_FETCH(); +#if defined(HAVE_MYSQLND) + mysqlnd_end_psession(*mysql); +#endif + mysqli_close(*mysql, MYSQLI_CLOSE_IMPLICIT); +} + +ZEND_RSRC_DTOR_FUNC(php_mysqli_dtor) +{ + if (rsrc->ptr) { + mysqli_plist_entry *plist = (mysqli_plist_entry *) rsrc->ptr; + zend_hash_destroy(&plist->free_links); + zend_hash_destroy(&plist->used_links); + free(plist); + } +} + + +int php_le_pmysqli(void) +{ + return le_pmysqli; +} + +#ifndef HAVE_MYSQLND /* {{{ php_free_stmt_bind_buffer */ void php_free_stmt_bind_buffer(BIND_BUFFER bbuf, int type) { @@ -80,7 +143,7 @@ void php_free_stmt_bind_buffer(BIND_BUFFER bbuf, int type) if (bbuf.vars[i]) { zval_ptr_dtor(&bbuf.vars[i]); - } + } } if (bbuf.vars) { @@ -100,30 +163,44 @@ void php_free_stmt_bind_buffer(BIND_BUFFER bbuf, int type) } bbuf.var_cnt = 0; - return; } /* }}} */ +#endif /* {{{ php_clear_stmt_bind */ -void php_clear_stmt_bind(MY_STMT *stmt) +void php_clear_stmt_bind(MY_STMT *stmt TSRMLS_DC) { if (stmt->stmt) { - mysql_stmt_close(stmt->stmt); + if (mysqli_stmt_close(stmt->stmt, TRUE)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error occured while closing statement"); + return; + } } + /* + mysqlnd keeps track of the binding and has freed its + structures in stmt_close() above + */ +#ifndef HAVE_MYSQLND + /* Clean param bind */ php_free_stmt_bind_buffer(stmt->param, FETCH_SIMPLE); + /* Clean output bind */ php_free_stmt_bind_buffer(stmt->result, FETCH_RESULT); +#endif if (stmt->query) { efree(stmt->query); } efree(stmt); - return; } /* }}} */ /* {{{ php_clear_mysql */ void php_clear_mysql(MY_MYSQL *mysql) { + if (mysql->hash_key) { + efree(mysql->hash_key); + mysql->hash_key = NULL; + } if (mysql->li_read) { efree(Z_STRVAL_P(mysql->li_read)); FREE_ZVAL(mysql->li_read); @@ -140,7 +217,7 @@ static void mysqli_objects_free_storage(void *object TSRMLS_DC) mysqli_object *intern = (mysqli_object *)zo; MYSQLI_RESOURCE *my_res = (MYSQLI_RESOURCE *)intern->ptr; - my_efree(my_res); + my_efree(my_res); zend_object_std_dtor(&intern->zo TSRMLS_CC); efree(intern); } @@ -157,7 +234,9 @@ static void mysqli_link_free_storage(void *object TSRMLS_DC) if (my_res && my_res->ptr) { MY_MYSQL *mysql = (MY_MYSQL *)my_res->ptr; if (mysql->mysql) { - mysql_close(mysql->mysql); + if (!mysql->persistent) { + mysqli_close(mysql->mysql, MYSQLI_CLOSE_IMPLICIT); + } } php_clear_mysql(mysql); efree(mysql); @@ -166,6 +245,13 @@ static void mysqli_link_free_storage(void *object TSRMLS_DC) } /* }}} */ +/* {{{ mysql_driver_free_storage */ +static void mysqli_driver_free_storage(void *object TSRMLS_DC) +{ + mysqli_objects_free_storage(object TSRMLS_CC); +} +/* }}} */ + /* {{{ mysqli_stmt_free_storage */ static void mysqli_stmt_free_storage(void *object TSRMLS_DC) @@ -176,7 +262,7 @@ static void mysqli_stmt_free_storage(void *object TSRMLS_DC) if (my_res && my_res->ptr) { MY_STMT *stmt = (MY_STMT *)my_res->ptr; - php_clear_stmt_bind(stmt); + php_clear_stmt_bind(stmt TSRMLS_CC); } mysqli_objects_free_storage(object TSRMLS_CC); } @@ -243,7 +329,7 @@ zval *mysqli_read_property(zval *object, zval *member, int type TSRMLS_DC) ret = FAILURE; obj = (mysqli_object *)zend_objects_get_address(object TSRMLS_CC); - if (member->type != IS_STRING) { + if (member->type != IS_STRING) { tmp_member = *member; zval_copy_ctor(&tmp_member); convert_to_string(&tmp_member); @@ -256,7 +342,8 @@ zval *mysqli_read_property(zval *object, zval *member, int type TSRMLS_DC) if (ret == SUCCESS) { if (strcmp(obj->zo.ce->name, "mysqli_driver") && - (!obj->ptr || ((MYSQLI_RESOURCE *)(obj->ptr))->status < MYSQLI_STATUS_INITIALIZED)) { + (!obj->ptr || ((MYSQLI_RESOURCE *)(obj->ptr))->status < MYSQLI_STATUS_INITIALIZED)) + { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't fetch %s", obj->zo.ce->name ); retval = EG(uninitialized_zval_ptr); return(retval); @@ -290,7 +377,7 @@ void mysqli_write_property(zval *object, zval *member, zval *value TSRMLS_DC) zend_object_handlers *std_hnd; int ret; - if (member->type != IS_STRING) { + if (member->type != IS_STRING) { tmp_member = *member; zval_copy_ctor(&tmp_member); convert_to_string(&tmp_member); @@ -333,7 +420,6 @@ void mysqli_add_property(HashTable *h, char *pname, mysqli_read_t r_func, mysqli static union _zend_function *php_mysqli_constructor_get(zval *object TSRMLS_DC) { - mysqli_object *obj = (mysqli_object *)zend_objects_get_address(object TSRMLS_CC); zend_class_entry * ce = Z_OBJCE_P(object); if (ce != mysqli_link_class_entry && ce != mysqli_stmt_class_entry && @@ -342,6 +428,7 @@ static union _zend_function *php_mysqli_constructor_get(zval *object TSRMLS_DC) return zend_std_get_constructor(object TSRMLS_CC); } else { static zend_internal_function f; + mysqli_object *obj = (mysqli_object *)zend_objects_get_address(object TSRMLS_CC); f.function_name = obj->zo.ce->name; f.scope = obj->zo.ce; @@ -361,7 +448,7 @@ static union _zend_function *php_mysqli_constructor_get(zval *object TSRMLS_DC) } else if (obj->zo.ce == mysqli_warning_class_entry) { f.handler = ZEND_MN(mysqli_warning___construct); } - + return (union _zend_function*)&f; } } @@ -382,8 +469,7 @@ PHP_MYSQLI_EXPORT(zend_object_value) mysqli_objects_new(zend_class_entry *class_ intern->prop_handler = NULL; mysqli_base_class = class_type; - while (mysqli_base_class->type != ZEND_INTERNAL_CLASS && mysqli_base_class->parent != NULL) - { + while (mysqli_base_class->type != ZEND_INTERNAL_CLASS && mysqli_base_class->parent != NULL) { mysqli_base_class = mysqli_base_class->parent; } zend_hash_find(&classes, mysqli_base_class->name, mysqli_base_class->name_length + 1, @@ -396,6 +482,8 @@ PHP_MYSQLI_EXPORT(zend_object_value) mysqli_objects_new(zend_class_entry *class_ /* link object */ if (instanceof_function(class_type, mysqli_link_class_entry TSRMLS_CC)) { free_storage = mysqli_link_free_storage; + } else if (instanceof_function(class_type, mysqli_driver_class_entry TSRMLS_CC)) { /* driver object */ + free_storage = mysqli_driver_free_storage; } else if (instanceof_function(class_type, mysqli_stmt_class_entry TSRMLS_CC)) { /* stmt object */ free_storage = mysqli_stmt_free_storage; } else if (instanceof_function(class_type, mysqli_result_class_entry TSRMLS_CC)) { /* result object */ @@ -412,17 +500,21 @@ PHP_MYSQLI_EXPORT(zend_object_value) mysqli_objects_new(zend_class_entry *class_ return retval; } /* }}} */ - -/* {{{ mysqli_module_entry - */ + + /* Dependancies */ -static const zend_module_dep mysqli_deps[] = { +const static zend_module_dep mysqli_deps[] = { #if defined(HAVE_SPL) && ((PHP_MAJOR_VERSION > 5) || (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 1)) ZEND_MOD_REQUIRED("spl") #endif +#if defined(HAVE_MYSQLND) + ZEND_MOD_REQUIRED("mysqlnd") +#endif {NULL, NULL, NULL} }; +/* {{{ mysqli_module_entry + */ zend_module_entry mysqli_module_entry = { #if ZEND_MODULE_API_NO >= 20050922 STANDARD_MODULE_HEADER_EX, NULL, @@ -454,22 +546,33 @@ ZEND_GET_MODULE(mysqli) */ PHP_INI_BEGIN() STD_PHP_INI_ENTRY_EX("mysqli.max_links", "-1", PHP_INI_SYSTEM, OnUpdateLong, max_links, zend_mysqli_globals, mysqli_globals, display_link_numbers) + STD_PHP_INI_ENTRY_EX("mysqli.max_persistent", "-1", PHP_INI_SYSTEM, OnUpdateLong, max_persistent, zend_mysqli_globals, mysqli_globals, display_link_numbers) + STD_PHP_INI_BOOLEAN("mysqli.allow_persistent", "1", PHP_INI_SYSTEM, OnUpdateLong, allow_persistent, zend_mysqli_globals, mysqli_globals) STD_PHP_INI_ENTRY("mysqli.default_host", NULL, PHP_INI_ALL, OnUpdateString, default_host, zend_mysqli_globals, mysqli_globals) STD_PHP_INI_ENTRY("mysqli.default_user", NULL, PHP_INI_ALL, OnUpdateString, default_user, zend_mysqli_globals, mysqli_globals) STD_PHP_INI_ENTRY("mysqli.default_pw", NULL, PHP_INI_ALL, OnUpdateString, default_pw, zend_mysqli_globals, mysqli_globals) STD_PHP_INI_ENTRY("mysqli.default_port", "3306", PHP_INI_ALL, OnUpdateLong, default_port, zend_mysqli_globals, mysqli_globals) STD_PHP_INI_ENTRY("mysqli.default_socket", NULL, PHP_INI_ALL, OnUpdateStringUnempty, default_socket, zend_mysqli_globals, mysqli_globals) STD_PHP_INI_BOOLEAN("mysqli.reconnect", "0", PHP_INI_SYSTEM, OnUpdateLong, reconnect, zend_mysqli_globals, mysqli_globals) + STD_PHP_INI_BOOLEAN("mysqli.allow_local_infile", "1", PHP_INI_SYSTEM, OnUpdateLong, allow_local_infile, zend_mysqli_globals, mysqli_globals) +#ifdef HAVE_MYSQLND + STD_PHP_INI_ENTRY("mysqli.cache_size", "2000", PHP_INI_SYSTEM, OnUpdateLong, cache_size, zend_mysqli_globals, mysqli_globals) +#endif PHP_INI_END() - /* }}} */ + /* {{{ PHP_GINIT_FUNCTION */ static PHP_GINIT_FUNCTION(mysqli) { mysqli_globals->num_links = 0; - mysqli_globals->max_links = 0; + mysqli_globals->num_active_persistent = 0; + mysqli_globals->num_inactive_persistent = 0; + mysqli_globals->max_links = -1; + mysqli_globals->max_links = -1; + mysqli_globals->max_persistent = -1; + mysqli_globals->allow_persistent = 1; mysqli_globals->default_port = 0; mysqli_globals->default_host = NULL; mysqli_globals->default_user = NULL; @@ -478,11 +581,16 @@ static PHP_GINIT_FUNCTION(mysqli) mysqli_globals->reconnect = 0; mysqli_globals->report_mode = 0; mysqli_globals->report_ht = 0; + mysqli_globals->allow_local_infile = 1; #ifdef HAVE_EMBEDDED_MYSQLI mysqli_globals->embedded = 1; #else mysqli_globals->embedded = 0; #endif +#ifdef HAVE_MYSQLND + mysqli_globals->cache_size = 0; + mysqli_globals->mysqlnd_thd_zval_cache = NULL; +#endif } /* }}} */ @@ -494,6 +602,16 @@ PHP_MINIT_FUNCTION(mysqli) zend_object_handlers *std_hnd = zend_get_std_object_handlers(); REGISTER_INI_ENTRIES(); +#ifndef HAVE_MYSQLND +#if MYSQL_VERSION_ID >= 40000 + if (mysql_server_init(0, NULL, NULL)) { + return FAILURE; + } +#endif +#else + mysqli_mysqlnd_zval_cache = mysqlnd_palloc_init_cache(MyG(cache_size)); + mysqli_mysqlnd_qcache = mysqlnd_qcache_init_cache(); +#endif memcpy(&mysqli_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); mysqli_object_handlers.clone_obj = NULL; @@ -504,6 +622,10 @@ PHP_MINIT_FUNCTION(mysqli) zend_hash_init(&classes, 0, NULL, NULL, 1); + /* persistent connections */ + le_pmysqli = zend_register_list_destructors_ex(NULL, php_mysqli_dtor, + "MySqli persistent connection", module_number); + INIT_CLASS_ENTRY(cex, "mysqli_sql_exception", mysqli_exception_methods); #ifdef HAVE_SPL mysqli_exception_class_entry = zend_register_internal_class_ex(&cex, spl_ce_RuntimeException, NULL TSRMLS_CC); @@ -552,6 +674,13 @@ PHP_MINIT_FUNCTION(mysqli) REGISTER_LONG_CONSTANT("MYSQLI_OPT_CONNECT_TIMEOUT", MYSQL_OPT_CONNECT_TIMEOUT, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MYSQLI_OPT_LOCAL_INFILE", MYSQL_OPT_LOCAL_INFILE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MYSQLI_INIT_COMMAND", MYSQL_INIT_COMMAND, CONST_CS | CONST_PERSISTENT); +#if defined(HAVE_MYSQLND) + REGISTER_LONG_CONSTANT("MYSQLI_OPT_NET_CMD_BUFFER_SIZE", MYSQLND_OPT_NET_CMD_BUFFER_SIZE, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MYSQLI_OPT_NET_READ_BUFFER_SIZE", MYSQLND_OPT_NET_READ_BUFFER_SIZE, CONST_CS | CONST_PERSISTENT); +#endif +#ifdef MYSQLND_STRING_TO_INT_CONVERSION + REGISTER_LONG_CONSTANT("MYSQLI_OPT_INT_AND_YEAR_AS_INT", MYSQLND_OPT_INT_AND_YEAR_AS_INT, CONST_CS | CONST_PERSISTENT); +#endif /* mysqli_real_connect flags */ REGISTER_LONG_CONSTANT("MYSQLI_CLIENT_SSL", CLIENT_SSL, CONST_CS | CONST_PERSISTENT); @@ -573,7 +702,7 @@ PHP_MINIT_FUNCTION(mysqli) /* for mysqli_stmt_set_attr */ REGISTER_LONG_CONSTANT("MYSQLI_STMT_ATTR_UPDATE_MAX_LENGTH", STMT_ATTR_UPDATE_MAX_LENGTH, CONST_CS | CONST_PERSISTENT); -#if MYSQL_VERSION_ID > 50003 +#if MYSQL_VERSION_ID > 50003 || defined(HAVE_MYSQLND) REGISTER_LONG_CONSTANT("MYSQLI_STMT_ATTR_CURSOR_TYPE", STMT_ATTR_CURSOR_TYPE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MYSQLI_CURSOR_TYPE_NO_CURSOR", CURSOR_TYPE_NO_CURSOR, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MYSQLI_CURSOR_TYPE_READ_ONLY", CURSOR_TYPE_READ_ONLY, CONST_CS | CONST_PERSISTENT); @@ -581,7 +710,7 @@ PHP_MINIT_FUNCTION(mysqli) REGISTER_LONG_CONSTANT("MYSQLI_CURSOR_TYPE_SCROLLABLE", CURSOR_TYPE_SCROLLABLE, CONST_CS | CONST_PERSISTENT); #endif -#if MYSQL_VERSION_ID > 50007 +#if MYSQL_VERSION_ID > 50007 || defined(HAVE_MYSQLND) REGISTER_LONG_CONSTANT("MYSQLI_STMT_ATTR_PREFETCH_ROWS", STMT_ATTR_PREFETCH_ROWS, CONST_CS | CONST_PERSISTENT); #endif @@ -627,17 +756,19 @@ PHP_MINIT_FUNCTION(mysqli) REGISTER_LONG_CONSTANT("MYSQLI_TYPE_INTERVAL", FIELD_TYPE_INTERVAL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MYSQLI_TYPE_GEOMETRY", FIELD_TYPE_GEOMETRY, CONST_CS | CONST_PERSISTENT); -#if MYSQL_VERSION_ID > 50002 +#if MYSQL_VERSION_ID > 50002 || defined(HAVE_MYSQLND) REGISTER_LONG_CONSTANT("MYSQLI_TYPE_NEWDECIMAL", FIELD_TYPE_NEWDECIMAL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MYSQLI_TYPE_BIT", FIELD_TYPE_BIT, CONST_CS | CONST_PERSISTENT); #endif - + REGISTER_LONG_CONSTANT("MYSQLI_SET_CHARSET_NAME", MYSQL_SET_CHARSET_NAME, CONST_CS | CONST_PERSISTENT); /* replication */ +#if !defined(HAVE_MYSQLND) REGISTER_LONG_CONSTANT("MYSQLI_RPL_MASTER", MYSQL_RPL_MASTER, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MYSQLI_RPL_SLAVE", MYSQL_RPL_SLAVE, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MYSQLI_RPL_ADMIN", MYSQL_RPL_ADMIN, CONST_CS | CONST_PERSISTENT); +#endif /* bind support */ REGISTER_LONG_CONSTANT("MYSQLI_NO_DATA", MYSQL_NO_DATA, CONST_CS | CONST_PERSISTENT); @@ -652,10 +783,6 @@ PHP_MINIT_FUNCTION(mysqli) REGISTER_LONG_CONSTANT("MYSQLI_REPORT_ALL", MYSQLI_REPORT_ALL, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("MYSQLI_REPORT_OFF", 0, CONST_CS | CONST_PERSISTENT); - if (mysql_server_init(0, NULL, NULL)) { - return FAILURE; - } - return SUCCESS; } /* }}} */ @@ -664,15 +791,25 @@ PHP_MINIT_FUNCTION(mysqli) */ PHP_MSHUTDOWN_FUNCTION(mysqli) { +#ifndef HAVE_MYSQLND +#if MYSQL_VERSION_ID >= 40000 #ifdef PHP_WIN32 unsigned long client_ver = mysql_get_client_version(); - /* Can't call mysql_server_end() multiple times prior to 5.0.42 on Windows */ - if ((client_ver > 50042 && client_ver < 50100) || client_ver > 50122) { + /* + Can't call mysql_server_end() multiple times prior to 5.0.42 on Windows. + PHP bug#41350 MySQL bug#25621 + */ + if ((client_ver >= 50042 && client_ver < 50100) || client_ver > 50122) { mysql_server_end(); } #else mysql_server_end(); #endif +#endif +#else + mysqlnd_palloc_free_cache(mysqli_mysqlnd_zval_cache); + mysqlnd_qcache_free_cache_reference(&mysqli_mysqlnd_qcache); +#endif zend_hash_destroy(&mysqli_driver_properties); zend_hash_destroy(&mysqli_result_properties); @@ -690,13 +827,16 @@ PHP_MSHUTDOWN_FUNCTION(mysqli) */ PHP_RINIT_FUNCTION(mysqli) { -#ifdef ZTS +#if !defined(HAVE_MYSQLND) && defined(ZTS) && MYSQL_VERSION_ID >= 40000 if (mysql_thread_init()) { return FAILURE; } #endif MyG(error_msg) = NULL; MyG(error_no) = 0; +#ifdef HAVE_MYSQLND + MyG(mysqlnd_thd_zval_cache) = mysqlnd_palloc_rinit(mysqli_mysqlnd_zval_cache); +#endif return SUCCESS; } @@ -706,27 +846,55 @@ PHP_RINIT_FUNCTION(mysqli) */ PHP_RSHUTDOWN_FUNCTION(mysqli) { -#ifdef ZTS + /* check persistent connections, move used to free */ + zend_hash_apply(&EG(persistent_list), (apply_func_t) php_mysqli_persistent_on_rshut TSRMLS_CC); + +#if !defined(HAVE_MYSQLND) && defined(ZTS) && MYSQL_VERSION_ID >= 40000 mysql_thread_end(); #endif if (MyG(error_msg)) { efree(MyG(error_msg)); } +#ifdef HAVE_MYSQLND + mysqlnd_palloc_rshutdown(MyG(mysqlnd_thd_zval_cache)); +#endif return SUCCESS; } /* }}} */ + /* {{{ PHP_MINFO_FUNCTION */ PHP_MINFO_FUNCTION(mysqli) { + char buf[32]; + php_info_print_table_start(); php_info_print_table_header(2, "MysqlI Support", "enabled"); php_info_print_table_row(2, "Client API library version", mysql_get_client_info()); + snprintf(buf, sizeof(buf), "%ld", MyG(num_active_persistent)); + php_info_print_table_row(2, "Active Persistent Links", buf); + snprintf(buf, sizeof(buf), "%ld", MyG(num_inactive_persistent)); + php_info_print_table_row(2, "Inactive Persistent Links", buf); + snprintf(buf, sizeof(buf), "%ld", MyG(num_links)); + php_info_print_table_row(2, "Active Links", buf); +#if !defined(HAVE_MYSQLND) php_info_print_table_row(2, "Client API header version", MYSQL_SERVER_VERSION); php_info_print_table_row(2, "MYSQLI_SOCKET", MYSQL_UNIX_ADDR); - - +#else + { + zval values; + + php_info_print_table_header(2, "Persistent cache", mysqli_mysqlnd_zval_cache? "enabled":"disabled"); + + if (mysqli_mysqlnd_zval_cache) { + /* Now report cache status */ + mysqlnd_palloc_stats(mysqli_mysqlnd_zval_cache, &values); + mysqlnd_minfo_print_hash(&values); + zval_dtor(&values); + } + } +#endif php_info_print_table_end(); DISPLAY_INI_ENTRIES(); @@ -742,16 +910,16 @@ Parameters: ZEND_FUNCTION(mysqli_stmt_construct) { MY_MYSQL *mysql; - zval *mysql_link; + zval *mysql_link; MY_STMT *stmt; - MYSQLI_RESOURCE *mysqli_resource; + MYSQLI_RESOURCE *mysqli_resource; char *statement; - int stmt_len; + int statement_len; switch (ZEND_NUM_ARGS()) { case 1: /* mysql_stmt_init */ - if (zend_parse_parameters(1 TSRMLS_CC, "O", &mysql_link, mysqli_link_class_entry)==FAILURE) { + if (zend_parse_parameters(1 TSRMLS_CC, "O", &mysql_link, mysqli_link_class_entry)==FAILURE) { return; } MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); @@ -761,15 +929,15 @@ ZEND_FUNCTION(mysqli_stmt_construct) stmt->stmt = mysql_stmt_init(mysql->mysql); break; case 2: - if (zend_parse_parameters(2 TSRMLS_CC, "Os", &mysql_link, mysqli_link_class_entry, &statement, &stmt_len)==FAILURE) { + if (zend_parse_parameters(2 TSRMLS_CC, "Os", &mysql_link, mysqli_link_class_entry, &statement, &statement_len)==FAILURE) { return; } MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); stmt = (MY_STMT *)ecalloc(1,sizeof(MY_STMT)); - + if ((stmt->stmt = mysql_stmt_init(mysql->mysql))) { - mysql_stmt_prepare(stmt->stmt, statement, stmt_len); + mysql_stmt_prepare(stmt->stmt, statement, statement_len); } break; default: @@ -800,20 +968,24 @@ ZEND_FUNCTION(mysqli_result_construct) MY_MYSQL *mysql; MYSQL_RES *result; zval *mysql_link; - MYSQLI_RESOURCE *mysqli_resource; + MYSQLI_RESOURCE *mysqli_resource; long resmode = MYSQLI_STORE_RESULT; switch (ZEND_NUM_ARGS()) { case 1: - if (zend_parse_parameters(1 TSRMLS_CC, "O", &mysql_link, mysqli_link_class_entry)==FAILURE) { + if (zend_parse_parameters(1 TSRMLS_CC, "O", &mysql_link, mysqli_link_class_entry)==FAILURE) { return; } - break; + break; case 2: - if (zend_parse_parameters(2 TSRMLS_CC, "Ol", &mysql_link, mysqli_link_class_entry, &resmode)==FAILURE) { + if (zend_parse_parameters(2 TSRMLS_CC, "Ol", &mysql_link, mysqli_link_class_entry, &resmode)==FAILURE) { return; } - break; + if (resmode != MYSQLI_USE_RESULT && resmode != MYSQLI_STORE_RESULT) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid value for resultmode"); + RETURN_FALSE; + } + break; default: WRONG_PARAM_COUNT; } @@ -830,7 +1002,7 @@ ZEND_FUNCTION(mysqli_result_construct) mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE)); mysqli_resource->ptr = (void *)result; mysqli_resource->status = MYSQLI_STATUS_VALID; - + ((mysqli_object *) zend_object_store_get_object(getThis() TSRMLS_CC))->ptr = mysqli_resource; } @@ -843,12 +1015,14 @@ void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flags MYSQL_RES *result; zval *mysql_result; long fetchtype; + zval *ctor_params = NULL; + zend_class_entry *ce = NULL; +#if !defined(HAVE_MYSQLND) unsigned int i; MYSQL_FIELD *fields; MYSQL_ROW row; unsigned long *field_len; - zval *ctor_params = NULL; - zend_class_entry *ce = NULL; +#endif if (into_object) { char *class_name; @@ -882,11 +1056,12 @@ void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flags } MYSQLI_FETCH_RESOURCE(result, MYSQL_RES *, &mysql_result, "mysqli_result", MYSQLI_STATUS_VALID); - if ((fetchtype & MYSQLI_BOTH) == 0) { + if (fetchtype < MYSQLI_ASSOC || fetchtype > MYSQLI_BOTH) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "The result type should be either MYSQLI_NUM, MYSQLI_ASSOC or MYSQLI_BOTH"); RETURN_FALSE; } +#if !defined(HAVE_MYSQLND) if (!(row = mysql_fetch_row(result))) { RETURN_NULL(); } @@ -930,16 +1105,19 @@ void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flags } } } +#else + mysqlnd_fetch_into(result, MYSQLND_FETCH_ASSOC, return_value, MYSQLND_MYSQLI); +#endif - if (into_object) { + if (into_object && Z_TYPE_P(return_value) != IS_NULL) { zval dataset = *return_value; zend_fcall_info fci; zend_fcall_info_cache fcc; zval *retval_ptr; - + object_and_properties_init(return_value, ce, NULL); zend_merge_properties(return_value, Z_ARRVAL(dataset), 1 TSRMLS_CC); - + if (ce->constructor) { fci.size = sizeof(fci); fci.function_table = &ce->function_table; @@ -951,7 +1129,7 @@ void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flags if (Z_TYPE_P(ctor_params) == IS_ARRAY) { HashTable *ht = Z_ARRVAL_P(ctor_params); Bucket *p; - + fci.param_count = 0; fci.params = safe_emalloc(sizeof(zval*), ht->nNumOfElements, 0); p = ht->pListHead; @@ -979,7 +1157,7 @@ void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flags fcc.function_handler = ce->constructor; fcc.calling_scope = EG(scope); fcc.object_pp = &return_value; - + if (zend_call_function(&fci, &fcc TSRMLS_CC) == FAILURE) { zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_CC, "Could not execute %s::%s()", ce->name, ce->constructor->common.function_name); } else { @@ -1009,6 +1187,8 @@ PHP_MYSQLI_API void php_mysqli_set_error(long mysql_errno, char *mysql_err TSRML } /* }}} */ +#if !defined(HAVE_MYSQLND) + #define ALLOC_CALLBACK_ARGS(a, b, c)\ if (c) {\ a = (zval ***)safe_emalloc(c, sizeof(zval **), 0);\ @@ -1029,7 +1209,7 @@ if (a) {\ #define LOCAL_INFILE_ERROR_MSG(source,dest)\ memset(source, 0, LOCAL_INFILE_ERROR_LEN);\ -memcpy(source, dest, LOCAL_INFILE_ERROR_LEN-1); +memcpy(source, dest, MIN(strlen(dest), LOCAL_INFILE_ERROR_LEN-1)); /* {{{ void php_set_local_infile_handler_default */ @@ -1037,7 +1217,10 @@ void php_set_local_infile_handler_default(MY_MYSQL *mysql) { /* register internal callback functions */ mysql_set_local_infile_handler(mysql->mysql, &php_local_infile_init, &php_local_infile_read, &php_local_infile_end, &php_local_infile_error, (void *)mysql); - mysql->li_read = NULL; + if (mysql->li_read) { + zval_ptr_dtor(&mysql->li_read); + mysql->li_read = NULL; + } } /* }}} */ @@ -1047,7 +1230,7 @@ int php_local_infile_init(void **ptr, const char *filename, void *userdata) { mysqli_local_infile *data; MY_MYSQL *mysql; - php_stream_context *context = NULL; + php_stream_context *context = NULL; TSRMLS_FETCH(); @@ -1086,7 +1269,7 @@ int php_local_infile_init(void **ptr, const char *filename, void *userdata) int php_local_infile_read(void *ptr, char *buf, uint buf_len) { mysqli_local_infile *data; - MY_MYSQL *mysql; + MY_MYSQL *mysql; zval ***callback_args; zval *retval; zval *fp; @@ -1101,9 +1284,7 @@ int php_local_infile_read(void *ptr, char *buf, uint buf_len) /* default processing */ if (!mysql->li_read) { - int count; - - count = (int)php_stream_read(mysql->li_stream, buf, buf_len); + int count = (int)php_stream_read(mysql->li_stream, buf, buf_len); if (count < 0) { LOCAL_INFILE_ERROR_MSG(data->error_msg, ER(2)); @@ -1113,21 +1294,21 @@ int php_local_infile_read(void *ptr, char *buf, uint buf_len) } ALLOC_CALLBACK_ARGS(callback_args, 1, argc); - + /* set parameters: filepointer, buffer, buffer_len, errormsg */ MAKE_STD_ZVAL(fp); php_stream_to_zval(mysql->li_stream, fp); callback_args[0] = &fp; - ZVAL_STRING(*callback_args[1], "", 1); - ZVAL_LONG(*callback_args[2], buf_len); - ZVAL_STRING(*callback_args[3], "", 1); + ZVAL_STRING(*callback_args[1], "", 1); + ZVAL_LONG(*callback_args[2], buf_len); + ZVAL_STRING(*callback_args[3], "", 1); if (call_user_function_ex(EG(function_table), NULL, mysql->li_read, &retval, - argc, + argc, callback_args, 0, NULL TSRMLS_CC) == SUCCESS) { @@ -1136,22 +1317,36 @@ int php_local_infile_read(void *ptr, char *buf, uint buf_len) zval_ptr_dtor(&retval); if (rc > 0) { - if (rc > buf_len) { + if (rc >= 0 && rc != Z_STRLEN_P(*callback_args[1])) { + LOCAL_INFILE_ERROR_MSG(data->error_msg, + "Mismatch between the return value of the callback and the content " + "length of the buffer."); + rc = -1; + } else if (rc > buf_len) { /* check buffer overflow */ - LOCAL_INFILE_ERROR_MSG(data->error_msg, "Read buffer too large"); + LOCAL_INFILE_ERROR_MSG(data->error_msg, "Too much data returned"); rc = -1; } else { - memcpy(buf, Z_STRVAL_P(*callback_args[1]), rc); + memcpy(buf, Z_STRVAL_P(*callback_args[1]), MIN(rc, Z_STRLEN_P(*callback_args[1]))); } - } - if (rc < 0) { + } else if (rc < 0) { LOCAL_INFILE_ERROR_MSG(data->error_msg, Z_STRVAL_P(*callback_args[3])); } } else { LOCAL_INFILE_ERROR_MSG(data->error_msg, "Can't execute load data local init callback function"); rc = -1; } - + /* + If the (ab)user has closed the file handle we should + not try to use it anymore or even close it + */ + if (!zend_rsrc_list_get_rsrc_type(Z_LVAL_P(fp) TSRMLS_CC)) { + LOCAL_INFILE_ERROR_MSG(data->error_msg, "File handle closed"); + rc = -1; + /* Thus the end handler won't try to free already freed memory */ + mysql->li_stream = NULL; + } + FREE_CALLBACK_ARGS(callback_args, 1, argc); efree(fp); return rc; @@ -1167,7 +1362,7 @@ int php_local_infile_error(void *ptr, char *error_msg, uint error_msg_len) if (data) { strlcpy(error_msg, data->error_msg, error_msg_len); return 2000; - } + } strlcpy(error_msg, ER(CR_OUT_OF_MEMORY), error_msg_len); return CR_OUT_OF_MEMORY; } @@ -1175,10 +1370,10 @@ int php_local_infile_error(void *ptr, char *error_msg, uint error_msg_len) /* {{{ php_local_infile_end */ -void php_local_infile_end(void *ptr) +void php_local_infile_end(void *ptr) { - mysqli_local_infile *data; - MY_MYSQL *mysql; + mysqli_local_infile *data; + MY_MYSQL *mysql; TSRMLS_FETCH(); @@ -1193,9 +1388,10 @@ void php_local_infile_end(void *ptr) php_stream_close(mysql->li_stream); free(data); - return; + return; } /* }}} */ +#endif /* * Local variables: diff --git a/ext/mysqli/mysqli_api.c b/ext/mysqli/mysqli_api.c index fd01ba9ffa..5e55be7d51 100644 --- a/ext/mysqli/mysqli_api.c +++ b/ext/mysqli/mysqli_api.c @@ -12,7 +12,9 @@ | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ - | Author: Georg Richter <georg@php.net> | + | Authors: Georg Richter <georg@php.net> | + | Andrey Hristov <andrey@php.net> | + | Ulf Wendel <uw@php.net> | +----------------------------------------------------------------------+ $Id$ @@ -26,8 +28,9 @@ #include "php.h" #include "php_ini.h" +#include "php_globals.h" #include "ext/standard/info.h" -#include "php_mysqli.h" +#include "php_mysqli_structs.h" /* {{{ proto mixed mysqli_affected_rows(object link) Get number of affected rows in previous MySQL operation */ @@ -51,16 +54,17 @@ PHP_FUNCTION(mysqli_affected_rows) } /* }}} */ + /* {{{ proto bool mysqli_autocommit(object link, bool mode) Turn auto commit on or of */ PHP_FUNCTION(mysqli_autocommit) { - MY_MYSQL *mysql; - zval *mysql_link; - zend_bool automode; + MY_MYSQL *mysql; + zval *mysql_link; + zend_bool automode; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ob", &mysql_link, mysqli_link_class_entry, &automode) == FAILURE) { - return; + return; } MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); @@ -71,83 +75,38 @@ PHP_FUNCTION(mysqli_autocommit) } /* }}} */ -/* {{{ proto bool mysqli_stmt_bind_param(object stmt, string types, mixed variable [,mixed,....]) - Bind variables to a prepared statement as parameters */ -PHP_FUNCTION(mysqli_stmt_bind_param) +/* {{{ mysqli_stmt_bind_param_do_bind */ +#ifndef HAVE_MYSQLND +static +int mysqli_stmt_bind_param_do_bind(MY_STMT *stmt, unsigned int argc, unsigned int num_vars, + zval ***args, unsigned int start, const char * const types TSRMLS_DC) { - zval ***args; - int argc = ZEND_NUM_ARGS(); - int i; - int num_vars; - int start = 2; - int ofs; - MY_STMT *stmt; - zval *mysql_stmt; + int i, ofs; MYSQL_BIND *bind; - char *types; - int typelen; unsigned long rc; - /* calculate and check number of parameters */ - if (argc < 2) { - /* there has to be at least one pair */ - WRONG_PARAM_COUNT; - } - - if (zend_parse_method_parameters((getThis()) ? 1:2 TSRMLS_CC, getThis(), "Os", &mysql_stmt, mysqli_stmt_class_entry, &types, &typelen) == FAILURE) { - return; - } - - MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &mysql_stmt, "mysqli_stmt", MYSQLI_STATUS_VALID); - - num_vars = argc - 1; - if (getThis()) { - start = 1; - } else { - /* ignore handle parameter in procedural interface*/ - --num_vars; - } - - if (typelen != argc - start) { - /* number of bind variables doesn't match number of elements in type definition string */ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Number of elements in type definition string doesn't match number of bind variables"); - RETURN_FALSE; - } - - if (typelen != stmt->stmt->param_count) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Number of variables doesn't match number of parameters in prepared statement"); - RETURN_FALSE; - } - /* prevent leak if variables are already bound */ if (stmt->param.var_cnt) { php_free_stmt_bind_buffer(stmt->param, FETCH_SIMPLE); } - args = (zval ***)safe_emalloc(argc, sizeof(zval **), 0); - - if (zend_get_parameters_array_ex(argc, args) == FAILURE) { - efree(args); - WRONG_PARAM_COUNT; - } - stmt->param.is_null = ecalloc(num_vars, sizeof(char)); - bind = (MYSQL_BIND *)ecalloc(num_vars, sizeof(MYSQL_BIND)); + bind = (MYSQL_BIND *) ecalloc(num_vars, sizeof(MYSQL_BIND)); ofs = 0; - for (i=start; i < argc; i++) { + for (i = start; i < argc; i++) { /* set specified type */ switch (types[ofs]) { case 'd': /* Double */ bind[ofs].buffer_type = MYSQL_TYPE_DOUBLE; - bind[ofs].buffer = (char*)&Z_DVAL_PP(args[i]); + bind[ofs].buffer = &Z_DVAL_PP(args[i]); bind[ofs].is_null = &stmt->param.is_null[ofs]; break; case 'i': /* Integer */ - bind[ofs].buffer_type = MYSQL_TYPE_LONG; - bind[ofs].buffer = (char*)&Z_LVAL_PP(args[i]); + bind[ofs].buffer_type = (sizeof(long) > 4) ? MYSQL_TYPE_LONGLONG : MYSQL_TYPE_LONG; + bind[ofs].buffer = &Z_LVAL_PP(args[i]); bind[ofs].is_null = &stmt->param.is_null[ofs]; break; @@ -164,85 +123,162 @@ PHP_FUNCTION(mysqli_stmt_bind_param) default: php_error_docref(NULL TSRMLS_CC, E_WARNING, "Undefined fieldtype %c (parameter %d)", types[ofs], i+1); - RETVAL_FALSE; - goto end; + rc = 1; + goto end_1; } ofs++; } rc = mysql_stmt_bind_param(stmt->stmt, bind); - MYSQLI_REPORT_STMT_ERROR(stmt->stmt); +end_1: if (rc) { - RETVAL_FALSE; - goto end; + efree(stmt->param.is_null); + } else { + stmt->param.var_cnt = num_vars; + stmt->param.vars = (zval **)safe_emalloc(num_vars, sizeof(zval), 0); + for (i = 0; i < num_vars; i++) { + if (bind[i].buffer_type != MYSQL_TYPE_LONG_BLOB) { + ZVAL_ADDREF(*args[i+start]); + stmt->param.vars[i] = *args[i+start]; + } else { + stmt->param.vars[i] = NULL; + } + } } + efree(bind); - stmt->param.var_cnt = num_vars; - stmt->param.vars = (zval **)safe_emalloc(num_vars, sizeof(zval), 0); - for (i = 0; i < num_vars; i++) { - if (bind[i].buffer_type != MYSQL_TYPE_LONG_BLOB) { - ZVAL_ADDREF(*args[i+start]); - stmt->param.vars[i] = *args[i+start]; - } else { - stmt->param.vars[i] = NULL; + return rc; +} +#else +static +int mysqli_stmt_bind_param_do_bind(MY_STMT *stmt, unsigned int argc, unsigned int num_vars, + zval ***args, unsigned int start, const char * const types TSRMLS_DC) +{ + int i; + MYSQLND_PARAM_BIND *params; + enum_func_status ret = FAIL; + + /* If no params -> skip binding and return directly */ + if (argc == start) { + return PASS; + } + params = emalloc((argc - start) * sizeof(MYSQLND_PARAM_BIND)); + for (i = 0; i < (argc - start); i++) { + zend_uchar type; + switch (types[i]) { + case 'd': /* Double */ + type = MYSQL_TYPE_DOUBLE; + break; + case 'i': /* Integer */ +#if SIZEOF_LONG==8 + type = MYSQL_TYPE_LONGLONG; +#elif SIZEOF_LONG==4 + type = MYSQL_TYPE_LONG; +#endif + break; + case 'b': /* Blob (send data) */ + type = MYSQL_TYPE_LONG_BLOB; + break; + case 's': /* string */ + type = MYSQL_TYPE_VAR_STRING; + break; + default: + /* We count parameters from 1 */ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Undefined fieldtype %c (parameter %d)", types[i], i + start + 1); + ret = FAIL; + efree(params); + goto end; } + params[i].zv = *(args[i + start]); + params[i].type = type; } - RETVAL_TRUE; + ret = mysqlnd_stmt_bind_param(stmt->stmt, params); + end: - efree(args); - efree(bind); + return ret; } +#endif /* }}} */ -/* {{{ proto bool mysqli_stmt_bind_result(object stmt, mixed var, [,mixed, ...]) - Bind variables to a prepared statement for result storage */ - -/* TODO: - do_alloca, free_alloca -*/ - -PHP_FUNCTION(mysqli_stmt_bind_result) +/* {{{ proto bool mysqli_stmt_bind_param(object stmt, string types, mixed variable [,mixed,....]) U + Bind variables to a prepared statement as parameters */ +PHP_FUNCTION(mysqli_stmt_bind_param) { - zval ***args; - int argc = ZEND_NUM_ARGS(); - int i; - int start = 1; - int var_cnt; - int ofs; - long col_type; - ulong rc; - MY_STMT *stmt; - zval *mysql_stmt; - MYSQL_BIND *bind; + zval ***args; + int argc = ZEND_NUM_ARGS(); + int num_vars; + int start = 2; + MY_STMT *stmt; + zval *mysql_stmt; + char *types; + int types_len; + unsigned long rc; - if (getThis()) { - start = 0; + /* calculate and check number of parameters */ + if (argc < 2) { + /* there has to be at least one pair */ + WRONG_PARAM_COUNT; } - if (zend_parse_method_parameters((getThis()) ? 0:1 TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { + if (zend_parse_method_parameters((getThis()) ? 1:2 TSRMLS_CC, getThis(), "Os", &mysql_stmt, mysqli_stmt_class_entry, + &types, &types_len) == FAILURE) { return; } - MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &mysql_stmt, "mysqli_stmt", MYSQLI_STATUS_VALID); + MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &mysql_stmt, "mysqli_stmt", MYSQLI_STATUS_VALID); - if (argc < (getThis() ? 1 : 2)) { - WRONG_PARAM_COUNT; + num_vars = argc - 1; + if (getThis()) { + start = 1; + } else { + /* ignore handle parameter in procedural interface*/ + --num_vars; + } + if (!types_len) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid type or no types specified"); + RETURN_FALSE; + } + + if (types_len != argc - start) { + /* number of bind variables doesn't match number of elements in type definition string */ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Number of elements in type definition string doesn't match number of bind variables"); + RETURN_FALSE; + } + + if (types_len != mysql_stmt_param_count(stmt->stmt)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Number of variables doesn't match number of parameters in prepared statement"); + RETURN_FALSE; } args = (zval ***)safe_emalloc(argc, sizeof(zval **), 0); if (zend_get_parameters_array_ex(argc, args) == FAILURE) { - efree(args); - WRONG_PARAM_COUNT; + zend_wrong_param_count(TSRMLS_C); + rc = 1; + } else { + rc = mysqli_stmt_bind_param_do_bind(stmt, argc, num_vars, args, start, types TSRMLS_CC); + MYSQLI_REPORT_STMT_ERROR(stmt->stmt); } - var_cnt = argc - start; + efree(args); - if (var_cnt != mysql_stmt_field_count(stmt->stmt)) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Number of bind variables doesn't match number of fields in prepared statement"); - efree(args); - RETURN_FALSE; - } + RETURN_BOOL(!rc); +} +/* }}} */ + +/* {{{ mysqli_stmt_bind_result_do_bind */ +#ifndef HAVE_MYSQLND +/* TODO: + do_alloca, free_alloca +*/ +static int +mysqli_stmt_bind_result_do_bind(MY_STMT *stmt, zval ***args, unsigned int argc, unsigned int start TSRMLS_DC) +{ + MYSQL_BIND *bind; + int i, ofs; + int var_cnt = argc - start; + long col_type; + ulong rc; /* prevent leak if variables are already bound */ if (stmt->result.var_cnt) { @@ -268,7 +304,7 @@ PHP_FUNCTION(mysqli_stmt_bind_result) convert_to_double_ex(args[i]); stmt->result.buf[ofs].type = IS_DOUBLE; stmt->result.buf[ofs].buflen = sizeof(double); - + /* allocate buffer for double */ stmt->result.buf[ofs].val = (char *)emalloc(sizeof(double)); bind[ofs].buffer_type = MYSQL_TYPE_DOUBLE; @@ -305,7 +341,7 @@ PHP_FUNCTION(mysqli_stmt_bind_result) break; case MYSQL_TYPE_LONGLONG: -#if MYSQL_VERSION_ID > 50002 +#if MYSQL_VERSION_ID > 50002 || defined(HAVE_MYSQLND) case MYSQL_TYPE_BIT: #endif stmt->result.buf[ofs].type = IS_STRING; @@ -324,8 +360,8 @@ PHP_FUNCTION(mysqli_stmt_bind_result) case MYSQL_TYPE_NEWDATE: case MYSQL_TYPE_VAR_STRING: case MYSQL_TYPE_STRING: - case MYSQL_TYPE_BLOB: case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_BLOB: case MYSQL_TYPE_MEDIUM_BLOB: case MYSQL_TYPE_LONG_BLOB: case MYSQL_TYPE_TIMESTAMP: @@ -385,7 +421,6 @@ PHP_FUNCTION(mysqli_stmt_bind_result) } /* Don't free stmt->result.is_null because is_null & buf are one block of memory */ efree(stmt->result.buf); - RETVAL_FALSE; } else { stmt->result.var_cnt = var_cnt; stmt->result.vars = (zval **)safe_emalloc((var_cnt), sizeof(zval), 0); @@ -394,10 +429,69 @@ PHP_FUNCTION(mysqli_stmt_bind_result) ZVAL_ADDREF(*args[i]); stmt->result.vars[ofs] = *args[i]; } - RETVAL_TRUE; } - efree(args); efree(bind); + + return rc; +} +#else +static int +mysqli_stmt_bind_result_do_bind(MY_STMT *stmt, zval ***args, unsigned int argc, unsigned int start TSRMLS_DC) +{ + unsigned int i; + MYSQLND_RESULT_BIND *params; + + params = emalloc((argc - start) * sizeof(MYSQLND_RESULT_BIND)); + for (i = 0; i < (argc - start); i++) { + params[i].zv = *(args[i + start]); + } + return mysqlnd_stmt_bind_result(stmt->stmt, params); +} +#endif +/* }}} */ + +/* {{{ proto bool mysqli_stmt_bind_result(object stmt, mixed var, [,mixed, ...]) U + Bind variables to a prepared statement for result storage */ +PHP_FUNCTION(mysqli_stmt_bind_result) +{ + zval ***args; + int argc = ZEND_NUM_ARGS(); + int start = 1; + ulong rc; + MY_STMT *stmt; + zval *mysql_stmt; + + if (getThis()) { + start = 0; + } + + if (zend_parse_method_parameters((getThis()) ? 0:1 TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { + return; + } + + MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &mysql_stmt, "mysqli_stmt", MYSQLI_STATUS_VALID); + + if (argc < (getThis() ? 1 : 2)) { + WRONG_PARAM_COUNT; + } + + if ((argc - start) != mysql_stmt_field_count(stmt->stmt)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Number of bind variables doesn't match number of fields in prepared statement"); + RETURN_FALSE; + } + + args = (zval ***)safe_emalloc(argc, sizeof(zval **), 0); + + if (zend_get_parameters_array_ex(argc, args) == FAILURE) { + efree(args); + WRONG_PARAM_COUNT; + } + + rc = mysqli_stmt_bind_result_do_bind(stmt, args, argc, start TSRMLS_CC); + + efree(args); + + RETURN_BOOL(!rc); } /* }}} */ @@ -406,9 +500,9 @@ PHP_FUNCTION(mysqli_stmt_bind_result) PHP_FUNCTION(mysqli_change_user) { MY_MYSQL *mysql; - zval *mysql_link = NULL; - char *user, *password, *dbname; - int user_len, password_len, dbname_len; + zval *mysql_link = NULL; + char *user, *password, *dbname; + int user_len, password_len, dbname_len; ulong rc; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osss", &mysql_link, mysqli_link_class_entry, &user, &user_len, &password, &password_len, &dbname, &dbname_len) == FAILURE) { @@ -431,8 +525,8 @@ PHP_FUNCTION(mysqli_change_user) Returns the name of the character set used for this connection */ PHP_FUNCTION(mysqli_character_set_name) { - MY_MYSQL *mysql; - zval *mysql_link; + MY_MYSQL *mysql; + zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -440,7 +534,7 @@ PHP_FUNCTION(mysqli_character_set_name) MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); - RETURN_STRING((char *) mysql_character_set_name(mysql->mysql), 1); + RETURN_STRING((char *)mysql_character_set_name(mysql->mysql), 1); } /* }}} */ @@ -448,8 +542,8 @@ PHP_FUNCTION(mysqli_character_set_name) Close connection */ PHP_FUNCTION(mysqli_close) { - zval *mysql_link; - MY_MYSQL *mysql; + zval *mysql_link; + MY_MYSQL *mysql; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -457,11 +551,32 @@ PHP_FUNCTION(mysqli_close) MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_INITIALIZED); - mysql_close(mysql->mysql); - mysql->mysql = NULL; + if (!mysql->persistent) { + mysqli_close(mysql->mysql, MYSQLI_CLOSE_EXPLICIT); + mysql->mysql = NULL; + } else { + zend_rsrc_list_entry *le; + if (zend_hash_find(&EG(persistent_list), mysql->hash_key, strlen(mysql->hash_key) + 1, (void **)&le) == SUCCESS) { + if (Z_TYPE_P(le) == php_le_pmysqli()) { + mysqli_plist_entry *plist = (mysqli_plist_entry *) le->ptr; + dtor_func_t pDestructor = plist->used_links.pDestructor; + + plist->used_links.pDestructor = NULL; /* Don't call pDestructor now */ + zend_hash_index_del(&plist->used_links, mysql->hash_index); + plist->used_links.pDestructor = pDestructor; /* Restore the destructor */ + + zend_hash_next_index_insert(&plist->free_links, &mysql->mysql, sizeof(MYSQL *), NULL); + MyG(num_links)--; + MyG(num_active_persistent)--; + MyG(num_inactive_persistent)++; + } + } + } + php_clear_mysql(mysql); + + MYSQLI_CLEAR_RESOURCE(&mysql_link); efree(mysql); - MYSQLI_CLEAR_RESOURCE(&mysql_link); RETURN_TRUE; } /* }}} */ @@ -470,8 +585,8 @@ PHP_FUNCTION(mysqli_close) Commit outstanding actions and close transaction */ PHP_FUNCTION(mysqli_commit) { - MY_MYSQL *mysql; - zval *mysql_link; + MY_MYSQL *mysql; + zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -489,8 +604,8 @@ PHP_FUNCTION(mysqli_commit) PHP_FUNCTION(mysqli_data_seek) { MYSQL_RES *result; - zval *mysql_result; - long offset; + zval *mysql_result; + long offset; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &mysql_result, mysqli_result_class_entry, &offset) == FAILURE) { return; @@ -498,13 +613,13 @@ PHP_FUNCTION(mysqli_data_seek) MYSQLI_FETCH_RESOURCE(result, MYSQL_RES *, &mysql_result, "mysqli_result", MYSQLI_STATUS_VALID); - if (result->handle && result->handle->status == MYSQL_STATUS_USE_RESULT) { + if (mysqli_result_is_unbuffered(result)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Function cannot be used with MYSQL_USE_RESULT"); RETURN_FALSE; } - if (offset < 0 || offset >= result->row_count) { - RETURN_FALSE; + if (offset < 0 || offset >= mysql_num_rows(result)) { + RETURN_FALSE; } mysql_data_seek(result, offset); @@ -512,12 +627,12 @@ PHP_FUNCTION(mysqli_data_seek) } /* }}} */ -/* {{{ proto void mysqli_debug(string debug) +/* {{{ proto void mysqli_debug(string debug) U */ PHP_FUNCTION(mysqli_debug) { - char *debug; - int debug_len; + char *debug; + int debug_len; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &debug, &debug_len) == FAILURE) { return; @@ -528,25 +643,20 @@ PHP_FUNCTION(mysqli_debug) } /* }}} */ + /* {{{ proto bool mysqli_dump_debug_info(object link) */ PHP_FUNCTION(mysqli_dump_debug_info) { - MY_MYSQL *mysql; - zval *mysql_link; - ulong rc; + MY_MYSQL *mysql; + zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); - rc = mysql_dump_debug_info(mysql->mysql); - - if (rc) { - RETURN_FALSE; - } - RETURN_TRUE; + RETURN_BOOL(!mysql_dump_debug_info(mysql->mysql)) } /* }}} */ @@ -554,8 +664,8 @@ PHP_FUNCTION(mysqli_dump_debug_info) Returns the numerical value of the error message from previous MySQL operation */ PHP_FUNCTION(mysqli_errno) { - MY_MYSQL *mysql; - zval *mysql_link; + MY_MYSQL *mysql; + zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -569,8 +679,8 @@ PHP_FUNCTION(mysqli_errno) Returns the text of the error message from previous MySQL operation */ PHP_FUNCTION(mysqli_error) { - MY_MYSQL *mysql; - zval *mysql_link; + MY_MYSQL *mysql; + zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -584,16 +694,19 @@ PHP_FUNCTION(mysqli_error) Execute a prepared statement */ PHP_FUNCTION(mysqli_stmt_execute) { - MY_STMT *stmt; - zval *mysql_stmt; - unsigned int i; + MY_STMT *stmt; + zval *mysql_stmt; +#ifndef HAVE_MYSQLND + unsigned int i; +#endif if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &mysql_stmt, "mysqli_stmt", MYSQLI_STATUS_VALID); - - for (i = 0; i < stmt->param.var_cnt; i++) { + +#ifndef HAVE_MYSQLND + for (i = 0; i < stmt->param.var_cnt; i++) { if (stmt->param.vars[i]) { if ( !(stmt->param.is_null[i] = (stmt->param.vars[i]->type == IS_NULL)) ) { switch (stmt->stmt->params[i].buffer_type) { @@ -604,40 +717,43 @@ PHP_FUNCTION(mysqli_stmt_execute) break; case MYSQL_TYPE_DOUBLE: convert_to_double_ex(&stmt->param.vars[i]); - stmt->stmt->params[i].buffer = (char*)&Z_LVAL_PP(&stmt->param.vars[i]); + stmt->stmt->params[i].buffer = &Z_LVAL_PP(&stmt->param.vars[i]); break; case MYSQL_TYPE_LONG: convert_to_long_ex(&stmt->param.vars[i]); - stmt->stmt->params[i].buffer = (char*)&Z_LVAL_PP(&stmt->param.vars[i]); + stmt->stmt->params[i].buffer = &Z_LVAL_PP(&stmt->param.vars[i]); break; default: break; } - } + } } } +#endif + if (mysql_stmt_execute(stmt->stmt)) { MYSQLI_REPORT_STMT_ERROR(stmt->stmt); - RETURN_FALSE; + RETVAL_FALSE; + } else { + RETVAL_TRUE; } if (MyG(report_mode) & MYSQLI_REPORT_INDEX) { - php_mysqli_report_index(stmt->query, stmt->stmt->mysql->server_status TSRMLS_CC); + php_mysqli_report_index(stmt->query, mysqli_stmt_server_status(stmt->stmt) TSRMLS_CC); } - - RETURN_TRUE; } /* }}} */ -/* {{{ proto mixed mysqli_stmt_fetch(object stmt) +#ifndef HAVE_MYSQLND +/* {{{ void mysqli_stmt_fetch_libmysql Fetch results from a prepared statement into the bound variables */ -PHP_FUNCTION(mysqli_stmt_fetch) +void mysqli_stmt_fetch_libmysql(INTERNAL_FUNCTION_PARAMETERS) { - MY_STMT *stmt; - zval *mysql_stmt; - unsigned int i; - ulong ret; - unsigned int uval; + MY_STMT *stmt; + zval *mysql_stmt; + unsigned int i; + ulong ret; + unsigned int uval; my_ulonglong llval; @@ -647,8 +763,6 @@ PHP_FUNCTION(mysqli_stmt_fetch) MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &mysql_stmt, "mysqli_stmt", MYSQLI_STATUS_VALID); /* reset buffers */ - - for (i = 0; i < stmt->result.var_cnt; i++) { if (stmt->result.buf[i].type == IS_STRING) { memset(stmt->result.buf[i].val, 0, stmt->result.buf[i].buflen); @@ -661,6 +775,11 @@ PHP_FUNCTION(mysqli_stmt_fetch) if (!ret) { #endif for (i = 0; i < stmt->result.var_cnt; i++) { + /* + QQ: Isn't it quite better to call zval_dtor(). What if the user has + assigned a resource, or an array to the bound variable? We are going + to leak probably. zval_dtor() will handle also Unicode/Non-unicode mode. + */ /* Even if the string is of length zero there is one byte alloced so efree() in all cases */ if (Z_TYPE_P(stmt->result.vars[i]) == IS_STRING) { efree(stmt->result.vars[i]->value.str.val); @@ -669,11 +788,11 @@ PHP_FUNCTION(mysqli_stmt_fetch) switch (stmt->result.buf[i].type) { case IS_LONG: if ((stmt->stmt->fields[i].type == MYSQL_TYPE_LONG) - && (stmt->stmt->fields[i].flags & UNSIGNED_FLAG)) + && (stmt->stmt->fields[i].flags & UNSIGNED_FLAG)) { /* unsigned int (11) */ uval= *(unsigned int *) stmt->result.buf[i].val; - +#if SIZEOF_LONG==4 if (uval > INT_MAX) { char *tmp, *p; int j=10; @@ -681,13 +800,14 @@ PHP_FUNCTION(mysqli_stmt_fetch) p= &tmp[9]; do { *p-- = (uval % 10) + 48; - uval = uval / 10; + uval = uval / 10; } while (--j > 0); tmp[10]= '\0'; - /* unsigned int > INT_MAX is 10 digis - ALWAYS */ + /* unsigned int > INT_MAX is 10 digits - ALWAYS */ ZVAL_STRINGL(stmt->result.vars[i], tmp, 10, 0); break; } +#endif } if (stmt->stmt->fields[i].flags & UNSIGNED_FLAG) { ZVAL_LONG(stmt->result.vars[i], *(unsigned int *)stmt->result.buf[i].val); @@ -702,11 +822,12 @@ PHP_FUNCTION(mysqli_stmt_fetch) if (stmt->stmt->bind[i].buffer_type == MYSQL_TYPE_LONGLONG) { my_bool uns= (stmt->stmt->fields[i].flags & UNSIGNED_FLAG)? 1:0; llval= *(my_ulonglong *) stmt->result.buf[i].val; -#if SIZEOF_LONG==8 +#if SIZEOF_LONG==8 if (uns && llval > 9223372036854775807L) { #elif SIZEOF_LONG==4 if ((uns && llval > L64(2147483647)) || - (!uns && (( L64(2147483647) < (my_longlong) llval) || (L64(-2147483648) > (my_longlong) llval)))) + (!uns && (( L64(2147483647) < (my_longlong) llval) || + (L64(-2147483648) > (my_longlong) llval)))) { #endif char tmp[22]; @@ -719,7 +840,7 @@ PHP_FUNCTION(mysqli_stmt_fetch) } else { ZVAL_LONG(stmt->result.vars[i], llval); } - } + } #if MYSQL_VERSION_ID > 50002 else if (stmt->stmt->bind[i].buffer_type == MYSQL_TYPE_BIT) { llval = *(my_ulonglong *)stmt->result.buf[i].val; @@ -728,19 +849,21 @@ PHP_FUNCTION(mysqli_stmt_fetch) #endif else { #if defined(MYSQL_DATA_TRUNCATED) && MYSQL_VERSION_ID > 50002 - if(ret == MYSQL_DATA_TRUNCATED && *(stmt->stmt->bind[i].error) != 0) { + if (ret == MYSQL_DATA_TRUNCATED && *(stmt->stmt->bind[i].error) != 0) { /* result was truncated */ - ZVAL_STRINGL(stmt->result.vars[i], stmt->result.buf[i].val, stmt->stmt->bind[i].buffer_length, 1); + ZVAL_STRINGL(stmt->result.vars[i], stmt->result.buf[i].val, + stmt->stmt->bind[i].buffer_length, 1); } else { #else { #endif - ZVAL_STRINGL(stmt->result.vars[i], stmt->result.buf[i].val, stmt->result.buf[i].buflen, 1); + ZVAL_STRINGL(stmt->result.vars[i], stmt->result.buf[i].val, + stmt->result.buf[i].buflen, 1); } } break; default: - break; + break; } } else { ZVAL_NULL(stmt->result.vars[i]); @@ -770,14 +893,68 @@ PHP_FUNCTION(mysqli_stmt_fetch) } } /* }}} */ +#else +/* {{{ mixed mysqli_stmt_fetch_mysqlnd */ +void mysqli_stmt_fetch_mysqlnd(INTERNAL_FUNCTION_PARAMETERS) +{ + MY_STMT *stmt; + zval *mysql_stmt; + zend_bool fetched_anything; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { + return; + } + MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &mysql_stmt, "mysqli_stmt", MYSQLI_STATUS_VALID); + + if (FAIL == mysqlnd_stmt_fetch(stmt->stmt, &fetched_anything)) { + RETURN_BOOL(FALSE); + } else if (fetched_anything == TRUE) { + RETURN_BOOL(TRUE); + } else { + RETURN_NULL(); + } +} +#endif +/* }}} */ + + +/* {{{ proto mixed mysqli_stmt_fetch(object stmt) U + Fetch results from a prepared statement into the bound variables */ +PHP_FUNCTION(mysqli_stmt_fetch) +{ +#if !defined(HAVE_MYSQLND) + mysqli_stmt_fetch_libmysql(INTERNAL_FUNCTION_PARAM_PASSTHRU); +#else + mysqli_stmt_fetch_mysqlnd(INTERNAL_FUNCTION_PARAM_PASSTHRU); +#endif +} +/* }}} */ + +/* {{{ php_add_field_properties */ +static void php_add_field_properties(zval *value, MYSQL_FIELD *field TSRMLS_DC) +{ + add_property_string(value, "name",(field->name ? field->name : ""), 1); + add_property_string(value, "orgname",(field->org_name ? field->org_name : ""), 1); + add_property_string(value, "table",(field->table ? field->table : ""), 1); + add_property_string(value, "orgtable",(field->org_table ? field->org_table : ""), 1); + add_property_string(value, "def",(field->def ? field->def : ""), 1); + + add_property_long(value, "max_length", field->max_length); + add_property_long(value, "length", field->length); + add_property_long(value, "charsetnr", field->charsetnr); + add_property_long(value, "flags", field->flags); + add_property_long(value, "type", field->type); + add_property_long(value, "decimals", field->decimals); +} +/* }}} */ /* {{{ proto mixed mysqli_fetch_field (object result) Get column information from a result and return as an object */ PHP_FUNCTION(mysqli_fetch_field) { - MYSQL_RES *result; - zval *mysql_result; - MYSQL_FIELD *field; + MYSQL_RES *result; + zval *mysql_result; + MYSQL_FIELD *field; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_result, mysqli_result_class_entry) == FAILURE) { return; @@ -790,18 +967,7 @@ PHP_FUNCTION(mysqli_fetch_field) } object_init(return_value); - - add_property_string(return_value, "name",(field->name ? field->name : ""), 1); - add_property_string(return_value, "orgname",(field->org_name ? field->org_name : ""), 1); - add_property_string(return_value, "table",(field->table ? field->table : ""), 1); - add_property_string(return_value, "orgtable",(field->org_table ? field->org_table : ""), 1); - add_property_string(return_value, "def",(field->def ? field->def : ""), 1); - add_property_long(return_value, "max_length", field->max_length); - add_property_long(return_value, "length", field->length); - add_property_long(return_value, "charsetnr", field->charsetnr); - add_property_long(return_value, "flags", field->flags); - add_property_long(return_value, "type", field->type); - add_property_long(return_value, "decimals", field->decimals); + php_add_field_properties(return_value, field TSRMLS_CC); } /* }}} */ @@ -810,9 +976,9 @@ PHP_FUNCTION(mysqli_fetch_field) PHP_FUNCTION(mysqli_fetch_fields) { MYSQL_RES *result; - zval *mysql_result; + zval *mysql_result; MYSQL_FIELD *field; - zval *obj; + zval *obj; unsigned int i; @@ -827,22 +993,10 @@ PHP_FUNCTION(mysqli_fetch_fields) for (i = 0; i < mysql_num_fields(result); i++) { field = mysql_fetch_field_direct(result, i); - MAKE_STD_ZVAL(obj); object_init(obj); - add_property_string(obj, "name",(field->name ? field->name : ""), 1); - add_property_string(obj, "orgname",(field->org_name ? field->org_name : ""), 1); - add_property_string(obj, "table",(field->table ? field->table : ""), 1); - add_property_string(obj, "orgtable",(field->org_table ? field->org_table : ""), 1); - add_property_string(obj, "def",(field->def ? field->def : ""), 1); - add_property_long(obj, "max_length", field->max_length); - add_property_long(obj, "length", field->length); - add_property_long(obj, "charsetnr", field->charsetnr); - add_property_long(obj, "flags", field->flags); - add_property_long(obj, "type", field->type); - add_property_long(obj, "decimals", field->decimals); - + php_add_field_properties(obj, field TSRMLS_CC); add_index_zval(return_value, i, obj); } } @@ -854,8 +1008,8 @@ PHP_FUNCTION(mysqli_fetch_field_direct) { MYSQL_RES *result; zval *mysql_result; - MYSQL_FIELD *field; - long offset; + MYSQL_FIELD *field; + long offset; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &mysql_result, mysqli_result_class_entry, &offset) == FAILURE) { return; @@ -873,18 +1027,7 @@ PHP_FUNCTION(mysqli_fetch_field_direct) } object_init(return_value); - - add_property_string(return_value, "name",(field->name ? field->name : ""), 1); - add_property_string(return_value, "orgname",(field->org_name ? field->org_name : ""), 1); - add_property_string(return_value, "table",(field->table ? field->table : ""), 1); - add_property_string(return_value, "orgtable",(field->org_table ? field->org_table : ""), 1); - add_property_string(return_value, "def",(field->def ? field->def : ""), 1); - add_property_long(return_value, "max_length", field->max_length); - add_property_long(return_value, "length", field->length); - add_property_long(return_value, "charsetnr", field->charsetnr); - add_property_long(return_value, "flags", field->flags); - add_property_long(return_value, "type", field->type); - add_property_long(return_value, "decimals", field->decimals); + php_add_field_properties(return_value, field TSRMLS_CC); } /* }}} */ @@ -896,7 +1039,7 @@ PHP_FUNCTION(mysqli_fetch_lengths) zval *mysql_result; unsigned int i; unsigned long *ret; - + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_result, mysqli_result_class_entry) == FAILURE) { return; } @@ -910,7 +1053,7 @@ PHP_FUNCTION(mysqli_fetch_lengths) array_init(return_value); for (i = 0; i < mysql_num_fields(result); i++) { - add_index_long(return_value, i, ret[i]); + add_index_long(return_value, i, ret[i]); } } /* }}} */ @@ -919,17 +1062,28 @@ PHP_FUNCTION(mysqli_fetch_lengths) Get a result row as an enumerated array */ PHP_FUNCTION(mysqli_fetch_row) { +#if !defined(HAVE_MYSQLND) php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQLI_NUM, 0); +#else + MYSQL_RES *result; + zval *mysql_result; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_result, mysqli_result_class_entry) == FAILURE) { + return; + } + MYSQLI_FETCH_RESOURCE(result, MYSQL_RES *, &mysql_result, "mysqli_result", MYSQLI_STATUS_VALID); + mysqlnd_fetch_into(result, MYSQLND_FETCH_NUM, return_value, MYSQLND_MYSQLI); +#endif } /* }}} */ /* {{{ proto int mysqli_field_count(object link) Fetch the number of fields returned by the last query for the given link */ -PHP_FUNCTION(mysqli_field_count) +PHP_FUNCTION(mysqli_field_count) { - MY_MYSQL *mysql; - zval *mysql_link; + MY_MYSQL *mysql; + zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -958,7 +1112,7 @@ PHP_FUNCTION(mysqli_field_seek) php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid field offset"); RETURN_FALSE; } - + mysql_field_seek(result, fieldnr); RETURN_TRUE; } @@ -975,7 +1129,7 @@ PHP_FUNCTION(mysqli_field_tell) return; } MYSQLI_FETCH_RESOURCE(result, MYSQL_RES *, &mysql_result, "mysqli_result", MYSQLI_STATUS_VALID); - + RETURN_LONG(mysql_field_tell(result)); } /* }}} */ @@ -992,12 +1146,12 @@ PHP_FUNCTION(mysqli_free_result) } MYSQLI_FETCH_RESOURCE(result, MYSQL_RES *, &mysql_result, "mysqli_result", MYSQLI_STATUS_VALID); - mysql_free_result(result); - MYSQLI_CLEAR_RESOURCE(&mysql_result); + mysqli_free_result(result, FALSE); + MYSQLI_CLEAR_RESOURCE(&mysql_result); } /* }}} */ -/* {{{ proto string mysqli_get_client_info(void) +/* {{{ proto string mysqli_get_client_info(void) Get MySQL client info */ PHP_FUNCTION(mysqli_get_client_info) { @@ -1005,7 +1159,7 @@ PHP_FUNCTION(mysqli_get_client_info) } /* }}} */ -/* {{{ proto int mysqli_get_client_version(void) +/* {{{ proto int mysqli_get_client_version(void) Get MySQL client info */ PHP_FUNCTION(mysqli_get_client_version) { @@ -1018,7 +1172,7 @@ PHP_FUNCTION(mysqli_get_client_version) PHP_FUNCTION(mysqli_get_host_info) { MY_MYSQL *mysql; - zval *mysql_link = NULL; + zval *mysql_link = NULL; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -1033,24 +1187,23 @@ PHP_FUNCTION(mysqli_get_host_info) Get MySQL protocol information */ PHP_FUNCTION(mysqli_get_proto_info) { - MY_MYSQL *mysql; - zval *mysql_link = NULL; + MY_MYSQL *mysql; + zval *mysql_link = NULL; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); - RETURN_LONG(mysql_get_proto_info(mysql->mysql)); } /* }}} */ -/* {{{ proto string mysqli_get_server_info(object link) +/* {{{ proto string mysqli_get_server_info(object link) Get MySQL server info */ PHP_FUNCTION(mysqli_get_server_info) { MY_MYSQL *mysql; - zval *mysql_link = NULL; + zval *mysql_link = NULL; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -1083,14 +1236,16 @@ PHP_FUNCTION(mysqli_get_server_version) PHP_FUNCTION(mysqli_info) { MY_MYSQL *mysql; - zval *mysql_link = NULL; + zval *mysql_link = NULL; + const char *info; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); - RETURN_STRING((mysql->mysql->info) ? mysql->mysql->info : "", 1); + info = mysql_info(mysql->mysql); + RETURN_STRING((info) ? (char *)info : "", 1); } /* }}} */ @@ -1101,7 +1256,12 @@ PHP_FUNCTION(mysqli_init) MYSQLI_RESOURCE *mysqli_resource; MY_MYSQL *mysql = (MY_MYSQL *)ecalloc(1, sizeof(MY_MYSQL)); - if (!(mysql->mysql = mysql_init(NULL))) { +#if !defined(HAVE_MYSQLND) + if (!(mysql->mysql = mysql_init(NULL))) +#else + if (!(mysql->mysql = mysql_init(FALSE))) +#endif + { efree(mysql); RETURN_FALSE; } @@ -1123,8 +1283,8 @@ PHP_FUNCTION(mysqli_init) PHP_FUNCTION(mysqli_insert_id) { MY_MYSQL *mysql; - my_ulonglong rc; - zval *mysql_link; + my_ulonglong rc; + zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -1139,15 +1299,20 @@ PHP_FUNCTION(mysqli_insert_id) Kill a mysql process on the server */ PHP_FUNCTION(mysqli_kill) { - MY_MYSQL *mysql; - zval *mysql_link; - long processid; + MY_MYSQL *mysql; + zval *mysql_link; + long processid; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &mysql_link, mysqli_link_class_entry, &processid) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); - + + if (processid <= 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "processid should have positive value"); + RETURN_FALSE; + } + if (mysql_kill(mysql->mysql, processid)) { MYSQLI_REPORT_MYSQL_ERROR(mysql->mysql); RETURN_FALSE; @@ -1158,6 +1323,7 @@ PHP_FUNCTION(mysqli_kill) /* {{{ proto void mysqli_set_local_infile_default(object link) unsets user defined handler for load local infile command */ +#if !defined(HAVE_MYSQLND) PHP_FUNCTION(mysqli_set_local_infile_default) { MY_MYSQL *mysql; @@ -1182,7 +1348,7 @@ PHP_FUNCTION(mysqli_set_local_infile_default) PHP_FUNCTION(mysqli_set_local_infile_handler) { MY_MYSQL *mysql; - zval *mysql_link; + zval *mysql_link; char *callback_name; zval *callback_func; @@ -1197,24 +1363,29 @@ PHP_FUNCTION(mysqli_set_local_infile_handler) if (!zend_is_callable(callback_func, 0, &callback_name)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Not a valid callback function %s", callback_name); efree(callback_name); - RETURN_FALSE; + RETURN_FALSE; } efree(callback_name); /* save callback function */ - ALLOC_ZVAL(mysql->li_read); - ZVAL_STRING(mysql->li_read, callback_func->value.str.val, 1); + if (!mysql->li_read) { + MAKE_STD_ZVAL(mysql->li_read); + } else { + zval_dtor(mysql->li_read); + } + ZVAL_STRINGL(mysql->li_read, Z_STRVAL_P(callback_func), Z_STRLEN_P(callback_func), 1); RETURN_TRUE; } +#endif /* }}} */ /* {{{ proto bool mysqli_more_results(object link) check if there any more query results from a multi query */ PHP_FUNCTION(mysqli_more_results) { - MY_MYSQL *mysql; - zval *mysql_link; + MY_MYSQL *mysql; + zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -1228,14 +1399,20 @@ PHP_FUNCTION(mysqli_more_results) /* {{{ proto bool mysqli_next_result(object link) read next result from multi_query */ PHP_FUNCTION(mysqli_next_result) { - MY_MYSQL *mysql; - zval *mysql_link; + MY_MYSQL *mysql; + zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); + if (!mysql_more_results(mysql->mysql)) { + php_error_docref(NULL TSRMLS_CC, E_STRICT, "There is no next result set. " + "Please, call mysqli_more_results()/mysqli::more_results() to check " + "whether to call this function/method"); + } + RETURN_BOOL(!mysql_next_result(mysql->mysql)); } /* }}} */ @@ -1268,7 +1445,7 @@ PHP_FUNCTION(mysqli_num_rows) } MYSQLI_FETCH_RESOURCE(result, MYSQL_RES *, &mysql_result, "mysqli_result", MYSQLI_STATUS_VALID); - if (result->handle && result->handle->status == MYSQL_STATUS_USE_RESULT) { + if (mysqli_result_is_unbuffered(result)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Function cannot be used with MYSQL_USE_RESULT"); RETURN_LONG(0); } @@ -1281,12 +1458,12 @@ PHP_FUNCTION(mysqli_num_rows) Set options */ PHP_FUNCTION(mysqli_options) { - MY_MYSQL *mysql; - zval *mysql_link = NULL; - zval *mysql_value; - long mysql_option; - unsigned int l_value; - long ret; + MY_MYSQL *mysql; + zval *mysql_link = NULL; + zval *mysql_value; + long mysql_option; + unsigned int l_value; + long ret; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Olz", &mysql_link, mysqli_link_class_entry, &mysql_option, &mysql_value) == FAILURE) { return; @@ -1311,7 +1488,7 @@ PHP_FUNCTION(mysqli_options) } RETURN_BOOL(!ret); -} +} /* }}} */ @@ -1319,8 +1496,8 @@ PHP_FUNCTION(mysqli_options) Ping a server connection or reconnect if there is no connection */ PHP_FUNCTION(mysqli_ping) { - MY_MYSQL *mysql; - zval *mysql_link; + MY_MYSQL *mysql; + zval *mysql_link; long rc; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { @@ -1339,60 +1516,71 @@ PHP_FUNCTION(mysqli_ping) PHP_FUNCTION(mysqli_prepare) { MY_MYSQL *mysql; - MY_STMT *stmt; + MY_STMT *stmt; char *query = NULL; unsigned int query_len; zval *mysql_link; - MYSQLI_RESOURCE *mysqli_resource; + MYSQLI_RESOURCE *mysqli_resource; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os",&mysql_link, mysqli_link_class_entry, &query, &query_len) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); + +#if !defined(HAVE_MYSQLND) if (mysql->mysql->status == MYSQL_STATUS_GET_RESULT) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "All data must be fetched before a new statement prepare takes place"); RETURN_FALSE; } +#endif stmt = (MY_STMT *)ecalloc(1,sizeof(MY_STMT)); if ((stmt->stmt = mysql_stmt_init(mysql->mysql))) { if (mysql_stmt_prepare(stmt->stmt, query, query_len)) { - char last_error[MYSQL_ERRMSG_SIZE]; - char sqlstate[SQLSTATE_LENGTH+1]; + /* mysql_stmt_close() clears errors, so we have to store them temporarily */ +#if !defined(HAVE_MYSQLND) + char last_error[MYSQL_ERRMSG_SIZE]; + char sqlstate[SQLSTATE_LENGTH+1]; unsigned int last_errno; - /* mysql_stmt_close clears errors, so we have to store them temporarily */ last_errno = stmt->stmt->last_errno; memcpy(last_error, stmt->stmt->last_error, MYSQL_ERRMSG_SIZE); memcpy(sqlstate, mysql->mysql->net.sqlstate, SQLSTATE_LENGTH+1); - - mysql_stmt_close(stmt->stmt); +#else + mysqlnd_error_info error_info = mysql->mysql->error_info; +#endif + mysqli_stmt_close(stmt->stmt, FALSE); stmt->stmt = NULL; /* restore error messages */ +#if !defined(HAVE_MYSQLND) mysql->mysql->net.last_errno = last_errno; memcpy(mysql->mysql->net.last_error, last_error, MYSQL_ERRMSG_SIZE); memcpy(mysql->mysql->net.sqlstate, sqlstate, SQLSTATE_LENGTH+1); +#else + mysql->mysql->error_info = error_info; +#endif } } - /* don't joing to the previous if because it won't work if mysql_stmt_prepare_fails */ + + /* don't initialize stmt->query with NULL, we ecalloc()-ed the memory */ + /* Get performance boost if reporting is switched off */ + if (stmt->stmt && query_len && (MyG(report_mode) & MYSQLI_REPORT_INDEX)) { + stmt->query = (char *)emalloc(query_len + 1); + memcpy(stmt->query, query, query_len); + stmt->query[query_len] = '\0'; + } + + /* don't join to the previous if because it won't work if mysql_stmt_prepare_fails */ if (!stmt->stmt) { MYSQLI_REPORT_MYSQL_ERROR(mysql->mysql); efree(stmt); RETURN_FALSE; } - mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE)); mysqli_resource->ptr = (void *)stmt; - /* don't initialize stmt->query with NULL, we ecalloc()-ed the memory */ - /* Get performance boost if reporting is switched off */ - if (query_len && (MyG(report_mode) & MYSQLI_REPORT_INDEX)) { - stmt->query = (char *)emalloc(query_len + 1); - memcpy(stmt->query, query, query_len); - stmt->query[query_len] = '\0'; - } /* change status */ mysqli_resource->status = MYSQLI_STATUS_VALID; @@ -1404,10 +1592,10 @@ PHP_FUNCTION(mysqli_prepare) Open a connection to a mysql server */ PHP_FUNCTION(mysqli_real_connect) { - MY_MYSQL *mysql; - char *hostname = NULL, *username=NULL, *passwd=NULL, *dbname=NULL, *socket=NULL; - unsigned int hostname_len = 0, username_len = 0, passwd_len = 0, dbname_len = 0, socket_len = 0; - unsigned long port=0, flags=0; + MY_MYSQL *mysql; + char *hostname = NULL, *username=NULL, *passwd=NULL, *dbname=NULL, *socket=NULL; + unsigned int hostname_len = 0, username_len = 0, passwd_len = 0, dbname_len = 0, socket_len = 0; + unsigned long port=0, flags=0; zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|sssslsl", &mysql_link, mysqli_link_class_entry, @@ -1419,37 +1607,38 @@ PHP_FUNCTION(mysqli_real_connect) if (!socket_len) { socket = NULL; } - - /* TODO: safe mode handling */ - if (PG(sql_safe_mode)) { - } else { - if (!passwd) { - passwd = MyG(default_pw); - if (!username){ - username = MyG(default_user); - if (!hostname) { - hostname = MyG(default_host); - } - } - } + if (!socket) { + socket = MyG(default_socket); } + if (!passwd) { + passwd = MyG(default_pw); + passwd_len = strlen(passwd); + } + if (!username){ + username = MyG(default_user); + } + if (!hostname) { + hostname = MyG(default_host); + } MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_INITIALIZED); /* remove some insecure options */ flags &= ~CLIENT_MULTI_STATEMENTS; /* don't allow multi_queries via connect parameter */ - if ((PG(open_basedir) && PG(open_basedir)[0] != '\0') || PG(safe_mode)) { - flags &= ~CLIENT_LOCAL_FILES; - } - - if (!socket) { - socket = MyG(default_socket); + if (PG(open_basedir) && PG(open_basedir)[0] != '\0') { + flags ^= CLIENT_LOCAL_FILES; } - if (mysql_real_connect(mysql->mysql,hostname,username,passwd,dbname,port,socket,flags) == NULL) { +#if !defined(HAVE_MYSQLND) + if (mysql_real_connect(mysql->mysql, hostname, username, passwd, dbname ,port, socket ,flags) == NULL) +#else + if (mysqlnd_connect(mysql->mysql, hostname, username, passwd, passwd_len, dbname, dbname_len, + port, socket, flags, MyG(mysqlnd_thd_zval_cache) TSRMLS_CC) == NULL) +#endif + { php_mysqli_set_error(mysql_errno(mysql->mysql), (char *) mysql_error(mysql->mysql) TSRMLS_CC); - php_mysqli_throw_sql_exception( mysql->mysql->net.sqlstate, mysql->mysql->net.last_errno TSRMLS_CC, - "%s", mysql->mysql->net.last_error); + php_mysqli_throw_sql_exception((char *)mysql_sqlstate(mysql->mysql), mysql_errno(mysql->mysql) TSRMLS_CC, + "%s", mysql_error(mysql->mysql)); /* change status */ MYSQLI_SET_STATUS(&mysql_link, MYSQLI_STATUS_INITIALIZED); @@ -1458,10 +1647,14 @@ PHP_FUNCTION(mysqli_real_connect) php_mysqli_set_error(mysql_errno(mysql->mysql), (char *)mysql_error(mysql->mysql) TSRMLS_CC); +#if !defined(HAVE_MYSQLND) mysql->mysql->reconnect = MyG(reconnect); /* set our own local_infile handler */ php_set_local_infile_handler_default(mysql); +#endif + + mysql_options(mysql->mysql, MYSQL_OPT_LOCAL_INFILE, (char *)&MyG(allow_local_infile)); /* change status */ MYSQLI_SET_STATUS(&mysql_link, MYSQLI_STATUS_VALID); @@ -1477,7 +1670,7 @@ PHP_FUNCTION(mysqli_real_query) MY_MYSQL *mysql; zval *mysql_link; char *query = NULL; - unsigned int query_len; + unsigned int query_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &mysql_link, mysqli_link_class_entry, &query, &query_len) == FAILURE) { return; @@ -1493,7 +1686,7 @@ PHP_FUNCTION(mysqli_real_query) if (!mysql_field_count(mysql->mysql)) { if (MyG(report_mode) & MYSQLI_REPORT_INDEX) { - php_mysqli_report_index(query, mysql->mysql->server_status TSRMLS_CC); + php_mysqli_report_index(query, mysqli_server_status(mysql->mysql) TSRMLS_CC); } } @@ -1517,7 +1710,7 @@ PHP_FUNCTION(mysqli_real_escape_string) { newstr = safe_emalloc(2, escapestr_len, 1); newstr_len = mysql_real_escape_string(mysql->mysql, newstr, escapestr, escapestr_len); newstr = erealloc(newstr, newstr_len + 1); - + RETURN_STRINGL(newstr, newstr_len, 0); } /* }}} */ @@ -1527,7 +1720,7 @@ PHP_FUNCTION(mysqli_real_escape_string) { PHP_FUNCTION(mysqli_rollback) { MY_MYSQL *mysql; - zval *mysql_link; + zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -1546,12 +1739,11 @@ PHP_FUNCTION(mysqli_rollback) PHP_FUNCTION(mysqli_stmt_send_long_data) { MY_STMT *stmt; - zval *mysql_stmt; + zval *mysql_stmt; char *data; long param_nr; int data_len; - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ols", &mysql_stmt, mysqli_stmt_class_entry, ¶m_nr, &data, &data_len) == FAILURE) { return; } @@ -1573,8 +1765,8 @@ PHP_FUNCTION(mysqli_stmt_send_long_data) Return the number of rows affected in the last query for the given link */ PHP_FUNCTION(mysqli_stmt_affected_rows) { - MY_STMT *stmt; - zval *mysql_stmt; + MY_STMT *stmt; + zval *mysql_stmt; my_ulonglong rc; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { @@ -1590,21 +1782,21 @@ PHP_FUNCTION(mysqli_stmt_affected_rows) } /* }}} */ -/* {{{ proto bool mysqli_stmt_close(object stmt) +/* {{{ proto bool mysqli_stmt_close(object stmt) Close statement */ PHP_FUNCTION(mysqli_stmt_close) { - MY_STMT *stmt; - zval *mysql_stmt; + MY_STMT *stmt; + zval *mysql_stmt; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &mysql_stmt, "mysqli_stmt", MYSQLI_STATUS_VALID); - mysql_stmt_close(stmt->stmt); + mysqli_stmt_close(stmt->stmt, FALSE); stmt->stmt = NULL; - php_clear_stmt_bind(stmt); + php_clear_stmt_bind(stmt TSRMLS_CC); MYSQLI_CLEAR_RESOURCE(&mysql_stmt); RETURN_TRUE; } @@ -1614,9 +1806,9 @@ PHP_FUNCTION(mysqli_stmt_close) Move internal result pointer */ PHP_FUNCTION(mysqli_stmt_data_seek) { - MY_STMT *stmt; - zval *mysql_stmt; - long offset; + MY_STMT *stmt; + zval *mysql_stmt; + long offset; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Ol", &mysql_stmt, mysqli_stmt_class_entry, &offset) == FAILURE) { return; @@ -1636,7 +1828,7 @@ PHP_FUNCTION(mysqli_stmt_data_seek) Return the number of result columns for the given statement */ PHP_FUNCTION(mysqli_stmt_field_count) { - MY_STMT *stmt; + MY_STMT *stmt; zval *mysql_stmt; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { @@ -1652,8 +1844,8 @@ PHP_FUNCTION(mysqli_stmt_field_count) Free stored result memory for the given statement handle */ PHP_FUNCTION(mysqli_stmt_free_result) { - MY_STMT *stmt; - zval *mysql_stmt; + MY_STMT *stmt; + zval *mysql_stmt; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { return; @@ -1669,9 +1861,9 @@ PHP_FUNCTION(mysqli_stmt_free_result) Get the ID generated from the previous INSERT operation */ PHP_FUNCTION(mysqli_stmt_insert_id) { - MY_STMT *stmt; - my_ulonglong rc; - zval *mysql_stmt; + MY_STMT *stmt; + my_ulonglong rc; + zval *mysql_stmt; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { return; @@ -1682,13 +1874,13 @@ PHP_FUNCTION(mysqli_stmt_insert_id) } /* }}} */ -/* {{{ proto int mysqli_stmt_param_count(object stmt) { +/* {{{ proto int mysqli_stmt_param_count(object stmt) Return the number of parameter for the given statement */ PHP_FUNCTION(mysqli_stmt_param_count) { - MY_STMT *stmt; + MY_STMT *stmt; zval *mysql_stmt; - + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { return; } @@ -1700,10 +1892,10 @@ PHP_FUNCTION(mysqli_stmt_param_count) /* {{{ proto bool mysqli_stmt_reset(object stmt) reset a prepared statement */ -PHP_FUNCTION(mysqli_stmt_reset) +PHP_FUNCTION(mysqli_stmt_reset) { - MY_STMT *stmt; - zval *mysql_stmt; + MY_STMT *stmt; + zval *mysql_stmt; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { return; @@ -1722,8 +1914,8 @@ PHP_FUNCTION(mysqli_stmt_reset) Return the number of rows in statements result set */ PHP_FUNCTION(mysqli_stmt_num_rows) { - MY_STMT *stmt; - zval *mysql_stmt; + MY_STMT *stmt; + zval *mysql_stmt; my_ulonglong rc; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { @@ -1737,27 +1929,25 @@ PHP_FUNCTION(mysqli_stmt_num_rows) } /* }}} */ -/* {{{ proto string mysqli_select_db(object link, string dbname) +/* {{{ proto bool mysqli_select_db(object link, string dbname) Select a MySQL database */ PHP_FUNCTION(mysqli_select_db) { MY_MYSQL *mysql; - zval *mysql_link; - char *dbname; - int dbname_len; - + zval *mysql_link; + char *dbname; + int dbname_len; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Os", &mysql_link, mysqli_link_class_entry, &dbname, &dbname_len) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); - - if (!mysql_select_db(mysql->mysql, dbname)) { - RETURN_TRUE; + + if (mysql_select_db(mysql->mysql, dbname)) { + MYSQLI_REPORT_MYSQL_ERROR(mysql->mysql); + RETURN_FALSE; } - - MYSQLI_REPORT_MYSQL_ERROR(mysql->mysql); - RETURN_FALSE; + RETURN_TRUE; } /* }}} */ @@ -1765,8 +1955,8 @@ PHP_FUNCTION(mysqli_select_db) Returns the SQLSTATE error from previous MySQL operation */ PHP_FUNCTION(mysqli_sqlstate) { - MY_MYSQL *mysql; - zval *mysql_link; + MY_MYSQL *mysql; + zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -1776,21 +1966,22 @@ PHP_FUNCTION(mysqli_sqlstate) } /* }}} */ -/* {{{ proto bool mysqli_ssl_set(object link ,string key ,string cert ,string ca ,string capath ,string cipher]) +/* {{{ proto bool mysqli_ssl_set(object link ,string key ,string cert ,string ca ,string capath ,string cipher]) U */ +#if !defined(HAVE_MYSQLND) PHP_FUNCTION(mysqli_ssl_set) { MY_MYSQL *mysql; - zval *mysql_link; - char *ssl_parm[5]; + zval *mysql_link; + char *ssl_parm[5]; int ssl_parm_len[5], i; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Osssss", &mysql_link, mysqli_link_class_entry, &ssl_parm[0], &ssl_parm_len[0], &ssl_parm[1], &ssl_parm_len[1], &ssl_parm[2], &ssl_parm_len[2], &ssl_parm[3], &ssl_parm_len[3], &ssl_parm[4], &ssl_parm_len[4]) == FAILURE) { return; } - MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_INITIALIZED); + MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); - for (i=0; i < 5; i++) { + for (i = 0; i < 5; i++) { if (!ssl_parm_len[i]) { ssl_parm[i] = NULL; } @@ -1800,25 +1991,37 @@ PHP_FUNCTION(mysqli_ssl_set) RETURN_TRUE; } +#endif /* }}} */ /* {{{ proto mixed mysqli_stat(object link) Get current system status */ PHP_FUNCTION(mysqli_stat) { - MY_MYSQL *mysql; - zval *mysql_link; + MY_MYSQL *mysql; + zval *mysql_link; char *stat; +#if defined(HAVE_MYSQLND) + uint stat_len; +#endif if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); - if ((stat = (char *)mysql_stat(mysql->mysql))) { +#if !defined(HAVE_MYSQLND) + if ((stat = (char *)mysql_stat(mysql->mysql))) + { RETURN_STRING(stat, 1); +#else + if (mysqlnd_stat(mysql->mysql, &stat, &stat_len) == PASS) + { + RETURN_STRINGL(stat, stat_len, 0); +#endif + } else { + RETURN_FALSE; } - RETURN_FALSE; } /* }}} */ @@ -1828,16 +2031,23 @@ PHP_FUNCTION(mysqli_stat) PHP_FUNCTION(mysqli_stmt_attr_set) { MY_STMT *stmt; - zval *mysql_stmt; - ulong mode; + zval *mysql_stmt; + long mode_in; + ulong mode; ulong attr; int rc; - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oll", &mysql_stmt, mysqli_stmt_class_entry, &attr, &mode) == FAILURE) { + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "Oll", &mysql_stmt, mysqli_stmt_class_entry, &attr, &mode_in) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &mysql_stmt, "mysqli_stmt", MYSQLI_STATUS_VALID); + if (mode_in < 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "mode should be non-negative, %ld passed", mode_in); + RETURN_FALSE; + } + + mode = mode_in; if ((rc = mysql_stmt_attr_set(stmt->stmt, attr, (void *)&mode))) { RETURN_FALSE; } @@ -1850,8 +2060,8 @@ PHP_FUNCTION(mysqli_stmt_attr_set) PHP_FUNCTION(mysqli_stmt_attr_get) { MY_STMT *stmt; - zval *mysql_stmt; -#if MYSQL_VERSION_ID > 50099 + zval *mysql_stmt; +#if !defined(HAVE_MYSQLND) && MYSQL_VERSION_ID > 50099 my_bool value; #else ulong value = 0; @@ -1876,7 +2086,7 @@ PHP_FUNCTION(mysqli_stmt_attr_get) PHP_FUNCTION(mysqli_stmt_errno) { MY_STMT *stmt; - zval *mysql_stmt; + zval *mysql_stmt; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { return; @@ -1909,9 +2119,9 @@ PHP_FUNCTION(mysqli_stmt_error) PHP_FUNCTION(mysqli_stmt_init) { MY_MYSQL *mysql; - MY_STMT *stmt; + MY_STMT *stmt; zval *mysql_link; - MYSQLI_RESOURCE *mysqli_resource; + MYSQLI_RESOURCE *mysqli_resource; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O",&mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -1961,9 +2171,9 @@ PHP_FUNCTION(mysqli_stmt_prepare) return result set from statement */ PHP_FUNCTION(mysqli_stmt_result_metadata) { - MY_STMT *stmt; + MY_STMT *stmt; MYSQL_RES *result; - zval *mysql_stmt; + zval *mysql_stmt; MYSQLI_RESOURCE *mysqli_resource; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { @@ -1979,7 +2189,7 @@ PHP_FUNCTION(mysqli_stmt_result_metadata) mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE)); mysqli_resource->ptr = (void *)result; mysqli_resource->status = MYSQLI_STATUS_VALID; - MYSQLI_RETURN_RESOURCE(mysqli_resource, mysqli_result_class_entry); + MYSQLI_RETURN_RESOURCE(mysqli_resource, mysqli_result_class_entry); } /* }}} */ @@ -1987,32 +2197,37 @@ PHP_FUNCTION(mysqli_stmt_result_metadata) */ PHP_FUNCTION(mysqli_stmt_store_result) { - MY_STMT *stmt; - zval *mysql_stmt; - int i=0; + MY_STMT *stmt; + zval *mysql_stmt; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &mysql_stmt, "mysqli_stmt", MYSQLI_STATUS_VALID); - /* - If the user wants to store the data and we have BLOBs/TEXTs we try to allocate - not the maximal length of the type (which is 16MB even for LONGBLOB) but - the maximal length of the field in the result set. If he/she has quite big - BLOB/TEXT columns after calling store_result() the memory usage of PHP will - double - but this is a known problem of the simple MySQL API ;) - */ - for (i = mysql_stmt_field_count(stmt->stmt) - 1; i >=0; --i) { - if (stmt->stmt->fields && (stmt->stmt->fields[i].type == MYSQL_TYPE_BLOB || - stmt->stmt->fields[i].type == MYSQL_TYPE_MEDIUM_BLOB || - stmt->stmt->fields[i].type == MYSQL_TYPE_LONG_BLOB)) - { - my_bool tmp=1; - mysql_stmt_attr_set(stmt->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &tmp); - break; +#if !defined(HAVE_MYSQLND) + { + /* + If the user wants to store the data and we have BLOBs/TEXTs we try to allocate + not the maximal length of the type (which is 16MB even for LONGBLOB) but + the maximal length of the field in the result set. If he/she has quite big + BLOB/TEXT columns after calling store_result() the memory usage of PHP will + double - but this is a known problem of the simple MySQL API ;) + */ + int i = 0; + + for (i = mysql_stmt_field_count(stmt->stmt) - 1; i >=0; --i) { + if (stmt->stmt->fields && (stmt->stmt->fields[i].type == MYSQL_TYPE_BLOB || + stmt->stmt->fields[i].type == MYSQL_TYPE_MEDIUM_BLOB || + stmt->stmt->fields[i].type == MYSQL_TYPE_LONG_BLOB)) + { + my_bool tmp=1; + mysql_stmt_attr_set(stmt->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &tmp); + break; + } } } +#endif if (mysql_stmt_store_result(stmt->stmt)){ MYSQLI_REPORT_STMT_ERROR(stmt->stmt); @@ -2024,16 +2239,16 @@ PHP_FUNCTION(mysqli_stmt_store_result) /* {{{ proto string mysqli_stmt_sqlstate(object stmt) */ -PHP_FUNCTION(mysqli_stmt_sqlstate) +PHP_FUNCTION(mysqli_stmt_sqlstate) { - MY_STMT *stmt; - zval *mysql_stmt; + MY_STMT *stmt; + zval *mysql_stmt; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &mysql_stmt, "mysqli_stmt", MYSQLI_STATUS_VALID); - + RETURN_STRING((char *)mysql_stmt_sqlstate(stmt->stmt),1); } /* }}} */ @@ -2042,9 +2257,9 @@ PHP_FUNCTION(mysqli_stmt_sqlstate) Buffer result set on client */ PHP_FUNCTION(mysqli_store_result) { - MY_MYSQL *mysql; - MYSQL_RES *result; - zval *mysql_link; + MY_MYSQL *mysql; + MYSQL_RES *result; + zval *mysql_link; MYSQLI_RESOURCE *mysqli_resource; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { @@ -2057,22 +2272,23 @@ PHP_FUNCTION(mysqli_store_result) RETURN_FALSE; } if (MyG(report_mode) & MYSQLI_REPORT_INDEX) { - php_mysqli_report_index("from previous query", mysql->mysql->server_status TSRMLS_CC); + php_mysqli_report_index("from previous query", mysqli_server_status(mysql->mysql) TSRMLS_CC); } mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE)); mysqli_resource->ptr = (void *)result; mysqli_resource->status = MYSQLI_STATUS_VALID; - MYSQLI_RETURN_RESOURCE(mysqli_resource, mysqli_result_class_entry); + MYSQLI_RETURN_RESOURCE(mysqli_resource, mysqli_result_class_entry); } /* }}} */ + /* {{{ proto int mysqli_thread_id(object link) Return the current thread ID */ PHP_FUNCTION(mysqli_thread_id) { - MY_MYSQL *mysql; - zval *mysql_link; + MY_MYSQL *mysql; + zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -2089,17 +2305,16 @@ PHP_FUNCTION(mysqli_thread_safe) { RETURN_BOOL(mysql_thread_safe()); } - /* }}} */ /* {{{ proto mixed mysqli_use_result(object link) Directly retrieve query results - do not buffer results on client side */ PHP_FUNCTION(mysqli_use_result) { - MY_MYSQL *mysql; - MYSQL_RES *result; - zval *mysql_link; - MYSQLI_RESOURCE *mysqli_resource; + MY_MYSQL *mysql; + MYSQL_RES *result; + zval *mysql_link; + MYSQLI_RESOURCE *mysqli_resource; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -2112,12 +2327,12 @@ PHP_FUNCTION(mysqli_use_result) } if (MyG(report_mode) & MYSQLI_REPORT_INDEX) { - php_mysqli_report_index("from previous query", mysql->mysql->server_status TSRMLS_CC); + php_mysqli_report_index("from previous query", mysqli_server_status(mysql->mysql) TSRMLS_CC); } mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE)); mysqli_resource->ptr = (void *)result; mysqli_resource->status = MYSQLI_STATUS_VALID; - MYSQLI_RETURN_RESOURCE(mysqli_resource, mysqli_result_class_entry); + MYSQLI_RETURN_RESOURCE(mysqli_resource, mysqli_result_class_entry); } /* }}} */ @@ -2125,8 +2340,8 @@ PHP_FUNCTION(mysqli_use_result) Return number of warnings from the last query for the given link */ PHP_FUNCTION(mysqli_warning_count) { - MY_MYSQL *mysql; - zval *mysql_link; + MY_MYSQL *mysql; + zval *mysql_link; if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; diff --git a/ext/mysqli/mysqli_driver.c b/ext/mysqli/mysqli_driver.c index 494c2a554b..e8a9f71806 100644 --- a/ext/mysqli/mysqli_driver.c +++ b/ext/mysqli/mysqli_driver.c @@ -25,7 +25,7 @@ #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" -#include "php_mysqli.h" +#include "php_mysqli_structs.h" #include "zend_exceptions.h" @@ -110,7 +110,7 @@ static int driver_client_version_read(mysqli_object *obj, zval **retval TSRMLS_D static int driver_client_info_read(mysqli_object *obj, zval **retval TSRMLS_DC) { ALLOC_ZVAL(*retval); - ZVAL_STRING(*retval, MYSQL_SERVER_VERSION, 1); + ZVAL_STRING(*retval, (char *)mysql_get_client_info(), 1); return SUCCESS; } /* }}} */ @@ -130,9 +130,17 @@ MAP_PROPERTY_MYG_LONG_READ(driver_report_read, report_mode); ZEND_FUNCTION(mysqli_driver_construct) { +#if G0 + MYSQLI_RESOURCE *mysqli_resource; + + mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE)); + mysqli_resource->ptr = 1; + mysqli_resource->status = (ZEND_NUM_ARGS() == 1) ? MYSQLI_STATUS_INITIALIZED : MYSQLI_STATUS_VALID; + ((mysqli_object *) zend_object_store_get_object(getThis() TSRMLS_CC))->ptr = mysqli_resource; +#endif } -mysqli_property_entry mysqli_driver_property_entries[] = { +const mysqli_property_entry mysqli_driver_property_entries[] = { {"client_info", driver_client_info_read, NULL}, {"client_version", driver_client_version_read, NULL}, {"driver_version", driver_driver_version_read, NULL}, @@ -145,8 +153,10 @@ mysqli_property_entry mysqli_driver_property_entries[] = { /* {{{ mysqli_driver_methods[] */ const zend_function_entry mysqli_driver_methods[] = { +#if defined(HAVE_EMBEDDED_MYSQLI) PHP_FALIAS(embedded_server_start, mysqli_embedded_server_start, NULL) PHP_FALIAS(embedded_server_end, mysqli_embedded_server_end, NULL) +#endif {NULL, NULL, NULL} }; /* }}} */ diff --git a/ext/mysqli/mysqli_embedded.c b/ext/mysqli/mysqli_embedded.c index 12affc87c2..328025af21 100644 --- a/ext/mysqli/mysqli_embedded.c +++ b/ext/mysqli/mysqli_embedded.c @@ -25,7 +25,7 @@ #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" -#include "php_mysqli.h" +#include "php_mysqli_structs.h" /* {{{ proto bool mysqli_embedded_server_start(bool start, array arguments, array groups) initialize and start embedded server */ diff --git a/ext/mysqli/mysqli_exception.c b/ext/mysqli/mysqli_exception.c index b6b1d8903e..7b8a1d63e1 100644 --- a/ext/mysqli/mysqli_exception.c +++ b/ext/mysqli/mysqli_exception.c @@ -25,7 +25,7 @@ #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" -#include "php_mysqli.h" +#include "php_mysqli_structs.h" #include "zend_exceptions.h" /* {{{ mysqli_exception_methods[] diff --git a/ext/mysqli/mysqli_fe.c b/ext/mysqli/mysqli_fe.c index f82d31c405..623042e576 100644 --- a/ext/mysqli/mysqli_fe.c +++ b/ext/mysqli/mysqli_fe.c @@ -27,7 +27,7 @@ #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" -#include "php_mysqli.h" +#include "php_mysqli_structs.h" static @@ -61,14 +61,24 @@ const zend_function_entry mysqli_functions[] = { PHP_FE(mysqli_connect_errno, NULL) PHP_FE(mysqli_connect_error, NULL) PHP_FE(mysqli_data_seek, NULL) + PHP_FE(mysqli_dump_debug_info, NULL) PHP_FE(mysqli_debug, NULL) +#if !defined(HAVE_MYSQLND) PHP_FE(mysqli_disable_reads_from_master, NULL) PHP_FE(mysqli_disable_rpl_parse, NULL) - PHP_FE(mysqli_dump_debug_info, NULL) PHP_FE(mysqli_enable_reads_from_master, NULL) PHP_FE(mysqli_enable_rpl_parse, NULL) + PHP_FE(mysqli_send_query, NULL) + PHP_FE(mysqli_slave_query, NULL) + PHP_FE(mysqli_master_query, NULL) + PHP_FE(mysqli_rpl_parse_enabled, NULL) + PHP_FE(mysqli_rpl_probe, NULL) + PHP_FE(mysqli_rpl_query_type, NULL) +#endif +#if defined(HAVE_EMBEDDED_MYSQLI) PHP_FE(mysqli_embedded_server_end, NULL) PHP_FE(mysqli_embedded_server_start, NULL) +#endif PHP_FE(mysqli_errno, NULL) PHP_FE(mysqli_error, NULL) PHP_FE(mysqli_stmt_execute, NULL) @@ -77,14 +87,22 @@ const zend_function_entry mysqli_functions[] = { PHP_FE(mysqli_fetch_fields, NULL) PHP_FE(mysqli_fetch_field_direct, NULL) PHP_FE(mysqli_fetch_lengths, NULL) +#ifdef HAVE_MYSQLND + PHP_FE(mysqli_fetch_all, NULL) +#endif PHP_FE(mysqli_fetch_array, NULL) PHP_FE(mysqli_fetch_assoc, NULL) - PHP_FE(mysqli_fetch_object, NULL) + PHP_FE(mysqli_fetch_object, NULL) PHP_FE(mysqli_fetch_row, NULL) PHP_FE(mysqli_field_count, NULL) PHP_FE(mysqli_field_seek, NULL) PHP_FE(mysqli_field_tell, NULL) PHP_FE(mysqli_free_result, NULL) +#if defined(HAVE_MYSQLND) + PHP_FE(mysqli_get_cache_stats, NULL) + PHP_FE(mysqli_get_connection_stats, NULL) + PHP_FE(mysqli_get_client_stats, NULL) +#endif #ifdef HAVE_MYSQLI_GET_CHARSET PHP_FE(mysqli_get_charset, NULL) #endif @@ -99,9 +117,10 @@ const zend_function_entry mysqli_functions[] = { PHP_FE(mysqli_info, NULL) PHP_FE(mysqli_insert_id, NULL) PHP_FE(mysqli_kill, NULL) +#if !defined(HAVE_MYSQLND) PHP_FE(mysqli_set_local_infile_default, NULL) PHP_FE(mysqli_set_local_infile_handler, NULL) - PHP_FE(mysqli_master_query, NULL) +#endif PHP_FE(mysqli_more_results, NULL) PHP_FE(mysqli_multi_query, NULL) PHP_FE(mysqli_next_result, NULL) @@ -116,9 +135,6 @@ const zend_function_entry mysqli_functions[] = { PHP_FE(mysqli_real_escape_string, NULL) PHP_FE(mysqli_real_query, NULL) PHP_FE(mysqli_rollback, NULL) - PHP_FE(mysqli_rpl_parse_enabled, NULL) - PHP_FE(mysqli_rpl_probe, NULL) - PHP_FE(mysqli_rpl_query_type, NULL) PHP_FE(mysqli_select_db, NULL) #ifdef HAVE_MYSQLI_SET_CHARSET PHP_FE(mysqli_set_charset, NULL) @@ -134,14 +150,17 @@ const zend_function_entry mysqli_functions[] = { PHP_FE(mysqli_stmt_bind_result, second_arg_force_by_ref_rest) PHP_FE(mysqli_stmt_fetch, NULL) PHP_FE(mysqli_stmt_free_result, NULL) +#if defined(HAVE_MYSQLND) + PHP_FE(mysqli_stmt_get_result, NULL) +#endif PHP_FE(mysqli_stmt_get_warnings, NULL) PHP_FE(mysqli_stmt_insert_id, NULL) PHP_FE(mysqli_stmt_reset, NULL) PHP_FE(mysqli_stmt_param_count, NULL) - PHP_FE(mysqli_send_query, NULL) - PHP_FE(mysqli_slave_query, NULL) PHP_FE(mysqli_sqlstate, NULL) +#if !defined(HAVE_MYSQLND) PHP_FE(mysqli_ssl_set, NULL) +#endif PHP_FE(mysqli_stat, NULL) PHP_FE(mysqli_stmt_affected_rows, NULL) PHP_FE(mysqli_stmt_close, NULL) @@ -150,8 +169,8 @@ const zend_function_entry mysqli_functions[] = { PHP_FE(mysqli_stmt_error, NULL) PHP_FE(mysqli_stmt_num_rows, NULL) PHP_FE(mysqli_stmt_sqlstate, NULL) - PHP_FE(mysqli_store_result, NULL) PHP_FE(mysqli_stmt_store_result, NULL) + PHP_FE(mysqli_store_result, NULL) PHP_FE(mysqli_thread_id, NULL) PHP_FE(mysqli_thread_safe, NULL) PHP_FE(mysqli_use_result, NULL) @@ -184,23 +203,34 @@ const zend_function_entry mysqli_link_methods[] = { PHP_FALIAS(close,mysqli_close,NULL) PHP_FALIAS(commit,mysqli_commit,NULL) PHP_FALIAS(connect,mysqli_connect,NULL) + PHP_FALIAS(dump_debug_info,mysqli_dump_debug_info,NULL) PHP_FALIAS(debug,mysqli_debug,NULL) +#if !defined(HAVE_MYSQLND) PHP_FALIAS(disable_reads_from_master,mysqli_disable_reads_from_master,NULL) PHP_FALIAS(disable_rpl_parse,mysqli_disable_rpl_parse,NULL) - PHP_FALIAS(dump_debug_info,mysqli_dump_debug_info,NULL) PHP_FALIAS(enable_reads_from_master,mysqli_enable_reads_from_master,NULL) PHP_FALIAS(enable_rpl_parse,mysqli_enable_rpl_parse,NULL) + PHP_FALIAS(rpl_parse_enabled,mysqli_rpl_parse_enabled,NULL) + PHP_FALIAS(rpl_probe,mysqli_rpl_probe,NULL) + PHP_FALIAS(rpl_query_type,mysqli_rpl_query_type,NULL) + PHP_FALIAS(master_query,mysqli_master_query,NULL) + PHP_FALIAS(slave_query,mysqli_slave_query,NULL) +#endif #ifdef HAVE_MYSQLI_GET_CHARSET PHP_FALIAS(get_charset,mysqli_get_charset,NULL) #endif PHP_FALIAS(get_client_info,mysqli_get_client_info,NULL) +#if defined(HAVE_MYSQLND) + PHP_FALIAS(get_connection_stats,mysqli_get_connection_stats,NULL) +#endif PHP_FALIAS(get_server_info,mysqli_get_server_info,NULL) PHP_FALIAS(get_warnings, mysqli_get_warnings, NULL) PHP_FALIAS(init,mysqli_init,NULL) PHP_FALIAS(kill,mysqli_kill,NULL) +#if !defined(HAVE_MYSQLND) PHP_FALIAS(set_local_infile_default,mysqli_set_local_infile_default,NULL) PHP_FALIAS(set_local_infile_handler,mysqli_set_local_infile_handler,NULL) - PHP_FALIAS(master_query,mysqli_master_query,NULL) +#endif PHP_FALIAS(multi_query,mysqli_multi_query,NULL) PHP_FALIAS(mysqli,mysqli_connect,NULL) PHP_FALIAS(more_results,mysqli_more_results, NULL) @@ -214,16 +244,14 @@ const zend_function_entry mysqli_link_methods[] = { PHP_FALIAS(escape_string, mysqli_real_escape_string,NULL) PHP_FALIAS(real_query,mysqli_real_query,NULL) PHP_FALIAS(rollback,mysqli_rollback,NULL) - PHP_FALIAS(rpl_parse_enabled,mysqli_rpl_parse_enabled,NULL) - PHP_FALIAS(rpl_probe,mysqli_rpl_probe,NULL) - PHP_FALIAS(rpl_query_type,mysqli_rpl_query_type,NULL) PHP_FALIAS(select_db,mysqli_select_db,NULL) #ifdef HAVE_MYSQLI_SET_CHARSET PHP_FALIAS(set_charset,mysqli_set_charset,NULL) #endif PHP_FALIAS(set_opt, mysqli_options,NULL) - PHP_FALIAS(slave_query,mysqli_slave_query,NULL) +#if !defined(HAVE_MYSQLND) PHP_FALIAS(ssl_set,mysqli_ssl_set,NULL) +#endif PHP_FALIAS(stat,mysqli_stat,NULL) PHP_FALIAS(stmt_init,mysqli_stmt_init, NULL) PHP_FALIAS(store_result,mysqli_store_result,NULL) @@ -238,18 +266,20 @@ const zend_function_entry mysqli_link_methods[] = { * Every user visible function must have an entry in mysqli_result_functions[]. */ const zend_function_entry mysqli_result_methods[] = { - PHP_FALIAS(mysqli_result, mysqli_result_construct, NULL) + PHP_FALIAS(__construct, mysqli_result_construct, NULL) PHP_FALIAS(close,mysqli_free_result,NULL) PHP_FALIAS(free,mysqli_free_result,NULL) PHP_FALIAS(data_seek,mysqli_data_seek,NULL) PHP_FALIAS(fetch_field,mysqli_fetch_field,NULL) PHP_FALIAS(fetch_fields,mysqli_fetch_fields,NULL) PHP_FALIAS(fetch_field_direct,mysqli_fetch_field_direct,NULL) +#if defined(HAVE_MYSQLND) + PHP_FALIAS(fetch_all,mysqli_fetch_all,NULL) +#endif PHP_FALIAS(fetch_array,mysqli_fetch_array,NULL) PHP_FALIAS(fetch_assoc,mysqli_fetch_assoc,NULL) PHP_FALIAS(fetch_object,mysqli_fetch_object,NULL) PHP_FALIAS(fetch_row,mysqli_fetch_row,NULL) - PHP_FALIAS(field_count,mysqli_field_count,NULL) PHP_FALIAS(field_seek,mysqli_field_seek,NULL) PHP_FALIAS(free_result,mysqli_free_result,NULL) {NULL, NULL, NULL} @@ -261,7 +291,7 @@ const zend_function_entry mysqli_result_methods[] = { * Every user visible function must have an entry in mysqli_stmt_functions[]. */ const zend_function_entry mysqli_stmt_methods[] = { - PHP_FALIAS(mysqli_stmt, mysqli_stmt_construct, NULL) + PHP_FALIAS(__construct, mysqli_stmt_construct, NULL) PHP_FALIAS(attr_get,mysqli_stmt_attr_get,NULL) PHP_FALIAS(attr_set,mysqli_stmt_attr_set,NULL) PHP_FALIAS(bind_param,mysqli_stmt_bind_param,second_arg_force_by_ref_rest) @@ -279,6 +309,9 @@ const zend_function_entry mysqli_stmt_methods[] = { PHP_FALIAS(reset,mysqli_stmt_reset,NULL) PHP_FALIAS(prepare,mysqli_stmt_prepare, NULL) PHP_FALIAS(store_result,mysqli_stmt_store_result,NULL) +#if defined(HAVE_MYSQLND) + PHP_FALIAS(get_result,mysqli_stmt_get_result,NULL) +#endif {NULL, NULL, NULL} }; /* }}} */ diff --git a/ext/mysqli/mysqli_libmysql.h b/ext/mysqli/mysqli_libmysql.h new file mode 100644 index 0000000000..1ea1fc3bf4 --- /dev/null +++ b/ext/mysqli/mysqli_libmysql.h @@ -0,0 +1,36 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ + +*/ + +/* These are unused */ +#define MYSQLI_CLOSE_EXPLICIT +#define MYSQLI_CLOSE_IMPLICIT +#define MYSQLI_CLOSE_DISCONNECTED +#define MYSQLND_OPT_NUMERIC_AND_DATETIME_AS_UNICODE 200 +#define MYSQLND_OPT_INT_AND_YEAR_AS_INT 201 + +#define mysqli_result_is_unbuffered(r) ((r)->handle && (r)->handle->status == MYSQL_STATUS_USE_RESULT) +#define mysqli_server_status(c) (c)->server_status +#define mysqli_stmt_warning_count(s) mysql_warning_count((s)->mysql) +#define mysqli_stmt_server_status(s) (s)->mysql->server_status +#define mysqli_stmt_get_connection(s) (s)->mysql +#define mysqli_close(c, is_forced) mysql_close((c)) +#define mysqli_stmt_close(c, implicit) mysql_stmt_close((c)) +#define mysqli_free_result(r, is_forced) mysql_free_result((r)) diff --git a/ext/mysqli/mysqli_mysqlnd.h b/ext/mysqli/mysqli_mysqlnd.h new file mode 100644 index 0000000000..27032f15c8 --- /dev/null +++ b/ext/mysqli/mysqli_mysqlnd.h @@ -0,0 +1,41 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ + +*/ + +#ifndef MYSQLI_MYSQLND_H +#define MYSQLI_MYSQLND_H + +#include "ext/mysqlnd/mysqlnd_libmysql_compat.h" + +/* Here comes non-libmysql API to have less ifdefs in mysqli*/ +#define MYSQLI_CLOSE_EXPLICIT MYSQLND_CLOSE_EXPLICIT +#define MYSQLI_CLOSE_IMPLICIT MYSQLND_CLOSE_IMPLICIT +#define MYSQLI_CLOSE_DISCONNECTED MYSQLND_CLOSE_DISCONNECTED + +#define mysqli_result_is_unbuffered(r) ((r)->unbuf) +#define mysqli_server_status(c) (c)->upsert_status.server_status +#define mysqli_stmt_warning_count(s) mysqlnd_stmt_warning_count((s)) +#define mysqli_stmt_server_status(s) (s)->upsert_status.server_status +#define mysqli_stmt_get_connection(s) (s)->conn +#define mysqli_close(c, how) mysqlnd_close((c), (how)) +#define mysqli_stmt_close(c, implicit) mysqlnd_stmt_close((c), (implicit)) +#define mysqli_free_result(r, implicit) mysqlnd_free_result((r), (implicit)) + +#endif diff --git a/ext/mysqli/mysqli_nonapi.c b/ext/mysqli/mysqli_nonapi.c index 2f9ba5325b..918594ad76 100644 --- a/ext/mysqli/mysqli_nonapi.c +++ b/ext/mysqli/mysqli_nonapi.c @@ -12,7 +12,9 @@ | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ - | Author: Georg Richter <georg@php.net> | + | Authors: Georg Richter <georg@php.net> | + | Andrey Hristov <andrey@php.net> | + | Ulf Wendel <uw@php.net> | +----------------------------------------------------------------------+ $Id$ @@ -27,46 +29,54 @@ #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" -#include "php_mysqli.h" +#include "php_mysqli_structs.h" + +#define SAFE_STR(a) ((a)?a:"") + /* {{{ proto object mysqli_connect([string hostname [,string username [,string passwd [,string dbname [,int port [,string socket]]]]]]) Open a connection to a mysql server */ PHP_FUNCTION(mysqli_connect) { - MY_MYSQL *mysql = NULL; - MYSQLI_RESOURCE *mysqli_resource = NULL; - zval *object = getThis(); - char *hostname = NULL, *username=NULL, *passwd=NULL, *dbname=NULL, *socket=NULL; - unsigned int hostname_len = 0, username_len = 0, passwd_len = 0, dbname_len = 0, socket_len = 0; - long port=0; + MY_MYSQL *mysql = NULL; + MYSQLI_RESOURCE *mysqli_resource = NULL; + zval *object = getThis(); + char *hostname = NULL, *username=NULL, *passwd=NULL, *dbname=NULL, *socket=NULL; + unsigned int hostname_len = 0, username_len = 0, passwd_len = 0, dbname_len = 0, socket_len = 0; + zend_bool persistent = FALSE; + long port = 0; + uint hash_len; + char *hash_key = NULL; + zend_bool new_connection = FALSE; + zend_rsrc_list_entry *le; + mysqli_plist_entry *plist = NULL; if (getThis() && !ZEND_NUM_ARGS()) { RETURN_NULL(); } + hostname = username = dbname = passwd = socket = NULL; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|ssssls", &hostname, &hostname_len, &username, &username_len, &passwd, &passwd_len, &dbname, &dbname_len, &port, &socket, &socket_len) == FAILURE) { return; } - if (!socket_len) { - socket = NULL; + if (!socket_len || !socket) { + socket = MyG(default_socket); } - - /* TODO: safe mode handling */ - if (PG(sql_safe_mode)){ - } else { - if (!passwd) { - passwd = MyG(default_pw); - if (!username){ - username = MyG(default_user); - if (!hostname) { - hostname = MyG(default_host); - } - } - } + + if (!passwd) { + passwd = MyG(default_pw); + passwd_len = strlen(SAFE_STR(passwd)); + } + if (!username){ + username = MyG(default_user); + } + if (!hostname || !hostname_len) { + hostname = MyG(default_host); } + if (object && instanceof_function(Z_OBJCE_P(object), mysqli_link_class_entry TSRMLS_CC)) { mysqli_resource = ((mysqli_object *) zend_object_store_get_object(object TSRMLS_CC))->ptr; if (mysqli_resource && mysqli_resource->ptr && @@ -75,7 +85,7 @@ PHP_FUNCTION(mysqli_connect) mysql = (MY_MYSQL*)mysqli_resource->ptr; php_clear_mysql(mysql); if (mysql->mysql) { - mysql_close(mysql->mysql); + mysqli_close(mysql->mysql, MYSQLI_CLOSE_EXPLICIT); mysql->mysql = NULL; } } @@ -84,61 +94,194 @@ PHP_FUNCTION(mysqli_connect) mysql = (MY_MYSQL *) ecalloc(1, sizeof(MY_MYSQL)); } + if (strlen(SAFE_STR(hostname)) > 2 && !strncasecmp(hostname, "p:", 2)) { + hostname += 2; + if (!MyG(allow_persistent)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Persistent connections are disabled. Downgrading to normal"); + } else { + mysql->persistent = persistent = TRUE; + + if (!strlen(hostname)) { + hostname = MyG(default_host); + } + + hash_len = spprintf(&hash_key, 0, "mysqli_%s%ld%s%s%s", SAFE_STR(hostname), + port, SAFE_STR(username), SAFE_STR(dbname), + SAFE_STR(passwd)); + + /* check if we can reuse exisiting connection ... */ + if (zend_hash_find(&EG(persistent_list), hash_key, hash_len + 1, (void **)&le) == SUCCESS) { + if (Z_TYPE_P(le) == php_le_pmysqli()) { + plist = (mysqli_plist_entry *) le->ptr; + + do { + if (zend_hash_num_elements(&plist->free_links)) { + HashPosition pos; + MYSQL **free_mysql; + ulong idx; + dtor_func_t pDestructor = plist->free_links.pDestructor; + + zend_hash_internal_pointer_reset_ex(&plist->free_links, &pos); + if (SUCCESS != zend_hash_get_current_data_ex(&plist->free_links, + (void **)&free_mysql, &pos)) { + break; + } + if (HASH_KEY_IS_LONG != zend_hash_get_current_key_ex(&plist->free_links, NULL, + NULL, &idx, FALSE, &pos)) { + break; + } + plist->free_links.pDestructor = NULL; /* Don't call pDestructor now */ + if (SUCCESS != zend_hash_index_del(&plist->free_links, idx)) { + plist->used_links.pDestructor = pDestructor; /* Restore the destructor */ + break; + } + plist->free_links.pDestructor = pDestructor; /* Restore the destructor */ + mysql->mysql = *free_mysql; + + MyG(num_inactive_persistent)--; + MyG(num_active_persistent)++; + /* reset variables */ + /* todo: option for ping or change_user */ +#if G0 + if (!mysql_change_user(mysql->mysql, username, passwd, dbname)) { +#else + if (!mysql_ping(mysql->mysql)) { +#endif +#ifdef HAVE_MYSQLND + mysqlnd_restart_psession(mysql->mysql); +#endif + idx = zend_hash_next_free_element(&plist->used_links); + if (SUCCESS != zend_hash_next_index_insert(&plist->used_links, &free_mysql, + sizeof(MYSQL *), NULL)) { + php_mysqli_dtor_p_elements(free_mysql); + MyG(num_links)--; + break; + } + mysql->hash_index = idx; + mysql->hash_key = hash_key; + goto end; + } + } + } while (0); + } + } else { + zend_rsrc_list_entry le; + le.type = php_le_pmysqli(); + le.ptr = plist = calloc(1, sizeof(mysqli_plist_entry)); + + zend_hash_init(&plist->free_links, MAX(2, MyG(max_persistent)), NULL, php_mysqli_dtor_p_elements, 1); + zend_hash_init(&plist->used_links, MAX(2, MyG(max_persistent)), NULL, php_mysqli_dtor_p_elements, 1); + zend_hash_update(&EG(persistent_list), hash_key, hash_len + 1, (void *)&le, sizeof(le), NULL); + } + } + } + + if (MyG(max_links) != -1 && MyG(num_links) >= MyG(max_links)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Too many open links (%ld)", MyG(num_links)); + goto err; + } + if (persistent && MyG(max_persistent) != -1 && + (MyG(num_active_persistent) + MyG(num_inactive_persistent))>= MyG(max_persistent)) + { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Too many open persistent links (%ld)", + MyG(num_active_persistent) + MyG(num_inactive_persistent)); + goto err; + } + +#if !defined(HAVE_MYSQLND) if (!(mysql->mysql = mysql_init(NULL))) { - efree(mysql); - RETURN_FALSE; +#else + if (!(mysql->mysql = mysqlnd_init(persistent))) { +#endif + goto err; } + new_connection = TRUE; #ifdef HAVE_EMBEDDED_MYSQLI - if (strcmp(hostname, ":embedded")) { + if (hostname_len) { unsigned int external=1; mysql_options(mysql->mysql, MYSQL_OPT_USE_REMOTE_CONNECTION, (char *)&external); } else { - hostname[0] = '\0'; mysql_options(mysql->mysql, MYSQL_OPT_USE_EMBEDDED_CONNECTION, 0); } #endif - if (!socket) { - socket = MyG(default_socket); - } - - if (mysql_real_connect(mysql->mysql,hostname,username,passwd,dbname,port,socket,CLIENT_MULTI_RESULTS) == NULL) { +#if !defined(HAVE_MYSQLND) + if (mysql_real_connect(mysql->mysql, hostname, username, passwd, dbname, port, socket, CLIENT_MULTI_RESULTS) == NULL) +#else + if (mysqlnd_connect(mysql->mysql, hostname, username, passwd, passwd_len, dbname, dbname_len, + port, socket, CLIENT_MULTI_RESULTS, MyG(mysqlnd_thd_zval_cache) TSRMLS_CC) == NULL) +#endif + { /* Save error messages */ - - php_mysqli_throw_sql_exception( mysql->mysql->net.sqlstate, mysql->mysql->net.last_errno TSRMLS_CC, - "%s", mysql->mysql->net.last_error); - php_mysqli_set_error(mysql_errno(mysql->mysql), (char *) mysql_error(mysql->mysql) TSRMLS_CC); + php_mysqli_throw_sql_exception((char *)mysql_sqlstate(mysql->mysql), mysql_errno(mysql->mysql) TSRMLS_CC, + "%s", mysql_error(mysql->mysql)); /* free mysql structure */ - mysql_close(mysql->mysql); - efree(mysql); - RETURN_FALSE; + mysqli_close(mysql->mysql, MYSQLI_CLOSE_DISCONNECTED); + goto err; } /* clear error */ php_mysqli_set_error(mysql_errno(mysql->mysql), (char *) mysql_error(mysql->mysql) TSRMLS_CC); +#if !defined(HAVE_MYSQLND) mysql->mysql->reconnect = MyG(reconnect); /* set our own local_infile handler */ php_set_local_infile_handler_default(mysql); +#endif + + mysql_options(mysql->mysql, MYSQL_OPT_LOCAL_INFILE, (char *)&MyG(allow_local_infile)); +end: if (!mysqli_resource) { mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE)); mysqli_resource->ptr = (void *)mysql; } mysqli_resource->status = MYSQLI_STATUS_VALID; + /* store persistent connection */ + if (persistent && new_connection) { + /* save persistent connection */ + ulong hash_index = zend_hash_next_free_element(&plist->used_links); + if (SUCCESS != zend_hash_next_index_insert(&plist->used_links, &mysql->mysql, + sizeof(MYSQL *), NULL)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can't store persistent connection"); + } else { + mysql->hash_index = hash_index; + } + MyG(num_active_persistent)++; + } + + mysql->hash_key = hash_key; + MyG(num_links)++; + +#if !defined(HAVE_MYSQLND) + mysql->multi_query = 0; +#else + mysql->multi_query = 1; +#endif + + if (!object || !instanceof_function(Z_OBJCE_P(object), mysqli_link_class_entry TSRMLS_CC)) { MYSQLI_RETURN_RESOURCE(mysqli_resource, mysqli_link_class_entry); } else { ((mysqli_object *) zend_object_store_get_object(object TSRMLS_CC))->ptr = mysqli_resource; } + return; + +err: + efree(mysql); + if (persistent) { + efree(hash_key); + } + RETVAL_FALSE; } /* }}} */ + /* {{{ proto int mysqli_connect_errno(void) Returns the numerical value of the error message from last connect command */ PHP_FUNCTION(mysqli_connect_errno) @@ -159,11 +302,30 @@ PHP_FUNCTION(mysqli_connect_error) } /* }}} */ + /* {{{ proto mixed mysqli_fetch_array (object result [,int resulttype]) Fetch a result row as an associative array, a numeric array, or both */ PHP_FUNCTION(mysqli_fetch_array) { +#if !defined(HAVE_MYSQLND) php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0, 0); +#else + MYSQL_RES *result; + zval *mysql_result; + long mode = MYSQLND_FETCH_BOTH; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|l", &mysql_result, mysqli_result_class_entry, &mode) == FAILURE) { + return; + } + MYSQLI_FETCH_RESOURCE(result, MYSQL_RES *, &mysql_result, "mysqli_result", MYSQLI_STATUS_VALID); + + if (mode < MYSQLI_ASSOC || mode > MYSQLI_BOTH) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "The result type should be either MYSQLI_NUM, MYSQLI_ASSOC or MYSQLI_BOTH"); + RETURN_FALSE; + } + + mysqlnd_fetch_into(result, mode, return_value, MYSQLND_MYSQLI); +#endif } /* }}} */ @@ -171,20 +333,102 @@ PHP_FUNCTION(mysqli_fetch_array) Fetch a result row as an associative array */ PHP_FUNCTION(mysqli_fetch_assoc) { +#if !defined(HAVE_MYSQLND) php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQLI_ASSOC, 0); +#else + MYSQL_RES *result; + zval *mysql_result; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_result, mysqli_result_class_entry) == FAILURE) { + return; + } + MYSQLI_FETCH_RESOURCE(result, MYSQL_RES *, &mysql_result, "mysqli_result", MYSQLI_STATUS_VALID); + mysqlnd_fetch_into(result, MYSQLND_FETCH_ASSOC, return_value, MYSQLND_MYSQLI); + +#endif } /* }}} */ + +/* {{{ proto mixed mysqli_fetch_all (object result [,int resulttype]) + Fetches all result rows as an associative array, a numeric array, or both */ +#if defined(HAVE_MYSQLND) +PHP_FUNCTION(mysqli_fetch_all) +{ + MYSQL_RES *result; + zval *mysql_result; + long mode = MYSQLND_FETCH_NUM; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|l", &mysql_result, mysqli_result_class_entry, &mode) == FAILURE) { + return; + } + MYSQLI_FETCH_RESOURCE(result, MYSQL_RES *, &mysql_result, "mysqli_result", MYSQLI_STATUS_VALID); + + if (!mode || (mode & ~MYSQLND_FETCH_BOTH)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Mode can be only MYSQLI_FETCH_NUM, " + "MYSQLI_FETCH_ASSOC or MYSQLI_FETCH_BOTH"); + RETURN_FALSE; + } + + mysqlnd_fetch_all(result, mode, return_value); +} +/* }}} */ + + +/* {{{ proto array mysqli_cache_stats(void) U + Returns statistics about the zval cache */ +PHP_FUNCTION(mysqli_get_cache_stats) +{ + if (ZEND_NUM_ARGS()) { + WRONG_PARAM_COUNT; + } + mysqlnd_palloc_stats(mysqli_mysqlnd_zval_cache, return_value); +} +/* }}} */ + + +/* {{{ proto array mysqli_get_client_stats(void) + Returns statistics about the zval cache */ +PHP_FUNCTION(mysqli_get_client_stats) +{ + if (ZEND_NUM_ARGS()) { + WRONG_PARAM_COUNT; + } + mysqlnd_get_client_stats(return_value); +} +/* }}} */ + + +/* {{{ proto array mysqli_get_connection_stats(void) + Returns statistics about the zval cache */ +PHP_FUNCTION(mysqli_get_connection_stats) +{ + MY_MYSQL *mysql; + zval *mysql_link; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", + &mysql_link, mysqli_link_class_entry) == FAILURE) { + return; + } + MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL *, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); + + mysqlnd_get_connection_stats(mysql->mysql, return_value); +} +#endif +/* }}} */ + + /* {{{ proto mixed mysqli_fetch_object (object result [, string class_name [, NULL|array ctor_params]]) Fetch a result row as an object */ PHP_FUNCTION(mysqli_fetch_object) { php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAM_PASSTHRU, MYSQLI_ASSOC, 1); +/* todo: mysqlnd support */ } /* }}} */ /* {{{ proto bool mysqli_multi_query(object link, string query) - Binary-safe version of mysql_query() */ + allows to execute multiple queries */ PHP_FUNCTION(mysqli_multi_query) { MY_MYSQL *mysql; @@ -199,25 +443,30 @@ PHP_FUNCTION(mysqli_multi_query) MYSQLI_ENABLE_MQ; if (mysql_real_query(mysql->mysql, query, query_len)) { +#ifndef HAVE_MYSQLND char s_error[MYSQL_ERRMSG_SIZE], s_sqlstate[SQLSTATE_LENGTH+1]; unsigned int s_errno; - MYSQLI_REPORT_MYSQL_ERROR(mysql->mysql); - /* we have to save error information, cause MYSQLI_DISABLE_MQ will reset error information */ strcpy(s_error, mysql_error(mysql->mysql)); strcpy(s_sqlstate, mysql_sqlstate(mysql->mysql)); s_errno = mysql_errno(mysql->mysql); - +#else + mysqlnd_error_info error_info = mysql->mysql->error_info; +#endif + MYSQLI_REPORT_MYSQL_ERROR(mysql->mysql); MYSQLI_DISABLE_MQ; +#ifndef HAVE_MYSQLND /* restore error information */ strcpy(mysql->mysql->net.last_error, s_error); strcpy(mysql->mysql->net.sqlstate, s_sqlstate); mysql->mysql->net.last_errno = s_errno; - +#else + mysql->mysql->error_info = error_info; +#endif RETURN_FALSE; - } + } RETURN_TRUE; } /* }}} */ @@ -258,7 +507,7 @@ PHP_FUNCTION(mysqli_query) if (!mysql_field_count(mysql->mysql)) { /* no result set - not a SELECT */ if (MyG(report_mode) & MYSQLI_REPORT_INDEX) { - php_mysqli_report_index(query, mysql->mysql->server_status TSRMLS_CC); + php_mysqli_report_index(query, mysqli_server_status(mysql->mysql) TSRMLS_CC); } RETURN_TRUE; } @@ -266,13 +515,13 @@ PHP_FUNCTION(mysqli_query) result = (resultmode == MYSQLI_USE_RESULT) ? mysql_use_result(mysql->mysql) : mysql_store_result(mysql->mysql); if (!result) { - php_mysqli_throw_sql_exception(mysql->mysql->net.sqlstate, mysql->mysql->net.last_errno TSRMLS_CC, - "%s", mysql->mysql->net.last_error); + php_mysqli_throw_sql_exception((char *)mysql_sqlstate(mysql->mysql), mysql_errno(mysql->mysql) TSRMLS_CC, + "%s", mysql_error(mysql->mysql)); RETURN_FALSE; } if (MyG(report_mode) & MYSQLI_REPORT_INDEX) { - php_mysqli_report_index(query, mysql->mysql->server_status TSRMLS_CC); + php_mysqli_report_index(query, mysqli_server_status(mysql->mysql) TSRMLS_CC); } mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE)); @@ -282,6 +531,36 @@ PHP_FUNCTION(mysqli_query) } /* }}} */ + +#if defined(HAVE_MYSQLND) +/* {{{ proto object mysqli_stmt_get_result(object link) U + Buffer result set on client */ +PHP_FUNCTION(mysqli_stmt_get_result) +{ + MYSQL_RES *result; + MYSQLI_RESOURCE *mysqli_resource; + MY_STMT *stmt; + zval *mysql_stmt; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_stmt, mysqli_stmt_class_entry) == FAILURE) { + return; + } + MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &mysql_stmt, "mysqli_stmt", MYSQLI_STATUS_VALID); + + if (!(result = mysqlnd_stmt_get_result(stmt->stmt))) { + MYSQLI_REPORT_STMT_ERROR(stmt->stmt); + RETURN_FALSE; + } + + mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE)); + mysqli_resource->ptr = (void *)result; + mysqli_resource->status = MYSQLI_STATUS_VALID; + MYSQLI_RETURN_RESOURCE(mysqli_resource, mysqli_result_class_entry); +} +/* }}} */ +#endif + + /* {{{ proto object mysqli_get_warnings(object link) */ PHP_FUNCTION(mysqli_get_warnings) { @@ -296,12 +575,13 @@ PHP_FUNCTION(mysqli_get_warnings) MYSQLI_FETCH_RESOURCE(mysql, MY_MYSQL*, &mysql_link, "mysqli_link", MYSQLI_STATUS_VALID); if (mysql_warning_count(mysql->mysql)) { - w = php_get_warnings(mysql->mysql); + w = php_get_warnings(mysql->mysql TSRMLS_CC); } else { RETURN_FALSE; } mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE)); mysqli_resource->ptr = mysqli_resource->info = (void *)w; + mysqli_resource->status = MYSQLI_STATUS_VALID; MYSQLI_RETURN_RESOURCE(mysqli_resource, mysqli_warning_class_entry); } /* }}} */ @@ -317,15 +597,16 @@ PHP_FUNCTION(mysqli_stmt_get_warnings) if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &stmt_link, mysqli_stmt_class_entry) == FAILURE) { return; } - MYSQLI_FETCH_RESOURCE(stmt, MY_STMT*, &stmt_link, "mysqli_stmt", 1); + MYSQLI_FETCH_RESOURCE(stmt, MY_STMT*, &stmt_link, "mysqli_stmt", MYSQLI_STATUS_VALID); - if (mysql_warning_count(stmt->stmt->mysql)) { - w = php_get_warnings(stmt->stmt->mysql); + if (mysqli_stmt_warning_count(stmt->stmt)) { + w = php_get_warnings(mysqli_stmt_get_connection(stmt->stmt) TSRMLS_CC); } else { RETURN_FALSE; } mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE)); mysqli_resource->ptr = mysqli_resource->info = (void *)w; + mysqli_resource->status = MYSQLI_STATUS_VALID; MYSQLI_RETURN_RESOURCE(mysqli_resource, mysqli_warning_class_entry); } /* }}} */ @@ -354,13 +635,19 @@ PHP_FUNCTION(mysqli_set_charset) #endif #ifdef HAVE_MYSQLI_GET_CHARSET -/* {{{ proto object mysqli_get_charset(object link) +/* {{{ proto object mysqli_get_charset(object link) U returns a character set object */ PHP_FUNCTION(mysqli_get_charset) { MY_MYSQL *mysql; zval *mysql_link; + char *name = NULL, *collation = NULL, *dir = NULL; + uint minlength, maxlength, number, state; +#if !defined(HAVE_MYSQLND) MY_CHARSET_INFO cs; +#else + const MYSQLND_CHARSET *cs; +#endif if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { return; @@ -369,16 +656,32 @@ PHP_FUNCTION(mysqli_get_charset) object_init(return_value); +#if !defined(HAVE_MYSQLND) mysql_get_character_set_info(mysql->mysql, &cs); + name = (char *)cs.csname; + collation = (char *)cs.name; + dir = (char *)cs.dir; + minlength = cs.mbminlen; + maxlength = cs.mbmaxlen; + number = cs.number; + state = cs.state; +#else + cs = mysql->mysql->charset; + name = cs->name; + collation = cs->collation; + minlength = cs->char_minlen; + maxlength = cs->char_maxlen; + number = cs->nr; + state = 1; /* all charsets are compiled in */ +#endif - add_property_string(return_value, "charset", (cs.name) ? (char *)cs.csname : "", 1); - add_property_string(return_value, "collation",(cs.name) ? (char *)cs.name : "", 1); - add_property_string(return_value, "comment", (cs.comment) ? (char *)cs.comment : "", 1); - add_property_string(return_value, "dir", (cs.dir) ? (char *)cs.dir : "", 1); - add_property_long(return_value, "min_length", cs.mbminlen); - add_property_long(return_value, "max_length", cs.mbmaxlen); - add_property_long(return_value, "number", cs.number); - add_property_long(return_value, "state", cs.state); + add_property_string(return_value, "charset", (name) ? (char *)name : "", 1); + add_property_string(return_value, "collation",(collation) ? (char *)collation : "", 1); + add_property_string(return_value, "dir", (dir) ? (char *)dir : "", 1); + add_property_long(return_value, "min_length", minlength); + add_property_long(return_value, "max_length", maxlength); + add_property_long(return_value, "number", number); + add_property_long(return_value, "state", state); } /* }}} */ #endif diff --git a/ext/mysqli/mysqli_prop.c b/ext/mysqli/mysqli_prop.c index fd0b8a21fa..fd8281b551 100644 --- a/ext/mysqli/mysqli_prop.c +++ b/ext/mysqli/mysqli_prop.c @@ -27,7 +27,7 @@ #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" -#include "php_mysqli.h" +#include "php_mysqli_structs.h" #define CHECK_STATUS(value) \ if (((MYSQLI_RESOURCE *)obj->ptr)->status < value ) { \ @@ -221,24 +221,23 @@ static int result_type_read(mysqli_object *obj, zval **retval TSRMLS_DC) static int result_lengths_read(mysqli_object *obj, zval **retval TSRMLS_DC) { MYSQL_RES *p; + ulong *ret; ALLOC_ZVAL(*retval); CHECK_STATUS(MYSQLI_STATUS_VALID); p = (MYSQL_RES *)((MYSQLI_RESOURCE *)(obj->ptr))->ptr; - if (!p || !p->field_count) { + if (!p || !p->field_count || !(ret = mysql_fetch_lengths(p))) + { ZVAL_NULL(*retval); } else { ulong i; - zval *l; array_init(*retval); for (i=0; i < p->field_count; i++) { - MAKE_STD_ZVAL(l); - ZVAL_LONG(l, p->lengths[i]); - add_index_zval(*retval, i, l); - } + add_index_long(*retval, i, ret[i]); + } } return SUCCESS; } @@ -312,7 +311,7 @@ MYSQLI_MAP_PROPERTY_FUNC_STRING(stmt_error_read, mysql_stmt_error, MYSQLI_GET_ST MYSQLI_MAP_PROPERTY_FUNC_STRING(stmt_sqlstate_read, mysql_stmt_sqlstate, MYSQLI_GET_STMT(MYSQLI_STATUS_INITIALIZED)); /* }}} */ -mysqli_property_entry mysqli_link_property_entries[] = { +const mysqli_property_entry mysqli_link_property_entries[] = { {"affected_rows", link_affected_rows_read, NULL}, {"client_info", link_client_info_read, NULL}, {"client_version", link_client_version_read, NULL}, @@ -333,7 +332,7 @@ mysqli_property_entry mysqli_link_property_entries[] = { {NULL, NULL, NULL} }; -mysqli_property_entry mysqli_result_property_entries[] = { +const mysqli_property_entry mysqli_result_property_entries[] = { {"current_field", result_current_field_read, NULL}, {"field_count", result_field_count_read, NULL}, {"lengths", result_lengths_read, NULL}, @@ -342,7 +341,7 @@ mysqli_property_entry mysqli_result_property_entries[] = { {NULL, NULL, NULL} }; -mysqli_property_entry mysqli_stmt_property_entries[] = { +const mysqli_property_entry mysqli_stmt_property_entries[] = { {"affected_rows", stmt_affected_rows_read, NULL}, {"insert_id", stmt_insert_id_read, NULL}, {"num_rows", stmt_num_rows_read, NULL}, diff --git a/ext/mysqli/mysqli_repl.c b/ext/mysqli/mysqli_repl.c index b12706fa6b..8096c0239a 100644 --- a/ext/mysqli/mysqli_repl.c +++ b/ext/mysqli/mysqli_repl.c @@ -27,7 +27,7 @@ #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" -#include "php_mysqli.h" +#include "php_mysqli_structs.h" /* {{{ proto void mysqli_disable_reads_from_master(object link) */ diff --git a/ext/mysqli/mysqli_report.c b/ext/mysqli/mysqli_report.c index be6e857226..dae2f9242d 100644 --- a/ext/mysqli/mysqli_report.c +++ b/ext/mysqli/mysqli_report.c @@ -25,7 +25,7 @@ #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" -#include "php_mysqli.h" +#include "php_mysqli_structs.h" /* {{{ proto bool mysqli_report(int flags) sets report level */ @@ -45,13 +45,14 @@ PHP_FUNCTION(mysqli_report) /* }}} */ /* {{{ void php_mysqli_report_error(char *sqlstate, int errorno, char *error) */ -void php_mysqli_report_error(char *sqlstate, int errorno, char *error TSRMLS_DC) { - php_mysqli_throw_sql_exception(sqlstate, errorno TSRMLS_CC, "%s", error); +void php_mysqli_report_error(const char *sqlstate, int errorno, const char *error TSRMLS_DC) +{ + php_mysqli_throw_sql_exception((char *)sqlstate, errorno TSRMLS_CC, "%s", error); } /* }}} */ /* {{{ void php_mysqli_report_index() */ -void php_mysqli_report_index(char *query, unsigned int status TSRMLS_DC) { +void php_mysqli_report_index(const char *query, unsigned int status TSRMLS_DC) { char index[15]; if (status & SERVER_QUERY_NO_GOOD_INDEX_USED) { diff --git a/ext/mysqli/mysqli_warning.c b/ext/mysqli/mysqli_warning.c index 92dc8ce327..7b02d9117a 100644 --- a/ext/mysqli/mysqli_warning.c +++ b/ext/mysqli/mysqli_warning.c @@ -25,55 +25,144 @@ #include "php.h" #include "php_ini.h" #include "ext/standard/info.h" -#include "php_mysqli.h" +#include "php_mysqli_structs.h" + +/* Define these in the PHP5 tree to make merging easy process */ +#define ZSTR_DUPLICATE (1<<0) +#define ZSTR_AUTOFREE (1<<1) + +#define ZVAL_UTF8_STRING(z, s, flags) ZVAL_STRING((z), (char*)(s), ((flags) & ZSTR_DUPLICATE)) +#define ZVAL_UTF8_STRINGL(z, s, l, flags) ZVAL_STRINGL((z), (char*)(s), (l), ((flags) & ZSTR_DUPLICATE)) + /* {{{ void php_clear_warnings() */ void php_clear_warnings(MYSQLI_WARNING *w) { - MYSQLI_WARNING *n; + MYSQLI_WARNING *n; while (w) { n = w; - efree(w->reason); + zval_dtor(&(w->reason)); + zval_dtor(&(w->sqlstate)); w = w->next; efree(n); } } /* }}} */ + +#ifndef HAVE_MYSQLND /* {{{ MYSQLI_WARNING *php_new_warning */ -MYSQLI_WARNING *php_new_warning(char *reason, char *sqlstate, int errorno) +static +MYSQLI_WARNING *php_new_warning(const char *reason, int errorno TSRMLS_DC) { - MYSQLI_WARNING *w; + MYSQLI_WARNING *w; w = (MYSQLI_WARNING *)ecalloc(1, sizeof(MYSQLI_WARNING)); - w->reason = safe_estrdup(reason); - if (sqlstate) { - strcpy(w->sqlstate, sqlstate); - } else { - strcpy(w->sqlstate, "00000"); - } + ZVAL_UTF8_STRING(&(w->reason), reason, ZSTR_DUPLICATE); + + ZVAL_UTF8_STRINGL(&(w->sqlstate), "HY000", sizeof("HY000") - 1, ZSTR_DUPLICATE); + w->errorno = errorno; return w; } /* }}} */ -/* {{{ MYSQLI_WARNING *php_get_warnings(MYSQL *mysql) */ -MYSQLI_WARNING *php_get_warnings(MYSQL *mysql) + +/* {{{ MYSQLI_WARNING *php_get_warnings(MYSQL *mysql TSRMLS_DC) */ +MYSQLI_WARNING *php_get_warnings(MYSQL *mysql TSRMLS_DC) { - MYSQLI_WARNING *w, *first = NULL, *prev = NULL; + MYSQLI_WARNING *w, *first = NULL, *prev = NULL; MYSQL_RES *result; MYSQL_ROW row; - if (mysql_query(mysql, "SHOW WARNINGS")) { + if (mysql_real_query(mysql, "SHOW WARNINGS", 13)) { return NULL; } result = mysql_store_result(mysql); + while ((row = mysql_fetch_row(result))) { - w = php_new_warning(row[2], "HY000", atoi(row[1])); + w = php_new_warning(row[2], atoi(row[1]) TSRMLS_CC); + if (!first) { + first = w; + } + if (prev) { + prev->next = w; + } + prev = w; + } + mysql_free_result(result); + return first; +} +/* }}} */ +#else +/* {{{ MYSQLI_WARNING *php_new_warning */ +static +MYSQLI_WARNING *php_new_warning(const zval *reason, int errorno TSRMLS_DC) +{ + MYSQLI_WARNING *w; + + w = (MYSQLI_WARNING *)ecalloc(1, sizeof(MYSQLI_WARNING)); + + w->reason = *reason; + zval_copy_ctor(&(w->reason)); + + ZVAL_UTF8_STRINGL(&(w->reason), Z_STRVAL(w->reason), Z_STRLEN(w->reason), ZSTR_AUTOFREE); + + ZVAL_UTF8_STRINGL(&(w->sqlstate), "HY000", sizeof("HY000") - 1, ZSTR_DUPLICATE); + + w->errorno = errorno; + + return w; +} +/* }}} */ + + +/* {{{ MYSQLI_WARNING *php_get_warnings(MYSQL *mysql TSRMLS_DC) */ +MYSQLI_WARNING *php_get_warnings(MYSQL *mysql TSRMLS_DC) +{ + MYSQLI_WARNING *w, *first = NULL, *prev = NULL; + MYSQL_RES *result; + zval *row; + + if (mysql_real_query(mysql, "SHOW WARNINGS", 13)) { + return NULL; + } + + result = mysql_use_result(mysql); + + for (;;) { + zval **entry; + int errno; + + MAKE_STD_ZVAL(row); + mysqlnd_fetch_into(result, MYSQLND_FETCH_NUM, row, MYSQLND_MYSQLI); + if (Z_TYPE_P(row) != IS_ARRAY) { + zval_ptr_dtor(&row); + break; + } + zend_hash_internal_pointer_reset(Z_ARRVAL_P(row)); + /* 0. we don't care about the first */ + zend_hash_move_forward(Z_ARRVAL_P(row)); + + /* 1. Here comes the error no */ + zend_hash_get_current_data(Z_ARRVAL_P(row), (void **)&entry); + convert_to_long_ex(entry); + errno = Z_LVAL_PP(entry); + zend_hash_move_forward(Z_ARRVAL_P(row)); + + /* 2. Here comes the reason */ + zend_hash_get_current_data(Z_ARRVAL_P(row), (void **)&entry); + + w = php_new_warning(*entry, errno TSRMLS_CC); + /* + Don't destroy entry, because the row destroy will decrease + the refcounter. Decreased twice then mysqlnd_free_result() + will crash, because it will try to access already freed memory. + */ if (!first) { first = w; } @@ -81,11 +170,16 @@ MYSQLI_WARNING *php_get_warnings(MYSQL *mysql) prev->next = (void *)w; } prev = w; + + zval_ptr_dtor(&row); } + mysql_free_result(result); return first; } /* }}} */ +#endif + /* {{{ bool mysqli_warning::next() */ PHP_METHOD(mysqli_warning, next) @@ -112,7 +206,9 @@ PHP_METHOD(mysqli_warning, next) } /* }}} */ + /* {{{ property mysqli_warning_message */ +static int mysqli_warning_message(mysqli_object *obj, zval **retval TSRMLS_DC) { MYSQLI_WARNING *w; @@ -122,17 +218,16 @@ int mysqli_warning_message(mysqli_object *obj, zval **retval TSRMLS_DC) } w = (MYSQLI_WARNING *)((MYSQLI_RESOURCE *)(obj->ptr))->ptr; - ALLOC_ZVAL(*retval); - if (w->reason) { - ZVAL_STRING(*retval, w->reason, 1); - } else { - ZVAL_NULL(*retval); - } + MAKE_STD_ZVAL(*retval); + **retval = w->reason; + zval_copy_ctor(*retval); return SUCCESS; } /* }}} */ + /* {{{ property mysqli_warning_sqlstate */ +static int mysqli_warning_sqlstate(mysqli_object *obj, zval **retval TSRMLS_DC) { MYSQLI_WARNING *w; @@ -142,13 +237,16 @@ int mysqli_warning_sqlstate(mysqli_object *obj, zval **retval TSRMLS_DC) } w = (MYSQLI_WARNING *)((MYSQLI_RESOURCE *)(obj->ptr))->ptr; - ALLOC_ZVAL(*retval); - ZVAL_STRING(*retval, w->sqlstate, 1); + MAKE_STD_ZVAL(*retval); + **retval = w->sqlstate; + zval_copy_ctor(*retval); return SUCCESS; } /* }}} */ + /* {{{ property mysqli_warning_error */ +static int mysqli_warning_errno(mysqli_object *obj, zval **retval TSRMLS_DC) { MYSQLI_WARNING *w; @@ -157,7 +255,7 @@ int mysqli_warning_errno(mysqli_object *obj, zval **retval TSRMLS_DC) return FAILURE; } w = (MYSQLI_WARNING *)((MYSQLI_RESOURCE *)(obj->ptr))->ptr; - ALLOC_ZVAL(*retval); + MAKE_STD_ZVAL(*retval); ZVAL_LONG(*retval, w->errorno); return SUCCESS; } @@ -187,20 +285,22 @@ PHP_METHOD(mysqli_warning, __construct) } else if (obj->zo.ce == mysqli_stmt_class_entry) { MY_STMT *stmt; MYSQLI_FETCH_RESOURCE(stmt, MY_STMT *, &z, "mysqli_stmt", MYSQLI_STATUS_VALID); - hdl = stmt->stmt->mysql; + hdl = mysqli_stmt_get_connection(stmt->stmt); } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid class argument"); RETURN_FALSE; } if (mysql_warning_count(hdl)) { - w = php_get_warnings(hdl); + w = php_get_warnings(hdl TSRMLS_CC); } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "No warnings found"); RETURN_FALSE; } mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE)); - mysqli_resource->status = MYSQLI_STATUS_VALID; mysqli_resource->ptr = mysqli_resource->info = (void *)w; + mysqli_resource->status = MYSQLI_STATUS_VALID; if (!getThis() || !instanceof_function(Z_OBJCE_P(getThis()), mysqli_warning_class_entry TSRMLS_CC)) { MYSQLI_RETURN_RESOURCE(mysqli_resource, mysqli_warning_class_entry); @@ -211,18 +311,22 @@ PHP_METHOD(mysqli_warning, __construct) } /* }}} */ +/* {{{ mysqli_warning_methods */ const zend_function_entry mysqli_warning_methods[] = { PHP_ME(mysqli_warning, __construct, NULL, ZEND_ACC_PROTECTED) PHP_ME(mysqli_warning, next, NULL, ZEND_ACC_PUBLIC) {NULL, NULL, NULL} }; +/* }}} */ -mysqli_property_entry mysqli_warning_property_entries[] = { +/* {{{ mysqli_warning_property_entries */ +const mysqli_property_entry mysqli_warning_property_entries[] = { {"message", mysqli_warning_message, NULL}, {"sqlstate", mysqli_warning_sqlstate, NULL}, {"errno", mysqli_warning_errno, NULL}, {NULL, NULL, NULL} }; +/* }}} */ /* * Local variables: diff --git a/ext/mysqli/php_mysqli.h b/ext/mysqli/php_mysqli.h index 965bec7082..d166d00f52 100644 --- a/ext/mysqli/php_mysqli.h +++ b/ext/mysqli/php_mysqli.h @@ -12,299 +12,17 @@ | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ - | Author: Georg Richter <georg@php.net> | + | Authors: Georg Richter <georg@php.net> | + | Andrey Hristov <andrey@php.net> | + | Ulf Wendel <uw@php.net> | +----------------------------------------------------------------------+ $Id$ */ -/* A little hack to prevent build break, when mysql is used together with - * c-client, which also defines LIST. - */ -#ifdef LIST -#undef LIST -#endif - -#include <mysql.h> - -/* character set support */ -#if MYSQL_VERSION_ID > 50009 -#define HAVE_MYSQLI_GET_CHARSET -#endif - -#if (MYSQL_VERSION_ID > 40112 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID > 50005 -#define HAVE_MYSQLI_SET_CHARSET -#endif - - -#include <errmsg.h> - #ifndef PHP_MYSQLI_H #define PHP_MYSQLI_H -#define MYSQLI_VERSION_ID 101009 - -enum mysqli_status { - MYSQLI_STATUS_UNKNOWN=0, - MYSQLI_STATUS_CLEARED, - MYSQLI_STATUS_INITIALIZED, - MYSQLI_STATUS_VALID -}; - -typedef struct { - ulong buflen; - char *val; - ulong type; -} VAR_BUFFER; - -typedef struct { - unsigned int var_cnt; - VAR_BUFFER *buf; - zval **vars; - char *is_null; -} BIND_BUFFER; - -typedef struct { - MYSQL_STMT *stmt; - BIND_BUFFER param; - BIND_BUFFER result; - char *query; -} MY_STMT; - -typedef struct { - MYSQL *mysql; - zval *li_read; - php_stream *li_stream; - unsigned int multi_query; -} MY_MYSQL; - -typedef struct { - int mode; - int socket; - FILE *fp; -} PROFILER; - -typedef struct { - void *ptr; /* resource: (mysql, result, stmt) */ - void *info; /* additional buffer */ - enum mysqli_status status; -} MYSQLI_RESOURCE; - -typedef struct _mysqli_object { - zend_object zo; - void *ptr; - HashTable *prop_handler; -} mysqli_object; /* extends zend_object */ - -typedef struct { - char *reason; - char sqlstate[6]; - int errorno; - void *next; -} MYSQLI_WARNING; - -typedef struct _mysqli_property_entry { - char *pname; - int (*r_func)(mysqli_object *obj, zval **retval TSRMLS_DC); - int (*w_func)(mysqli_object *obj, zval *value TSRMLS_DC); -} mysqli_property_entry; - -typedef struct { - char error_msg[LOCAL_INFILE_ERROR_LEN]; - void *userdata; -} mysqli_local_infile; - -#define phpext_mysqli_ptr &mysqli_module_entry - -#ifdef PHP_WIN32 -#define PHP_MYSQLI_API __declspec(dllexport) -#define MYSQLI_LLU_SPEC "%I64u" -#define MYSQLI_LL_SPEC "%I64d" -#else -#define PHP_MYSQLI_API -#define MYSQLI_LLU_SPEC "%llu" -#define MYSQLI_LL_SPEC "%lld" -#endif - -#ifdef ZTS -#include "TSRM.h" -#endif - -#define PHP_MYSQLI_EXPORT(__type) PHP_MYSQLI_API __type - -extern zend_module_entry mysqli_module_entry; -extern const zend_function_entry mysqli_functions[]; -extern const zend_function_entry mysqli_link_methods[]; -extern const zend_function_entry mysqli_stmt_methods[]; -extern const zend_function_entry mysqli_result_methods[]; -extern const zend_function_entry mysqli_driver_methods[]; -extern const zend_function_entry mysqli_warning_methods[]; -extern const zend_function_entry mysqli_exception_methods[]; - -extern mysqli_property_entry mysqli_link_property_entries[]; -extern mysqli_property_entry mysqli_result_property_entries[]; -extern mysqli_property_entry mysqli_stmt_property_entries[]; -extern mysqli_property_entry mysqli_driver_property_entries[]; -extern mysqli_property_entry mysqli_warning_property_entries[]; - -extern void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flag, int into_object); -extern void php_clear_stmt_bind(MY_STMT *stmt); -extern void php_clear_mysql(MY_MYSQL *); -extern MYSQLI_WARNING *php_get_warnings(MYSQL *mysql); -extern void php_clear_warnings(MYSQLI_WARNING *w); -extern void php_free_stmt_bind_buffer(BIND_BUFFER bbuf, int type); -extern void php_mysqli_report_error(char *sqlstate, int errorno, char *error TSRMLS_DC); -extern void php_mysqli_report_index(char *query, unsigned int status TSRMLS_DC); -extern int php_local_infile_init(void **, const char *, void *); -extern int php_local_infile_read(void *, char *, uint); -extern void php_local_infile_end(void *); -extern int php_local_infile_error(void *, char *, uint); -extern void php_set_local_infile_handler_default(MY_MYSQL *); -extern void php_mysqli_throw_sql_exception(char *sqlstate, int errorno TSRMLS_DC, char *format, ...); -extern zend_class_entry *mysqli_link_class_entry; -extern zend_class_entry *mysqli_stmt_class_entry; -extern zend_class_entry *mysqli_result_class_entry; -extern zend_class_entry *mysqli_driver_class_entry; -extern zend_class_entry *mysqli_warning_class_entry; -extern zend_class_entry *mysqli_exception_class_entry; - -#ifdef HAVE_SPL -extern PHPAPI zend_class_entry *spl_ce_RuntimeException; -#endif - -PHP_MYSQLI_EXPORT(zend_object_value) mysqli_objects_new(zend_class_entry * TSRMLS_DC); - -#define MYSQLI_DISABLE_MQ if (mysql->multi_query) { \ - mysql_set_server_option(mysql->mysql, MYSQL_OPTION_MULTI_STATEMENTS_OFF); \ - mysql->multi_query = 0; \ -} - -#define MYSQLI_ENABLE_MQ if (!mysql->multi_query) { \ - mysql_set_server_option(mysql->mysql, MYSQL_OPTION_MULTI_STATEMENTS_ON); \ - mysql->multi_query = 1; \ -} - -#define REGISTER_MYSQLI_CLASS_ENTRY(name, mysqli_entry, class_functions) { \ - zend_class_entry ce; \ - INIT_CLASS_ENTRY(ce, name,class_functions); \ - ce.create_object = mysqli_objects_new; \ - mysqli_entry = zend_register_internal_class(&ce TSRMLS_CC); \ -} \ - -#define MYSQLI_REGISTER_RESOURCE_EX(__ptr, __zval) \ - ((mysqli_object *) zend_object_store_get_object(__zval TSRMLS_CC))->ptr = __ptr; \ - -#define MYSQLI_RETURN_RESOURCE(__ptr, __ce) \ - Z_TYPE_P(return_value) = IS_OBJECT; \ - (return_value)->value.obj = mysqli_objects_new(__ce TSRMLS_CC); \ - MYSQLI_REGISTER_RESOURCE_EX(__ptr, return_value) - -#define MYSQLI_REGISTER_RESOURCE(__ptr, __ce) \ -{\ - zval *object = getThis();\ - if (!object || !instanceof_function(Z_OBJCE_P(object), mysqli_link_class_entry TSRMLS_CC)) {\ - object = return_value;\ - Z_TYPE_P(object) = IS_OBJECT;\ - (object)->value.obj = mysqli_objects_new(__ce TSRMLS_CC);\ - }\ - MYSQLI_REGISTER_RESOURCE_EX(__ptr, object)\ -} - -#define MYSQLI_FETCH_RESOURCE(__ptr, __type, __id, __name, __check) \ -{ \ - MYSQLI_RESOURCE *my_res; \ - mysqli_object *intern = (mysqli_object *)zend_object_store_get_object(*(__id) TSRMLS_CC);\ - if (!(my_res = (MYSQLI_RESOURCE *)intern->ptr)) {\ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't fetch %s", intern->zo.ce->name);\ - RETURN_NULL();\ - }\ - __ptr = (__type)my_res->ptr; \ - if (__check && my_res->status < __check) { \ - php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid object or resource %s\n", intern->zo.ce->name); \ - RETURN_NULL();\ - }\ -} - -#define MYSQLI_SET_STATUS(__id, __value) \ -{ \ - mysqli_object *intern = (mysqli_object *)zend_object_store_get_object(*(__id) TSRMLS_CC);\ - ((MYSQLI_RESOURCE *)intern->ptr)->status = __value; \ -} \ - -#define MYSQLI_CLEAR_RESOURCE(__id) \ -{ \ - mysqli_object *intern = (mysqli_object *)zend_object_store_get_object(*(__id) TSRMLS_CC);\ - efree(intern->ptr); \ - intern->ptr = NULL; \ -} - -#define MYSQLI_RETURN_LONG_LONG(__val) \ -{ \ - if ((__val) < LONG_MAX) { \ - RETURN_LONG((long) (__val)); \ - } else { \ - char *ret; \ - /* always used with my_ulonglong -> %llu */ \ - int l = spprintf(&ret, 0, "%llu", (__val)); \ - RETURN_STRINGL(ret, l, 0); \ - } \ -} - -#define MYSQLI_ADD_PROPERTIES(a,b) \ -{ \ - int i = 0; \ - while (b[i].pname != NULL) { \ - mysqli_add_property(a, b[i].pname, (mysqli_read_t)b[i].r_func, (mysqli_write_t)b[i].w_func TSRMLS_CC); \ - i++; \ - }\ -} - -#if WIN32|WINNT -#define SCLOSE(a) closesocket(a) -#else -#define SCLOSE(a) close(a) -#endif - -#define MYSQLI_STORE_RESULT 0 -#define MYSQLI_USE_RESULT 1 - -/* for mysqli_fetch_assoc */ -#define MYSQLI_ASSOC 1 -#define MYSQLI_NUM 2 -#define MYSQLI_BOTH 3 - -/* for mysqli_bind_param */ -#define MYSQLI_BIND_INT 1 -#define MYSQLI_BIND_DOUBLE 2 -#define MYSQLI_BIND_STRING 3 -#define MYSQLI_BIND_SEND_DATA 4 - -/* fetch types */ -#define FETCH_SIMPLE 1 -#define FETCH_RESULT 2 - -/*** REPORT MODES ***/ -#define MYSQLI_REPORT_OFF 0 -#define MYSQLI_REPORT_ERROR 1 -#define MYSQLI_REPORT_STRICT 2 -#define MYSQLI_REPORT_INDEX 4 -#define MYSQLI_REPORT_CLOSE 8 -#define MYSQLI_REPORT_ALL 255 - -#define MYSQLI_REPORT_MYSQL_ERROR(mysql) \ -if ((MyG(report_mode) & MYSQLI_REPORT_ERROR) && mysql->net.last_errno) { \ - php_mysqli_report_error(mysql->net.sqlstate, mysql->net.last_errno, mysql->net.last_error TSRMLS_CC); \ -} - -#define MYSQLI_REPORT_STMT_ERROR(stmt) \ -if ((MyG(report_mode) & MYSQLI_REPORT_ERROR) && stmt->last_errno) { \ - php_mysqli_report_error(stmt->sqlstate, stmt->last_errno, stmt->last_error TSRMLS_CC); \ -} - -PHP_MYSQLI_API void mysqli_register_link(zval *return_value, void *link TSRMLS_DC); -PHP_MYSQLI_API void mysqli_register_stmt(zval *return_value, void *stmt TSRMLS_DC); -PHP_MYSQLI_API void mysqli_register_result(zval *return_value, void *result TSRMLS_DC); -PHP_MYSQLI_API void php_mysqli_set_error(long mysql_errno, char *mysql_err TSRMLS_DC); PHP_MINIT_FUNCTION(mysqli); PHP_MSHUTDOWN_FUNCTION(mysqli); @@ -317,9 +35,7 @@ PHP_FUNCTION(mysqli_affected_rows); PHP_FUNCTION(mysqli_autocommit); PHP_FUNCTION(mysqli_change_user); PHP_FUNCTION(mysqli_character_set_name); -#ifdef HAVE_MYSQLI_SET_CHARSET PHP_FUNCTION(mysqli_set_charset); -#endif PHP_FUNCTION(mysqli_close); PHP_FUNCTION(mysqli_commit); PHP_FUNCTION(mysqli_connect); @@ -334,6 +50,7 @@ PHP_FUNCTION(mysqli_enable_reads_from_master); PHP_FUNCTION(mysqli_enable_rpl_parse); PHP_FUNCTION(mysqli_errno); PHP_FUNCTION(mysqli_error); +PHP_FUNCTION(mysqli_fetch_all); PHP_FUNCTION(mysqli_fetch_array); PHP_FUNCTION(mysqli_fetch_assoc); PHP_FUNCTION(mysqli_fetch_object); @@ -346,9 +63,10 @@ PHP_FUNCTION(mysqli_field_count); PHP_FUNCTION(mysqli_field_seek); PHP_FUNCTION(mysqli_field_tell); PHP_FUNCTION(mysqli_free_result); -#ifdef HAVE_MYSQLI_GET_CHARSET +PHP_FUNCTION(mysqli_get_cache_stats); +PHP_FUNCTION(mysqli_get_client_stats); +PHP_FUNCTION(mysqli_get_connection_stats); PHP_FUNCTION(mysqli_get_charset); -#endif PHP_FUNCTION(mysqli_get_client_info); PHP_FUNCTION(mysqli_get_client_version); PHP_FUNCTION(mysqli_get_host_info); @@ -408,6 +126,7 @@ PHP_FUNCTION(mysqli_stmt_data_seek); PHP_FUNCTION(mysqli_stmt_errno); PHP_FUNCTION(mysqli_stmt_error); PHP_FUNCTION(mysqli_stmt_free_result); +PHP_FUNCTION(mysqli_stmt_get_result); PHP_FUNCTION(mysqli_stmt_get_warnings); PHP_FUNCTION(mysqli_stmt_reset); PHP_FUNCTION(mysqli_stmt_insert_id); @@ -425,53 +144,11 @@ ZEND_FUNCTION(mysqli_result_construct); ZEND_FUNCTION(mysqli_driver_construct); ZEND_METHOD(mysqli_warning,__construct); -ZEND_BEGIN_MODULE_GLOBALS(mysqli) - long default_link; - long num_links; - long max_links; - unsigned int default_port; - char *default_host; - char *default_user; - char *default_socket; - char *default_pw; - int reconnect; - int strict; - long error_no; - char *error_msg; - int report_mode; - HashTable *report_ht; - unsigned int multi_query; - unsigned int embedded; -ZEND_END_MODULE_GLOBALS(mysqli) - - -#define MYSQLI_PROPERTY(a) extern int a(mysqli_object *obj, zval **retval TSRMLS_DC) - -MYSQLI_PROPERTY(my_prop_link_host); - -#ifdef ZTS -#define MyG(v) TSRMG(mysqli_globals_id, zend_mysqli_globals *, v) -#else -#define MyG(v) (mysqli_globals.v) -#endif - -#define my_estrdup(x) (x) ? estrdup(x) : NULL -#define my_efree(x) if (x) efree(x) - -#ifdef PHP_WIN32 -#define L64(x) x##i64 -typedef __int64 my_longlong; -#else -#define L64(x) x##LL -typedef long long my_longlong; -#endif - - -ZEND_EXTERN_MODULE_GLOBALS(mysqli) +#define phpext_mysqli_ptr &mysqli_module_entry +extern zend_module_entry mysqli_module_entry; #endif /* PHP_MYSQLI.H */ - /* * Local variables: * tab-width: 4 diff --git a/ext/mysqli/php_mysqli_structs.h b/ext/mysqli/php_mysqli_structs.h new file mode 100644 index 0000000000..5c6e23f4f3 --- /dev/null +++ b/ext/mysqli/php_mysqli_structs.h @@ -0,0 +1,396 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Georg Richter <georg@php.net> | + +----------------------------------------------------------------------+ + + $Id$ +*/ + +#ifndef PHP_MYSQLI_STRUCTS_H +#define PHP_MYSQLI_STRUCTS_H + +/* A little hack to prevent build break, when mysql is used together with + * c-client, which also defines LIST. + */ +#ifdef LIST +#undef LIST +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifdef HAVE_MYSQLND +#include "ext/mysqlnd/mysqlnd.h" +#include "ext/mysqli/mysqli_mysqlnd.h" +#else +#include <mysql.h> +#include <errmsg.h> +#include "ext/mysqli/mysqli_libmysql.h" +#endif + +#include "php_mysqli.h" + +/* character set support */ +#if defined(MYSQLND_VERSION_ID) || MYSQL_VERSION_ID > 50009 +#define HAVE_MYSQLI_GET_CHARSET +#endif + +#if defined(MYSQLND_VERSION_ID) || (MYSQL_VERSION_ID > 40112 && MYSQL_VERSION_ID < 50000) || MYSQL_VERSION_ID > 50005 +#define HAVE_MYSQLI_SET_CHARSET +#endif + +#define MYSQLI_VERSION_ID 101009 + +enum mysqli_status { + MYSQLI_STATUS_UNKNOWN=0, + MYSQLI_STATUS_CLEARED, + MYSQLI_STATUS_INITIALIZED, + MYSQLI_STATUS_VALID +}; + +typedef struct { + ulong buflen; + char *val; + ulong type; +} VAR_BUFFER; + +typedef struct { + unsigned int var_cnt; + VAR_BUFFER *buf; + zval **vars; + char *is_null; +} BIND_BUFFER; + +typedef struct { + MYSQL_STMT *stmt; + BIND_BUFFER param; + BIND_BUFFER result; + char *query; +} MY_STMT; + +typedef struct { + MYSQL *mysql; + char *hash_key; + zval *li_read; + php_stream *li_stream; + zend_bool persistent; + unsigned long hash_index; /* Used when persistent, hold the index in plist->used_links */ + unsigned int multi_query; +} MY_MYSQL; + +typedef struct { + int mode; + int socket; + FILE *fp; +} PROFILER; + +typedef struct { + void *ptr; /* resource: (mysql, result, stmt) */ + void *info; /* additional buffer */ + enum mysqli_status status; /* object status */ +} MYSQLI_RESOURCE; + +typedef struct _mysqli_object { + zend_object zo; + void *ptr; + HashTable *prop_handler; +} mysqli_object; /* extends zend_object */ + +typedef struct st_mysqli_warning MYSQLI_WARNING; + +struct st_mysqli_warning { + zval reason; + zval sqlstate; + int errorno; + MYSQLI_WARNING *next; +}; + +typedef struct _mysqli_property_entry { + char *pname; + int (*r_func)(mysqli_object *obj, zval **retval TSRMLS_DC); + int (*w_func)(mysqli_object *obj, zval *value TSRMLS_DC); +} mysqli_property_entry; + +#if !defined(HAVE_MYSQLND) +typedef struct { + char error_msg[LOCAL_INFILE_ERROR_LEN]; + void *userdata; +} mysqli_local_infile; +#endif + +typedef struct { + HashTable free_links; + HashTable used_links; +} mysqli_plist_entry; + +#ifdef PHP_WIN32 +#define PHP_MYSQLI_API __declspec(dllexport) +#define MYSQLI_LLU_SPEC "%I64u" +#define MYSQLI_LL_SPEC "%I64d" +#define L64(x) x##i64 +typedef __int64 my_longlong; +#else +#define PHP_MYSQLI_API +#define MYSQLI_LLU_SPEC "%llu" +#define MYSQLI_LL_SPEC "%lld" +#define L64(x) x##LL +typedef long long my_longlong; +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#define PHP_MYSQLI_EXPORT(__type) PHP_MYSQLI_API __type + +extern const zend_function_entry mysqli_functions[]; +extern const zend_function_entry mysqli_link_methods[]; +extern const zend_function_entry mysqli_stmt_methods[]; +extern const zend_function_entry mysqli_result_methods[]; +extern const zend_function_entry mysqli_driver_methods[]; +extern const zend_function_entry mysqli_warning_methods[]; +extern const zend_function_entry mysqli_exception_methods[]; + +extern const mysqli_property_entry mysqli_link_property_entries[]; +extern const mysqli_property_entry mysqli_result_property_entries[]; +extern const mysqli_property_entry mysqli_stmt_property_entries[]; +extern const mysqli_property_entry mysqli_driver_property_entries[]; +extern const mysqli_property_entry mysqli_warning_property_entries[]; + +#ifdef HAVE_MYSQLND +extern MYSQLND_ZVAL_PCACHE *mysqli_mysqlnd_zval_cache; +extern MYSQLND_QCACHE *mysqli_mysqlnd_qcache; +#endif + +extern void php_mysqli_fetch_into_hash(INTERNAL_FUNCTION_PARAMETERS, int override_flag, int into_object); +extern void php_clear_stmt_bind(MY_STMT *stmt TSRMLS_DC); +extern void php_clear_mysql(MY_MYSQL *); +extern MYSQLI_WARNING *php_get_warnings(MYSQL *mysql TSRMLS_DC); +extern void php_clear_warnings(MYSQLI_WARNING *w); +extern void php_free_stmt_bind_buffer(BIND_BUFFER bbuf, int type); +extern void php_mysqli_report_error(const char *sqlstate, int errorno, const char *error TSRMLS_DC); +extern void php_mysqli_report_index(const char *query, unsigned int status TSRMLS_DC); +extern int php_local_infile_init(void **, const char *, void *); +extern int php_local_infile_read(void *, char *, uint); +extern void php_local_infile_end(void *); +extern int php_local_infile_error(void *, char *, uint); +extern void php_set_local_infile_handler_default(MY_MYSQL *); +extern void php_mysqli_throw_sql_exception(char *sqlstate, int errorno TSRMLS_DC, char *format, ...); +extern zend_class_entry *mysqli_link_class_entry; +extern zend_class_entry *mysqli_stmt_class_entry; +extern zend_class_entry *mysqli_result_class_entry; +extern zend_class_entry *mysqli_driver_class_entry; +extern zend_class_entry *mysqli_warning_class_entry; +extern zend_class_entry *mysqli_exception_class_entry; +extern int php_le_pmysqli(void); +extern void php_mysqli_dtor_p_elements(void *data); + +#ifdef HAVE_SPL +extern PHPAPI zend_class_entry *spl_ce_RuntimeException; +#endif + +PHP_MYSQLI_EXPORT(zend_object_value) mysqli_objects_new(zend_class_entry * TSRMLS_DC); + +#define MYSQLI_DISABLE_MQ if (mysql->multi_query) { \ + mysql_set_server_option(mysql->mysql, MYSQL_OPTION_MULTI_STATEMENTS_OFF); \ + mysql->multi_query = 0; \ +} + +#define MYSQLI_ENABLE_MQ if (!mysql->multi_query) { \ + mysql_set_server_option(mysql->mysql, MYSQL_OPTION_MULTI_STATEMENTS_ON); \ + mysql->multi_query = 1; \ +} + +#define REGISTER_MYSQLI_CLASS_ENTRY(name, mysqli_entry, class_functions) { \ + zend_class_entry ce; \ + INIT_CLASS_ENTRY(ce, name,class_functions); \ + ce.create_object = mysqli_objects_new; \ + mysqli_entry = zend_register_internal_class(&ce TSRMLS_CC); \ +} \ + +#define MYSQLI_REGISTER_RESOURCE_EX(__ptr, __zval) \ + ((mysqli_object *) zend_object_store_get_object(__zval TSRMLS_CC))->ptr = __ptr; + +#define MYSQLI_RETURN_RESOURCE(__ptr, __ce) \ + Z_TYPE_P(return_value) = IS_OBJECT; \ + (return_value)->value.obj = mysqli_objects_new(__ce TSRMLS_CC); \ + MYSQLI_REGISTER_RESOURCE_EX(__ptr, return_value) + +#define MYSQLI_REGISTER_RESOURCE(__ptr, __ce) \ +{\ + zval *object = getThis();\ + if (!object || !instanceof_function(Z_OBJCE_P(object), mysqli_link_class_entry TSRMLS_CC)) {\ + object = return_value;\ + Z_TYPE_P(object) = IS_OBJECT;\ + (object)->value.obj = mysqli_objects_new(__ce TSRMLS_CC);\ + }\ + MYSQLI_REGISTER_RESOURCE_EX(__ptr, object)\ +} + +#define MYSQLI_FETCH_RESOURCE(__ptr, __type, __id, __name, __check) \ +{ \ + MYSQLI_RESOURCE *my_res; \ + mysqli_object *intern = (mysqli_object *)zend_object_store_get_object(*(__id) TSRMLS_CC);\ + if (!(my_res = (MYSQLI_RESOURCE *)intern->ptr)) {\ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Couldn't fetch %s", intern->zo.ce->name);\ + RETURN_NULL();\ + }\ + __ptr = (__type)my_res->ptr; \ + if (__check && my_res->status < __check) { \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid object or resource %s\n", intern->zo.ce->name); \ + RETURN_NULL();\ + }\ +} + +#define MYSQLI_SET_STATUS(__id, __value) \ +{ \ + mysqli_object *intern = (mysqli_object *)zend_object_store_get_object(*(__id) TSRMLS_CC);\ + ((MYSQLI_RESOURCE *)intern->ptr)->status = __value; \ +} \ + +#define MYSQLI_CLEAR_RESOURCE(__id) \ +{ \ + mysqli_object *intern = (mysqli_object *)zend_object_store_get_object(*(__id) TSRMLS_CC);\ + efree(intern->ptr); \ + intern->ptr = NULL; \ +} + +#define MYSQLI_RETURN_LONG_LONG(__val) \ +{ \ + if ((__val) < LONG_MAX) { \ + RETURN_LONG((__val)); \ + } else { \ + char *ret; \ + int l = spprintf(&ret, 0, "%llu", (__val)); \ + RETURN_STRINGL(ret, l, 0); \ + } \ +} + +#define MYSQLI_ADD_PROPERTIES(a,b) \ +{ \ + int i = 0; \ + while (b[i].pname != NULL) { \ + mysqli_add_property(a, b[i].pname, (mysqli_read_t)b[i].r_func, (mysqli_write_t)b[i].w_func TSRMLS_CC); \ + i++; \ + }\ +} + +#if WIN32|WINNT +#define SCLOSE(a) closesocket(a) +#else +#define SCLOSE(a) close(a) +#endif + +#define MYSQLI_STORE_RESULT 0 +#define MYSQLI_USE_RESULT 1 + +/* for mysqli_fetch_assoc */ +#define MYSQLI_ASSOC 1 +#define MYSQLI_NUM 2 +#define MYSQLI_BOTH 3 + +/* for mysqli_bind_param */ +#define MYSQLI_BIND_INT 1 +#define MYSQLI_BIND_DOUBLE 2 +#define MYSQLI_BIND_STRING 3 +#define MYSQLI_BIND_SEND_DATA 4 + +/* fetch types */ +#define FETCH_SIMPLE 1 +#define FETCH_RESULT 2 + +/*** REPORT MODES ***/ +#define MYSQLI_REPORT_OFF 0 +#define MYSQLI_REPORT_ERROR 1 +#define MYSQLI_REPORT_STRICT 2 +#define MYSQLI_REPORT_INDEX 4 +#define MYSQLI_REPORT_CLOSE 8 +#define MYSQLI_REPORT_ALL 255 + +#define MYSQLI_REPORT_MYSQL_ERROR(mysql) \ +if ((MyG(report_mode) & MYSQLI_REPORT_ERROR) && mysql_errno(mysql)) { \ + php_mysqli_report_error(mysql_sqlstate(mysql), mysql_errno(mysql), mysql_error(mysql) TSRMLS_CC); \ +} + +#define MYSQLI_REPORT_STMT_ERROR(stmt) \ +if ((MyG(report_mode) & MYSQLI_REPORT_ERROR) && mysql_stmt_errno(stmt)) { \ + php_mysqli_report_error(mysql_stmt_sqlstate(stmt), mysql_stmt_errno(stmt), mysql_stmt_error(stmt) TSRMLS_CC); \ +} + +PHP_MYSQLI_API void mysqli_register_link(zval *return_value, void *link TSRMLS_DC); +PHP_MYSQLI_API void mysqli_register_stmt(zval *return_value, void *stmt TSRMLS_DC); +PHP_MYSQLI_API void mysqli_register_result(zval *return_value, void *result TSRMLS_DC); +PHP_MYSQLI_API void php_mysqli_set_error(long mysql_errno, char *mysql_err TSRMLS_DC); + +ZEND_BEGIN_MODULE_GLOBALS(mysqli) + long default_link; + long num_links; + long max_links; + long num_active_persistent; + long num_inactive_persistent; + long max_persistent; + long allow_persistent; + long cache_size; + unsigned long default_port; + char *default_host; + char *default_user; + char *default_socket; + char *default_pw; + long reconnect; + long allow_local_infile; + long strict; + long error_no; + char *error_msg; + long report_mode; + HashTable *report_ht; + unsigned long multi_query; + unsigned long embedded; +#ifdef HAVE_MYSQLND + MYSQLND_THD_ZVAL_PCACHE *mysqlnd_thd_zval_cache; +#endif +ZEND_END_MODULE_GLOBALS(mysqli) + +#define MYSQLI_PROPERTY(a) extern int a(mysqli_object *obj, zval **retval TSRMLS_DC) + +MYSQLI_PROPERTY(my_prop_link_host); + +#ifdef ZTS +#define MyG(v) TSRMG(mysqli_globals_id, zend_mysqli_globals *, v) +#else +#define MyG(v) (mysqli_globals.v) +#endif + +#define my_estrdup(x) (x) ? estrdup(x) : NULL +#define my_efree(x) if (x) efree(x) + +ZEND_EXTERN_MODULE_GLOBALS(mysqli) + +#endif /* PHP_MYSQLI_STRUCTS.H */ + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * indent-tabs-mode: t + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/mysqlnd/CREDITS b/ext/mysqlnd/CREDITS new file mode 100644 index 0000000000..bac8d1a4a2 --- /dev/null +++ b/ext/mysqlnd/CREDITS @@ -0,0 +1,2 @@ +MySQLnd +Georg Richter, Andrey Hristov, Ulf Wendel diff --git a/ext/mysqlnd/config-win.h b/ext/mysqlnd/config-win.h new file mode 100644 index 0000000000..86018221a6 --- /dev/null +++ b/ext/mysqlnd/config-win.h @@ -0,0 +1,98 @@ +/* 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 */ + +#include <sys/locking.h> +#include <windows.h> +#include <math.h> /* Because of rint() */ +#include <fcntl.h> +#include <io.h> +#include <malloc.h> + +#if defined(__NT__) +#define SYSTEM_TYPE "NT" +#elif defined(__WIN2000__) +#define SYSTEM_TYPE "WIN2000" +#else +#define SYSTEM_TYPE "Win95/Win98" +#endif + +#ifdef _WIN64 +#define MACHINE_TYPE "ia64" /* Define to machine type name */ +#else +#define MACHINE_TYPE "i32" /* Define to machine type name */ +#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 + +#define LONGLONG_MIN ((__int64) 0x8000000000000000) +#define LONGLONG_MAX ((__int64) 0x7FFFFFFFFFFFFFFF) +#define LL(A) ((__int64) A) + +/* Type information */ + +typedef unsigned short ushort; +typedef unsigned int uint; +typedef unsigned __int64 ulonglong; /* Microsofts 64 bit types */ +typedef __int64 longlong; +typedef int sigset_t; +#define longlong_defined + +#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 *) (A))) +#define sint3korr(A) ((int32) ((((uchar) (A)[2]) & 128) ? \ + (((uint32) 255L << 24) | \ + (((uint32) (uchar) (A)[2]) << 16) |\ + (((uint32) (uchar) (A)[1]) << 8) | \ + ((uint32) (uchar) (A)[0])) : \ + (((uint32) (uchar) (A)[2]) << 16) |\ + (((uint32) (uchar) (A)[1]) << 8) | \ + ((uint32) (uchar) (A)[0]))) +#define sint4korr(A) (*((long *) (A))) +#define uint2korr(A) (*((uint16 *) (A))) +#define uint3korr(A) (long) (*((unsigned long *) (A)) & 0xFFFFFF) +#define uint4korr(A) (*((unsigned long *) (A))) +#define uint5korr(A) ((ulonglong)(((uint32) ((uchar) (A)[0])) +\ + (((uint32) ((uchar) (A)[1])) << 8) +\ + (((uint32) ((uchar) (A)[2])) << 16) +\ + (((uint32) ((uchar) (A)[3])) << 24)) +\ + (((ulonglong) ((uchar) (A)[4])) << 32)) +#define uint8korr(A) (*((ulonglong *) (A))) +#define sint8korr(A) (*((longlong *) (A))) +#define int2store(T,A) *((uint16*) (T))= (uint16) (A) +#define int3store(T,A) { *(T)= (uchar) ((A));\ + *(T+1)=(uchar) (((uint) (A) >> 8));\ + *(T+2)=(uchar) (((A) >> 16)); } +#define int4store(T,A) *((long *) (T))= (long) (A) +#define int5store(T,A) { *(T)= (uchar)((A));\ + *((T)+1)=(uchar) (((A) >> 8));\ + *((T)+2)=(uchar) (((A) >> 16));\ + *((T)+3)=(uchar) (((A) >> 24)); \ + *((T)+4)=(uchar) (((A) >> 32)); } +#define int8store(T,A) *((ulonglong *) (T))= (ulonglong) (A) + +#define doubleget(V,M) { *((long *) &V) = *((long*) M); \ + *(((long *) &V)+1) = *(((long*) M)+1); } +#define doublestore(T,V) { *((long *) T) = *((long*) &V); \ + *(((long *) T)+1) = *(((long*) &V)+1); } +#define float4get(V,M) { *((long *) &(V)) = *((long*) (M)); } +#define float8get(V,M) doubleget((V),(M)) +#define float4store(V,M) memcpy((byte*) V,(byte*) (&M),sizeof(float)) +#define float8store(V,M) doublestore((V),(M)) + +#endif /* _WIN64 */ diff --git a/ext/mysqlnd/config.w32 b/ext/mysqlnd/config.w32 new file mode 100644 index 0000000000..99a702f1f0 --- /dev/null +++ b/ext/mysqlnd/config.w32 @@ -0,0 +1,20 @@ +// $Id$ +// vim:ft=javascript + +mysqld_source = ""; +if (CHECK_LIB("ws2_32.lib", "mysqlnd")) { + mysqlnd_source = + "mysqlnd.c " + + "mysqlnd_debug.c " + + "mysqlnd_charset.c " + + "mysqlnd_loaddata.c " + + "mysqlnd_palloc.c " + + "mysqlnd_ps.c " + + "mysqlnd_ps_codec.c " + + "mysqlnd_qcache.c " + + "mysqlnd_result.c " + + "mysqlnd_result_meta.c " + + "mysqlnd_statistics.c " + + "mysqlnd_wireprotocol.c"; + EXTENSION("mysqlnd", mysqlnd_source, false); +} diff --git a/ext/mysqlnd/config9.m4 b/ext/mysqlnd/config9.m4 new file mode 100644 index 0000000000..9d09ba71da --- /dev/null +++ b/ext/mysqlnd/config9.m4 @@ -0,0 +1,29 @@ +dnl +dnl $Id$ +dnl config.m4 for mysqlnd driver + +dnl If some extension uses mysqlnd it will get compiled in PHP core +if test "$PHP_MYSQLND_ENABLED" = "yes"; then + mysqlnd_sources="mysqlnd.c mysqlnd_charset.c mysqlnd_wireprotocol.c \ + mysqlnd_ps.c mysqlnd_loaddata.c mysqlnd_palloc.c \ + mysqlnd_ps_codec.c mysqlnd_statistics.c mysqlnd_qcache.c\ + mysqlnd_result.c mysqlnd_result_meta.c mysqlnd_debug.c" + + PHP_NEW_EXTENSION(mysqlnd, $mysqlnd_sources, no) + PHP_ADD_BUILD_DIR([ext/mysqlnd], 1) + PHP_INSTALL_HEADERS([ext/mysqlnd]) + PHP_INSTALL_HEADERS([$ext_builddir/php_mysqlnd_config.h]) + AC_DEFINE([HAVE_MYSQLND], 1, [Whether mysqlnd is enabled]) + + 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_builddir/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 0000000000..98b3ab7dc9 --- /dev/null +++ b/ext/mysqlnd/mysqlnd.c @@ -0,0 +1,2078 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_result.h" +#include "mysqlnd_statistics.h" +#include "mysqlnd_charset.h" +#include "mysqlnd_debug.h" +#include "php_ini.h" +#include "ext/standard/basic_functions.h" +#include "ext/standard/php_lcg.h" +#include "ext/standard/info.h" + +/* the server doesn't support 4byte utf8, but let's make it forward compatible */ +#define MYSQLND_MAX_ALLOWED_USER_LEN 256 /* 64 char * 4byte */ +#define MYSQLND_MAX_ALLOWED_DB_LEN 256 /* 64 char * 4byte */ +/* + TODO : + - Don't bind so tightly the metadata with the result set. This means + that the metadata reading should not expect a MYSQLND_RES pointer, it + does not need it, but return a pointer to the metadata (MYSQLND_FIELD *). + For normal statements we will then just assign it to a member of + MYSQLND_RES. For PS statements, it will stay as part of the statement + (MYSQLND_STMT) between prepare and execute. At execute the new metadata + will be sent by the server, so we will discard the old one and then + finally attach it to the result set. This will make the code more clean, + as a prepared statement won't have anymore stmt->result != NULL, as it + is now, just to have where to store the metadata. + + - Change mysqlnd_simple_command to accept a heap dynamic array of MYSQLND_STRING + terminated by a string with ptr being NULL. Thus, multi-part messages can be + sent to the network like writev() and this can save at least for + mysqlnd_stmt_send_long_data() new malloc. This change will probably make the + code in few other places cleaner. +*/ + +extern MYSQLND_CHARSET *mysqlnd_charsets; + + + + +const char * mysqlnd_server_gone = "MySQL server has gone away"; +const char * mysqlnd_out_of_sync = "Commands out of sync; you can't run this command now"; + +MYSQLND_STATS *mysqlnd_global_stats = NULL; +static zend_bool mysqlnd_library_initted = FALSE; + + +enum_func_status mysqlnd_send_close(MYSQLND * conn TSRMLS_DC); + +/* {{{ mysqlnd_library_init */ +static +void mysqlnd_library_init() +{ + if (mysqlnd_library_initted == FALSE) { + mysqlnd_library_initted = TRUE; + _mysqlnd_init_ps_subsystem(); + /* Should be calloc, as mnd_calloc will reference LOCK_access*/ + mysqlnd_global_stats = calloc(1, sizeof(MYSQLND_STATS)); +#ifdef ZTS + mysqlnd_global_stats->LOCK_access = tsrm_mutex_alloc(); +#endif + } +} +/* }}} */ + + +/* {{{ mysqlnd_library_end */ +static +void mysqlnd_library_end() +{ + if (mysqlnd_library_initted == TRUE) { +#ifdef ZTS + tsrm_mutex_free(mysqlnd_global_stats->LOCK_access); +#endif + /* mnd_free will reference LOCK_access and crash...*/ + free(mysqlnd_global_stats); + mysqlnd_global_stats = NULL; + mysqlnd_library_initted = FALSE; + } +} +/* }}} */ + + +/* {{{ mysqlnd_conn::free_contents */ +static void +MYSQLND_METHOD(mysqlnd_conn, free_contents)(MYSQLND *conn TSRMLS_DC) +{ + zend_bool pers = conn->persistent; + + DBG_ENTER("mysqlnd_conn::free_contents"); + + mysqlnd_local_infile_default(conn); + if (conn->current_result) { + conn->current_result->m.free_result_contents(conn->current_result TSRMLS_CC); + mnd_efree(conn->current_result); + conn->current_result = NULL; + } + + if (conn->net.stream) { + DBG_INF_FMT("Freeing stream. abstract=%p", conn->net.stream->abstract); + if (pers) { + php_stream_free(conn->net.stream, PHP_STREAM_FREE_CLOSE_PERSISTENT | PHP_STREAM_FREE_RSRC_DTOR); + } else { + php_stream_free(conn->net.stream, PHP_STREAM_FREE_CLOSE); + + } + conn->net.stream = NULL; + } + + DBG_INF("Freeing memory of members"); + if (conn->host) { + DBG_INF("Freeing host"); + mnd_pefree(conn->host, pers); + conn->host = NULL; + } + if (conn->user) { + DBG_INF("Freeing user"); + mnd_pefree(conn->user, pers); + conn->user = NULL; + } + if (conn->passwd) { + DBG_INF("Freeing passwd"); + mnd_pefree(conn->passwd, pers); + conn->passwd = NULL; + } + if (conn->unix_socket) { + DBG_INF("Freeing unix_socket"); + mnd_pefree(conn->unix_socket, pers); + conn->unix_socket = NULL; + } + if (conn->scheme) { + DBG_INF("Freeing scheme"); + mnd_pefree(conn->scheme, pers); + conn->scheme = NULL; + } + if (conn->server_version) { + DBG_INF("Freeing server_version"); + mnd_pefree(conn->server_version, pers); + conn->server_version = NULL; + } + if (conn->host_info) { + DBG_INF("Freeing host_info"); + mnd_pefree(conn->host_info, pers); + conn->host_info = NULL; + } + if (conn->scramble) { + DBG_INF("Freeing scramble"); + mnd_pefree(conn->scramble, pers); + conn->scramble = NULL; + } + if (conn->last_message) { + mnd_pefree(conn->last_message, pers); + conn->last_message = NULL; + } + if (conn->options.charset_name) { + mnd_pefree(conn->options.charset_name, pers); + conn->options.charset_name = NULL; + } + if (conn->options.num_commands) { + unsigned int i; + for (i=0; i < conn->options.num_commands; i++) { + mnd_pefree(conn->options.init_commands[i], pers); + } + mnd_pefree(conn->options.init_commands, pers); + conn->options.init_commands = NULL; + } + if (conn->options.cfg_file) { + mnd_pefree(conn->options.cfg_file, pers); + conn->options.cfg_file = NULL; + } + if (conn->options.cfg_section) { + mnd_pefree(conn->options.cfg_section, pers); + conn->options.cfg_section = NULL; + } + if (conn->options.ssl_key) { + mnd_pefree(conn->options.ssl_key, pers); + conn->options.ssl_key = NULL; + } + if (conn->options.ssl_cert) { + mnd_pefree(conn->options.ssl_cert, pers); + conn->options.ssl_cert = NULL; + } + if (conn->options.ssl_ca) { + mnd_pefree(conn->options.ssl_ca, pers); + conn->options.ssl_ca = NULL; + } + if (conn->options.ssl_capath) { + mnd_pefree(conn->options.ssl_capath, pers); + conn->options.ssl_capath = NULL; + } + if (conn->options.ssl_cipher) { + mnd_pefree(conn->options.ssl_cipher, pers); + conn->options.ssl_cipher = NULL; + } + if (conn->zval_cache) { + DBG_INF("Freeing zval cache reference"); + mysqlnd_palloc_free_thd_cache_reference(&conn->zval_cache); + conn->zval_cache = NULL; + } + if (conn->qcache) { + DBG_INF("Freeing qcache reference"); + mysqlnd_qcache_free_cache_reference(&conn->qcache); + conn->qcache = NULL; + } + if (conn->net.cmd_buffer.buffer) { + DBG_INF("Freeing cmd buffer"); + mnd_pefree(conn->net.cmd_buffer.buffer, pers); + conn->net.cmd_buffer.buffer = NULL; + } + conn->charset = NULL; + conn->greet_charset = NULL; + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::dtor */ +static void +MYSQLND_METHOD_PRIVATE(mysqlnd_conn, dtor)(MYSQLND *conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn::dtor"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + conn->m->free_contents(conn TSRMLS_CC); + + mnd_pefree(conn, conn->persistent); + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_simple_command_handle_response */ +enum_func_status +mysqlnd_simple_command_handle_response(MYSQLND *conn, enum php_mysql_packet_type ok_packet, + zend_bool silent, enum php_mysqlnd_server_command command + TSRMLS_DC) +{ + enum_func_status ret; + + DBG_ENTER("mysqlnd_simple_command_handle_response"); + DBG_INF_FMT("silent=%d packet=%d command=%s", silent, ok_packet, mysqlnd_command_to_text[command]); + + switch (ok_packet) { + case PROT_OK_PACKET:{ + php_mysql_packet_ok ok_response; + PACKET_INIT_ALLOCA(ok_response, PROT_OK_PACKET); + if (FAIL == (ret = PACKET_READ_ALLOCA(ok_response, conn))) { + if (!silent) { + DBG_ERR_FMT("Error while reading %s's OK packet", mysqlnd_command_to_text[command]); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading %s's OK packet. PID=%d", + mysqlnd_command_to_text[command], getpid()); + } + } else { + DBG_INF_FMT("OK from server"); + if (0xFF == ok_response.field_count) { + /* The server signalled error. Set the error */ + SET_CLIENT_ERROR(conn->error_info, ok_response.error_no, + ok_response.sqlstate, ok_response.error); + ret = FAIL; + /* + Cover a protocol design error: error packet does not + contain the server status. Therefore, the client has no way + to find out whether there are more result sets of + a multiple-result-set statement pending. Luckily, in 5.0 an + error always aborts execution of a statement, wherever it is + a multi-statement or a stored procedure, so it should be + safe to unconditionally turn off the flag here. + */ + conn->upsert_status.server_status &= ~SERVER_MORE_RESULTS_EXISTS; + SET_ERROR_AFF_ROWS(conn); + } else { + SET_NEW_MESSAGE(conn->last_message, conn->last_message_len, + ok_response.message, ok_response.message_len, + conn->persistent); + + conn->upsert_status.warning_count = ok_response.warning_count; + conn->upsert_status.server_status = ok_response.server_status; + conn->upsert_status.affected_rows = ok_response.affected_rows; + conn->upsert_status.last_insert_id = ok_response.last_insert_id; + } + } + PACKET_FREE_ALLOCA(ok_response); + break; + } + case PROT_EOF_PACKET:{ + php_mysql_packet_eof ok_response; + PACKET_INIT_ALLOCA(ok_response, PROT_EOF_PACKET); + if (FAIL == (ret = PACKET_READ_ALLOCA(ok_response, conn))) { + SET_CLIENT_ERROR(conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, + "Malformed packet"); + if (!silent) { + DBG_ERR_FMT("Error while reading %s's EOF packet", mysqlnd_command_to_text[command]); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading %s's EOF packet. PID=%d", + mysqlnd_command_to_text[command], getpid()); + } + } else if (0xFF == ok_response.field_count) { + /* The server signalled error. Set the error */ + SET_CLIENT_ERROR(conn->error_info, ok_response.error_no, + ok_response.sqlstate, ok_response.error); + SET_ERROR_AFF_ROWS(conn); + } else if (0xFE != ok_response.field_count) { + SET_CLIENT_ERROR(conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, + "Malformed packet"); + if (!silent) { + DBG_ERR_FMT("EOF packet expected, field count wasn't 0xFE but 0x%2X", ok_response.field_count); + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "EOF packet expected, field count wasn't 0xFE but 0x%2X", + ok_response.field_count); + } + } else { + DBG_INF_FMT("OK from server"); + } + PACKET_FREE_ALLOCA(ok_response); + break; + } + default: + ret = FAIL; + SET_CLIENT_ERROR(conn->error_info, CR_MALFORMED_PACKET, UNKNOWN_SQLSTATE, + "Malformed packet"); + php_error_docref(NULL TSRMLS_CC, E_ERROR, "Wrong response packet %d passed to the function", + ok_packet); + break; + } + DBG_INF(ret == PASS ? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_simple_command */ +enum_func_status +mysqlnd_simple_command(MYSQLND *conn, enum php_mysqlnd_server_command command, + const char * const arg, size_t arg_len, + enum php_mysql_packet_type ok_packet, zend_bool silent TSRMLS_DC) +{ + enum_func_status ret = PASS; + php_mysql_packet_command cmd_packet; + + DBG_ENTER("mysqlnd_simple_command"); + DBG_INF_FMT("command=%s ok_packet=%d silent=%d", mysqlnd_command_to_text[command], ok_packet, silent); + + switch (conn->state) { + case CONN_READY: + break; + case CONN_QUIT_SENT: + SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); + DBG_ERR("Server is gone"); + DBG_RETURN(FAIL); + default: + SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, + mysqlnd_out_of_sync); + DBG_ERR("Command out of sync"); + DBG_RETURN(FAIL); + } + + /* clean UPSERT info */ + memset(&conn->upsert_status, 0, sizeof(conn->upsert_status)); + SET_ERROR_AFF_ROWS(conn); + SET_EMPTY_ERROR(conn->error_info); + + PACKET_INIT_ALLOCA(cmd_packet, PROT_CMD_PACKET); + cmd_packet.command = command; + if (arg && arg_len) { + cmd_packet.argument = arg; + cmd_packet.arg_len = arg_len; + } + + if (! PACKET_WRITE_ALLOCA(cmd_packet, conn)) { + if (!silent) { + DBG_ERR_FMT("Error while sending %s packet", mysqlnd_command_to_text[command]); + php_error(E_WARNING, "Error while sending %s packet. PID=%d", mysqlnd_command_to_text[command], getpid()); + } + DBG_ERR("Server is gone"); + ret = FAIL; + } else if (ok_packet != PROT_LAST) { + ret = mysqlnd_simple_command_handle_response(conn, ok_packet, silent, command TSRMLS_CC); + } + + /* + There is no need to call FREE_ALLOCA on cmd_packet as the + only allocated string is cmd_packet.argument and it was passed + to us. We should not free it. + */ + + DBG_INF(ret == PASS ? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::set_server_option */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, set_server_option)(MYSQLND * const conn, + enum_mysqlnd_server_option option TSRMLS_DC) +{ + enum_func_status ret; + char buffer[2]; + DBG_ENTER("mysqlnd_conn::set_server_option"); + + int2store(buffer, (uint) option); + ret = mysqlnd_simple_command(conn, COM_SET_OPTION, buffer, sizeof(buffer), + PROT_EOF_PACKET, FALSE TSRMLS_CC); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ _mysqlnd_restart_psession */ +PHPAPI void _mysqlnd_restart_psession(MYSQLND *conn TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_restart_psession"); + MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_CONNECT_REUSED); + /* Free here what should not be seen by the next script */ + if (conn->last_message) { + mnd_pefree(conn->last_message, conn->persistent); + conn->last_message = NULL; + } + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_end_psession */ +PHPAPI void mysqlnd_end_psession(MYSQLND *conn) +{ + +} +/* }}} */ + + +/* {{{ mysqlnd_connect */ +PHPAPI MYSQLND *mysqlnd_connect(MYSQLND *conn, + char *host, char *user, + char *passwd, unsigned int passwd_len, + char *db, unsigned int db_len, + unsigned int port, + char *socket, + unsigned int mysql_flags, + MYSQLND_THD_ZVAL_PCACHE *zval_cache + TSRMLS_DC) +{ + char *transport = NULL, *errstr = NULL; + char *hashed_details = NULL; + int transport_len, hashed_details_len, errcode = 0; + unsigned int streams_options = ENFORCE_SAFE_MODE; + unsigned int streams_flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT; + zend_bool self_alloced = FALSE; + struct timeval tv; + zend_bool unix_socket = FALSE; + const MYSQLND_CHARSET * charset; + zend_bool reconnect = FALSE; + + php_mysql_packet_greet greet_packet; + php_mysql_packet_auth *auth_packet; + php_mysql_packet_ok ok_packet; + + DBG_ENTER("mysqlnd_connect"); + DBG_INF_FMT("host=%s user=%s db=%s port=%d flags=%d persistent=%d state=%d", + host?host:"", user?user:"", db?db:"", port, mysql_flags, + conn? conn->persistent:0, conn? conn->state:-1); + + DBG_INF_FMT("state=%d", conn->state); + if (conn && conn->state > CONN_ALLOCED && conn->state ) { + DBG_INF("Connecting on a connected handle."); + + if (conn->state < CONN_QUIT_SENT) { + MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_CLOSE_IMPLICIT); + reconnect = TRUE; + mysqlnd_send_close(conn TSRMLS_CC); + } + + conn->m->free_contents(conn TSRMLS_CC); + MYSQLND_DEC_CONN_STATISTIC(&conn->stats, STAT_OPENED_CONNECTIONS); + if (conn->persistent) { + MYSQLND_DEC_CONN_STATISTIC(&conn->stats, STAT_OPENED_PERSISTENT_CONNECTIONS); + } + /* Now reconnect using the same handle */ + } + + if (!host || !host[0]) { + host = "localhost"; + } + if (!user || !user[0]) { + user = php_get_current_user(); + } + if (!passwd) { + passwd = ""; + passwd_len = 0; + } + if (!db) { + db = ""; + db_len = 0; + } + if (!port && !socket) { + port = 3306; + } +#ifndef PHP_WIN32 + if (!strncasecmp(host, "localhost", sizeof("localhost") - 1)) { + if (!socket) { + socket = "/tmp/mysql.sock"; + } + transport_len = spprintf(&transport, 0, "unix://%s", socket); + unix_socket = TRUE; + } else +#endif + { + transport_len = spprintf(&transport, 0, "tcp://%s:%d", host, port); + } + DBG_INF_FMT("transport=%p", transport); + + if (conn->persistent) { + struct timeval tv; + gettimeofday(&tv, NULL); + /* We should generate something unique */ + hashed_details_len = spprintf(&hashed_details, 0, "%s@%s@%s@%ld@%ld@%0.8F", + transport, user, db, tv.tv_sec, (long int)tv.tv_usec, + php_combined_lcg(TSRMLS_C) * 10); + DBG_INF_FMT("hashed_details=%s", hashed_details); + } + + PACKET_INIT_ALLOCA(greet_packet, PROT_GREET_PACKET); + PACKET_INIT(auth_packet, PROT_AUTH_PACKET, php_mysql_packet_auth *); + PACKET_INIT_ALLOCA(ok_packet, PROT_OK_PACKET); + + if (!conn) { + conn = mysqlnd_init(FALSE); + self_alloced = TRUE; + } + + conn->state = CONN_ALLOCED; + conn->net.packet_no = 0; + + if (conn->options.timeout_connect) { + tv.tv_sec = conn->options.timeout_connect; + tv.tv_usec = 0; + } + if (conn->persistent) { + conn->scheme = pestrndup(transport, transport_len, 1); + mnd_efree(transport); + } else { + conn->scheme = transport; + } + DBG_INF(conn->scheme); + conn->net.stream = php_stream_xport_create(conn->scheme, transport_len, streams_options, streams_flags, + hashed_details, + (conn->options.timeout_connect) ? &tv : NULL, + NULL /*ctx*/, &errstr, &errcode); + DBG_INF_FMT("stream=%p", conn->net.stream); + + if (hashed_details) { + /* + If persistent, the streams register it in EG(persistent_list). + This is unwanted. ext/mysql or ext/mysqli are responsible to clean, + whatever they have to. + */ + zend_rsrc_list_entry *le; + + if (zend_hash_find(&EG(persistent_list), hashed_details, hashed_details_len + 1, + (void*) &le) == SUCCESS) { + /* + in_free will let streams code skip destructing - big HACK, + but STREAMS suck big time regarding persistent streams. + Just not compatible for extensions that need persistency. + */ + conn->net.stream->in_free = 1; + zend_hash_del(&EG(persistent_list), hashed_details, hashed_details_len + 1); + conn->net.stream->in_free = 0; + } +#if ZEND_DEBUG + /* Shut-up the streams, they don't know what they are doing */ + conn->net.stream->__exposed = 1; +#endif + mnd_efree(hashed_details); + } + + if (errstr || !conn->net.stream) { + goto err; + } + + if (conn->options.timeout_read) + { + tv.tv_sec = conn->options.timeout_read; + tv.tv_usec = 0; + php_stream_set_option(conn->net.stream, PHP_STREAM_OPTION_READ_TIMEOUT, 0, &tv); + } + + if (!unix_socket) { + /* Set TCP_NODELAY */ + mysqlnd_set_sock_no_delay(conn->net.stream); + } + + if (FAIL == PACKET_READ_ALLOCA(greet_packet, conn)) { + DBG_ERR("Error while reading greeting packet"); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error while reading greeting packet. PID=%d", getpid()); + goto err; + } else if (greet_packet.error_no) { + DBG_ERR_FMT("errorno=%d error=%s", greet_packet.error_no, greet_packet.error); + SET_CLIENT_ERROR(conn->error_info, greet_packet.error_no, + greet_packet.sqlstate, greet_packet.error); + goto err; + } else if (greet_packet.pre41) { + DBG_ERR_FMT("Connecting to 3.22, 3.23 & 4.0 is not supported. Server is %-.32s", + greet_packet.server_version); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Connecting to 3.22, 3.23 & 4.0 " + " is not supported. Server is %-.32s", greet_packet.server_version); + SET_CLIENT_ERROR(conn->error_info, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, + "Connecting to 3.22, 3.23 & 4.0 servers is not supported"); + goto err; + } + + conn->thread_id = greet_packet.thread_id; + conn->protocol_version = greet_packet.protocol_version; + conn->server_version = greet_packet.server_version; + greet_packet.server_version = NULL; /* The string will be freed otherwise */ + + conn->greet_charset = mysqlnd_find_charset_nr(greet_packet.charset_no); + /* we allow load data local infile by default */ + mysql_flags |= CLIENT_LOCAL_FILES; + + auth_packet->user = user; + auth_packet->password = passwd; + + if (conn->options.charset_name && + (charset = mysqlnd_find_charset_name(conn->options.charset_name))) + { + auth_packet->charset_no = charset->nr; +#if PHP_MAJOR_VERSION >= 6 + } else if (UG(unicode)) { + auth_packet->charset_no = 200;/* utf8 - swedish collation, check mysqlnd_charset.c */ +#endif + } else { + auth_packet->charset_no = greet_packet.charset_no; + } + auth_packet->db = db; + auth_packet->db_len = db_len; + auth_packet->max_packet_size= 3UL*1024UL*1024UL*1024UL; + auth_packet->client_flags= mysql_flags; + + conn->scramble = auth_packet->server_scramble_buf = mnd_pemalloc(SCRAMBLE_LENGTH, conn->persistent); + memcpy(auth_packet->server_scramble_buf, greet_packet.scramble_buf, SCRAMBLE_LENGTH); + if (!PACKET_WRITE(auth_packet, conn)) { + conn->state = CONN_QUIT_SENT; + SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); + goto err; + } + + if (FAIL == PACKET_READ_ALLOCA(ok_packet, conn) || ok_packet.field_count >= 0xFE) { + if (ok_packet.field_count == 0xFE) { + /* old authentication with new server !*/ + DBG_ERR("mysqlnd cannot connect to MySQL 4.1+ using old authentication"); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "mysqlnd cannot connect to MySQL 4.1+ using old authentication"); + } else if (ok_packet.field_count == 0xFF) { + if (ok_packet.sqlstate[0]) { + if (!self_alloced) { + strncpy(conn->error_info.sqlstate, ok_packet.sqlstate, sizeof(conn->error_info.sqlstate)); + } + DBG_ERR_FMT("ERROR:%d [SQLSTATE:%s] %s", + ok_packet.error_no, ok_packet.sqlstate, ok_packet.error); + } + if (!self_alloced) { + conn->error_info.error_no = ok_packet.error_no; + strncpy(conn->error_info.error, ok_packet.error, sizeof(conn->error_info.error)); + } + } + } else { + conn->state = CONN_READY; + + conn->user = pestrdup(user, conn->persistent); + conn->passwd = pestrndup(passwd, passwd_len, conn->persistent); + conn->port = port; + if (host && !socket) { + char *p; + + conn->host = pestrdup(host, conn->persistent); + spprintf(&p, 0, "MySQL host info: %s via TCP/IP", conn->host); + if (conn->persistent) { + conn->host_info = pestrdup(p, 1); + mnd_efree(p); + } else { + conn->host_info = p; + } + } else { + conn->unix_socket = pestrdup(socket, conn->persistent); + conn->host_info = pestrdup("MySQL host info: Localhost via UNIX socket", conn->persistent); + } + conn->client_flag = auth_packet->client_flags; + conn->max_packet_size = auth_packet->max_packet_size; + /* todo: check if charset is available */ + conn->charset = mysqlnd_find_charset_nr(auth_packet->charset_no); + conn->server_capabilities = greet_packet.server_capabilities; + conn->upsert_status.warning_count = 0; + conn->upsert_status.server_status = greet_packet.server_status; + conn->upsert_status.affected_rows = 0; + SET_NEW_MESSAGE(conn->last_message, conn->last_message_len, + ok_packet.message, ok_packet.message_len, + conn->persistent); + + SET_EMPTY_ERROR(conn->error_info); + + PACKET_FREE_ALLOCA(greet_packet); + PACKET_FREE(auth_packet); + PACKET_FREE_ALLOCA(ok_packet); + + conn->zval_cache = mysqlnd_palloc_get_thd_cache_reference(zval_cache); + conn->net.cmd_buffer.length = 128L*1024L; + conn->net.cmd_buffer.buffer = mnd_pemalloc(conn->net.cmd_buffer.length, conn->persistent); + + mysqlnd_local_infile_default(conn); + { + uint buf_size; + buf_size = MYSQLND_G(net_read_buffer_size); /* this is long, cast to uint*/ + conn->m->set_client_option(conn, MYSQLND_OPT_NET_READ_BUFFER_SIZE, + (char *)&buf_size TSRMLS_CC); + + buf_size = MYSQLND_G(net_cmd_buffer_size); /* this is long, cast to uint*/ + conn->m->set_client_option(conn, MYSQLND_OPT_NET_CMD_BUFFER_SIZE, + (char *)&buf_size TSRMLS_CC); + } + + MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_CONNECT_SUCCESS); + if (reconnect) { + MYSQLND_INC_GLOBAL_STATISTIC(STAT_RECONNECT); + } + MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_OPENED_CONNECTIONS); + if (conn->persistent) { + MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_OPENED_PERSISTENT_CONNECTIONS); + } + + DBG_INF_FMT("connection_id=%llu", conn->thread_id); +#if PHP_MAJOR_VERSION >= 6 + { + uint as_unicode = 1; + conn->m->set_client_option(conn, MYSQLND_OPT_NUMERIC_AND_DATETIME_AS_UNICODE, + (char *)&as_unicode TSRMLS_CC); + DBG_INF("unicode set"); + } +#endif + DBG_RETURN(conn); + } +err: + PACKET_FREE_ALLOCA(greet_packet); + PACKET_FREE(auth_packet); + PACKET_FREE_ALLOCA(ok_packet); + + if (errstr) { + DBG_ERR_FMT("[%d] %.64s (trying to connect via %s)", errcode, errstr, conn->scheme); + SET_CLIENT_ERROR(conn->error_info, errcode, UNKNOWN_SQLSTATE, errstr); + + php_error_docref(NULL TSRMLS_CC, E_WARNING, "[%d] %.64s (trying to connect via %s)", errcode, errstr, conn->scheme); + + mnd_efree(errstr); + } + if (conn->scheme) { + mnd_pefree(conn->scheme, conn->persistent); + conn->scheme = NULL; + } + + + /* This will also close conn->net.stream if it has been opened */ + conn->m->free_contents(conn TSRMLS_CC); + + if (self_alloced) { + /* + We have alloced, thus there are no references to this + object - we are free to kill it! + */ + conn->m->dtor(conn TSRMLS_CC); + } else { + MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_CONNECT_FAILURE); + } + DBG_RETURN(NULL); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::query */ +/* + If conn->error_info.error_no is not zero, then we had an error. + Still the result from the query is PASS +*/ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, query)(MYSQLND *conn, const char *query, unsigned int query_len TSRMLS_DC) +{ + enum_func_status ret; + DBG_ENTER("mysqlnd_conn::query"); + DBG_INF_FMT("conn=%llu query=%s", conn->thread_id, query); + + if (PASS != mysqlnd_simple_command(conn, COM_QUERY, query, query_len, + PROT_LAST /* we will handle the OK packet*/, + FALSE TSRMLS_CC)) { + DBG_RETURN(FAIL); + } + + /* + Here read the result set. We don't do it in simple_command because it need + information from the ok packet. We will fetch it ourselves. + */ + ret = mysqlnd_query_read_result_set_header(conn, NULL TSRMLS_CC); + DBG_RETURN(ret); +} +/* }}} */ + + +/* + COM_FIELD_LIST is special, different from a SHOW FIELDS FROM : + - There is no result set header - status from the command, which + impacts us to allocate big chunk of memory for reading the metadata. + - The EOF packet is consumed by the metadata packet reader. +*/ + +/* {{{ mysqlnd_conn::list_fields */ +MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_conn, list_fields)(MYSQLND *conn, const char *table, const char *achtung_wild TSRMLS_DC) +{ + /* db + \0 + wild + \0 (for wild) */ + char buff[MYSQLND_MAX_ALLOWED_DB_LEN * 4 * 2 + 1 + 1], *p; + size_t table_len, wild_len; + MYSQLND_RES *result = NULL; + DBG_ENTER("mysqlnd_conn::list_fields"); + DBG_INF_FMT("conn=%llu table=%s wild=%s", conn->thread_id, table? table:"",achtung_wild? achtung_wild:""); + + p = buff; + if (table && (table_len = strlen(table))) { + memcpy(p, table, MIN(table_len, MYSQLND_MAX_ALLOWED_DB_LEN * 4)); + p += table_len; + *p++ = '\0'; + } + + if (achtung_wild && (wild_len = strlen(achtung_wild))) { + memcpy(p, achtung_wild, MIN(wild_len, MYSQLND_MAX_ALLOWED_DB_LEN * 4)); + p += wild_len; + *p++ = '\0'; + } + + if (PASS != mysqlnd_simple_command(conn, COM_FIELD_LIST, buff, p - buff, + PROT_LAST /* we will handle the OK packet*/, + FALSE TSRMLS_CC)) { + DBG_RETURN(NULL); + } + /* + Prepare for the worst case. + MyISAM goes to 2500 BIT columns, double it for safety. + */ + result = mysqlnd_result_init(5000, mysqlnd_palloc_get_thd_cache_reference(conn->zval_cache) TSRMLS_CC); + + + if (FAIL == result->m.read_result_metadata(result, conn TSRMLS_CC)) { + DBG_ERR("Error ocurred while reading metadata"); + result->m.free_result(result, TRUE TSRMLS_CC); + DBG_RETURN(NULL); + } + + result->type = MYSQLND_RES_NORMAL; + result->m.fetch_row = result->m.fetch_row_normal_unbuffered; + result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED)); + result->unbuf->eof_reached = TRUE; + + DBG_RETURN(result); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::list_method */ +MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_conn, list_method)(MYSQLND *conn, const char *query, + const char *achtung_wild, char *par1 TSRMLS_DC) +{ + char *show_query = NULL; + size_t show_query_len; + MYSQLND_RES *result = NULL; + + DBG_ENTER("mysqlnd_conn::list_method"); + DBG_INF_FMT("conn=%llu query=%s wild=%d", conn->thread_id, query, achtung_wild); + + if (par1) { + if (achtung_wild) { + show_query_len = spprintf(&show_query, 0, query, par1, achtung_wild); + } else { + show_query_len = spprintf(&show_query, 0, query, par1); + } + } else { + if (achtung_wild) { + show_query_len = spprintf(&show_query, 0, query, achtung_wild); + } else { + show_query_len = strlen(show_query = (char *)query); + } + } + + if (PASS == conn->m->query(conn, show_query, show_query_len TSRMLS_CC)) { + result = conn->m->store_result(conn TSRMLS_CC); + } + if (show_query != query) { + mnd_efree(show_query); + } + DBG_RETURN(result); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::errno */ +static unsigned int +MYSQLND_METHOD(mysqlnd_conn, errno)(const MYSQLND * const conn) +{ + return conn->error_info.error_no; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::error */ +static const char * +MYSQLND_METHOD(mysqlnd_conn, error)(const MYSQLND * const conn) +{ + return conn->error_info.error; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::sqlstate */ +static const char * +MYSQLND_METHOD(mysqlnd_conn, sqlstate)(const MYSQLND * const conn) +{ + return conn->error_info.sqlstate[0] ? conn->error_info.sqlstate:MYSQLND_SQLSTATE_NULL; +} +/* }}} */ + + +/* {{{ mysqlnd_old_escape_string */ +PHPAPI ulong mysqlnd_old_escape_string(char *newstr, const char *escapestr, int escapestr_len TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_old_escape_string"); + DBG_RETURN(mysqlnd_cset_escape_slashes(mysqlnd_find_charset_name("latin1"), + newstr, escapestr, escapestr_len TSRMLS_CC)); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::escape_string */ +static ulong +MYSQLND_METHOD(mysqlnd_conn, escape_string)(const MYSQLND * const conn, char *newstr, + const char *escapestr, int escapestr_len TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn::escape_string"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + if (conn->upsert_status.server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES) { + DBG_RETURN(mysqlnd_cset_escape_quotes(conn->charset, newstr, escapestr, escapestr_len TSRMLS_CC)); + } + DBG_RETURN(mysqlnd_cset_escape_slashes(conn->charset, newstr, escapestr, escapestr_len TSRMLS_CC)); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::dump_debug_info */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, dump_debug_info)(MYSQLND * const conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn::dump_debug_info"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + DBG_RETURN(mysqlnd_simple_command(conn, COM_DEBUG, NULL, 0, PROT_EOF_PACKET, FALSE TSRMLS_CC)); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::select_db */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, select_db)(MYSQLND * const conn, + const char * const db, + unsigned int db_len TSRMLS_DC) +{ + enum_func_status ret; + + DBG_ENTER("mysqlnd_conn::select_db"); + DBG_INF_FMT("conn=%llu db=%s", conn->thread_id, db); + + ret = mysqlnd_simple_command(conn, COM_INIT_DB, db, db_len, PROT_OK_PACKET, FALSE TSRMLS_CC); + /* + The server sends 0 but libmysql doesn't read it and has established + a protocol of giving back -1. Thus we have to follow it :( + */ + SET_ERROR_AFF_ROWS(conn); + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::ping */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, ping)(MYSQLND * const conn TSRMLS_DC) +{ + enum_func_status ret; + + DBG_ENTER("mysqlnd_conn::ping"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + ret = mysqlnd_simple_command(conn, COM_PING, NULL, 0, PROT_OK_PACKET, TRUE TSRMLS_CC); + /* + The server sends 0 but libmysql doesn't read it and has established + a protocol of giving back -1. Thus we have to follow it :( + */ + SET_ERROR_AFF_ROWS(conn); + + DBG_INF_FMT("ret=%d", ret); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::stat */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, stat)(MYSQLND *conn, char **message, unsigned int * message_len TSRMLS_DC) +{ + enum_func_status ret; + php_mysql_packet_stats stats_header; + + DBG_ENTER("mysqlnd_conn::stat"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + ret = mysqlnd_simple_command(conn, COM_STATISTICS, NULL, 0, PROT_LAST, FALSE TSRMLS_CC); + if (FAIL == ret) { + DBG_RETURN(FAIL); + } + PACKET_INIT_ALLOCA(stats_header, PROT_STATS_PACKET); + if (FAIL == (ret = PACKET_READ_ALLOCA(stats_header, conn))) { + DBG_RETURN(FAIL); + } + *message = stats_header.message; + *message_len = stats_header.message_len; + /* Ownership transfer */ + stats_header.message = NULL; + PACKET_FREE_ALLOCA(stats_header); + + DBG_INF(*message); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::kill */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, kill)(MYSQLND *conn, unsigned int pid TSRMLS_DC) +{ + enum_func_status ret; + char buff[4]; + + DBG_ENTER("mysqlnd_conn::kill"); + DBG_INF_FMT("conn=%llu pid=%lu", conn->thread_id, pid); + + int4store(buff, pid); + + /* If we kill ourselves don't expect OK packet, PROT_LAST will skip it */ + if (pid != conn->thread_id) { + ret = mysqlnd_simple_command(conn, COM_PROCESS_KILL, buff, 4, PROT_OK_PACKET, FALSE TSRMLS_CC); + /* + The server sends 0 but libmysql doesn't read it and has established + a protocol of giving back -1. Thus we have to follow it :( + */ + SET_ERROR_AFF_ROWS(conn); + } else if (PASS == (ret = mysqlnd_simple_command(conn, COM_PROCESS_KILL, buff, + 4, PROT_LAST, FALSE TSRMLS_CC))) { + conn->state = CONN_QUIT_SENT; + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ _mysqlnd_conn::set_charset */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, set_charset)(MYSQLND * const conn, const char * const csname TSRMLS_DC) +{ + enum_func_status ret = PASS; + char *query; + size_t query_len; + const MYSQLND_CHARSET * const charset = mysqlnd_find_charset_name(csname); + + DBG_ENTER("mysqlnd_conn::set_charset"); + DBG_INF_FMT("conn=%llu cs=%s", conn->thread_id, csname); + + if (!charset) { + SET_CLIENT_ERROR(conn->error_info, CR_CANT_FIND_CHARSET, UNKNOWN_SQLSTATE, + "Invalid characterset or character set not supported"); + DBG_RETURN(FAIL); + } + + query_len = spprintf(&query, 0, "SET NAMES %s", csname); + + if (FAIL == conn->m->query(conn, query, query_len TSRMLS_CC)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Error executing query"); + } else if (conn->error_info.error_no) { + ret = FAIL; + } else { + conn->charset = charset; + } + mnd_efree(query); + + DBG_INF(ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::refresh */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, refresh)(MYSQLND * const conn, unsigned long options TSRMLS_DC) +{ + zend_uchar bits[1]; + DBG_ENTER("mysqlnd_conn::refresh"); + DBG_INF_FMT("conn=%llu options=%lu", conn->thread_id, options); + + int1store(bits, options); + + DBG_RETURN(mysqlnd_simple_command(conn, COM_REFRESH, (char *)bits, 1, PROT_OK_PACKET, FALSE TSRMLS_CC)); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::shutdown */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, shutdown)(MYSQLND * const conn, unsigned long level TSRMLS_DC) +{ + zend_uchar bits[1]; + DBG_ENTER("mysqlnd_conn::shutdown"); + DBG_INF_FMT("conn=%llu level=%lu", conn->thread_id, level); + + int1store(bits, level); + + DBG_RETURN(mysqlnd_simple_command(conn, COM_SHUTDOWN, (char *)bits, 1, PROT_OK_PACKET, FALSE TSRMLS_CC)); +} +/* }}} */ + + +/* {{{ mysqlnd_send_close */ +enum_func_status +mysqlnd_send_close(MYSQLND * conn TSRMLS_DC) +{ + enum_func_status ret = PASS; + + DBG_ENTER("mysqlnd_send_close"); + DBG_INF_FMT("conn=%llu conn->net.stream->abstract=%p", + conn->thread_id, conn->net.stream? conn->net.stream->abstract:NULL); + + switch (conn->state) { + case CONN_READY: + DBG_INF("Connection clean, sending COM_QUIT"); + ret = mysqlnd_simple_command(conn, COM_QUIT, NULL, 0, PROT_LAST, + TRUE TSRMLS_CC); + /* Do nothing */ + break; + case CONN_SENDING_LOAD_DATA: + /* + Don't send COM_QUIT if we are in a middle of a LOAD DATA or we + will crash (assert) a debug server. + */ + case CONN_NEXT_RESULT_PENDING: + case CONN_QUERY_SENT: + case CONN_FETCHING_DATA: + MYSQLND_INC_GLOBAL_STATISTIC(STAT_CLOSE_IN_MIDDLE); + DBG_ERR_FMT("Brutally closing connection [%p][%s]", conn, conn->scheme); + /* + Do nothing, the connection will be brutally closed + and the server will catch it and free close from its side. + */ + case CONN_ALLOCED: + /* + Allocated but not connected or there was failure when trying + to connect with pre-allocated connect. + + Fall-through + */ + case CONN_QUIT_SENT: + /* The user has killed its own connection */ + break; + } + /* + We hold one reference, and every other object which needs the + connection does increase it by 1. + */ + conn->state = CONN_QUIT_SENT; + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::close */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, close)(MYSQLND * conn, enum_connection_close_type close_type TSRMLS_DC) +{ + enum_func_status ret = PASS; + static enum_mysqlnd_collected_stats + close_type_to_stat_map[MYSQLND_CLOSE_LAST] = { + STAT_CLOSE_EXPLICIT, + STAT_CLOSE_IMPLICIT, + STAT_CLOSE_DISCONNECT + }; + enum_mysqlnd_collected_stats stat = close_type_to_stat_map[close_type]; + + DBG_ENTER("mysqlnd_conn::close"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + MYSQLND_INC_CONN_STATISTIC(&conn->stats, stat); + MYSQLND_DEC_CONN_STATISTIC(&conn->stats, STAT_OPENED_CONNECTIONS); + if (conn->persistent) { + MYSQLND_DEC_CONN_STATISTIC(&conn->stats, STAT_OPENED_PERSISTENT_CONNECTIONS); + } + + /* + Close now, free_reference will try, + if we are last, but that's not a problem. + */ + ret = mysqlnd_send_close(conn TSRMLS_CC); + + ret = conn->m->free_reference(conn TSRMLS_CC); + + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::get_reference */ +static MYSQLND * +MYSQLND_METHOD_PRIVATE(mysqlnd_conn, get_reference)(MYSQLND * const conn) +{ + ++conn->refcount; + return conn; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::free_reference */ +static enum_func_status +MYSQLND_METHOD_PRIVATE(mysqlnd_conn, free_reference)(MYSQLND * const conn TSRMLS_DC) +{ + enum_func_status ret = PASS; + DBG_ENTER("mysqlnd_conn::free_reference"); + DBG_INF_FMT("conn=%llu conn->refcount=%u", conn->thread_id, conn->refcount); + if (!(--conn->refcount)) { + /* + No multithreading issues as we don't share the connection :) + This will free the object too, of course because references has + reached zero. + */ + ret = mysqlnd_send_close(conn TSRMLS_CC); + conn->m->dtor(conn TSRMLS_CC); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::field_count */ +static unsigned int +MYSQLND_METHOD(mysqlnd_conn, field_count)(const MYSQLND * const conn) +{ + return conn->field_count; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::insert_id */ +static mynd_ulonglong +MYSQLND_METHOD(mysqlnd_conn, insert_id)(const MYSQLND * const conn) +{ + return conn->upsert_status.last_insert_id; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::affected_rows */ +static mynd_ulonglong +MYSQLND_METHOD(mysqlnd_conn, affected_rows)(const MYSQLND * const conn) +{ + return conn->upsert_status.affected_rows; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::warning_count */ +static unsigned int +MYSQLND_METHOD(mysqlnd_conn, warning_count)(const MYSQLND * const conn) +{ + return conn->upsert_status.warning_count; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::info */ +static const char * +MYSQLND_METHOD(mysqlnd_conn, info)(const MYSQLND * const conn) +{ + return conn->last_message; +} +/* }}} */ + + +/* {{{ mysqlnd_get_client_info */ +PHPAPI const char * mysqlnd_get_client_info() +{ + return MYSQLND_VERSION; +} +/* }}} */ + + +/* {{{ mysqlnd_get_client_version */ +PHPAPI unsigned int mysqlnd_get_client_version() +{ + return MYSQLND_VERSION_ID; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::get_server_info */ +static const char * +MYSQLND_METHOD(mysqlnd_conn, get_server_info)(const MYSQLND * const conn) +{ + return conn->server_version; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::get_host_info */ +static const char * +MYSQLND_METHOD(mysqlnd_conn, get_host_info)(const MYSQLND * const conn) +{ + return conn->host_info; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::get_proto_info */ +static unsigned int +MYSQLND_METHOD(mysqlnd_conn, get_proto_info)(const MYSQLND *const conn) +{ + return conn->protocol_version; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::charset_name */ +static const char * +MYSQLND_METHOD(mysqlnd_conn, charset_name)(const MYSQLND * const conn) +{ + return conn->charset->name; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::thread_id */ +static mynd_ulonglong +MYSQLND_METHOD(mysqlnd_conn, thread_id)(const MYSQLND * const conn) +{ + return conn->thread_id; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::get_server_version */ +static unsigned long +MYSQLND_METHOD(mysqlnd_conn, get_server_version)(const MYSQLND * const conn) +{ + long major, minor, patch; + char *p; + + if (!(p = conn->server_version)) { + return 0; + } + + major = strtol(p, &p, 10); + p += 1; /* consume the dot */ + minor = strtol(p, &p, 10); + p += 1; /* consume the dot */ + patch = strtol(p, &p, 10); + + return (unsigned long)(major * 10000L + (unsigned long)(minor * 100L + patch)); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::more_results */ +static zend_bool +MYSQLND_METHOD(mysqlnd_conn,more_results)(const MYSQLND * const conn) +{ + /* (conn->state == CONN_NEXT_RESULT_PENDING) too */ + return conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS? TRUE:FALSE; +} +/* }}} */ + + +/* {{{ mysqlnd_conn::next_result */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, next_result)(MYSQLND * const conn TSRMLS_DC) +{ + enum_func_status ret; + + DBG_ENTER("mysqlnd_conn::next_result"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + if (conn->state != CONN_NEXT_RESULT_PENDING) { + DBG_RETURN(FAIL); + } + + SET_EMPTY_ERROR(conn->error_info); + conn->upsert_status.affected_rows= ~(mynd_ulonglong) 0; + /* + We are sure that there is a result set, since conn->state is set accordingly + in mysqlnd_store_result() or mysqlnd_fetch_row_unbuffered() + */ + if (FAIL == (ret = mysqlnd_query_read_result_set_header(conn, NULL TSRMLS_CC))) { + DBG_ERR_FMT("Serious error. %s::%d", __FILE__, __LINE__); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Serious error. PID=%d", getpid()); + conn->state = CONN_QUIT_SENT; + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_field_type_name */ +PHPAPI const char *mysqlnd_field_type_name(enum mysqlnd_field_types field_type) +{ + switch(field_type) { + case FIELD_TYPE_STRING: + case FIELD_TYPE_VAR_STRING: + return "string"; + case FIELD_TYPE_TINY: + case FIELD_TYPE_SHORT: + case FIELD_TYPE_LONG: + case FIELD_TYPE_LONGLONG: + case FIELD_TYPE_INT24: + return "int"; + case FIELD_TYPE_FLOAT: + case FIELD_TYPE_DOUBLE: + case FIELD_TYPE_DECIMAL: + case FIELD_TYPE_NEWDECIMAL: + return "real"; + case FIELD_TYPE_TIMESTAMP: + return "timestamp"; + case FIELD_TYPE_YEAR: + return "year"; + case FIELD_TYPE_DATE: + case FIELD_TYPE_NEWDATE: + return "date"; + case FIELD_TYPE_TIME: + return "time"; + case FIELD_TYPE_SET: + return "set"; + case FIELD_TYPE_ENUM: + return "enum"; + case FIELD_TYPE_GEOMETRY: + return "geometry"; + case FIELD_TYPE_DATETIME: + return "datetime"; + case FIELD_TYPE_TINY_BLOB: + case FIELD_TYPE_MEDIUM_BLOB: + case FIELD_TYPE_LONG_BLOB: + case FIELD_TYPE_BLOB: + return "blob"; + case FIELD_TYPE_NULL: + return "null"; + case FIELD_TYPE_BIT: + return "bit"; + default: + return "unknown"; + } +} +/* }}} */ + + +/* {{{ mysqlnd_conn::change_user */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, change_user)(MYSQLND * const conn, + const char *user, + const char *passwd, + const char *db TSRMLS_DC) +{ + /* + User could be max 16 * 3 (utf8), pass is 20 usually, db is up to 64*3 + Stack space is not that expensive, so use a bit more to be protected against + stack overrungs. + */ + size_t user_len; + enum_func_status ret; + php_mysql_packet_chg_user_resp chg_user_resp; + char buffer[MYSQLND_MAX_ALLOWED_USER_LEN + 1 + SCRAMBLE_LENGTH + MYSQLND_MAX_ALLOWED_DB_LEN + 1]; + char *p = buffer; + + DBG_ENTER("mysqlnd_conn::change_user"); + DBG_INF_FMT("conn=%llu user=%s passwd=%s db=%s", + conn->thread_id, user?user:"", passwd?"***":"null", db?db:""); + + if (!user) { + user = ""; + } + if (!passwd) { + passwd = ""; + } + if (!db) { + db = ""; + } + + /* 1. user ASCIIZ */ + user_len = MIN(strlen(user), MYSQLND_MAX_ALLOWED_DB_LEN); + memcpy(p, user, user_len); + p += user_len; + *p++ = '\0'; + + /* 2. password SCRAMBLE_LENGTH followed by the scramble or \0 */ + if (passwd[0]) { + *p++ = SCRAMBLE_LENGTH; + php_mysqlnd_scramble((unsigned char *)p, conn->scramble, (unsigned char *)passwd); + p += SCRAMBLE_LENGTH; + } else { + *p++ = '\0'; + } + + /* 3. db ASCIIZ */ + if (db[0]) { + size_t db_len = strlen(db); + memcpy(p, db, MIN(db_len, MYSQLND_MAX_ALLOWED_DB_LEN)); + p += db_len; + } + *p++ = '\0'; + + if (PASS != mysqlnd_simple_command(conn, COM_CHANGE_USER, buffer, p - buffer, + PROT_LAST /* we will handle the OK packet*/, + FALSE TSRMLS_CC)) { + DBG_RETURN(FAIL); + } + + PACKET_INIT_ALLOCA(chg_user_resp, PROT_CHG_USER_PACKET); + ret = PACKET_READ_ALLOCA(chg_user_resp, conn); + conn->error_info = chg_user_resp.error_info; + PACKET_FREE_ALLOCA(chg_user_resp); + + if (conn->error_info.error_no) { + ret = FAIL; + /* + COM_CHANGE_USER is broken in 5.1. At least in 5.1.15 and 5.1.14, 5.1.11 is immune. + bug#25371 mysql_change_user() triggers "packets out of sync" + When it gets fixed, there should be one more check here + */ + if (mysqlnd_get_server_version(conn) > 50113L && + mysqlnd_get_server_version(conn) < 50118L) + { + php_mysql_packet_ok redundant_error_packet; + PACKET_INIT_ALLOCA(redundant_error_packet, PROT_OK_PACKET); + PACKET_READ_ALLOCA(redundant_error_packet, conn); + PACKET_FREE_ALLOCA(redundant_error_packet); + DBG_INF_FMT("Server is %d, buggy, sends two ERR messages", mysqlnd_get_server_version(conn)); + } + } + if (ret == PASS) { + mnd_pefree(conn->user, conn->persistent); + conn->user = pestrndup(user, user_len, conn->persistent); + mnd_pefree(conn->passwd, conn->persistent); + conn->passwd = pestrdup(passwd, conn->persistent); + if (conn->last_message) { + mnd_pefree(conn->last_message, conn->persistent); + conn->last_message = NULL; + } + conn->charset = conn->greet_charset; + memset(&conn->upsert_status, 0, sizeof(conn->upsert_status)); + } + + SET_ERROR_AFF_ROWS(conn); + + /* + Here we should close all statements. Unbuffered queries should not be a + problem as we won't allow sending COM_CHANGE_USER. + */ + DBG_INF(ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::set_client_option */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_conn, set_client_option)(MYSQLND * const conn, + enum mysqlnd_option option, + const char * const value + TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn::set_client_option"); + DBG_INF_FMT("conn=%llu option=%d", conn->thread_id, option); + switch (option) { +#if PHP_MAJOR_VERSION >= 6 + case MYSQLND_OPT_NUMERIC_AND_DATETIME_AS_UNICODE: + conn->options.numeric_and_datetime_as_unicode = *(uint*) value; + break; +#endif + case MYSQLND_OPT_NET_CMD_BUFFER_SIZE: + conn->net.cmd_buffer.length = *(uint*) value; + if (!conn->net.cmd_buffer.buffer) { + conn->net.cmd_buffer.buffer = mnd_pemalloc(conn->net.cmd_buffer.length, conn->persistent); + } else { + conn->net.cmd_buffer.buffer = mnd_perealloc(conn->net.cmd_buffer.buffer, + conn->net.cmd_buffer.length, + conn->persistent); + } + break; + case MYSQLND_OPT_NET_READ_BUFFER_SIZE: + conn->options.net_read_buffer_size = *(uint*) value; + break; +#ifdef MYSQLND_STRING_TO_INT_CONVERSION + case MYSQLND_OPT_INT_AND_YEAR_AS_INT: + conn->options.int_and_year_as_int = *(uint*) value; + break; +#endif + case MYSQL_OPT_CONNECT_TIMEOUT: + conn->options.timeout_connect = *(uint*) value; + break; +#ifdef WHEN_SUPPORTED_BY_MYSQLI + case MYSQL_OPT_READ_TIMEOUT: + conn->options.timeout_read = *(uint*) value; + break; + case MYSQL_OPT_WRITE_TIMEOUT: + conn->options.timeout_write = *(uint*) value; + break; +#endif + case MYSQL_OPT_LOCAL_INFILE: + if (!value || (*(uint*) value) ? 1 : 0) { + conn->options.flags |= CLIENT_LOCAL_FILES; + } else { + conn->options.flags &= ~CLIENT_LOCAL_FILES; + } + break; +#ifdef WHEN_SUPPORTED_BY_MYSQLI + case MYSQL_OPT_COMPRESS: +#endif + case MYSQL_INIT_COMMAND: + case MYSQL_READ_DEFAULT_FILE: + case MYSQL_READ_DEFAULT_GROUP: +#ifdef WHEN_SUPPORTED_BY_MYSQLI + case MYSQL_SET_CLIENT_IP: + case MYSQL_REPORT_DATA_TRUNCATION: + case MYSQL_OPT_SSL_VERIFY_SERVER_CERT: +#endif + /* currently not supported. Todo!! */ + break; + case MYSQL_SET_CHARSET_NAME: + conn->options.charset_name = pestrdup(value, conn->persistent); + break; +#ifdef WHEN_SUPPORTED_BY_MYSQLI + case MYSQL_SET_CHARSET_DIR: + case MYSQL_OPT_RECONNECT: + case MYSQL_OPT_PROTOCOL: + /* we don't need external character sets, all character sets are + compiled in. For compatibility we just ignore this setting. + Same for protocol, we don't support old protocol */ + case MYSQL_OPT_USE_REMOTE_CONNECTION: + case MYSQL_OPT_USE_EMBEDDED_CONNECTION: + case MYSQL_OPT_GUESS_CONNECTION: + /* todo: throw an error, we don't support embedded */ + break; +#endif + +#ifdef WHEN_SUPPORTED_BY_MYSQLI + case MYSQL_OPT_NAMED_PIPE: + case MYSQL_SHARED_MEMORY_BASE_NAME: + case MYSQL_OPT_USE_RESULT: + case MYSQL_SECURE_AUTH: + /* not sure, todo ? */ +#endif + default: + DBG_RETURN(FAIL); + } + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ _mysqlnd_conn::use_result */ +MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_conn, use_result)(MYSQLND * const conn TSRMLS_DC) +{ + MYSQLND_RES *result; + + DBG_ENTER("mysqlnd_conn::use_result"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + if (!conn->current_result) { + DBG_RETURN(NULL); + } + + /* Nothing to store for UPSERT/LOAD DATA */ + if (conn->last_query_type != QUERY_SELECT || conn->state != CONN_FETCHING_DATA) { + SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, + mysqlnd_out_of_sync); + DBG_ERR("Command out of sync"); + DBG_RETURN(NULL); + } + + MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_UNBUFFERED_SETS); + + result = conn->current_result; + conn->current_result = NULL; + result->conn = conn->m->get_reference(conn); + + result = result->m.use_result(result, FALSE TSRMLS_CC); + DBG_RETURN(result); +} +/* }}} */ + + +/* {{{ _mysqlnd_conn::store_result */ +MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_conn, store_result)(MYSQLND * const conn TSRMLS_DC) +{ + MYSQLND_RES *result; + + DBG_ENTER("mysqlnd_conn::store_result"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + + if (!conn->current_result) { + DBG_RETURN(NULL); + } + + /* Nothing to store for UPSERT/LOAD DATA*/ + if (conn->last_query_type != QUERY_SELECT || conn->state != CONN_FETCHING_DATA) { + SET_CLIENT_ERROR(conn->error_info, CR_COMMANDS_OUT_OF_SYNC, UNKNOWN_SQLSTATE, + mysqlnd_out_of_sync); + DBG_ERR("Command out of sync"); + DBG_RETURN(NULL); + } + + MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_BUFFERED_SETS); + + result = conn->current_result; + conn->current_result = NULL; + + result = result->m.store_result(result, conn, FALSE TSRMLS_CC); + DBG_RETURN(result); +} +/* }}} */ + + +/* {{{ mysqlnd_conn::get_connection_stats */ +void +MYSQLND_METHOD(mysqlnd_conn, get_connection_stats)(const MYSQLND * const conn, + zval *return_value + TSRMLS_DC ZEND_FILE_LINE_DC) +{ + DBG_ENTER("mysqlnd_conn::get_connection_stats"); + DBG_INF_FMT("conn=%llu", conn->thread_id); + mysqlnd_fill_stats_hash(&(conn->stats), return_value TSRMLS_CC ZEND_FILE_LINE_CC); + DBG_VOID_RETURN; +} +/* }}} */ + + +MYSQLND_STMT * _mysqlnd_stmt_init(MYSQLND * const conn TSRMLS_DC); + + +MYSQLND_CLASS_METHODS_START(mysqlnd_conn) + MYSQLND_METHOD(mysqlnd_conn, escape_string), + MYSQLND_METHOD(mysqlnd_conn, set_charset), + MYSQLND_METHOD(mysqlnd_conn, query), + MYSQLND_METHOD(mysqlnd_conn, use_result), + MYSQLND_METHOD(mysqlnd_conn, store_result), + MYSQLND_METHOD(mysqlnd_conn, next_result), + MYSQLND_METHOD(mysqlnd_conn, more_results), + + _mysqlnd_stmt_init, + + MYSQLND_METHOD(mysqlnd_conn, shutdown), + MYSQLND_METHOD(mysqlnd_conn, refresh), + + MYSQLND_METHOD(mysqlnd_conn, ping), + MYSQLND_METHOD(mysqlnd_conn, kill), + MYSQLND_METHOD(mysqlnd_conn, select_db), + MYSQLND_METHOD(mysqlnd_conn, dump_debug_info), + MYSQLND_METHOD(mysqlnd_conn, change_user), + + MYSQLND_METHOD(mysqlnd_conn, errno), + MYSQLND_METHOD(mysqlnd_conn, error), + MYSQLND_METHOD(mysqlnd_conn, sqlstate), + MYSQLND_METHOD(mysqlnd_conn, thread_id), + + MYSQLND_METHOD(mysqlnd_conn, get_connection_stats), + + MYSQLND_METHOD(mysqlnd_conn, get_server_version), + MYSQLND_METHOD(mysqlnd_conn, get_server_info), + MYSQLND_METHOD(mysqlnd_conn, stat), + MYSQLND_METHOD(mysqlnd_conn, get_host_info), + MYSQLND_METHOD(mysqlnd_conn, get_proto_info), + MYSQLND_METHOD(mysqlnd_conn, info), + MYSQLND_METHOD(mysqlnd_conn, charset_name), + MYSQLND_METHOD(mysqlnd_conn, list_fields), + MYSQLND_METHOD(mysqlnd_conn, list_method), + + MYSQLND_METHOD(mysqlnd_conn, insert_id), + MYSQLND_METHOD(mysqlnd_conn, affected_rows), + MYSQLND_METHOD(mysqlnd_conn, warning_count), + MYSQLND_METHOD(mysqlnd_conn, field_count), + + MYSQLND_METHOD(mysqlnd_conn, set_server_option), + MYSQLND_METHOD(mysqlnd_conn, set_client_option), + MYSQLND_METHOD(mysqlnd_conn, free_contents), + MYSQLND_METHOD(mysqlnd_conn, close), + + MYSQLND_METHOD_PRIVATE(mysqlnd_conn, dtor), + + MYSQLND_METHOD_PRIVATE(mysqlnd_conn, get_reference), + MYSQLND_METHOD_PRIVATE(mysqlnd_conn, free_reference), +MYSQLND_CLASS_METHODS_END; + + +/* {{{ mysqlnd_init */ +PHPAPI MYSQLND *_mysqlnd_init(zend_bool persistent TSRMLS_DC) +{ + MYSQLND *ret = mnd_pecalloc(1, sizeof(MYSQLND), persistent); + + DBG_ENTER("mysqlnd_init"); + DBG_INF_FMT("persistent=%d", persistent); + + SET_ERROR_AFF_ROWS(ret); + ret->persistent = persistent; + + ret->m = & mysqlnd_mysqlnd_conn_methods; + ret->m->get_reference(ret); + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_functions[] + * + * Every user visible function must have an entry in mysqlnd_functions[]. + */ +static zend_function_entry mysqlnd_functions[] = { + {NULL, NULL, NULL} /* Must be the last line in mysqlnd_functions[] */ +}; +/* }}} */ + + +/* {{{ mysqlnd_minfo_print_hash */ +#if PHP_MAJOR_VERSION >= 6 +PHPAPI void mysqlnd_minfo_print_hash(zval *values) +{ + zval **values_entry; + HashPosition pos_values; + + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(values), &pos_values); + while (zend_hash_get_current_data_ex(Z_ARRVAL_P(values), + (void **)&values_entry, &pos_values) == SUCCESS) { + TSRMLS_FETCH(); + zstr string_key; + uint string_key_len; + ulong num_key; + char *s = NULL; + + zend_hash_get_current_key_ex(Z_ARRVAL_P(values), &string_key, &string_key_len, &num_key, 0, &pos_values); + + convert_to_string(*values_entry); + + if (UG(unicode)) { + int s_len; + if (zend_unicode_to_string(ZEND_U_CONVERTER(UG(runtime_encoding_conv)), + &s, &s_len, string_key.u, string_key_len TSRMLS_CC) == SUCCESS) { + php_info_print_table_row(2, s, Z_STRVAL_PP(values_entry)); + } + if (s) { + mnd_efree(s); + } + } else { + php_info_print_table_row(2, string_key.s, Z_STRVAL_PP(values_entry)); + } + + zend_hash_move_forward_ex(Z_ARRVAL_P(values), &pos_values); + } +} +#else +void mysqlnd_minfo_print_hash(zval *values) +{ + zval **values_entry; + HashPosition pos_values; + + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(values), &pos_values); + while (zend_hash_get_current_data_ex(Z_ARRVAL_P(values), (void **)&values_entry, &pos_values) == SUCCESS) { + char *string_key; + uint string_key_len; + ulong num_key; + + zend_hash_get_current_key_ex(Z_ARRVAL_P(values), &string_key, &string_key_len, &num_key, 0, &pos_values); + + convert_to_string(*values_entry); + php_info_print_table_row(2, string_key, Z_STRVAL_PP(values_entry)); + + zend_hash_move_forward_ex(Z_ARRVAL_P(values), &pos_values); + } +} +#endif +/* }}} */ + + +/* {{{ PHP_MINFO_FUNCTION + */ +PHP_MINFO_FUNCTION(mysqlnd) +{ + char buf[32]; + zval values; + + php_info_print_table_start(); + php_info_print_table_header(2, "mysqlnd", "enabled"); + php_info_print_table_row(2, "Version", mysqlnd_get_client_info()); + + /* Print client stats */ + php_info_print_table_header(2, "Client statistics", ""); + mysqlnd_get_client_stats(&values); + mysqlnd_minfo_print_hash(&values); + php_info_print_table_row(2, "Collecting statistics", MYSQLND_G(collect_statistics)? "Yes":"No"); + php_info_print_table_row(2, "Collecting memory statistics", MYSQLND_G(collect_memory_statistics)? "Yes":"No"); + + snprintf(buf, sizeof(buf), "%ld", MYSQLND_G(net_cmd_buffer_size)); + php_info_print_table_row(2, "Command buffer size", buf); + snprintf(buf, sizeof(buf), "%ld", MYSQLND_G(net_read_buffer_size)); + php_info_print_table_row(2, "Read buffer size", buf); + + zval_dtor(&values); + php_info_print_table_end(); +} +/* }}} */ + + +ZEND_DECLARE_MODULE_GLOBALS(mysqlnd); + + +/* {{{ PHP_GINIT_FUNCTION + */ +static PHP_GINIT_FUNCTION(mysqlnd) +{ + mysqlnd_globals->collect_statistics = TRUE; + mysqlnd_globals->collect_memory_statistics = FALSE; + mysqlnd_globals->debug = NULL; /* The actual string */ + mysqlnd_globals->dbg = NULL; /* The DBG object*/ + mysqlnd_globals->net_cmd_buffer_size = 2048; + mysqlnd_globals->net_read_buffer_size = 32768; +} +/* }}} */ + + +/* {{{ PHP_INI_BEGIN +*/ +PHP_INI_BEGIN() + STD_PHP_INI_BOOLEAN("mysqlnd.collect_statistics", "1", PHP_INI_ALL, OnUpdateBool, collect_statistics, zend_mysqlnd_globals, mysqlnd_globals) + STD_PHP_INI_BOOLEAN("mysqlnd.collect_memory_statistics", "0", PHP_INI_SYSTEM, OnUpdateBool, collect_memory_statistics, zend_mysqlnd_globals, mysqlnd_globals) + STD_PHP_INI_ENTRY("mysqlnd.debug", NULL, PHP_INI_SYSTEM, OnUpdateString, debug, zend_mysqlnd_globals, mysqlnd_globals) + STD_PHP_INI_ENTRY("mysqlnd.net_cmd_buffer_size", "2048", PHP_INI_ALL, OnUpdateLong, net_cmd_buffer_size, zend_mysqlnd_globals, mysqlnd_globals) + STD_PHP_INI_ENTRY("mysqlnd.net_read_buffer_size", "32768",PHP_INI_ALL, OnUpdateLong, net_read_buffer_size, zend_mysqlnd_globals, mysqlnd_globals) +PHP_INI_END() +/* }}} */ + + +/* {{{ PHP_MINIT_FUNCTION + */ +static PHP_MINIT_FUNCTION(mysqlnd) +{ + REGISTER_INI_ENTRIES(); + + mysqlnd_library_init(); + return SUCCESS; +} +/* }}} */ + + +/* {{{ PHP_MSHUTDOWN_FUNCTION + */ +static PHP_MSHUTDOWN_FUNCTION(mysqlnd) +{ + mysqlnd_library_end(); + + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + + +#if PHP_DEBUG +/* {{{ PHP_RINIT_FUNCTION + */ +static PHP_RINIT_FUNCTION(mysqlnd) +{ +#ifdef PHP_DEBUG + if (MYSQLND_G(debug)) { + MYSQLND_DEBUG *dbg = mysqlnd_debug_init(TSRMLS_C); + if (!dbg) { + return FAILURE; + } + dbg->m->set_mode(dbg, MYSQLND_G(debug)); + MYSQLND_G(dbg) = dbg; + } +#endif + return SUCCESS; +} +/* }}} */ + + +/* {{{ PHP_RSHUTDOWN_FUNCTION + */ +static PHP_RSHUTDOWN_FUNCTION(mysqlnd) +{ +#ifdef PHP_DEBUG + MYSQLND_DEBUG *dbg = MYSQLND_G(dbg); + DBG_ENTER("RSHUTDOWN"); + if (dbg) { + dbg->m->close(dbg); + dbg->m->free(dbg); + MYSQLND_G(dbg) = NULL; + } +#endif + return SUCCESS; +} +/* }}} */ +#endif + + +/* {{{ mysqlnd_module_entry + */ +zend_module_entry mysqlnd_module_entry = { + STANDARD_MODULE_HEADER, + "mysqlnd", + mysqlnd_functions, + PHP_MINIT(mysqlnd), + PHP_MSHUTDOWN(mysqlnd), +#if PHP_DEBUG + PHP_RINIT(mysqlnd), + PHP_RSHUTDOWN(mysqlnd), +#else + NULL, + NULL, +#endif + PHP_MINFO(mysqlnd), + MYSQLND_VERSION, + PHP_MODULE_GLOBALS(mysqlnd), + PHP_GINIT(mysqlnd), + NULL, + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; +/* }}} */ + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/ext/mysqlnd/mysqlnd.h b/ext/mysqlnd/mysqlnd.h new file mode 100644 index 0000000000..f648207fdc --- /dev/null +++ b/ext/mysqlnd/mysqlnd.h @@ -0,0 +1,354 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef MYSQLND_H +#define MYSQLND_H + +#define MYSQLND_VERSION "mysqlnd 5.0.2-dev - 070928 - $Revision$" +#define MYSQLND_VERSION_ID 50002 + +/* This forces inlining of some accessor functions */ +#define MYSQLND_USE_OPTIMISATIONS 0 + +/* #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 PHP_DEBUG +#define MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND 1 +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +#include "mysqlnd_portability.h" +#include "mysqlnd_enum_n_def.h" +#include "mysqlnd_structs.h" + + +/* Library related */ +#define mysqlnd_restart_psession(conn) _mysqlnd_restart_psession((conn) TSRMLS_CC) +PHPAPI void _mysqlnd_restart_psession(MYSQLND *conn TSRMLS_DC); +PHPAPI void mysqlnd_end_psession(MYSQLND *conn); +PHPAPI void mysqlnd_minfo_print_hash(zval *values); +#define mysqlnd_thread_safe() TRUE + +PHPAPI const MYSQLND_CHARSET * mysqlnd_find_charset_nr(uint 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, + char *host, char *user, + char *passwd, unsigned int passwd_len, + char *db, unsigned int db_len, + unsigned int port, + char *socket, + unsigned int mysql_flags, + MYSQLND_THD_ZVAL_PCACHE *zval_cache + TSRMLS_DC); + +#define mysqlnd_change_user(conn, user, passwd, db) (conn)->m->change_user((conn), (user), (passwd), (db) TSRMLS_CC) + +#define mysqlnd_debug(x) _mysqlnd_debug((x) TSRMLS_CC) +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_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)->m->get_statistics((conn), (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)->m->query((conn), (query_str), (query_len) TSRMLS_CC) +#define mysqlnd_unbuffered_skip_result(result) (result)->m.skip_result((result) TSRMLS_CC) + + +#define mysqlnd_use_result(conn) (conn)->m->use_result((conn) TSRMLS_CC) +#define mysqlnd_store_result(conn) (conn)->m->store_result((conn) TSRMLS_CC) +#define mysqlnd_next_result(conn) (conn)->m->next_result((conn) TSRMLS_CC) +#define mysqlnd_more_results(conn) (conn)->m->more_results((conn)) +#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) + +/*****************************************************************************************************/ +#if defined(MYSQLND_USE_OPTIMISATIONS) && MYSQLND_USE_OPTIMISATIONS == 1 + +/* Errors */ +#define mysqlnd_errno(conn) (conn)->error_info.error_no +#define mysqlnd_error(conn) (conn)->error_info.error +#define mysqlnd_sqlstate(conn) ((conn)->error_info.sqlstate[0] ? conn->error_info.sqlstate:MYSQLND_SQLSTATE_NULL) + +/* Charset */ +#define mysqlnd_character_set_name(conn) (conn)->charset->name + +/* Simple metadata */ +#define mysqlnd_field_count(conn) (conn)->field_count +#define mysqlnd_insert_id(conn) (conn)->upsert_status.last_insert_id +#define mysqlnd_affected_rows(conn) (conn)->upsert_status.affected_rows +#define mysqlnd_warning_count(conn) (conn)->upsert_status.warning_count +#define mysqlnd_info(conn) (conn)->last_message +#define mysqlnd_get_server_info(conn) (conn)->server_version +#define mysqlnd_get_host_info(conn) (conn)->host_info +#define mysqlnd_get_proto_info(conn) (conn)->protocol_version +#define mysqlnd_thread_id(conn) (conn)->thread_id + +#define mysqlnd_num_rows(result) ((result)->data? (result)->data->row_count:0) +#define mysqlnd_num_fields(result) (result)->field_count + +#define mysqlnd_fetch_lengths(result) ((result)->m.fetch_lengths? (result)->m.fetch_lengths((result)):NULL) + +#define mysqlnd_field_seek(result, ofs) (result)->m.seek_field((result), (ofs)) +#define mysqlnd_field_tell(result) (result)->meta? (result)->meta->current_field:0) +#define mysqlnd_fetch_field(result) (result)->m.fetch_field((result) TSRMLS_CC) +#define mysqlnd_fetch_field_direct(result,fnr) ((result)->meta? &((result)->meta->fields[(fnr)]):NULL) + +/* mysqlnd metadata */ +#define mysqlnd_get_client_info() MYSQLND_VERSION +#define mysqlnd_get_client_version() MYSQLND_VERSION_ID + +/* PS */ +#define mysqlnd_stmt_insert_id(stmt) (stmt)->upsert_status.last_insert_id +#define mysqlnd_stmt_affected_rows(stmt) (stmt)->upsert_status.affected_rows +#define mysqlnd_stmt_num_rows(stmt) (stmt)->result? mysqlnd_num_rows((stmt)->result):0 +#define mysqlnd_stmt_param_count(stmt) (stmt)->param_count +#define mysqlnd_stmt_field_count(stmt) (stmt)->field_count +#define mysqlnd_stmt_warning_count(stmt) (stmt)->upsert_status.warning_count +#define mysqlnd_stmt_errno(stmt) (stmt)->error_info.error_no +#define mysqlnd_stmt_error(stmt) (stmt)->error_info.error +#define mysqlnd_stmt_sqlstate(stmt) ((stmt)->error_info.sqlstate[0] ? (stmt)->error_info.sqlstate:MYSQLND_SQLSTATE_NULL) + + + +/*****************************************************************************************************/ +#else /* Using plain functions */ +/*****************************************************************************************************/ + +/* Errors */ +#define mysqlnd_errno(conn) (conn)->m->get_error_no((conn)) +#define mysqlnd_error(conn) (conn)->m->get_error_str((conn)) +#define mysqlnd_sqlstate(conn) (conn)->m->get_sqlstate((conn)) + +/* Charset */ +#define mysqlnd_character_set_name(conn) (conn)->m->charset_name((conn)) + +/* Simple metadata */ +#define mysqlnd_field_count(conn) (conn)->m->get_field_count((conn)) +#define mysqlnd_insert_id(conn) (conn)->m->get_last_insert_id((conn)) +#define mysqlnd_affected_rows(conn) (conn)->m->get_affected_rows((conn)) +#define mysqlnd_warning_count(conn) (conn)->m->get_warning_count((conn)) +#define mysqlnd_info(conn) (conn)->m->get_last_message((conn)) +#define mysqlnd_get_server_info(conn) (conn)->m->get_server_information((conn)) +#define mysqlnd_get_host_info(conn) (conn)->m->get_host_information((conn)) +#define mysqlnd_get_proto_info(conn) (conn)->m->get_protocol_information((conn)) +#define mysqlnd_thread_id(conn) (conn)->m->get_thread_id((conn)) + +#define mysqlnd_num_rows(result) (result)->m.num_rows((result)) +#define mysqlnd_num_fields(result) (result)->m.num_fields((result)) + +PHPAPI unsigned long * mysqlnd_fetch_lengths(MYSQLND_RES * const result); + +#define mysqlnd_field_seek(result, ofs) (result)->m.seek_field((result), (ofs)) +#define mysqlnd_field_tell(result) (result)->m.field_tell((result)) +#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) + +/* mysqlnd metadata */ +PHPAPI const char * mysqlnd_get_client_info(); +PHPAPI unsigned int mysqlnd_get_client_version(); + +/* PS */ +#define mysqlnd_stmt_insert_id(stmt) (stmt)->m->get_last_insert_id((stmt)) +#define mysqlnd_stmt_affected_rows(stmt) (stmt)->m->get_affected_rows((stmt)) +#define mysqlnd_stmt_num_rows(stmt) (stmt)->m->get_num_rows((stmt)) +#define mysqlnd_stmt_param_count(stmt) (stmt)->m->get_param_count((stmt)) +#define mysqlnd_stmt_field_count(stmt) (stmt)->m->get_field_count((stmt)) +#define mysqlnd_stmt_warning_count(stmt) (stmt)->m->get_warning_count((stmt)) +#define mysqlnd_stmt_errno(stmt) (stmt)->m->get_error_no((stmt)) +#define mysqlnd_stmt_error(stmt) (stmt)->m->get_error_str((stmt)) +#define mysqlnd_stmt_sqlstate(stmt) (stmt)->m->get_sqlstate((stmt)) +#endif /* MYSQLND_USE_OPTIMISATIONS */ +/*****************************************************************************************************/ + + + +PHPAPI const char * mysqlnd_field_type_name(enum mysqlnd_field_types field_type); + +/* LOAD DATA LOCAL */ +PHPAPI void mysqlnd_local_infile_default(MYSQLND *conn); +PHPAPI void mysqlnd_set_local_infile_handler(MYSQLND * const conn, const char * const funcname); + +/* Simple commands */ +#define mysqlnd_autocommit(conn, mode) (conn)->m->query((conn),(mode) ? "SET AUTOCOMMIT=1":"SET AUTOCOMMIT=0", 16 TSRMLS_CC) +#define mysqlnd_commit(conn) (conn)->m->query((conn), "COMMIT", sizeof("COMMIT")-1 TSRMLS_CC) +#define mysqlnd_rollback(conn) (conn)->m->query((conn), "ROLLBACK", sizeof("ROLLBACK")-1 TSRMLS_CC) +#define mysqlnd_list_dbs(conn, wild) (conn)->m->list_method((conn), wild? "SHOW DATABASES LIKE %s":"SHOW DATABASES", (wild), NULL TSRMLS_CC) +#define mysqlnd_list_fields(conn, tab,wild) (conn)->m->list_fields((conn), (tab), (wild) TSRMLS_CC) +#define mysqlnd_list_processes(conn) (conn)->m->list_method((conn), "SHOW PROCESSLIST", NULL, NULL TSRMLS_CC) +#define mysqlnd_list_tables(conn, wild) (conn)->m->list_method((conn), wild? "SHOW TABLES LIKE %s":"SHOW TABLES", (wild), NULL TSRMLS_CC) +#define mysqlnd_dump_debug_info(conn) (conn)->m->server_dump_debug_information((conn) TSRMLS_CC) +#define mysqlnd_select_db(conn, db, db_len) (conn)->m->select_db((conn), (db), (db_len) TSRMLS_CC) +#define mysqlnd_ping(conn) (conn)->m->ping((conn) TSRMLS_CC) +#define mysqlnd_kill(conn, pid) (conn)->m->kill_connection((conn), (pid) TSRMLS_CC) +#define mysqlnd_refresh(conn, options) (conn)->m->refresh_server((conn), (options) TSRMLS_CC) +#define mysqlnd_shutdown(conn, level) (conn)->m->shutdown_server((conn), (level) TSRMLS_CC) +#define mysqlnd_get_server_version(conn) (conn)->m->get_server_version((conn)) +#define mysqlnd_set_character_set(conn, cs) (conn)->m->set_charset((conn), (cs) TSRMLS_CC) +#define mysqlnd_stat(conn, msg, msg_len) (conn)->m->get_server_statistics((conn), (msg), (msg_len) TSRMLS_CC) +#define mysqlnd_options(conn, opt, value) (conn)->m->set_client_option((conn), (opt), (value) TSRMLS_CC) +#define mysqlnd_set_server_option(conn, op) (conn)->m->set_server_option((conn), (op) TSRMLS_CC) + +/* Escaping */ +#define mysqlnd_real_escape_string(conn, newstr, escapestr, escapestr_len) \ + (conn)->m->escape_string((conn), (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, int escapestr_len TSRMLS_DC); + + +/* PS */ +#define mysqlnd_stmt_init(conn) (conn)->m->stmt_init((conn) 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_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(s,p,d,l) (s)->m->send_long_data((s), (p), (d), (l) TSRMLS_CC) +#define mysqlnd_stmt_bind_param(stmt,bind) (stmt)->m->bind_param((stmt), (bind) TSRMLS_CC) +#define mysqlnd_stmt_bind_result(stmt,bind) (stmt)->m->bind_result((stmt), (bind) 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_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); + +/* Persistent caching zval allocator */ +#define mysqlnd_palloc_init_cache(size) _mysqlnd_palloc_init_cache((size) TSRMLS_CC) +#define mysqlnd_palloc_free_cache(cache) _mysqlnd_palloc_free_cache((cache) TSRMLS_CC) +PHPAPI MYSQLND_ZVAL_PCACHE* _mysqlnd_palloc_init_cache(unsigned int cache_size TSRMLS_DC); +PHPAPI void _mysqlnd_palloc_free_cache(MYSQLND_ZVAL_PCACHE *cache TSRMLS_DC); +PHPAPI void mysqlnd_palloc_stats(const MYSQLND_ZVAL_PCACHE * const cache, + zval *return_value); + +#define mysqlnd_palloc_rinit(cache) _mysqlnd_palloc_rinit((cache) TSRMLS_CC) +#define mysqlnd_palloc_rshutdown(cache) _mysqlnd_palloc_rshutdown((cache) TSRMLS_CC) +PHPAPI MYSQLND_THD_ZVAL_PCACHE * _mysqlnd_palloc_rinit(MYSQLND_ZVAL_PCACHE * cache TSRMLS_DC); +PHPAPI void _mysqlnd_palloc_rshutdown(MYSQLND_THD_ZVAL_PCACHE * cache TSRMLS_DC); + + +#define mysqlnd_palloc_init_thd_cache(cache) _mysqlnd_palloc_init_thd_cache((cache) TSRMLS_CC) +#define mysqlnd_palloc_free_thd_cache_reference(cache) _mysqlnd_palloc_free_thd_cache_reference((cache) TSRMLS_CC) + +PHPAPI MYSQLND_THD_ZVAL_PCACHE* _mysqlnd_palloc_init_thd_cache(MYSQLND_ZVAL_PCACHE * const cache TSRMLS_DC); +MYSQLND_THD_ZVAL_PCACHE* mysqlnd_palloc_get_thd_cache_reference(MYSQLND_THD_ZVAL_PCACHE * const cache); +PHPAPI void _mysqlnd_palloc_free_thd_cache_reference(MYSQLND_THD_ZVAL_PCACHE **cache TSRMLS_DC); + + +/* There two should not be used from outside */ +void * mysqlnd_palloc_get_zval(MYSQLND_THD_ZVAL_PCACHE * const cache, zend_bool *allocated TSRMLS_DC); +void mysqlnd_palloc_zval_ptr_dtor(zval **zv, MYSQLND_THD_ZVAL_PCACHE * const cache, + enum_mysqlnd_res_type type, zend_bool *copy_ctor_called TSRMLS_DC); + + + +/* ---------------------- QUERY CACHE ---------------*/ +struct st_mysqlnd_qcache { + HashTable *ht; + unsigned int references; +#ifdef ZTS + MUTEX_T LOCK_access; +#endif +}; + + +typedef struct st_mysqlnd_qcache_element { + MYSQLND_RES_BUFFERED *data; + MYSQLND_RES_METADATA *meta; + const char * query; + size_t query_len; +} MYSQLND_QCACHE_ELEMENT; + + +PHPAPI MYSQLND_QCACHE * mysqlnd_qcache_init_cache(); +PHPAPI MYSQLND_QCACHE * mysqlnd_qcache_get_cache_reference(MYSQLND_QCACHE * const cache); +PHPAPI void mysqlnd_qcache_free_cache_reference(MYSQLND_QCACHE **cache); +PHPAPI void mysqlnd_qcache_stats(const MYSQLND_QCACHE * const cache, zval *return_value); +MYSQLND_RES * mysqlnd_qcache_get(MYSQLND_QCACHE * const cache, const char * query, + size_t query_len); +void mysqlnd_qcache_put(MYSQLND_QCACHE * const cache, char * query, size_t query_len, + MYSQLND_RES_BUFFERED * const result, MYSQLND_RES_METADATA * const meta); + + + +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; +ZEND_END_MODULE_GLOBALS(mysqlnd) + +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 + + +#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 0000000000..9b3b819fcd --- /dev/null +++ b/ext/mysqlnd/mysqlnd_alloc.c @@ -0,0 +1,285 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ + +*/ + +/* $Id$ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_palloc.h" + + +#define MYSQLND_SILENT +#define MYSQLND_DONT_DUMP_STATS + +#define MYSQLND_ZVALS_MAX_CACHE 5000 + + +#if A0 +/* Caching zval allocator */ +zval * mysqlnd_alloc_get_zval(MYSQLND_ZVAL_CACHE * const cache); +void mysqlnd_alloc_zval_ptr_dtor(zval **zv, MYSQLND_ZVAL_CACHE * const cache); +MYSQLND_ZVAL_CACHE* mysqlnd_alloc_init_cache(); +MYSQLND_ZVAL_CACHE* mysqlnd_alloc_get_cache_reference(MYSQLND_ZVAL_CACHE *cache); +void mysqlnd_alloc_free_cache_reference(MYSQLND_ZVAL_CACHE **cache); +#endif + + +/* + The cache line is a big contiguous array of zval pointers. + Because the CPU cache will cache starting from an address, and not + before it, then we have to organize our structure according to this. + Thus, if 'last_added' is valid pointer (not NULL) then last_added is + increased. When zval is cached, if there is room, last_added is decreased + and then the zval pointer will be assigned to it. This means that some + positions may become hot points and stay in the cache. + Imagine we have 5 pointers in a line + 1. last_added = list_item->ptr_line + cache->max_items; + 2. get_zval -> *last_added = NULL. Use MAKE_STD_ZVAL + 3. get_zval -> *last_added = NULL. Use MAKE_STD_ZVAL + 4. get_zval -> *last_added = NULL. Use MAKE_STD_ZVAL + 0x0 + 0x0 + 0x0 + 0x0 + 0x0 + --- + empty_position, always 0x0 <-- last_added + + 5. free_zval -> if (free_items++ != max_items) {// we can add more + *(--last_added) = zval_ptr; + } + (memory addresses increase downwards) + 0x0 + 0x0 + 0x0 + 0x0 + 0xA <-- last_added + --- + 0x0 + + 6. free_zval -> if (free_items++ != max_items) {// we can add more + *(--last_added) = zval_ptr; + } + 0x0 + 0x0 + 0x0 + 0xB <-- last_added + 0xA + --- + 0x0 + + 7. free_zval -> if (free_items++ != max_items) {// we can add more + *(--last_added) = zval_ptr; + } + 0x0 + 0x0 + 0xC <-- last_added + 0xB + 0xA + --- + 0x0 + + 8. get_zval -> *last_added != NULL. -> p = *last_added; *last_added++ = NULL; + 0x0 + 0x0 + 0x0 + 0xB <-- last_added + 0xA + --- + 0x0 + + 9. get_zval -> *last_added != NULL. -> p = *last_added; *last_added++ = NULL; + 0x0 + 0x0 + 0x0 + 0x0 + 0xA <-- last_added + --- + 0x0 + +10. get_zval -> *last_added != NULL. -> p = *last_added; *last_added++ = NULL; + 0x0 + 0x0 + 0x0 + 0x0 + 0x0 + --- + 0x0 <-- last_added + +*/ + + +zval * mysqlnd_alloc_get_zval(MYSQLND_ZVAL_CACHE * const cache) +{ + zval *ret = NULL; + +#ifndef MYSQLND_SILENT + php_printf("[mysqlnd_alloc_get_zval %p] *last_added=%p free_items=%d ", cache, cache? cache->free_list->last_added:NULL, cache->free_items); +#endif + + if (cache) { + if ((ret = *cache->free_list->last_added)) { + *cache->free_list->last_added++ = NULL; + + --cache->free_items; + ++cache->get_hits; + } else { + ++cache->get_misses; + } + } + if (!ret) { + ALLOC_ZVAL(ret); + } + INIT_PZVAL(ret); + +#ifndef MYSQLND_SILENT + php_printf("ret=%p\n", ret); +#endif + return ret; +} + +static +void mysqlnd_alloc_cache_prealloc(MYSQLND_ZVAL_CACHE * const cache, unsigned int count) +{ + zval *data; + cache->free_items = count; + while (count--) { + MAKE_STD_ZVAL(data); + ZVAL_NULL(data); +#ifndef MYSQLND_SILENT + php_printf("[mysqlnd_alloc_prealloc %p] items=%d data=%p\n", cache, cache->free_items, data); +#endif + + *(--cache->free_list->last_added) = data; + } +} + +void mysqlnd_alloc_zval_ptr_dtor(zval **zv, MYSQLND_ZVAL_CACHE * const cache) +{ + if (!cache || ZVAL_REFCOUNT(*zv) > 1 || cache->max_items == cache->free_items) { +#ifndef MYSQLND_SILENT + php_printf("[mysqlnd_alloc_zval_ptr_dtor %p]1 last_added-1=%p *zv=%p\n", cache->free_list->last_added, *zv); +#endif + /* We can't cache zval's with refcount > 1 */ + zval_ptr_dtor(zv); + if (cache) { + if (cache->max_items == cache->free_items) { + ++cache->put_full_misses; + } else { + ++cache->put_refcount_misses; + } + } + } else { + /* refcount is 1 and there is place. Go, cache it! */ + ++cache->free_items; + zval_dtor(*zv); + ZVAL_NULL(*zv); + *(--cache->free_list->last_added) = *zv; + ++cache->put_hits; + } +#ifndef MYSQLND_SILENT + php_printf("[mysqlnd_alloc_zval_ptr_dtor %p] free_items=%d\n", cache, cache->free_items); +#endif +} + + +MYSQLND_ZVAL_CACHE* mysqlnd_alloc_init_cache(void) +{ + MYSQLND_ZVAL_CACHE *ret = ecalloc(1, sizeof(MYSQLND_ZVAL_CACHE)); + +#ifndef MYSQLND_SILENT + php_printf("[mysqlnd_alloc_init_cache %p]\n", ret); +#endif + + ret->max_items = MYSQLND_ZVALS_MAX_CACHE; + ret->free_items = 0; + ret->references = 1; + + /* Let's have always one, so we don't need to do a check in get_zval */ + ret->free_list = ecalloc(1, sizeof(struct st_mysqlnd_zval_list)); + + /* One more for empty position of last_added */ + ret->free_list->ptr_line = ecalloc(ret->max_items + 1, sizeof(zval *)); + ret->free_list->last_added = ret->free_list->ptr_line + ret->max_items; + + mysqlnd_alloc_cache_prealloc(ret, (ret->max_items / 100) * 100); + + return ret; +} + + +MYSQLND_ZVAL_CACHE* mysqlnd_alloc_get_cache_reference(MYSQLND_ZVAL_CACHE *cache) +{ + if (cache) { + cache->references++; + } + return cache; +} + + +static +void mysqlnd_alloc_free_cache(MYSQLND_ZVAL_CACHE *cache) +{ +#ifndef MYSQLND_SILENT + uint i = 0; + php_printf("[mysqlnd_alloc_free_cache %p]\n", cache); +#endif + + while (*cache->free_list->last_added) { +#ifndef MYSQLND_SILENT + php_printf("\t[free_item %d %p]\n", i++, *cache->free_list->last_added); +#endif + zval_ptr_dtor(cache->free_list->last_added); + cache->free_list->last_added++; + } +#ifndef MYSQLND_DONT_DUMP_STATS + php_printf("CACHE STATS:\n\tGET\n\t\tHITS:%lu\n\t\tMISSES=%lu\n\t\tHIT RATIO=%1.3f\n\t" + "PUT\n\t\tHITS:%lu\n\t\tFULL_MISS=%lu\n\t\tREFC_MISS=%lu\n\t\tHIT RATIO=%1.3f\n\n", + cache->get_hits, cache->get_misses, (1.0*cache->get_hits/(cache->get_hits + cache->get_misses)), + cache->put_hits, cache->put_full_misses, cache->put_refcount_misses, + (1.0 * cache->put_hits / (cache->put_hits + cache->put_full_misses + cache->put_refcount_misses))); +#endif + efree(cache->free_list->ptr_line); + efree(cache->free_list); + efree(cache); +} + + + +void mysqlnd_alloc_free_cache_reference(MYSQLND_ZVAL_CACHE **cache) +{ +#ifndef MYSQLND_SILENT + php_printf("[mysqlnd_alloc_free_cache_reference %p] refs=%d\n", *cache, (*cache)->references); +#endif + if (*cache && --(*cache)->references == 0) { + mysqlnd_alloc_free_cache(*cache); + } + *cache = 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_charset.c b/ext/mysqlnd/mysqlnd_charset.c new file mode 100644 index 0000000000..cd6112af9f --- /dev/null +++ b/ext/mysqlnd/mysqlnd_charset.c @@ -0,0 +1,603 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ +#include "php.h" +#include "php_globals.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_debug.h" + +/* {{{ utf8 functions */ + +static uint 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; + } + return 0; +} + +static uint check_mb_utf8_valid(const char *start, const char *end) +{ + uint len = check_mb_utf8_sequence(start, end); + return (len > 1)? len:0; +} + +static uint mysqlnd_mbcharlen_utf8(uint 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 */ + } + /* We still don't support characters out of the BMP */ + + return 0; +} +/* }}} */ + + +/* {{{ big5 functions */ +#define valid_big5head(c) (0xA1 <= (uint)(c) && (uint)(c) <= 0xF9) +#define valid_big5tail(c) ((0x40 <= (uint)(c) && (uint)(c) <= 0x7E) || \ + (0xA1 <= (uint)(c) && (uint)(c) <= 0xFE)) + +#define isbig5code(c,d) (isbig5head(c) && isbig5tail(d)) + +static uint check_mb_big5(const char *start, const char *end) +{ + return (valid_big5head(*(start)) && (end - start) > 1 && valid_big5tail(*(start + 1)) ? 2 : 0); +} + + +static uint mysqlnd_mbcharlen_big5(uint 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 uint 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 uint mysqlnd_mbcharlen_cp932(uint cp932) +{ + return (valid_cp932head((zend_uchar)cp932)) ? 2 : 1; +} +/* }}} */ + + +/* {{{ euckr functions */ +#define valid_euckr(c) ((0xA1 <= (zend_uchar)(c) && (zend_uchar)(c) <= 0xFE)) + +static uint 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 uint mysqlnd_mbcharlen_euckr(uint 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 uint 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 uint mysqlnd_mbcharlen_eucjpms(uint 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 uint check_mb_gb2312(const char *start, const char *end) +{ + return (valid_gb2312_head((uint)start[0]) && end - start > 1 && + valid_gb2312_tail((uint)start[1])) ? 2 : 0; +} + + +static uint mysqlnd_mbcharlen_gb2312(uint 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 uint 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 uint mysqlnd_mbcharlen_gbk(uint 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) <= 0x7C)) + + +static uint 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 uint mysqlnd_mbcharlen_sjis(uint sjis) +{ + return (valid_sjis_head((zend_uchar)sjis)) ? 2 : 1; +} +/* }}} */ + + +/* {{{ ucs2 functions */ +static uint check_mb_ucs2(const char *start __attribute((unused)), const char *end __attribute((unused))) +{ + return 2; /* always 2 */ +} + +static uint mysqlnd_mbcharlen_ucs2(uint 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 uint check_mb_ujis(const char *start, const char *end) +{ + if (*(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 uint mysqlnd_mbcharlen_ujis(uint ujis) +{ + return (valid_ujis(ujis)? 2: valid_ujis_ss2(ujis)? 2: valid_ujis_ss3(ujis)? 3: 1); +} +/* }}} */ + + + +/* {{{ mysqlnd_charsets */ +const MYSQLND_CHARSET mysqlnd_charsets[] = +{ + { 1, "big5","big5_chinese_ci", 1, 2, 0, mysqlnd_mbcharlen_big5, check_mb_big5}, + { 3, "dec8", "dec8_swedisch_ci", 1, 1, 0, NULL, NULL}, + { 4, "cp850", "cp850_general_ci", 1, 1, 0, NULL, NULL}, + { 6, "hp8", "hp8_english_ci", 1, 1, 0, NULL, NULL}, + { 7, "koi8r", "koi8r_general_ci", 1, 1, 0, NULL, NULL}, + { 8, "latin1", "latin1_swedish_ci", 1, 1, 0, NULL, NULL}, + { 9, "latin2", "latin2_general_ci", 1, 1, 0, NULL, NULL}, + { 10, "swe7", "swe7_swedish_ci", 1, 1, 0, NULL, NULL}, + { 11, "ascii", "ascii_general_ci", 1, 1, 0, NULL, NULL}, + { 12, "ujis", "ujis_japanese_ci", 1, 3, 0, mysqlnd_mbcharlen_ujis, check_mb_ujis}, + { 13, "sjis", "sjis_japanese_ci", 1, 2, 0, mysqlnd_mbcharlen_sjis, check_mb_sjis}, + { 16, "hebrew", "hebrew_general_ci", 1, 1, 0, NULL, NULL}, + { 18, "tis620", "tis620_thai_ci", 1, 1, 0, NULL, NULL}, + { 19, "euckr", "euckr_korean_ci", 1, 2, 0, mysqlnd_mbcharlen_euckr, check_mb_euckr}, + { 22, "koi8u", "koi8u_general_ci", 1, 1, 0, NULL, NULL}, + { 24, "gb2312", "gb2312_chinese_ci", 1, 2, 0, mysqlnd_mbcharlen_gb2312, check_mb_gb2312}, + { 25, "greek", "greek_general_ci", 1, 1, 0, NULL, NULL}, + { 26, "cp1250", "cp1250_general_ci", 1, 1, 0, NULL, NULL}, + { 28, "gbk", "gbk_chinese_ci", 1, 2, 0, mysqlnd_mbcharlen_gbk, check_mb_gbk}, + { 30, "latin5", "latin5_turkish_ci", 1, 1, 0, NULL, NULL}, + { 32, "armscii8", "armscii8_general_ci", 1, 1, 0, NULL, NULL}, + { 33, "utf8", "utf8_general_ci", 1, 2, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 35, "ucs2", "ucs2_general_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 36, "cp866", "cp866_general_ci", 1, 1, 0, NULL, NULL}, + { 37, "keybcs2", "keybcs2_general_ci", 1, 1, 0, NULL, NULL}, + { 38, "macce", "macce_general_ci", 1, 1, 0, NULL, NULL}, + { 39, "macroman", "macroman_general_ci", 1, 1, 0, NULL, NULL}, + { 40, "cp852", "cp852_general_ci", 1, 1, 0, NULL, NULL}, + { 41, "latin7", "latin7_general_ci", 1, 1, 0, NULL, NULL}, + { 51, "cp1251", "cp1251_general_ci", 1, 1, 0, NULL, NULL}, + { 57, "cp1256", "cp1256_general_ci", 1, 1, 0, NULL, NULL}, + { 59, "cp1257", "cp1257_general_ci", 1, 1, 0, NULL, NULL}, + { 63, "binary", "binary", 1, 1, 0, NULL, NULL}, + { 92, "geostd8", "geostd8_general_ci", 1, 1, 0, NULL, NULL}, + { 95, "cp932", "cp932_japanese_ci", 1, 2, 1, mysqlnd_mbcharlen_cp932, check_mb_cp932}, + { 97, "eucjpms", "eucjpms_japanese_ci", 1, 3, 0, mysqlnd_mbcharlen_eucjpms, check_mb_eucjpms}, + { 2, "latin2", "latin2_czech_cs", 1, 1, 0, NULL, NULL}, + { 5, "latin1", "latin1_german_ci", 1, 1, 0, NULL, NULL}, + { 14, "cp1251", "cp1251_bulgarian_ci", 1, 1, 0, NULL, NULL}, + { 15, "latin1", "latin1_danish_ci", 1, 1, 0, NULL, NULL}, + { 17, "filename", "filename", 1, 5, 1, NULL, NULL}, + { 20, "latin7", "latin7_estonian_cs", 1, 1, 0, NULL, NULL}, + { 21, "latin2", "latin2_hungarian_ci", 1, 1, 0, NULL, NULL}, + { 23, "cp1251", "cp1251_ukrainian_ci", 1, 1, 0, NULL, NULL}, + { 27, "latin2", "latin2_croatian_ci", 1, 1, 0, NULL, NULL}, + { 29, "cp1257", "cp1257_lithunian_ci", 1, 1, 0, NULL, NULL}, + { 31, "latin1", "latin1_german2_ci", 1, 1, 0, NULL, NULL}, + { 34, "cp1250", "cp1250_czech_cs", 1, 1, 0, NULL, NULL}, + { 42, "latin7", "latin7_general_cs", 1, 1, 0, NULL, NULL}, + { 43, "macce", "macce_bin", 1, 1, 0, NULL, NULL}, + { 44, "cp1250", "cp1250_croatian_ci", 1, 1, 0, NULL, NULL}, + { 47, "latin1", "latin1_bin", 1, 1, 0, NULL, NULL}, + { 48, "latin1", "latin1_general_ci", 1, 1, 0, NULL, NULL}, + { 49, "latin1", "latin1_general_cs", 1, 1, 0, NULL, NULL}, + { 50, "cp1251", "cp1251_bin", 1, 1, 0, NULL, NULL}, + { 52, "cp1251", "cp1251_general_cs", 1, 1, 0, NULL, NULL}, + { 53, "macroman", "macroman_bin", 1, 1, 0, NULL, NULL}, + { 58, "cp1257", "cp1257_bin", 1, 1, 0, NULL, NULL}, + { 60, "armascii8", "armascii8_bin", 1, 1, 0, NULL, NULL}, + { 65, "ascii", "ascii_bin", 1, 1, 0, NULL, NULL}, + { 66, "cp1250", "cp1250_bin", 1, 1, 0, NULL, NULL}, + { 67, "cp1256", "cp1256_bin", 1, 1, 0, NULL, NULL}, + { 68, "cp866", "cp866_bin", 1, 1, 0, NULL, NULL}, + { 69, "dec8", "dec8_bin", 1, 1, 0, NULL, NULL}, + { 70, "greek", "greek_bin", 1, 1, 0, NULL, NULL}, + { 71, "hebew", "hebrew_bin", 1, 1, 0, NULL, NULL}, + { 72, "hp8", "hp8_bin", 1, 1, 0, NULL, NULL}, + { 73, "keybcs2", "keybcs2_bin", 1, 1, 0, NULL, NULL}, + { 74, "koi8r", "koi8r_bin", 1, 1, 0, NULL, NULL}, + { 75, "koi8u", "koi8u_bin", 1, 1, 0, NULL, NULL}, + { 77, "latin2", "latin2_bin", 1, 1, 0, NULL, NULL}, + { 78, "latin5", "latin5_bin", 1, 1, 0, NULL, NULL}, + { 79, "latin7", "latin7_bin", 1, 1, 0, NULL, NULL}, + { 80, "cp850", "cp850_bin", 1, 1, 0, NULL, NULL}, + { 81, "cp852", "cp852_bin", 1, 1, 0, NULL, NULL}, + { 82, "swe7", "swe7_bin", 1, 1, 0, NULL, NULL}, + { 93, "geostd8", "geostd8_bin", 1, 1, 0, NULL, NULL}, + { 83, "utf8", "utf8_bin", 1, 2, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 84, "big5", "big5_bin", 1, 2, 0, mysqlnd_mbcharlen_big5, check_mb_big5}, + { 85, "euckr", "euckr_bin", 1, 2, 0, mysqlnd_mbcharlen_euckr, check_mb_euckr}, + { 86, "gb2312", "gb2312_bin", 1, 2, 0, mysqlnd_mbcharlen_gb2312, check_mb_gb2312}, + { 87, "gbk", "gbk_bin", 1, 2, 0, mysqlnd_mbcharlen_gbk, check_mb_gbk}, + { 88, "sjis", "sjis_bin", 1, 2, 0, mysqlnd_mbcharlen_sjis, check_mb_sjis}, + { 89, "tis620", "tis620_bin", 1, 1, 0, NULL, NULL}, + { 90, "ucs2", "ucs2_bin", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 91, "ujis", "ujis_bin", 1, 3, 0, mysqlnd_mbcharlen_ujis, check_mb_ujis}, + { 94, "latin1", "latin1_spanish_ci", 1, 1, 0, NULL, NULL}, + { 96, "cp932", "cp932_bin", 1, 2, 1, mysqlnd_mbcharlen_cp932, check_mb_cp932}, + { 99, "cp1250", "cp1250_polish_ci", 1, 1, 0, NULL, NULL}, + { 98, "eucjpms", "eucjpms_bin", 1, 3, 0, mysqlnd_mbcharlen_eucjpms, check_mb_eucjpms}, + { 128, "ucs2", "ucs2_unicode_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 129, "ucs2", "ucs2_icelandic_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 130, "ucs2", "ucs2_latvian_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 131, "ucs2", "ucs2_romanian_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 132, "ucs2", "ucs2_slovenian_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 133, "ucs2", "ucs2_polish_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 134, "ucs2", "ucs2_estonian_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 135, "ucs2", "ucs2_spanish_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 136, "ucs2", "ucs2_swedish_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 137, "ucs2", "ucs2_turkish_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 138, "ucs2", "ucs2_czech_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 139, "ucs2", "ucs2_danish_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 140, "ucs2", "ucs2_lithunian_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 141, "ucs2", "ucs2_slovak_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 142, "ucs2", "ucs2_spanish2_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 143, "ucs2", "ucs2_roman_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 144, "ucs2", "ucs2_persian_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 145, "ucs2", "ucs2_esperanto_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 146, "ucs2", "ucs2_hungarian_ci", 2, 2, 0, mysqlnd_mbcharlen_ucs2, check_mb_ucs2}, + { 192, "utf8", "utf8_general_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 193, "utf8", "utf8_icelandic_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 194, "utf8", "utf8_latvian_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 195, "utf8", "utf8_romanian_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 196, "utf8", "utf8_slovenian_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 197, "utf8", "utf8_polish_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 198, "utf8", "utf8_estonian_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 119, "utf8", "utf8_spanish_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 200, "utf8", "utf8_swedish_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 201, "utf8", "utf8_turkish_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 202, "utf8", "utf8_czech_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 203, "utf8", "utf8_danish_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid }, + { 204, "utf8", "utf8_lithunian_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid }, + { 205, "utf8", "utf8_slovak_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 206, "utf8", "utf8_spanish2_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 207, "utf8", "utf8_roman_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 208, "utf8", "utf8_persian_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 209, "utf8", "utf8_esperanto_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 210, "utf8", "utf8_hungarian_ci", 1, 3, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid}, + { 254, "utf8", "utf8_general_cs", 1, 2, 0, mysqlnd_mbcharlen_utf8, check_mb_utf8_valid }, + { 0, NULL, NULL, 0, 0, 0, NULL, NULL} +}; +/* }}} */ + + +/* {{{ mysqlnd_find_charset_nr */ +PHPAPI const MYSQLND_CHARSET * mysqlnd_find_charset_nr(uint 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) +{ + 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, int 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++) { + uint 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, int 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"); + + for (;escapestr < end; escapestr++) { + char esc = '\0'; + uint 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)); +} +/* }}} */ + +/* + * 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 0000000000..d324d1ff0b --- /dev/null +++ b/ext/mysqlnd/mysqlnd_charset.h @@ -0,0 +1,35 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +PHPAPI ulong mysqlnd_cset_escape_quotes(const MYSQLND_CHARSET * const charset, char *newstr, + const char *escapestr, int escapestr_len TSRMLS_DC); + +PHPAPI ulong mysqlnd_cset_escape_slashes(const MYSQLND_CHARSET * const cset, char *newstr, + const char *escapestr, int escapestr_len TSRMLS_DC); + + +/* + * 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 0000000000..3dc3f592a6 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_debug.c @@ -0,0 +1,1345 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_debug.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_palloc.h" +#include "mysqlnd_statistics.h" +#include "zend_builtin_functions.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 + +#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 + +static char * mysqlnd_emalloc_name = "_mysqlnd_emalloc"; +static char * mysqlnd_pemalloc_name = "_mysqlnd_pemalloc"; +static char * mysqlnd_ecalloc_name = "_mysqlnd_ecalloc"; +static char * mysqlnd_pecalloc_name = "_mysqlnd_pecalloc"; +static char * mysqlnd_erealloc_name = "_mysqlnd_erealloc"; +static char * mysqlnd_perealloc_name= "_mysqlnd_perealloc"; +static char * mysqlnd_efree_name = "_mysqlnd_efree"; +static char * mysqlnd_pefree_name = "_mysqlnd_pefree"; +static char * mysqlnd_malloc_name = "_mysqlnd_malloc"; +static char * mysqlnd_calloc_name = "_mysqlnd_calloc"; +static char * mysqlnd_realloc_name = "_mysqlnd_realloc"; +static char * mysqlnd_free_name = "_mysqlnd_free"; + +/* {{{ 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; + uint 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) { + if (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 = spprintf(&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; + efree(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; + uint 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) { + if (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); + vspprintf(&buffer, 0, format, args); + va_end(args); + + message_line_len = spprintf(&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); + efree(buffer); + + ret = php_stream_write(self->stream, message_line, message_line_len)? PASS:FAIL; + efree(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_res_meta::func_enter */ +static zend_bool +MYSQLND_METHOD(mysqlnd_debug, func_enter)(MYSQLND_DEBUG * self, + unsigned int line, const char * const file, + char * func_name, uint func_name_len) +{ + if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) { + return FALSE; + } + if (zend_stack_count(&self->call_stack) >= self->nest_level_limit) { + return FALSE; + } + + if ((self->flags & MYSQLND_DEBUG_TRACE_MEMORY_CALLS) == 0 && + (func_name == mysqlnd_emalloc_name || func_name == mysqlnd_pemalloc_name || + func_name == mysqlnd_ecalloc_name || func_name == mysqlnd_pecalloc_name || + func_name == mysqlnd_erealloc_name || func_name == mysqlnd_perealloc_name || + func_name == mysqlnd_efree_name || func_name == mysqlnd_pefree_name || + func_name == mysqlnd_efree_name || func_name == mysqlnd_pefree_name || + func_name == mysqlnd_malloc_name || func_name == mysqlnd_calloc_name || + func_name == mysqlnd_realloc_name || func_name == mysqlnd_free_name || + func_name == mysqlnd_palloc_zval_ptr_dtor_name || func_name == mysqlnd_palloc_get_zval_name || + func_name == mysqlnd_read_header_name || func_name == mysqlnd_read_body_name)) { + zend_stack_push(&self->call_stack, "", sizeof("")); + return FALSE; + } + + zend_stack_push(&self->call_stack, func_name, func_name_len + 1); + + 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; +} +/* }}} */ + + +/* {{{ mysqlnd_res_meta::func_leave */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_debug, func_leave)(MYSQLND_DEBUG * self, unsigned int line, + const char * const file) +{ + char *func_name; + + if ((self->flags & MYSQLND_DEBUG_DUMP_TRACE) == 0 || self->file_name == NULL) { + return PASS; + } + if (zend_stack_count(&self->call_stack) >= self->nest_level_limit) { + return PASS; + } + + zend_stack_top(&self->call_stack, (void **)&func_name); + + 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)) + { + self->m->log_va(self, line, file, zend_stack_count(&self->call_stack) - 1, NULL, "<%s", func_name); + } + + return zend_stack_del_top(&self->call_stack) == SUCCESS? PASS:FAIL; +} +/* }}} */ + + +/* {{{ mysqlnd_res_meta::close */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_debug, close)(MYSQLND_DEBUG * self) +{ + MYSQLND_ZTS(self); + if (self->stream) { + php_stream_free(self->stream, PHP_STREAM_FREE_CLOSE); + self->stream = NULL; + } + 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_hash_destroy(&self->not_filtered_functions); + efree(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) +{ + uint mode_len = strlen(mode), i; + enum mysqlnd_debug_parser_state state = PARSER_WAIT_MODIFIER; + + 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; + 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 { + 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]; + uint 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; + } + } + } + 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; + 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 */ +MYSQLND_DEBUG *mysqlnd_debug_init(TSRMLS_D) +{ + MYSQLND_DEBUG *ret = ecalloc(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_hash_init(&ret->not_filtered_functions, 0, NULL, NULL, 0); + + ret->m = & mysqlnd_mysqlnd_debug_methods; + + return ret; +} +/* }}} */ + + +/* {{{ _mysqlnd_debug */ +void _mysqlnd_debug(const char *mode TSRMLS_DC) +{ +#ifdef PHP_DEBUG + MYSQLND_DEBUG *dbg = MYSQLND_G(dbg); + if (!dbg) { + MYSQLND_G(dbg) = dbg = mysqlnd_debug_init(TSRMLS_C); + 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); + } +#endif +} +/* }}} */ + + +#if ZEND_DEBUG +#else +#define __zend_filename "/unknown/unknown" +#define __zend_lineno 0 +#endif + + +/* {{{ _mysqlnd_emalloc */ +void * _mysqlnd_emalloc(size_t size MYSQLND_MEM_D) +{ + void *ret; + DBG_ENTER(mysqlnd_emalloc_name); + DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); + DBG_INF_FMT("before: %lu", zend_memory_usage(FALSE TSRMLS_CC)); + ret = emalloc(size); + DBG_INF_FMT("after : %lu", zend_memory_usage(FALSE TSRMLS_CC)); + DBG_INF_FMT("size=%lu ptr=%p", size, ret); + + if (MYSQLND_G(collect_memory_statistics)) { + MYSQLND_INC_GLOBAL_STATISTIC2_W_VALUE(STAT_MEM_EMALLOC_COUNT, 1, STAT_MEM_EMALLOC_AMMOUNT, size); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ _mysqlnd_pemalloc */ +void * _mysqlnd_pemalloc(size_t size, zend_bool persistent MYSQLND_MEM_D) +{ + void *ret; + DBG_ENTER(mysqlnd_pemalloc_name); + DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); + if (persistent == FALSE) { + DBG_INF_FMT("before: %lu", zend_memory_usage(persistent TSRMLS_CC)); + } + + ret = pemalloc(size, persistent); + DBG_INF_FMT("size=%lu ptr=%p persistent=%d", size, ret, persistent); + + if (persistent == FALSE) { + DBG_INF_FMT("after : %lu", zend_memory_usage(persistent TSRMLS_CC)); + } + + if (MYSQLND_G(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_AMMOUNT:STAT_MEM_EMALLOC_AMMOUNT; + MYSQLND_INC_GLOBAL_STATISTIC2_W_VALUE(s1, 1, s2, size); + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ _mysqlnd_ecalloc */ +void * _mysqlnd_ecalloc(uint nmemb, size_t size MYSQLND_MEM_D) +{ + void *ret; + DBG_ENTER(mysqlnd_ecalloc_name); + DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); + DBG_INF_FMT("before: %lu", zend_memory_usage(FALSE TSRMLS_CC)); + + ret = ecalloc(nmemb, size); + + DBG_INF_FMT("after : %lu", zend_memory_usage(FALSE TSRMLS_CC)); + DBG_INF_FMT("size=%lu ptr=%p", size, ret); + if (MYSQLND_G(collect_memory_statistics)) { + MYSQLND_INC_GLOBAL_STATISTIC2_W_VALUE(STAT_MEM_ECALLOC_COUNT, 1, STAT_MEM_ECALLOC_AMMOUNT, size); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ _mysqlnd_pecalloc */ +void * _mysqlnd_pecalloc(uint nmemb, size_t size, zend_bool persistent MYSQLND_MEM_D) +{ + void *ret; + DBG_ENTER(mysqlnd_pecalloc_name); + DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); + if (persistent == FALSE) { + DBG_INF_FMT("before: %lu", zend_memory_usage(persistent TSRMLS_CC)); + } + + ret = pecalloc(nmemb, size, persistent); + DBG_INF_FMT("size=%lu ptr=%p", size, ret); + + if (persistent == FALSE) { + DBG_INF_FMT("after : %lu", zend_memory_usage(persistent TSRMLS_CC)); + } + + if (MYSQLND_G(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_AMMOUNT:STAT_MEM_ECALLOC_AMMOUNT; + MYSQLND_INC_GLOBAL_STATISTIC2_W_VALUE(s1, 1, s2, size); + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ _mysqlnd_erealloc */ +void * _mysqlnd_erealloc(void *ptr, size_t new_size MYSQLND_MEM_D) +{ + void *ret; + DBG_ENTER(mysqlnd_erealloc_name); + DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); + DBG_INF_FMT("ptr=%p new_size=%lu", ptr, new_size); + DBG_INF_FMT("before: %lu", zend_memory_usage(FALSE TSRMLS_CC)); + + ret = erealloc(ptr, new_size); + + DBG_INF_FMT("after : %lu", zend_memory_usage(FALSE TSRMLS_CC)); + DBG_INF_FMT("new_ptr=%p", ret); + if (MYSQLND_G(collect_memory_statistics)) { + MYSQLND_INC_GLOBAL_STATISTIC2_W_VALUE(STAT_MEM_EREALLOC_COUNT, 1, STAT_MEM_EREALLOC_AMMOUNT, new_size); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ _mysqlnd_perealloc */ +void * _mysqlnd_perealloc(void *ptr, size_t new_size, zend_bool persistent MYSQLND_MEM_D) +{ + void *ret; + DBG_ENTER(mysqlnd_perealloc_name); + DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); + DBG_INF_FMT("ptr=%p new_size=%lu persist=%d", ptr, new_size, persistent); + if (persistent == FALSE) { + DBG_INF_FMT("before: %lu", zend_memory_usage(persistent TSRMLS_CC)); + } + + ret = perealloc(ptr, new_size, persistent); + + DBG_INF_FMT("new_ptr=%p", ret); + + if (persistent == FALSE) { + DBG_INF_FMT("after : %lu", zend_memory_usage(persistent TSRMLS_CC)); + } + MYSQLND_INC_GLOBAL_STATISTIC(persistent? STAT_MEM_EREALLOC_COUNT:STAT_MEM_REALLOC_COUNT); + if (MYSQLND_G(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_AMMOUNT:STAT_MEM_EREALLOC_AMMOUNT; + MYSQLND_INC_GLOBAL_STATISTIC2_W_VALUE(s1, 1, s2, new_size); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ _mysqlnd_efree */ +void _mysqlnd_efree(void *ptr MYSQLND_MEM_D) +{ + DBG_ENTER(mysqlnd_efree_name); + DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); + DBG_INF_FMT("ptr=%p", ptr); + DBG_INF_FMT("before: %lu", zend_memory_usage(FALSE TSRMLS_CC)); + MYSQLND_INC_GLOBAL_STATISTIC(STAT_MEM_EFREE_COUNT); + + efree(ptr); + + DBG_INF_FMT("after : %lu", zend_memory_usage(FALSE TSRMLS_CC)); + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ _mysqlnd_pefree */ +void _mysqlnd_pefree(void *ptr, zend_bool persistent MYSQLND_MEM_D) +{ + DBG_ENTER(mysqlnd_pefree_name); + DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); + DBG_INF_FMT("ptr=%p persistent=%d", ptr, persistent); + if (persistent == FALSE) { + DBG_INF_FMT("before: %lu", zend_memory_usage(persistent TSRMLS_CC)); + } + + pefree(ptr, persistent); + + if (persistent == FALSE) { + DBG_INF_FMT("after : %lu", zend_memory_usage(persistent TSRMLS_CC)); + } + if (MYSQLND_G(collect_memory_statistics)) { + MYSQLND_INC_GLOBAL_STATISTIC(persistent? STAT_MEM_FREE_COUNT:STAT_MEM_EFREE_COUNT); + } + DBG_VOID_RETURN; +} + + +/* {{{ _mysqlnd_malloc */ +void * _mysqlnd_malloc(size_t size MYSQLND_MEM_D) +{ + void *ret; + DBG_ENTER(mysqlnd_malloc_name); + DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); + + ret = malloc(size); + + DBG_INF_FMT("size=%lu ptr=%p", size, ret); + if (MYSQLND_G(collect_memory_statistics)) { + MYSQLND_INC_GLOBAL_STATISTIC2_W_VALUE(STAT_MEM_MALLOC_COUNT, 1, STAT_MEM_MALLOC_AMMOUNT, size); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ _mysqlnd_calloc */ +void * _mysqlnd_calloc(uint nmemb, size_t size MYSQLND_MEM_D) +{ + void *ret; + DBG_ENTER(mysqlnd_calloc_name); + DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); + + ret = calloc(nmemb, size); + + DBG_INF_FMT("size=%lu ptr=%p", size, ret); + if (MYSQLND_G(collect_memory_statistics)) { + MYSQLND_INC_GLOBAL_STATISTIC2_W_VALUE(STAT_MEM_CALLOC_COUNT, 1, STAT_MEM_CALLOC_AMMOUNT, size); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ _mysqlnd_realloc */ +void * _mysqlnd_realloc(void *ptr, size_t new_size MYSQLND_MEM_D) +{ + void *ret; + DBG_ENTER(mysqlnd_realloc_name); + DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); + DBG_INF_FMT("ptr=%p new_size=%lu ", new_size, ptr); + DBG_INF_FMT("before: %lu", zend_memory_usage(TRUE TSRMLS_CC)); + + ret = realloc(ptr, new_size); + + DBG_INF_FMT("new_ptr=%p", ret); + + if (MYSQLND_G(collect_memory_statistics)) { + MYSQLND_INC_GLOBAL_STATISTIC2_W_VALUE(STAT_MEM_REALLOC_COUNT, 1, STAT_MEM_REALLOC_AMMOUNT, new_size); + } + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ _mysqlnd_free */ +void _mysqlnd_free(void *ptr MYSQLND_MEM_D) +{ + DBG_ENTER(mysqlnd_free_name); + DBG_INF_FMT("file=%-15s line=%4d", strrchr(__zend_filename, PHP_DIR_SEPARATOR) + 1, __zend_lineno); + DBG_INF_FMT("ptr=%p", ptr); + + free(ptr); + + if (MYSQLND_G(collect_memory_statistics)) { + MYSQLND_INC_GLOBAL_STATISTIC(STAT_MEM_FREE_COUNT); + } + DBG_VOID_RETURN; +} +/* }}} */ + + + + +/* Follows code borrowed from zend_builtin_functions.c because the functions there are static */ + +#if PHP_MAJOR_VERSION >= 6 +/* {{{ 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, 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; + TSRMLS_FETCH(); + + /* + * 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; + TSRMLS_FETCH(); + + 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: { + zstr class_name; + zend_uint class_name_len; + int dup; + TSRMLS_FETCH(); + + TRACE_APPEND_STR("Object("); + + dup = zend_get_object_classname(*arg, &class_name, &class_name_len TSRMLS_CC); + + if (UG(unicode)) { + zval tmp; + + 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); + } else { + TRACE_APPEND_STRL(class_name.s, class_name_len); + } + 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, 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; + + str = va_arg(args, char**); + len = va_arg(args, int*); + num = va_arg(args, int*); + + 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), (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, 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; + TSRMLS_FETCH(); + + 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 dup; + TSRMLS_FETCH(); + + TRACE_APPEND_STR("Object("); + + dup = zend_get_object_classname(*arg, &class_name, &class_name_len TSRMLS_CC); + + TRACE_APPEND_STRL(class_name, class_name_len); + if(!dup) { + efree(class_name); + } + + TRACE_APPEND_STR("), "); + break; + } + default: + break; + } + return ZEND_HASH_APPLY_KEEP; +} +/* }}} */ + +static int mysqlnd_build_trace_string(zval **frame, 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; + + str = va_arg(args, char**); + len = va_arg(args, int*); + num = va_arg(args, int*); + + 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), (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 + + +char * mysqlnd_get_backtrace(TSRMLS_D) +{ + zval *trace; + char *res = estrdup(""), **str = &res, *s_tmp; + int res_len = 0, *len = &res_len, num = 0; + + MAKE_STD_ZVAL(trace); + zend_fetch_debug_backtrace(trace, 0, 0 TSRMLS_CC); + + zend_hash_apply_with_arguments(Z_ARRVAL_P(trace), (apply_func_args_t)mysqlnd_build_trace_string, 3, str, len, &num); + zval_ptr_dtor(&trace); + + 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'; + + 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_debug.h b/ext/mysqlnd/mysqlnd_debug.h new file mode 100644 index 0000000000..120d74aaa5 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_debug.h @@ -0,0 +1,145 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef MYSQLND_DEBUG_H +#define MYSQLND_DEBUG_H + +#include "zend_stack.h" + +#define MYSQLND_DEBUG_MEMORY 1 + +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, + char * func_name, uint func_name_len); + enum_func_status (*func_leave)(MYSQLND_DEBUG *self, unsigned int line, const char * const file); + enum_func_status (*close)(MYSQLND_DEBUG *self); + enum_func_status (*free)(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; + HashTable not_filtered_functions; + struct st_mysqlnd_debug_methods *m; +}; + + +MYSQLND_DEBUG *mysqlnd_debug_init(TSRMLS_D); + +#define MYSQLND_MEM_D TSRMLS_DC ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC + + +void * _mysqlnd_emalloc(size_t size MYSQLND_MEM_D); +void * _mysqlnd_pemalloc(size_t size, zend_bool persistent MYSQLND_MEM_D); +void * _mysqlnd_ecalloc(uint nmemb, size_t size MYSQLND_MEM_D); +void * _mysqlnd_pecalloc(uint nmemb, size_t size, zend_bool persistent MYSQLND_MEM_D); +void * _mysqlnd_erealloc(void *ptr, size_t new_size MYSQLND_MEM_D); +void * _mysqlnd_perealloc(void *ptr, size_t new_size, zend_bool persistent MYSQLND_MEM_D); +void _mysqlnd_efree(void *ptr MYSQLND_MEM_D); +void _mysqlnd_pefree(void *ptr, zend_bool persistent MYSQLND_MEM_D); +void * _mysqlnd_malloc(size_t size MYSQLND_MEM_D); +void * _mysqlnd_calloc(uint nmemb, size_t size MYSQLND_MEM_D); +void * _mysqlnd_realloc(void *ptr, size_t new_size MYSQLND_MEM_D); +void _mysqlnd_free(void *ptr MYSQLND_MEM_D); + +char * mysqlnd_get_backtrace(TSRMLS_D); + +#if PHP_DEBUG && !defined(PHP_WIN32) +#define DBG_INF(msg) do { if (dbg_skip_trace == FALSE) MYSQLND_G(dbg)->m->log(MYSQLND_G(dbg), __LINE__, __FILE__, -1, "info : ", (msg)); } while (0) +#define DBG_ERR(msg) do { if (dbg_skip_trace == FALSE) MYSQLND_G(dbg)->m->log(MYSQLND_G(dbg), __LINE__, __FILE__, -1, "error: ", (msg)); } while (0) +#define DBG_INF_FMT(...) do { if (dbg_skip_trace == FALSE) MYSQLND_G(dbg)->m->log_va(MYSQLND_G(dbg), __LINE__, __FILE__, -1, "info : ", __VA_ARGS__); } while (0) +#define DBG_ERR_FMT(...) do { if (dbg_skip_trace == FALSE) MYSQLND_G(dbg)->m->log_va(MYSQLND_G(dbg), __LINE__, __FILE__, -1, "error: ", __VA_ARGS__); } while (0) + +#define DBG_ENTER(func_name) zend_bool dbg_skip_trace = TRUE; if (MYSQLND_G(dbg)) dbg_skip_trace = !MYSQLND_G(dbg)->m->func_enter(MYSQLND_G(dbg), __LINE__, __FILE__, func_name, strlen(func_name)); +#define DBG_RETURN(value) do { if (MYSQLND_G(dbg)) MYSQLND_G(dbg)->m->func_leave(MYSQLND_G(dbg), __LINE__, __FILE__); return (value); } while (0) +#define DBG_VOID_RETURN do { if (MYSQLND_G(dbg)) MYSQLND_G(dbg)->m->func_leave(MYSQLND_G(dbg), __LINE__, __FILE__); return; } while (0) + + + +#else +static inline void DBG_INF(char *msg) {} +static inline void DBG_ERR(char *msg) {} +static inline void DBG_INF_FMT(char *format, ...) {} +static inline void DBG_ERR_FMT(char *format, ...) {} +static inline void DBG_ENTER(char *func_name) {} +#define DBG_RETURN(value) return (value) +#define DBG_VOID_RETURN return; +#endif + + +#if MYSQLND_DEBUG_MEMORY + +#define mnd_emalloc(size) _mysqlnd_emalloc((size) TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) +#define mnd_pemalloc(size, pers) _mysqlnd_pemalloc((size), (pers) TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) +#define mnd_ecalloc(nmemb, size) _mysqlnd_ecalloc((nmemb), (size) TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) +#define mnd_pecalloc(nmemb, size, p) _mysqlnd_pecalloc((nmemb), (size), (p) TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) +#define mnd_erealloc(ptr, new_size) _mysqlnd_erealloc((ptr), (new_size) TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) +#define mnd_perealloc(ptr, new_size, p) _mysqlnd_perealloc((ptr), (new_size), (p) TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) +#define mnd_efree(ptr) _mysqlnd_efree((ptr) TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) +#define mnd_pefree(ptr, pers) _mysqlnd_pefree((ptr), (pers) TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) +#define mnd_malloc(size) _mysqlnd_malloc((size) TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) +#define mnd_calloc(nmemb, size) _mysqlnd_calloc((nmemb), (size) TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) +#define mnd_realloc(ptr, new_size) _mysqlnd_realloc((ptr), (new_size) TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) +#define mnd_free(ptr) _mysqlnd_free((ptr) TSRMLS_CC ZEND_FILE_LINE_CC ZEND_FILE_LINE_EMPTY_CC) + +#else + +#define mnd_emalloc(size) emalloc((size)) +#define mnd_pemalloc(size, pers) pemalloc((size), (pers)) +#define mnd_ecalloc(nmemb, size) ecalloc((nmemb), (size)) +#define mnd_pecalloc(nmemb, size, p) pecalloc((nmemb), (size), (p)) +#define mnd_erealloc(ptr, new_size) erealloc((ptr), (new_size)) +#define mnd_perealloc(ptr, new_size, p) perealloc((ptr), (new_size), (p)) +#define mnd_efree(ptr) efree((ptr)) +#define mnd_pefree(ptr, pers) pefree((ptr), (pers)) +#define mnd_malloc(size) malloc((size)) +#define mnd_calloc(nmemb, size) calloc((nmemb), (size)) +#define mnd_realloc(ptr, new_size) realloc((ptr), (new_size)) +#define mnd_free(ptr) free((ptr)) + +#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_enum_n_def.h b/ext/mysqlnd/mysqlnd_enum_n_def.h new file mode 100644 index 0000000000..f8a3a8b57d --- /dev/null +++ b/ext/mysqlnd/mysqlnd_enum_n_def.h @@ -0,0 +1,367 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#ifndef MYSQLND_ENUM_N_DEF_H +#define MYSQLND_ENUM_N_DEF_H + + +#define MYSQLND_ERRMSG_SIZE 512 +#define MYSQLND_SQLSTATE_LENGTH 5 +#define MYSQLND_SQLSTATE_NULL "00000" + +#define MYSQLND_SERVER_QUERY_NO_GOOD_INDEX_USED 16 +#define MYSQLND_SERVER_QUERY_NO_INDEX_USED 32 + +#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 */ + +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, +#if PHP_MAJOR_VERSION >= 6 + MYSQLND_OPT_NUMERIC_AND_DATETIME_AS_UNICODE = 200, +#endif +#ifdef MYSQLND_STRING_TO_INT_CONVERSION + MYSQLND_OPT_INT_AND_YEAR_AS_INT = 201, +#endif + MYSQLND_OPT_NET_CMD_BUFFER_SIZE = 202, + MYSQLND_OPT_NET_READ_BUFFER_SIZE = 203, +} enum_mysqlnd_option; + + +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 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_RSET_QUERY, + STAT_NON_RSET_QUERY, + STAT_NO_INDEX_USED, + STAT_BAD_INDEX_USED, + 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_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_AMMOUNT, + STAT_MEM_ECALLOC_COUNT, + STAT_MEM_ECALLOC_AMMOUNT, + STAT_MEM_EREALLOC_COUNT, + STAT_MEM_EREALLOC_AMMOUNT, + STAT_MEM_EFREE_COUNT, + STAT_MEM_MALLOC_COUNT, + STAT_MEM_MALLOC_AMMOUNT, + STAT_MEM_CALLOC_COUNT, + STAT_MEM_CALLOC_AMMOUNT, + STAT_MEM_REALLOC_COUNT, + STAT_MEM_REALLOC_AMMOUNT, + STAT_MEM_FREE_COUNT, + STAT_LAST /* Should be always the last */ +} enum_mysqlnd_collected_stats; + + +#define MYSQLND_DEFAULT_PREFETCH_ROWS (ulong) 1 + + +#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_libmysql_compat.h b/ext/mysqlnd/mysqlnd_libmysql_compat.h new file mode 100644 index 0000000000..8f8e0e69bc --- /dev/null +++ b/ext/mysqlnd/mysqlnd_libmysql_compat.h @@ -0,0 +1,121 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ + +*/ + +#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 +#define MYSQL MYSQLND +#define my_bool zend_bool +#define my_ulonglong mynd_ulonglong + +#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 + +#define SERVER_QUERY_NO_GOOD_INDEX_USED MYSQLND_SERVER_QUERY_NO_GOOD_INDEX_USED +#define SERVER_QUERY_NO_INDEX_USED MYSQLND_SERVER_QUERY_NO_INDEX_USED + + +/* 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)) +#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_lengths(r) mysqlnd_fetch_lengths((r)) +#define mysql_fetch_row(r) mysqlnd_fetch_row((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_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_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_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_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_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)->data) + +#endif /* MYSQLND_LIBMYSQL_COMPAT_H */ diff --git a/ext/mysqlnd/mysqlnd_loaddata.c b/ext/mysqlnd/mysqlnd_loaddata.c new file mode 100644 index 0000000000..2e5466aa07 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_loaddata.c @@ -0,0 +1,263 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +#include "php.h" +#include "php_globals.h" +#include "mysqlnd.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_debug.h" + +enum_func_status mysqlnd_simple_command_handle_response(MYSQLND *conn, + enum php_mysql_packet_type ok_packet, + zend_bool silent, enum php_mysqlnd_server_command command + TSRMLS_DC); + + +#define ALLOC_CALLBACK_ARGS(a, b, c)\ +if (c) {\ + a = (zval ***)safe_emalloc(c, sizeof(zval **), 0);\ + for (i = b; i < c; i++) {\ + a[i] = mnd_emalloc(sizeof(zval *));\ + MAKE_STD_ZVAL(*a[i]);\ + }\ +} + +#define FREE_CALLBACK_ARGS(a, b, c)\ +if (a) {\ + for (i=b; i < c; i++) {\ + zval_ptr_dtor(a[i]);\ + mnd_efree(a[i]);\ + }\ + mnd_efree(a);\ +} + +/* {{{ 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"); + + *ptr= info= ((MYSQLND_INFILE_INFO *)mnd_ecalloc(1, sizeof(MYSQLND_INFILE_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, char *buf, uint 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, 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, uint error_buf_len TSRMLS_DC) +{ + MYSQLND_INFILE_INFO *info = (MYSQLND_INFILE_INFO *)ptr; + + DBG_ENTER("mysqlnd_local_infile_error"); + + if (info) { + strncpy(error_buf, info->error_msg, error_buf_len); + DBG_INF_FMT("have info, %d", info->error_no); + DBG_RETURN(info->error_no); + } + + strncpy(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) +{ + 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 * 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, const char *filename, zend_bool *is_warning TSRMLS_DC) +{ + char *buf; + char empty_packet[MYSQLND_HEADER_SIZE]; + enum_func_status result = FAIL; + uint buflen = 4096; + void *info = NULL; + int bufsize; + size_t ret; + MYSQLND_INFILE infile; + + 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 = mysqlnd_stream_write_w_header(conn, empty_packet, 0 TSRMLS_CC); + *is_warning = TRUE; + goto infile_error; + } + + infile = conn->infile; + /* allocate buffer for reading data */ + buf = (char *)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)) { + *is_warning = TRUE; + /* error occured */ + strcpy(conn->error_info.sqlstate, UNKNOWN_SQLSTATE); + conn->error_info.error_no = + infile.local_infile_error(info, conn->error_info.error, + sizeof(conn->error_info.error) TSRMLS_CC); + /* write empty packet to server */ + ret = mysqlnd_stream_write_w_header(conn, empty_packet, 0 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 = mysqlnd_stream_write_w_header(conn, buf, bufsize 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 = mysqlnd_stream_write_w_header(conn, empty_packet, 0 TSRMLS_CC)) < 0) { + SET_CLIENT_ERROR(conn->error_info, CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn); + goto infile_error; + } + + /* error during read occured */ + if (bufsize < 0) { + *is_warning = TRUE; + DBG_ERR_FMT("Bufsize < 0, warning, %d %s %s", CR_SERVER_LOST, UNKNOWN_SQLSTATE, lost_conn); + strcpy(conn->error_info.sqlstate, UNKNOWN_SQLSTATE); + conn->error_info.error_no = infile.local_infile_error(info, conn->error_info.error, + sizeof(conn->error_info.error) TSRMLS_CC); + goto infile_error; + } + + result = PASS; + +infile_error: + /* get response from server and update upsert values */ + if (FAIL == mysqlnd_simple_command_handle_response(conn, PROT_OK_PACKET, FALSE, COM_QUERY TSRMLS_CC)) { + result = FAIL; + goto infile_error; + } + + (*conn->infile.local_infile_end)(info TSRMLS_CC); + 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_palloc.c b/ext/mysqlnd/mysqlnd_palloc.c new file mode 100644 index 0000000000..fba769038a --- /dev/null +++ b/ext/mysqlnd/mysqlnd_palloc.c @@ -0,0 +1,565 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_palloc.h" +#include "mysqlnd_debug.h" + +/* Used in mysqlnd_debug.c */ +char * mysqlnd_palloc_zval_ptr_dtor_name = "mysqlnd_palloc_zval_ptr_dtor"; +char * mysqlnd_palloc_get_zval_name = "mysqlnd_palloc_get_zval"; + + +#ifdef ZTS +#define LOCK_PCACHE(cache) tsrm_mutex_lock((cache)->LOCK_access) +#define UNLOCK_PCACHE(cache) tsrm_mutex_unlock((cache)->LOCK_access) +#else +#define LOCK_PCACHE(cache) +#define UNLOCK_PCACHE(cache) +#endif + + +#if PHP_MAJOR_VERSION < 6 +#define IS_UNICODE_DISABLED (1) +#else +#define IS_UNICODE_DISABLED (!UG(unicode)) +#endif + + +/* {{{ _mysqlnd_palloc_init_cache */ +PHPAPI MYSQLND_ZVAL_PCACHE* _mysqlnd_palloc_init_cache(unsigned int cache_size TSRMLS_DC) +{ + MYSQLND_ZVAL_PCACHE *ret = calloc(1, sizeof(MYSQLND_ZVAL_PCACHE)); + unsigned int i; + + DBG_ENTER("_mysqlnd_palloc_init_cache"); + DBG_INF_FMT("cache=%p size=%u", ret, cache_size); + +#ifdef ZTS + ret->LOCK_access = tsrm_mutex_alloc(); +#endif + + ret->max_items = cache_size; + ret->free_items = cache_size; + ret->references = 1; + + /* 1. First initialize the free list part of the structure */ + /* One more for empty position of last_added - always 0x0, bounds checking */ + ret->free_list.ptr_line = calloc(ret->max_items + 1, sizeof(mysqlnd_zval *)); + ret->free_list.last_added = ret->free_list.ptr_line + ret->max_items; + + /* 3. Allocate and initialize our zvals and initialize the free list */ + ret->block = calloc(ret->max_items, sizeof(mysqlnd_zval)); + ret->last_in_block = &(ret->block[ret->max_items]); + for (i = 0; i < ret->max_items; i++) { + /* 1. Initialize */ + INIT_PZVAL(&(ret->block[i].zv)); + ZVAL_NULL(&(ret->block[i].zv)); + /* Assure it will never be freed before MSHUTDOWN */ + ZVAL_ADDREF(&(ret->block[i].zv)); + /* 2. Add to the free list */ + *(--ret->free_list.last_added) = &(ret->block[i]); + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_palloc_get_cache_reference */ +MYSQLND_ZVAL_PCACHE* mysqlnd_palloc_get_cache_reference(MYSQLND_ZVAL_PCACHE * const cache) +{ + if (cache) { + LOCK_PCACHE(cache); + cache->references++; + UNLOCK_PCACHE(cache); + } + return cache; +} +/* }}} */ + + +/* {{{ mysqlnd_palloc_free_cache */ +/* + As this call will happen on MSHUTDOWN(), then we don't need to copy the zvals with + copy_ctor but scrap what they point to with zval_dtor() and then just free our + pre-allocated block. Precondition is that we ZVAL_NULL() the zvals when we put them + to the free list after usage. We ZVAL_NULL() them when we allocate them in the + constructor of the cache. +*/ +void _mysqlnd_palloc_free_cache(MYSQLND_ZVAL_PCACHE *cache TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_palloc_free_cache"); + DBG_INF_FMT("cache=%p", cache); + +#ifdef ZTS + tsrm_mutex_free(cache->LOCK_access); +#endif + + /* Data in pointed by 'block' was cleaned in RSHUTDOWN */ + mnd_free(cache->block); + mnd_free(cache->free_list.ptr_line); + mnd_free(cache); + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ _mysqlnd_palloc_init_thd_cache */ +PHPAPI MYSQLND_THD_ZVAL_PCACHE* _mysqlnd_palloc_init_thd_cache(MYSQLND_ZVAL_PCACHE * const cache TSRMLS_DC) +{ + MYSQLND_THD_ZVAL_PCACHE *ret = calloc(1, sizeof(MYSQLND_THD_ZVAL_PCACHE)); + DBG_ENTER("_mysqlnd_palloc_init_thd_cache"); + DBG_INF_FMT("ret = %p", ret); + + ret->parent = mysqlnd_palloc_get_cache_reference(cache); + +#ifdef ZTS + ret->thread_id = tsrm_thread_id(); +#endif + + ret->references = 1; + + /* 1. Initialize the GC list */ + ret->gc_list.ptr_line = calloc(cache->max_items, sizeof(mysqlnd_zval *)); + /* Backward and forward looping is possible */ + ret->gc_list.last_added = ret->gc_list.ptr_line; + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_palloc_get_thd_cache_reference */ +MYSQLND_THD_ZVAL_PCACHE* mysqlnd_palloc_get_thd_cache_reference(MYSQLND_THD_ZVAL_PCACHE * const cache) +{ + if (cache) { + ++cache->references; + mysqlnd_palloc_get_cache_reference(cache->parent); + } + return cache; +} +/* }}} */ + + +/* {{{ mysqlnd_palloc_free_cache */ +/* + As this call will happen on MSHUTDOWN(), then we don't need to copy the zvals with + copy_ctor but scrap what they point to with zval_dtor() and then just free our + pre-allocated block. Precondition is that we ZVAL_NULL() the zvals when we put them + to the free list after usage. We ZVAL_NULL() them when we allocate them in the + constructor of the cache. +*/ +static +void mysqlnd_palloc_free_thd_cache(MYSQLND_THD_ZVAL_PCACHE *cache TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_palloc_free_thd_cache"); + DBG_INF_FMT("cache=%p", cache); + + mnd_free(cache->gc_list.ptr_line); + mnd_free(cache); + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ _mysqlnd_palloc_free_thd_cache_reference */ +PHPAPI void _mysqlnd_palloc_free_thd_cache_reference(MYSQLND_THD_ZVAL_PCACHE **cache TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_palloc_free_thd_cache_reference"); + if (*cache) { + DBG_INF_FMT("cache=%p refs=%d", *cache, (*cache)->references); + --(*cache)->parent->references; + + if (--(*cache)->references == 0) { + mysqlnd_palloc_free_thd_cache(*cache TSRMLS_CC); + } + *cache = NULL; + } + DBG_VOID_RETURN; +} +/* }}} */ + + +/* + The cache line is a big contiguous array of zval pointers. + Because the CPU cache will cache starting from an address, and not + before it, then we have to organize our structure according to this. + Thus, if 'last_added' is valid pointer (not NULL) then last_added is + increased. When zval is cached, if there is room, last_added is decreased + and then the zval pointer will be assigned to it. This means that some + positions may become hot points and stay in the cache. + Imagine we have 5 pointers in a line + 1. last_added = list_item->ptr_line + cache->max_items; + 2. get_zval -> *last_added = NULL. Use MAKE_STD_ZVAL + 3. get_zval -> *last_added = NULL. Use MAKE_STD_ZVAL + 4. get_zval -> *last_added = NULL. Use MAKE_STD_ZVAL + 0x0 + 0x0 + 0x0 + 0x0 + 0x0 + --- + empty_position, always 0x0 <-- last_added + + 5. free_zval -> if (free_items++ != max_items) {// we can add more + *(--last_added) = zval_ptr; + } + (memory addresses increase downwards) + 0x0 + 0x0 + 0x0 + 0x0 + 0xA <-- last_added + --- + 0x0 + + 6. free_zval -> if (free_items++ != max_items) {// we can add more + *(--last_added) = zval_ptr; + } + 0x0 + 0x0 + 0x0 + 0xB <-- last_added + 0xA + --- + 0x0 + + 7. free_zval -> if (free_items++ != max_items) {// we can add more + *(--last_added) = zval_ptr; + } + 0x0 + 0x0 + 0xC <-- last_added + 0xB + 0xA + --- + 0x0 + + 8. get_zval -> *last_added != NULL. -> p = *last_added; *last_added++ = NULL; + 0x0 + 0x0 + 0x0 + 0xB <-- last_added + 0xA + --- + 0x0 + + 9. get_zval -> *last_added != NULL. -> p = *last_added; *last_added++ = NULL; + 0x0 + 0x0 + 0x0 + 0x0 + 0xA <-- last_added + --- + 0x0 + + 10. get_zval -> *last_added != NULL. -> p = *last_added; *last_added++ = NULL; + 0x0 + 0x0 + 0x0 + 0x0 + 0x0 + --- + 0x0 <-- last_added + +*/ + + +/* {{{ mysqlnd_palloc_get_zval */ +void *mysqlnd_palloc_get_zval(MYSQLND_THD_ZVAL_PCACHE * const thd_cache, zend_bool *allocated TSRMLS_DC) +{ + void *ret = NULL; + + DBG_ENTER("mysqlnd_palloc_get_zval"); + DBG_INF_FMT("cache=%p *last_added=%p free_items=%d", + thd_cache, thd_cache? thd_cache->parent->free_list.last_added:NULL, + thd_cache->parent->free_items); + + if (thd_cache) { + MYSQLND_ZVAL_PCACHE *cache = thd_cache->parent; + LOCK_PCACHE(cache); + + if ((ret = *cache->free_list.last_added)) { + *cache->free_list.last_added++ = NULL; + *allocated = FALSE; +#ifdef ZTS + ((mysqlnd_zval *) ret)->thread_id = thd_cache->thread_id; +#endif + --cache->free_items; + ++cache->get_hits; + } else { + ++cache->get_misses; + } + UNLOCK_PCACHE(cache); + } + if (!ret) { + /* + We allocate a bit more. The user of this function will use it, but at + end it will use only the zval part. Because the zval part is first then + when freeing the zval part the whole allocated block will be cleaned, not + only the zval part (by the Engine when destructing the zval). + */ + ALLOC_ZVAL(ret); + INIT_PZVAL((zval *) ret); + *allocated = TRUE; + } else { + /* This will set the refcount to 1, increase it, to keep the variable */ + INIT_PZVAL(&((mysqlnd_zval *) ret)->zv); + ZVAL_ADDREF(&(((mysqlnd_zval *)ret)->zv)); + } + + DBG_INF_FMT("allocated=%d ret=%p", *allocated, ret); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_palloc_zval_ptr_dtor */ +void mysqlnd_palloc_zval_ptr_dtor(zval **zv, MYSQLND_THD_ZVAL_PCACHE * const thd_cache, + enum_mysqlnd_res_type type, zend_bool *copy_ctor_called TSRMLS_DC) +{ + MYSQLND_ZVAL_PCACHE *cache; + DBG_ENTER("mysqlnd_palloc_zval_ptr_dtor"); + DBG_INF_FMT("cache=%p parent_block=%p last_in_block=%p *zv=%p refc=%d type=%d ", + thd_cache, + thd_cache->parent? thd_cache->parent->block:NULL, + thd_cache->parent? thd_cache->parent->last_in_block:NULL, + *zv, ZVAL_REFCOUNT(*zv), type); + *copy_ctor_called = FALSE; + /* Check whether cache is used and the zval is from the cache */ + if (!thd_cache || !(cache = thd_cache->parent) || ((char *)*zv < (char *)thd_cache->parent->block || + (char *)*zv > (char *)thd_cache->parent->last_in_block)) { + /* + 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) { + ; /* do nothing, zval_ptr_dtor will do the job*/ + } else if (ZVAL_REFCOUNT(*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 { + if (Z_TYPE_PP(zv) == IS_STRING) { + ZVAL_NULL(*zv); + } + } + zval_ptr_dtor(zv); + DBG_VOID_RETURN; + } + + /* The zval is from our cache */ + /* refcount is always > 1, because we call ZVAL_ADDREF(). Thus test refcount > 2 */ + if (ZVAL_REFCOUNT(*zv) > 2) { + /* + Because the zval is first element in mysqlnd_zval structure, then we can + do upcasting from zval to mysqlnd_zval here. Because we know that this + zval is part of our pre-allocated block. + + Now check whether this zval points to ZE allocated memory or to our + buffers. If it points to the internal buffers, call copy_ctor() + which will do estrndup for strings. And nothing for null, int, double. + + This branch will be skipped for PS, because there is no need to copy + what is pointed by them, as they don't point to the internal buffers. + */ + if (((mysqlnd_zval *)*zv)->point_type == MYSQLND_POINTS_INT_BUFFER) { + zval_copy_ctor(*zv); + *copy_ctor_called = TRUE; + ((mysqlnd_zval *)*zv)->point_type = MYSQLND_POINTS_EXT_BUFFER; + } + /* + This will decrease the counter of the user-level (mysqlnd). When the engine + layer (the script) has finished working this this zval, when the variable is + no more used, out of scope whatever, then it will try zval_ptr_dtor() but + and the refcount will reach 1 and the engine won't try to destruct the + memory allocated by us. + */ + zval_ptr_dtor(zv); + + /* + Unfortunately, we can't return this variable to the free_list + because it's still used. And this cleaning up will happen at request + shutdown :(. + */ + LOCK_PCACHE(cache); + ++cache->put_misses; + *(thd_cache->gc_list.last_added++) = (mysqlnd_zval *)*zv; + UNLOCK_PCACHE(cache); + } else { + /* No user reference */ + if (((mysqlnd_zval *)*zv)->point_type == MYSQLND_POINTS_EXT_BUFFER) { + /* PS are here and also in Unicode mode, for non-binary */ + zval_dtor(*zv); + } + LOCK_PCACHE(cache); + ++cache->put_hits; + ++cache->free_items; + ((mysqlnd_zval *)*zv)->point_type = MYSQLND_POINTS_FREE; + ZVAL_DELREF(*zv); /* Make it 1 */ + ZVAL_NULL(*zv); +#ifdef ZTS + memset(&((mysqlnd_zval *)*zv)->thread_id, 0, sizeof(THREAD_T)); +#endif + *(--cache->free_list.last_added) = (mysqlnd_zval *)*zv; + + UNLOCK_PCACHE(cache); + } + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ _mysqlnd_palloc_rinit */ +PHPAPI MYSQLND_THD_ZVAL_PCACHE * _mysqlnd_palloc_rinit(MYSQLND_ZVAL_PCACHE * cache TSRMLS_DC) +{ + return mysqlnd_palloc_init_thd_cache(cache); +} +/* }}} */ + + +/* {{{ _mysqlnd_palloc_rshutdown */ +PHPAPI void _mysqlnd_palloc_rshutdown(MYSQLND_THD_ZVAL_PCACHE * thd_cache TSRMLS_DC) +{ + MYSQLND_ZVAL_PCACHE *cache; + mysqlnd_zval **p; + + DBG_ENTER("_mysqlnd_palloc_rshutdown"); + DBG_INF_FMT("cache=%p", thd_cache); + + if (!thd_cache || !(cache = thd_cache->parent)) { + return; + } + + /* + Keep in mind that for pthreads pthread_equal() should be used to be + fully standard compliant. However, the PHP code all-around, incl. the + the Zend MM uses direct comparison. + */ + p = thd_cache->gc_list.ptr_line; + while (p < thd_cache->gc_list.last_added) { + zval_dtor(&(*p)->zv); + p++; + } + + p = thd_cache->gc_list.ptr_line; + LOCK_PCACHE(cache); + while (p < thd_cache->gc_list.last_added) { + (*p)->point_type = MYSQLND_POINTS_FREE; + *(--cache->free_list.last_added) = *p; + ++cache->free_items; +#ifdef ZTS + memset(&((*p)->thread_id), 0, sizeof(THREAD_T)); +#endif + p++; + } + UNLOCK_PCACHE(cache); + + mysqlnd_palloc_free_thd_cache_reference(&thd_cache); + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_palloc_rshutdown */ +PHPAPI void mysqlnd_palloc_stats(const MYSQLND_ZVAL_PCACHE * const cache, zval *return_value) +{ + if (cache) { +#if PHP_MAJOR_VERSION >= 6 + TSRMLS_FETCH(); +#endif + + LOCK_PCACHE(cache); + array_init(return_value); +#if PHP_MAJOR_VERSION >= 6 + if (UG(unicode)) { + UChar *ustr; + int ulen; + + zend_string_to_unicode(UG(utf8_conv), &ustr, &ulen, "put_hits", sizeof("put_hits") TSRMLS_CC); + add_u_assoc_long_ex(return_value, IS_UNICODE, ZSTR(ustr), ulen + 1, cache->put_hits); + efree(ustr); + zend_string_to_unicode(UG(utf8_conv), &ustr, &ulen, "put_misses", sizeof("put_misses") TSRMLS_CC); + add_u_assoc_long_ex(return_value, IS_UNICODE, ZSTR(ustr), ulen + 1, cache->put_hits); + efree(ustr); + zend_string_to_unicode(UG(utf8_conv), &ustr, &ulen, "get_hits", sizeof("get_hits") TSRMLS_CC); + add_u_assoc_long_ex(return_value, IS_UNICODE, ZSTR(ustr), ulen + 1, cache->put_hits); + efree(ustr); + zend_string_to_unicode(UG(utf8_conv), &ustr, &ulen, "get_misses", sizeof("get_misses") TSRMLS_CC); + add_u_assoc_long_ex(return_value, IS_UNICODE, ZSTR(ustr), ulen + 1, cache->put_hits); + efree(ustr); + zend_string_to_unicode(UG(utf8_conv), &ustr, &ulen, "size", sizeof("size") TSRMLS_CC); + add_u_assoc_long_ex(return_value, IS_UNICODE, ZSTR(ustr), ulen + 1, cache->put_hits); + efree(ustr); + zend_string_to_unicode(UG(utf8_conv), &ustr, &ulen, "free_items", sizeof("free_items") TSRMLS_CC); + add_u_assoc_long_ex(return_value, IS_UNICODE, ZSTR(ustr), ulen + 1, cache->put_hits); + efree(ustr); + zend_string_to_unicode(UG(utf8_conv), &ustr, &ulen, "references", sizeof("references") TSRMLS_CC); + add_u_assoc_long_ex(return_value, IS_UNICODE, ZSTR(ustr), ulen + 1, cache->put_hits); + efree(ustr); + } else +#endif + { + add_assoc_long_ex(return_value, "put_hits", sizeof("put_hits"), cache->put_hits); + add_assoc_long_ex(return_value, "put_misses", sizeof("put_misses"), cache->put_misses); + add_assoc_long_ex(return_value, "get_hits", sizeof("get_hits"), cache->get_hits); + add_assoc_long_ex(return_value, "get_misses", sizeof("get_misses"), cache->get_misses); + add_assoc_long_ex(return_value, "size", sizeof("size"), cache->max_items); + add_assoc_long_ex(return_value, "free_items", sizeof("free_items"), cache->free_items); + add_assoc_long_ex(return_value, "references", sizeof("references"), cache->references); + } + UNLOCK_PCACHE(cache); + } else { + ZVAL_NULL(return_value); + } +} +/* }}} */ + +/* + * 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_palloc.h b/ext/mysqlnd/mysqlnd_palloc.h new file mode 100644 index 0000000000..8ef0eaab87 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_palloc.h @@ -0,0 +1,114 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#ifndef MYSQLND_PALLOC_H +#define MYSQLND_PALLOC_H + +/* Used in mysqlnd_debug.c */ +extern char * mysqlnd_palloc_zval_ptr_dtor_name; +extern char * mysqlnd_palloc_get_zval_name; + + +/* Session caching allocator */ +struct st_mysqlnd_zval_list { + zval **ptr_line; + zval **last_added; +}; + +typedef struct st_mysqlnd_zval_cache MYSQLND_ZVAL_CACHE; + +struct st_mysqlnd_zval_cache { + struct st_mysqlnd_zval_list *free_list; + unsigned int free_items; + unsigned int max_items; + unsigned int references; + unsigned long get_hits; + unsigned long get_misses; + unsigned long put_hits; + unsigned long put_full_misses; + unsigned long put_refcount_misses; +}; + + +enum mysqlnd_zval_ptr_type +{ + MYSQLND_POINTS_INT_BUFFER, + MYSQLND_POINTS_EXT_BUFFER, + MYSQLND_POINTS_FREE +}; + +/* Persistent caching allocator */ +typedef struct st_mysqlnd_zval { + /* Should be first */ + zval zv; + enum mysqlnd_zval_ptr_type point_type; +#ifdef ZTS + THREAD_T thread_id; +#endif +} mysqlnd_zval; + + +typedef struct st_mysqlnd_ndzval_list { + mysqlnd_zval **ptr_line; /* we allocate this, all are pointers to the block */ + mysqlnd_zval **last_added; /* this points to the ptr_line, and moves left-right. It's our stack */ +} mysqlnd_ndzval_list; + + +struct st_mysqlnd_zval_pcache { + mysqlnd_zval *block; + mysqlnd_zval *last_in_block; + mysqlnd_ndzval_list free_list; /* Fetch from here */ + +#ifdef ZTS + MUTEX_T LOCK_access; +#endif + unsigned int references; + + /* These are just for statistics and not used for operational purposes */ + unsigned int free_items; + unsigned int max_items; + + unsigned long get_hits; + unsigned long get_misses; + unsigned long put_hits; + unsigned long put_misses; +}; + +struct st_mysqlnd_thread_zval_pcache { + struct st_mysqlnd_zval_pcache *parent; + + unsigned int references; +#ifdef ZTS + THREAD_T thread_id; +#endif + mysqlnd_ndzval_list gc_list; /* GC these from time to time */ +}; + +#endif /* MYSQLND_PALLOC_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_portability.h b/ext/mysqlnd/mysqlnd_portability.h new file mode 100644 index 0000000000..37aeb67e21 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_portability.h @@ -0,0 +1,515 @@ +/* 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. +*/ + +/* 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... */ + +#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 +#ifndef HAVE_INT8_T +typedef signed char int8; /* Signed integer >= 8 bits */ +#else +typedef int8_t int8; /* Signed integer >= 8 bits */ +#endif +#endif + +#ifndef HAVE_UINT8 +#ifndef HAVE_UINT8_T +typedef unsigned char uint8; /* Unsigned integer >= 8 bits */ +#else +typedef uint8_t uint8; /* Signed integer >= 8 bits */ +#endif +#endif + +#ifndef HAVE_INT16 +#ifndef HAVE_INT16_T +typedef signed short int16; /* Signed integer >= 16 bits */ +#else +typedef int16_t int16; /* Signed integer >= 16 bits */ +#endif +#endif + +#ifndef HAVE_UINT16 +#ifndef HAVE_UINT16_T +typedef unsigned short uint16; /* Signed integer >= 16 bits */ +#else +typedef uint16_t uint16; /* Signed integer >= 16 bits */ +#endif +#endif + +#ifndef HAVE_UCHAR +typedef unsigned char uchar; /* Short for unsigned char */ +#endif + + +#if defined(HAVE_INT32_T) && defined(HAVE_UINT32_T) +typedef int32_t int32; +typedef uint32_t uint32; + +#elif SIZEOF_INT == 4 + +#ifndef HAVE_INT32 +typedef signed int int32; +#endif +#ifndef HAVE_UINT32 +typedef unsigned int uint32; +#endif + +#elif SIZEOF_LONG == 4 + +#ifndef HAVE_INT32 +typedef signed long int32; +#endif +#ifndef HAVE_UINT32 +typedef unsigned long uint32; +#endif + +#else +error "Neither int or long is of 4 bytes width" +#endif + +#if !defined(HAVE_ULONG) && !defined(__USE_MISC) && !defined(ulong) +typedef unsigned long ulong; /* Short for unsigned long */ +#endif +#ifndef longlong_defined +#if defined(HAVE_LONG_LONG) && SIZEOF_LONG != 8 +typedef unsigned long long int ulonglong; /* ulong or unsigned long long */ +typedef long long int longlong; +#else +typedef unsigned long ulonglong; /* ulong or unsigned long long */ +typedef long longlong; +#endif +#endif + + +#define int1store(T,A) do { *((zend_uchar*) (T)) = (A); } while(0) +#define uint1korr(A) (*(((uint8*)(A)))) + +/* Bit values are sent in reverted order of bytes, compared to normal !!! */ +#define bit_uint2korr(A) ((uint16) (((uint16) (((uchar*) (A))[1])) +\ + ((uint16) (((uchar*) (A))[0]) << 8))) +#define bit_uint3korr(A) ((uint32) (((uint32) (((uchar*) (A))[2])) +\ + (((uint32) (((uchar*) (A))[1])) << 8) +\ + (((uint32) (((uchar*) (A))[0])) << 16))) + +#define bit_uint4korr(A) ((uint32) (((uint32) (((uchar*) (A))[3])) +\ + (((uint32) (((uchar*) (A))[2])) << 8) +\ + (((uint32) (((uchar*) (A))[1])) << 16) +\ + (((uint32) (((uchar*) (A))[0])) << 24))) + +#define bit_uint5korr(A) ((ulonglong)(((uint32) ((uchar) (A)[4])) +\ + (((uint32) ((uchar) (A)[3])) << 8) +\ + (((uint32) ((uchar) (A)[2])) << 16) +\ + (((uint32) ((uchar) (A)[1])) << 24)) +\ + (((ulonglong) ((uchar) (A)[0])) << 32)) + +#define bit_uint6korr(A) ((ulonglong)(((uint32) (((uchar*) (A))[5])) +\ + (((uint32) (((uchar*) (A))[4])) << 8) +\ + (((uint32) (((uchar*) (A))[3])) << 16) +\ + (((uint32) (((uchar*) (A))[2])) << 24)) +\ + (((ulonglong) (((uint32) (((uchar*) (A))[1])) +\ + (((uint32) (((uchar*) (A))[0]) << 8)))) << 32)) + +#define bit_uint7korr(A) ((ulonglong)(((uint32) (((uchar*) (A))[6])) +\ + (((uint32) (((uchar*) (A))[5])) << 8) +\ + (((uint32) (((uchar*) (A))[4])) << 16) +\ + (((uint32) (((uchar*) (A))[3])) << 24)) +\ + (((ulonglong) (((uint32) (((uchar*) (A))[2])) +\ + (((uint32) (((uchar*) (A))[1])) << 8) +\ + (((uint32) (((uchar*) (A))[0])) << 16))) << 32)) + + +#define bit_uint8korr(A) ((ulonglong)(((uint32) (((uchar*) (A))[7])) +\ + (((uint32) (((uchar*) (A))[6])) << 8) +\ + (((uint32) (((uchar*) (A))[5])) << 16) +\ + (((uint32) (((uchar*) (A))[4])) << 24)) +\ + (((ulonglong) (((uint32) (((uchar*) (A))[3])) +\ + (((uint32) (((uchar*) (A))[2])) << 8) +\ + (((uint32) (((uchar*) (A))[1])) << 16) +\ + (((uint32) (((uchar*) (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 */ +#if defined(__i386__) && !defined(_WIN64) +#define sint2korr(A) (*((int16 *) (A))) +#define sint3korr(A) ((int32) ((((uchar) (A)[2]) & 128) ? \ + (((uint32) 255L << 24) | \ + (((uint32) (uchar) (A)[2]) << 16) |\ + (((uint32) (uchar) (A)[1]) << 8) | \ + ((uint32) (uchar) (A)[0])) : \ + (((uint32) (uchar) (A)[2]) << 16) |\ + (((uint32) (uchar) (A)[1]) << 8) | \ + ((uint32) (uchar) (A)[0]))) +#define sint4korr(A) (*((long *) (A))) + +#define uint2korr(A) (*((uint16 *) (A))) +#define uint3korr(A) (uint32) (((uint32) ((uchar) (A)[0])) +\ + (((uint32) ((uchar) (A)[1])) << 8) +\ + (((uint32) ((uchar) (A)[2])) << 16)) +#define uint4korr(A) (*((unsigned long *) (A))) + + + +#define uint8korr(A) (*((ulonglong *) (A))) +#define sint8korr(A) (*((longlong *) (A))) +#define int2store(T,A) *((uint16*) (T))= (uint16) (A) +#define int3store(T,A) { \ + *(T)= (uchar) ((A));\ + *(T+1)=(uchar) (((uint) (A) >> 8));\ + *(T+2)=(uchar) (((A) >> 16)); } +#define int4store(T,A) *((long *) (T))= (long) (A) +#define int5store(T,A) { \ + *((uchar *)(T))= (uchar)((A));\ + *(((uchar *)(T))+1)=(uchar) (((A) >> 8));\ + *(((uchar *)(T))+2)=(uchar) (((A) >> 16));\ + *(((uchar *)(T))+3)=(uchar) (((A) >> 24)); \ + *(((uchar *)(T))+4)=(uchar) (((A) >> 32)); } + +/* From Andrey Hristov, based on int5store() */ +#define int6store(T,A) { \ + *(((uchar *)(T)))= (uchar)((A));\ + *(((uchar *)(T))+1))=(uchar) (((A) >> 8));\ + *(((uchar *)(T))+2))=(uchar) (((A) >> 16));\ + *(((uchar *)(T))+3))=(uchar) (((A) >> 24)); \ + *(((uchar *)(T))+4))=(uchar) (((A) >> 32)); \ + *(((uchar *)(T))+5))=(uchar) (((A) >> 40)); } + +#define int8store(T,A) *((ulonglong *) (T))= (ulonglong) (A) + +typedef union { + double v; + long m[2]; +} doubleget_union; +#define doubleget(V,M) { ((doubleget_union *)&(V))->m[0] = *((long*) (M)); \ + ((doubleget_union *)&(V))->m[1] = *(((long*) (M))+1); } +#define doublestore(T,V) { *((long *) (T)) = ((doubleget_union *)&(V))->m[0]; \ + *(((long *) (T))+1) = ((doubleget_union *)&(V))->m[1]; } +#define float4get(V,M) { *((float *) &(V)) = *((float*) (M)); } +#define float8get(V,M) doubleget((V),(M)) +/* From Andrey Hristov based on doubleget */ +#define floatget(V,M) memcpy((char*) &(V),(char*) (M),sizeof(float)) +#define floatstore float4store +#define float4store(V,M) memcpy((char*) (V),(char*) (&M),sizeof(float)) +#define float8store(V,M) doublestore((V),(M)) +#endif /* __i386__ */ + +#ifndef sint2korr +#define sint2korr(A) (int16) (((int16) ((uchar) (A)[0])) +\ + ((int16) ((int16) (A)[1]) << 8)) +#define sint3korr(A) ((int32) ((((uchar) (A)[2]) & 128) ? \ + (((uint32) 255L << 24) | \ + (((uint32) (uchar) (A)[2]) << 16) |\ + (((uint32) (uchar) (A)[1]) << 8) | \ + ((uint32) (uchar) (A)[0])) : \ + (((uint32) (uchar) (A)[2]) << 16) |\ + (((uint32) (uchar) (A)[1]) << 8) | \ + ((uint32) (uchar) (A)[0]))) +#define sint4korr(A) (int32) (((int32) ((uchar) (A)[0])) +\ + (((int32) ((uchar) (A)[1]) << 8)) +\ + (((int32) ((uchar) (A)[2]) << 16)) +\ + (((int32) ((int16) (A)[3]) << 24))) + +#define sint8korr(A) (longlong) uint8korr(A) +#define uint2korr(A) (uint16) (((uint16) ((uchar) (A)[0])) +\ + ((uint16) ((uchar) (A)[1]) << 8)) +#define uint3korr(A) (uint32) (((uint32) ((uchar) (A)[0])) +\ + (((uint32) ((uchar) (A)[1])) << 8) +\ + (((uint32) ((uchar) (A)[2])) << 16)) +#define uint4korr(A) (uint32) (((uint32) ((uchar) (A)[0])) +\ + (((uint32) ((uchar) (A)[1])) << 8) +\ + (((uint32) ((uchar) (A)[2])) << 16) +\ + (((uint32) ((uchar) (A)[3])) << 24)) +#define bit_uint5korr(A) ((ulonglong)(((uint32) ((uchar) (A)[0])) +\ + (((uint32) ((uchar) (A)[1])) << 8) +\ + (((uint32) ((uchar) (A)[2])) << 16) +\ + (((uint32) ((uchar) (A)[3])) << 24)) +\ + (((ulonglong) ((uchar) (A)[4])) << 32)) +/* From Andrey Hristov, based on uint5korr */ +#define bit_uint6korr(A) ((ulonglong)(((uint32) (((uchar*) (A))[5])) +\ + (((uint32) (((uchar*) (A))[4])) << 8) +\ + (((uint32) (((uchar*) (A))[3])) << 16) +\ + (((uint32) (((uchar*) (A))[2])) << 24)) +\ + (((ulonglong) (((uint32) (((uchar*) (A))[1])) +\ + (((uint32) (((uchar*) (A))[0]) << 8)))) << 32)) + +#define bit_uint7korr(A) ((ulonglong)(((uint32) (((uchar*) (A))[6])) +\ + (((uint32) (((uchar*) (A))[5])) << 8) +\ + (((uint32) (((uchar*) (A))[4])) << 16) +\ + (((uint32) (((uchar*) (A))[3])) << 24)) +\ + (((ulonglong) (((uint32) (((uchar*) (A))[2])) +\ + (((uint32) (((uchar*) (A))[1])) << 8) +\ + (((uint32) (((uchar*) (A))[0])) << 16))) << 32)) + + +#define bit_uint8korr(A) ((ulonglong)(((uint32) (((uchar*) (A))[7])) +\ + (((uint32) (((uchar*) (A))[6])) << 8) +\ + (((uint32) (((uchar*) (A))[5])) << 16) +\ + (((uint32) (((uchar*) (A))[4])) << 24)) +\ + (((ulonglong) (((uint32) (((uchar*) (A))[3])) +\ + (((uint32) (((uchar*) (A))[2])) << 8) +\ + (((uint32) (((uchar*) (A))[1])) << 16) +\ + (((uint32) (((uchar*) (A))[0])) << 24))) << 32)) + +#define uint8korr(A) ((ulonglong)(((uint32) ((uchar) (A)[0])) +\ + (((uint32) ((uchar) (A)[1])) << 8) +\ + (((uint32) ((uchar) (A)[2])) << 16) +\ + (((uint32) ((uchar) (A)[3])) << 24)) +\ + (((ulonglong) (((uint32) ((uchar) (A)[4])) +\ + (((uint32) ((uchar) (A)[5])) << 8) +\ + (((uint32) ((uchar) (A)[6])) << 16) +\ + (((uint32) ((uchar) (A)[7])) << 24))) << 32)) + + +#define int2store(T,A) do { uint def_temp= (uint) (A) ;\ + *((uchar*) (T)) = (uchar)(def_temp); \ + *((uchar*) (T+1)) = (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) { uint def_temp= (uint) (A), def_temp2= (uint) ((A) >> 32); \ + int4store((T),def_temp); \ + int4store((T+4),def_temp2); \ + } +#ifdef WORDS_BIGENDIAN +#define float4store(T,A) do { \ + *(((char *)(T))) = (char) ((char *) &A)[3];\ + *(((char *)(T))+1) = (char) ((char *) &A)[2];\ + *(((char *)(T))+2) = (char) ((char *) &A)[1];\ + *(((char *)(T))+3) = (char) ((char *) &A)[0]; } while (0) + +#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)) +#define float4store(V,M) memcpy((char*) (V),(char*) (&M),sizeof(float)) + +#if defined(__FLOAT_WORD_ORDER) && (__FLOAT_WORD_ORDER == __BIG_ENDIAN) +#define doublestore(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 doubleget(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 */ + +#define float8get(V,M) doubleget((V),(M)) +#define float8store(V,M) doublestore((V),(M)) +#endif /* WORDS_BIGENDIAN */ + +#endif /* sint2korr */ + +/* 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 */ + +#ifdef WORDS_BIGENDIAN + +#define ushortget(V,M) { V = (uint16) (((uint16) ((uchar) (M)[1]))+\ + ((uint16) ((uint16) (M)[0]) << 8)); } +#define shortget(V,M) { V = (short) (((short) ((uchar) (M)[1]))+\ + ((short) ((short) (M)[0]) << 8)); } +#define longget(V,M) do { int32 def_temp;\ + ((char*) &def_temp)[0]=(M)[0];\ + ((char*) &def_temp)[1]=(M)[1];\ + ((char*) &def_temp)[2]=(M)[2];\ + ((char*) &def_temp)[3]=(M)[3];\ + (V)=def_temp; } while (0) +#define ulongget(V,M) do { uint32 def_temp;\ + ((char*) &def_temp)[0]=(M)[0];\ + ((char*) &def_temp)[1]=(M)[1];\ + ((char*) &def_temp)[2]=(M)[2];\ + ((char*) &def_temp)[3]=(M)[3];\ + (V)=def_temp; } while (0) +#define shortstore(T,A) do { \ + uint def_temp=(uint) (A) ;\ + *(((char *)(T))+1)=(char)(def_temp); \ + *(((char *)(T))+0)=(char)(def_temp >> 8); } while (0) +#define longstore(T,A) do { \ + *(((char *)(T))+3)=(char)((A));\ + *(((char *)(T))+2)=(char)(((A) >> 8));\ + *(((char *)(T))+1)=(char)(((A) >> 16));\ + *(((char *)(T))+0)=(char)(((A) >> 24)); } while (0) + +#define doubleget(V,M) memcpy((char*) &(V),(char*) (M), sizeof(double)) +#define doublestore(T,V) memcpy((char*) (T),(char*) &(V), sizeof(double)) +#define longlongget(V,M) memcpy((char*) &(V),(char*) (M), sizeof(ulonglong)) +#define longlongstore(T,V) memcpy((char*) (T),(char*) &(V), sizeof(ulonglong)) + +#else + +#define ushortget(V,M) { V = uint2korr((M)); } +#define shortget(V,M) { V = sint2korr((M)); } +#define longget(V,M) { V = sint4korr((M)); } +#define ulongget(V,M) { V = uint4korr((M)); } +#define shortstore(T,V) int2store((T),(V)) +#define longstore(T,V) int4store((T),(V)) +#ifndef doubleget +#define doubleget(V,M) memcpy((char*) &(V),(char*) (M),sizeof(double)) +#define doublestore(T,V) memcpy((char*) (T),(char*) &(V),sizeof(double)) +#endif /* doubleget */ +#define longlongget(V,M) memcpy((char*) &(V),(char*) (M),sizeof(ulonglong)) +#define longlongstore(T,V) memcpy((char*) (T),(char*) &(V),sizeof(ulonglong)) + +#endif /* WORDS_BIGENDIAN */ + + +#ifdef PHP_WIN32 +#define MYSQLND_LLU_SPEC "%I64u" +#define MYSQLND_LL_SPEC "%I64d" +#ifndef L64 +#define L64(x) x##i64 +#endif +typedef unsigned __int64 my_uint64; +typedef __int64 my_int64; +typedef unsigned __int64 mynd_ulonglong; +typedef __int64 mynd_longlong; +#else +#define MYSQLND_LLU_SPEC "%llu" +#define MYSQLND_LL_SPEC "%lld" +#ifndef L64 +#define L64(x) x##LL +#endif +#ifndef HAVE_UINT64_T +typedef unsigned long long my_uint64; +typedef unsigned long long mynd_ulonglong; +#else +typedef uint64_t my_uint64; +typedef uint64_t mynd_ulonglong; +#endif +#ifndef HAVE_INT64_T +typedef long long my_int64; +typedef long long mynd_longlong; +#else +typedef int64_t my_int64; +typedef int64_t mynd_longlong; +#endif +#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_priv.h b/ext/mysqlnd/mysqlnd_priv.h new file mode 100644 index 0000000000..83bc0665cb --- /dev/null +++ b/ext/mysqlnd/mysqlnd_priv.h @@ -0,0 +1,191 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef MYSQLND_PRIV_H +#define MYSQLND_PRIV_H + +#ifdef ZTS +#include "TSRM.h" +#endif + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef pestrndup +#define pestrndup(s, length, persistent) ((persistent)?zend_strndup((s),(length)):estrndup((s),(length))) +#endif + + +#define MYSQLND_CLASS_METHODS_START(class) static \ + struct st_##class##_methods mysqlnd_##class##_methods = { +#define MYSQLND_CLASS_METHODS_END } +#define MYSQLND_METHOD(class, method) php_##class##_##method##_pub +#define MYSQLND_METHOD_PRIVATE(class, method) php_##class##_##method##_priv + +#if PHP_MAJOR_VERSION < 6 +#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;\ +} +#else +#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;\ +} +#endif + + + +#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 */ +/* + 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 + + + +/* 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 = (mynd_ulonglong) ~0 + +/* Error handling */ +#define SET_NEW_MESSAGE(buf, buf_len, message, len, persistent) \ + {\ + if ((buf)) { \ + pefree((buf), (persistent)); \ + } \ + (buf) = (message); \ + (buf_len) = (len); \ + /* Transfer ownership*/ \ + (message) = NULL; \ + } + +#define SET_EMPTY_MESSAGE(buf, buf_len, persistent) \ + {\ + if ((buf)) { \ + pefree((buf), (persistent)); \ + (buf) = NULL; \ + } \ + (buf_len) = 0; \ + } + + +#define SET_EMPTY_ERROR(error_info) \ + { \ + error_info.error_no = 0; \ + error_info.error[0] = '\0'; \ + strncpy(error_info.sqlstate, "00000", sizeof("00000") - 1); \ + } + +#define SET_CLIENT_ERROR(error_info, a, b, c) \ + { \ + error_info.error_no = a; \ + strncpy(error_info.sqlstate, b, sizeof(error_info.sqlstate)); \ + strncpy(error_info.error, c, sizeof(error_info.error)); \ + } + +#define SET_STMT_ERROR(stmt, a, b, c) SET_CLIENT_ERROR(stmt->error_info, a, b, c) + + + +/* PS stuff */ +typedef void (*ps_field_fetch_func)(zval *zv, const MYSQLND_FIELD * const field, + uint 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]; + +extern const char * mysqlnd_out_of_sync; +extern const char * mysqlnd_server_gone; + + +enum_func_status mysqlnd_handle_local_infile(MYSQLND *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 ps_fetch_from_1_to_8_bytes(zval *zv, const MYSQLND_FIELD * const field, + uint pack_len, zend_uchar **row, zend_bool as_unicode, + unsigned int byte_count TSRMLS_DC); + + + +int mysqlnd_set_sock_no_delay(php_stream *stream); +#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 0000000000..00dc46761f --- /dev/null +++ b/ext/mysqlnd/mysqlnd_ps.c @@ -0,0 +1,1740 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_result.h" +#include "mysqlnd_result_meta.h" +#include "mysqlnd_statistics.h" +#include "mysqlnd_debug.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.c */ +enum_func_status mysqlnd_simple_command(MYSQLND *conn, enum php_mysqlnd_server_command command, + const char * const arg, size_t arg_len, + enum php_mysql_packet_type ok_packet, + zend_bool silent TSRMLS_DC); + +/* Exported by mysqlnd_ps_codec.c */ +zend_uchar* mysqlnd_stmt_execute_generate_request(MYSQLND_STMT *stmt, size_t *request_len, + zend_bool *free_buffer TSRMLS_DC); + + +MYSQLND_RES * _mysqlnd_stmt_use_result(MYSQLND_STMT *stmt TSRMLS_DC); + +enum_func_status mysqlnd_fetch_stmt_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); + +void mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const stmt TSRMLS_DC); + + +/* {{{ mysqlnd_stmt::store_result */ +static MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const stmt TSRMLS_DC) +{ + enum_func_status ret; + MYSQLND *conn = stmt->conn; + MYSQLND_RES *result; + zend_bool to_cache = FALSE; + + DBG_ENTER("mysqlnd_stmt::store_result"); + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + /* 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 */ + MYSQLND_RES * res = stmt->m->use_result(stmt TSRMLS_CC); + DBG_RETURN(res); + } + + /* Nothing to store for UPSERT/LOAD DATA*/ + if (conn->state != 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 = stmt->m->store_result; + + SET_EMPTY_ERROR(stmt->error_info); + SET_EMPTY_ERROR(stmt->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_fetch_stmt_row_buffered; + result->m.fetch_lengths = NULL;/* makes no sense */ + result->zval_cache = mysqlnd_palloc_get_thd_cache_reference(conn->zval_cache); + + /* Create room for 'next_extend' rows */ + + /* Not set for SHOW statements at PREPARE stage */ + if (result->conn) { + result->conn->m->free_reference(result->conn TSRMLS_CC); + result->conn = NULL; /* store result does not reference the connection */ + } + + ret = mysqlnd_store_result_fetch_data(conn, result, result->meta, + TRUE, stmt->update_max_length, + to_cache TSRMLS_CC); + + if (PASS == ret) { + /* libmysql API docs say it should be so for SELECT statements */ + stmt->upsert_status.affected_rows = stmt->result->data->row_count; + + stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED; + } else { + conn->error_info = result->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 stmt TSRMLS_DC) +{ + MYSQLND *conn = stmt->conn; + MYSQLND_RES *result; + + DBG_ENTER("mysqlnd_stmt::get_result"); + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + /* 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 */ + MYSQLND_RES * res = stmt->m->use_result(stmt TSRMLS_CC); + DBG_RETURN(res); + } + + /* Nothing to store for UPSERT/LOAD DATA*/ + if (conn->state != 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(stmt->conn->error_info); + MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_BUFFERED_SETS); + + result = mysqlnd_result_init(stmt->result->field_count, + mysqlnd_palloc_get_thd_cache_reference(conn->zval_cache) TSRMLS_CC); + + result->meta = stmt->result->meta->m->clone_metadata(stmt->result->meta, FALSE TSRMLS_CC); + + /* Not set for SHOW statements at PREPARE stage */ + if (stmt->result->conn) { + stmt->result->conn->m->free_reference(stmt->result->conn TSRMLS_CC); + stmt->result->conn = NULL; /* store result does not reference the connection */ + } + + if ((result = result->m.store_result(result, conn, TRUE TSRMLS_CC))) { + stmt->upsert_status.affected_rows = result->data->row_count; + stmt->state = MYSQLND_STMT_PREPARED; + result->type = MYSQLND_RES_PS_BUF; + } else { + stmt->error_info = conn->error_info; + stmt->state = MYSQLND_STMT_PREPARED; + } + + DBG_RETURN(result); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_skip_metadata */ +static enum_func_status +mysqlnd_stmt_skip_metadata(MYSQLND_STMT *stmt TSRMLS_DC) +{ + /* Follows parameter metadata, we have just to skip it, as libmysql does */ + unsigned int i = 0; + php_mysql_packet_res_field field_packet; + + DBG_ENTER("mysqlnd_stmt_skip_metadata"); + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + PACKET_INIT_ALLOCA(field_packet, PROT_RSET_FLD_PACKET); + field_packet.skip_parsing = TRUE; + for (;i < stmt->param_count; i++) { + if (FAIL == PACKET_READ_ALLOCA(field_packet, stmt->conn)) { + PACKET_FREE_ALLOCA(field_packet); + DBG_RETURN(FAIL); + } + } + PACKET_FREE_ALLOCA(field_packet); + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_read_prepare_response */ +static enum_func_status +mysqlnd_stmt_read_prepare_response(MYSQLND_STMT *stmt TSRMLS_DC) +{ + php_mysql_packet_prepare_response prepare_resp; + + DBG_ENTER("mysqlnd_stmt_read_prepare_response"); + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + PACKET_INIT_ALLOCA(prepare_resp, PROT_PREPARE_RESP_PACKET); + if (FAIL == PACKET_READ_ALLOCA(prepare_resp, stmt->conn)) { + PACKET_FREE_ALLOCA(prepare_resp); + return FAIL; + } + + if (0xFF == prepare_resp.error_code) { + stmt->error_info = stmt->conn->error_info = prepare_resp.error_info; + return FAIL; + } + + 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; + PACKET_FREE_ALLOCA(prepare_resp); + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_prepare_read_eof */ +static enum_func_status +mysqlnd_stmt_prepare_read_eof(MYSQLND_STMT *stmt TSRMLS_DC) +{ + php_mysql_packet_eof fields_eof; + enum_func_status ret; + + DBG_ENTER("mysqlnd_stmt_prepare_read_eof"); + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + PACKET_INIT_ALLOCA(fields_eof, PROT_EOF_PACKET); + if (FAIL == (ret = PACKET_READ_ALLOCA(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)); + 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_ALLOCA(fields_eof); + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::prepare */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, prepare)(MYSQLND_STMT * const stmt, const char * const query, + unsigned int query_len TSRMLS_DC) +{ + MYSQLND_STMT *stmt_to_prepare = stmt; + + DBG_ENTER("mysqlnd_stmt::prepare"); + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + 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 = stmt->m->use_result; + stmt->default_rset_handler(stmt TSRMLS_CC); + } + /* No 'else' here please :) */ + if (stmt->state > MYSQLND_STMT_WAITING_USE_OR_STORE) { + 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. + */ + stmt_to_prepare = mysqlnd_stmt_init(stmt->conn); + } + + if (FAIL == mysqlnd_simple_command(stmt_to_prepare->conn, COM_STMT_PREPARE, query, + query_len, PROT_LAST, FALSE TSRMLS_CC) || + FAIL == mysqlnd_stmt_read_prepare_response(stmt_to_prepare TSRMLS_CC)) { + goto fail; + } + + if (stmt_to_prepare->param_count) { + if (FAIL == mysqlnd_stmt_skip_metadata(stmt_to_prepare TSRMLS_CC) || + FAIL == mysqlnd_stmt_prepare_read_eof(stmt_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 = mysqlnd_result_init(stmt_to_prepare->field_count, NULL TSRMLS_CC); + /* 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); + + 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(stmt_to_prepare TSRMLS_CC)) { + goto fail; + } + } + + if (stmt_to_prepare != stmt) { + /* Free old buffers, binding and resources on server */ + stmt->m->close(stmt, TRUE TSRMLS_CC); + + memcpy(stmt, stmt_to_prepare, sizeof(MYSQLND_STMT)); + + /* Now we will have a clean new statement object */ + mnd_efree(stmt_to_prepare); + } + stmt->state = MYSQLND_STMT_PREPARED; + DBG_INF("PASS"); + DBG_RETURN(PASS); + +fail: + if (stmt_to_prepare != stmt) { + stmt_to_prepare->m->dtor(stmt_to_prepare, TRUE TSRMLS_CC); + } + stmt->state = MYSQLND_STMT_INITTED; + + DBG_INF("FAIL"); + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::execute */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, execute)(MYSQLND_STMT * const stmt TSRMLS_DC) +{ + enum_func_status ret; + MYSQLND *conn = stmt->conn; + zend_uchar *request; + size_t request_len; + zend_bool free_request; + + DBG_ENTER("mysqlnd_stmt::execute"); + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + SET_ERROR_AFF_ROWS(stmt); + SET_ERROR_AFF_ROWS(stmt->conn); + + if (stmt->state > MYSQLND_STMT_PREPARED && stmt->field_count) { + 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); + } + } + } + + /* + 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); + } 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 && !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); + } + + request = mysqlnd_stmt_execute_generate_request(stmt, &request_len, &free_request TSRMLS_CC); + + /* support for buffer types should be added here ! */ + + ret = mysqlnd_simple_command(stmt->conn, COM_STMT_EXECUTE, (char *)request, request_len, + PROT_LAST /* we will handle the response packet*/, + FALSE TSRMLS_CC); + + if (free_request) { + mnd_efree(request); + } + + if (ret == FAIL) { + stmt->error_info = conn->error_info; + DBG_INF("FAIL"); + DBG_RETURN(FAIL); + } + stmt->execute_count++; + + ret = mysqlnd_query_read_result_set_header(stmt->conn, stmt TSRMLS_CC); + if (ret == FAIL) { + stmt->error_info = conn->error_info; + stmt->upsert_status.affected_rows = conn->upsert_status.affected_rows; + if (conn->state == CONN_QUIT_SENT) { + /* close the statement here, the connection has been closed */ + } + } else { + SET_EMPTY_ERROR(stmt->error_info); + SET_EMPTY_ERROR(stmt->conn->error_info); + stmt->send_types_to_server = 0; + stmt->upsert_status = conn->upsert_status; + stmt->state = MYSQLND_STMT_EXECUTED; + if (conn->last_query_type == QUERY_UPSERT) { + stmt->upsert_status = conn->upsert_status; + DBG_INF("PASS"); + DBG_RETURN(PASS); + } else if (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); + } + + /* 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=%d cursor=%d\n", stmt->upsert_status.server_status, + stmt->upsert_status.server_status & SERVER_STATUS_CURSOR_EXISTS); + + if (stmt->upsert_status.server_status & SERVER_STATUS_CURSOR_EXISTS) { + stmt->cursor_exists = TRUE; + conn->state = CONN_READY; + /* Only cursor read */ + stmt->default_rset_handler = stmt->m->use_result; + DBG_INF("use_result"); + } else if (stmt->flags & CURSOR_TYPE_READ_ONLY) { + /* + 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 = stmt->m->store_result; + DBG_INF("store_result"); + } else { + /* preferred is unbuffered read */ + stmt->default_rset_handler = stmt->m->use_result; + DBG_INF("use_result"); + } + } + } + + DBG_INF(ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_fetch_stmt_row_buffered */ +enum_func_status +mysqlnd_fetch_stmt_row_buffered(MYSQLND_RES *result, void *param, unsigned int flags, + zend_bool *fetched_anything TSRMLS_DC) +{ + unsigned int i; + MYSQLND_STMT *stmt = (MYSQLND_STMT *) param; + + DBG_ENTER("mysqlnd_fetch_stmt_row_buffered"); + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + /* If we haven't read everything */ + if (result->data->data_cursor && + (result->data->data_cursor - result->data->data) < result->data->row_count) + { + /* The user could have skipped binding - don't crash*/ + if (stmt->result_bind) { + zval **current_row = *result->data->data_cursor; + 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 + zval_dtor(stmt->result_bind[i].zv); +#endif + /* copy the type */ + if (stmt->result_bind[i].bound == TRUE) { + DBG_INF_FMT("i=%d type=%d", 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); + } + } + } + } + result->data->data_cursor++; + *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 { + result->data->data_cursor = NULL; + *fetched_anything = FALSE; + 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 *stmt = (MYSQLND_STMT *) param; + unsigned int i, field_count = result->field_count; + php_mysql_packet_row *row_packet = result->row_packet; + + DBG_ENTER("mysqlnd_stmt_fetch_row_unbuffered"); + + if (result->unbuf->eof_reached) { + /* No more rows obviously */ + *fetched_anything = FALSE; + DBG_INF("eof reached"); + DBG_RETURN(PASS); + } + if (result->conn->state != 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); + } + /* 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 + mysqlnd_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++; + *fetched_anything = TRUE; + + if (!row_packet->skip_extraction) { + mysqlnd_unbuffered_free_last_data(result TSRMLS_CC); + + DBG_INF("extracting data"); + 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; + + 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 mysqlnd_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)) ) { + stmt->result_bind[i].zv->value = data->value; +#ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF + zval_copy_ctor(stmt->result_bind[i].zv); +#endif + if ( + (Z_TYPE_P(data) == IS_STRING +#if PHP_MAJOR_VERSION >= 6 + || Z_TYPE_P(data) == IS_UNICODE +#endif + ) + && (result->meta->fields[i].max_length < Z_STRLEN_P(data))) + { + result->meta->fields[i].max_length = Z_STRLEN_P(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 mysqlnd_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. + */ + mnd_efree(row_packet->row_buffer); + row_packet->row_buffer = NULL; + } + } else if (ret == FAIL) { + if (row_packet->error_info.error_no) { + stmt->conn->error_info = row_packet->error_info; + stmt->error_info = row_packet->error_info; + } + *fetched_anything = FALSE; + result->conn->state = 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) { + result->conn->state = CONN_NEXT_RESULT_PENDING; + } else { + result->conn->state = CONN_READY; + } + *fetched_anything = FALSE; + } + + DBG_INF_FMT("ret=%s fetched_anything=%d", ret == PASS? "PASS":"FAIL", *fetched_anything); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::use_result */ +static MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_stmt, use_result)(MYSQLND_STMT *stmt TSRMLS_DC) +{ + MYSQLND_RES *result; + MYSQLND *conn = stmt->conn; + + DBG_ENTER("mysqlnd_stmt::use_result"); + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + if (!stmt->field_count || + (!stmt->cursor_exists && conn->state != CONN_FETCHING_DATA) || + (stmt->cursor_exists && conn->state != 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); + SET_EMPTY_ERROR(stmt->conn->error_info); + + MYSQLND_INC_CONN_STATISTIC(&stmt->conn->stats, STAT_PS_UNBUFFERED_SETS); + + result = stmt->result; + result->type = MYSQLND_RES_PS_UNBUF; + result->m.fetch_row = stmt->cursor_exists? mysqlnd_fetch_stmt_row_cursor: + mysqlnd_stmt_fetch_row_unbuffered; + result->m.fetch_lengths = NULL; /* makes no sense */ + result->zval_cache = mysqlnd_palloc_get_thd_cache_reference(conn->zval_cache); + + result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED)); + + DBG_INF_FMT("cursor=%d zval_cache=%p", stmt->cursor_exists, result->zval_cache); + /* + 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. + */ + PACKET_INIT(result->row_packet, PROT_ROW_PACKET, php_mysql_packet_row *); + result->row_packet->field_count = result->field_count; + result->row_packet->binary_protocol = TRUE; + result->row_packet->fields_metadata = stmt->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; + result->lengths = NULL; + + stmt->state = MYSQLND_STMT_USE_OR_STORE_CALLED; + + /* No multithreading issues as we don't share the connection :) */ + + 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 *stmt = (MYSQLND_STMT *) param; + zend_uchar buf[STMT_ID_LENGTH /* statement id */ + 4 /* number of rows to fetch */]; + php_mysql_packet_row *row_packet = result->row_packet; + + DBG_ENTER("mysqlnd_fetch_stmt_row_cursor"); + DBG_INF_FMT("stmt=%lu flags=%u", stmt->stmt_id, flags); + + if (!stmt) { + DBG_ERR("no statement"); + DBG_RETURN(FAIL); + } + + 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); + } + + 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 == mysqlnd_simple_command(stmt->conn, COM_STMT_FETCH, (char *)buf, sizeof(buf), + PROT_LAST /* we will handle the response packet*/, + FALSE TSRMLS_CC)) { + 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; + mysqlnd_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 (!row_packet->skip_extraction) { + /* 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 mysqlnd_unbuffered_free_last_data() + */ + DBG_INF_FMT("i=%d type=%d", i, Z_TYPE_P(stmt->result_bind[i].zv)); + if (IS_NULL != (Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(data)) ) { + stmt->result_bind[i].zv->value = data->value; +#ifdef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF + zval_copy_ctor(stmt->result_bind[i].zv); +#endif + if ((Z_TYPE_P(data) == IS_STRING +#if PHP_MAJOR_VERSION >= 6 + || Z_TYPE_P(data) == IS_UNICODE +#endif + ) + && (result->meta->fields[i].max_length < Z_STRLEN_P(data))) + { + result->meta->fields[i].max_length = Z_STRLEN_P(data); + } + } + } + } + } + result->unbuf->row_count++; + *fetched_anything = TRUE; + /* 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) { + mnd_efree(row_packet->row_buffer); + row_packet->row_buffer = NULL; + } + MYSQLND_INC_CONN_STATISTIC(&stmt->conn->stats, STAT_ROWS_FETCHED_FROM_CLIENT_PS_CURSOR); + } 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=%d s_status=%d warns=%d eof=%d", + ret == PASS? "PASS":"FAIL", *fetched_anything, + row_packet->server_status, row_packet->warning_count, + result->unbuf->eof_reached); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::fetch */ +PHPAPI enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, fetch)(MYSQLND_STMT * const stmt, + zend_bool * const fetched_anything TSRMLS_DC) +{ + enum_func_status ret; + DBG_ENTER("mysqlnd_stmt::fetch"); + 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(stmt 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=%d", 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*)stmt, 0, fetched_anything TSRMLS_CC); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::reset */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, reset)(MYSQLND_STMT * const stmt TSRMLS_DC) +{ + enum_func_status ret = PASS; + MYSQLND * conn = stmt->conn; + zend_uchar cmd_buf[STMT_ID_LENGTH /* statement id */]; + + DBG_ENTER("mysqlnd_stmt::reset"); + 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) { + 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; + } + } + } + + /* + If the user decided to close the statement 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"); + stmt->default_rset_handler(stmt TSRMLS_CC); + stmt->state = MYSQLND_STMT_USER_FETCHING; + } + + if (stmt->result) { + DBG_INF("skipping result"); + stmt->result->m.skip_result(stmt->result TSRMLS_CC); + } + /* Now the line should be free, if it wasn't */ + + int4store(cmd_buf, stmt->stmt_id); + if (conn->state == CONN_READY && + FAIL == (ret = mysqlnd_simple_command(conn, COM_STMT_RESET, (char *)cmd_buf, + sizeof(cmd_buf), PROT_OK_PACKET, + FALSE TSRMLS_CC))) { + stmt->error_info = conn->error_info; + } + stmt->upsert_status = conn->upsert_status; + + stmt->state = MYSQLND_STMT_PREPARED; + } + 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 stmt, unsigned int param_no, + const char * const data, unsigned long length TSRMLS_DC) +{ + enum_func_status ret = FAIL; + MYSQLND * conn = stmt->conn; + zend_uchar *cmd_buf; + size_t packet_len; + enum php_mysqlnd_server_command cmd = COM_STMT_SEND_LONG_DATA; + + DBG_ENTER("mysqlnd_stmt::send_long_data"); + DBG_INF_FMT("stmt=%lu param_no=%d data_len=%lu", stmt->stmt_id, param_no, length); + + 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 + mysqlnd_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->state == CONN_READY) { + stmt->param_bind[param_no].flags |= MYSQLND_PARAM_BIND_BLOB_USED; + cmd_buf = mnd_emalloc(packet_len = STMT_ID_LENGTH + 2 + length); + + 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 = mysqlnd_simple_command(conn, cmd, (char *)cmd_buf, packet_len, + PROT_LAST , FALSE TSRMLS_CC); + mnd_efree(cmd_buf); + if (FAIL == ret) { + stmt->error_info = 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 = php_mysqlnd_consume_uneaten_data(conn, 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 %u 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_param */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, bind_param)(MYSQLND_STMT * const stmt, + MYSQLND_PARAM_BIND * const param_bind TSRMLS_DC) +{ + unsigned int i = 0; + + DBG_ENTER("mysqlnd_stmt::bind_param"); + 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) { + 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++) { + /* For BLOBS zv is NULL */ + if (stmt->param_bind[i].zv) { + /* + We may have the last reference, then call zval_ptr_dtor() + or we may leak memory. + */ + zval_ptr_dtor(&stmt->param_bind[i].zv); + stmt->param_bind[i].zv = NULL; + } + } + mnd_efree(stmt->param_bind); + } + + stmt->param_bind = param_bind; + for (i = 0; i < stmt->param_count; i++) { + /* The client will use stmt_send_long_data */ + DBG_INF_FMT("%d is of type %d", i, stmt->param_bind[i].type); + if (stmt->param_bind[i].type != MYSQL_TYPE_LONG_BLOB) { + /* Prevent from freeing */ + ZVAL_ADDREF(stmt->param_bind[i].zv); + /* Don't update is_ref, or we will leak during conversion */ + } else { + stmt->param_bind[i].zv = NULL; + 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_result */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, bind_result)(MYSQLND_STMT * const stmt, + MYSQLND_RESULT_BIND * const result_bind TSRMLS_DC) +{ + uint i = 0; + + DBG_ENTER("mysqlnd_stmt::bind_result"); + DBG_INF_FMT("stmt=%lu field_count=%u", stmt->stmt_id, stmt->field_count); + + 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); + if (result_bind) { + mnd_efree(result_bind); + } + DBG_ERR("not prepared"); + DBG_RETURN(FAIL); + } + + if (stmt->field_count) { + if (!result_bind) { + DBG_ERR("no result bind passed"); + DBG_RETURN(FAIL); + } + + mysqlnd_stmt_separate_result_bind(stmt TSRMLS_CC); + + stmt->result_bind = result_bind; + for (i = 0; i < stmt->field_count; i++) { + /* Prevent from freeing */ + ZVAL_ADDREF(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) { + mnd_efree(result_bind); + } + DBG_INF("PASS"); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::insert_id */ +static mynd_ulonglong +MYSQLND_METHOD(mysqlnd_stmt, insert_id)(const MYSQLND_STMT * const stmt) +{ + return stmt->upsert_status.last_insert_id; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::affected_rows */ +static mynd_ulonglong +MYSQLND_METHOD(mysqlnd_stmt, affected_rows)(const MYSQLND_STMT * const stmt) +{ + return stmt->upsert_status.affected_rows; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::num_rows */ +static mynd_ulonglong +MYSQLND_METHOD(mysqlnd_stmt, num_rows)(const MYSQLND_STMT * const stmt) +{ + return 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 stmt) +{ + return stmt->upsert_status.warning_count; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::field_count */ +static unsigned int +MYSQLND_METHOD(mysqlnd_stmt, field_count)(const MYSQLND_STMT * const stmt) +{ + return stmt->field_count; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::param_count */ +static unsigned int +MYSQLND_METHOD(mysqlnd_stmt, param_count)(const MYSQLND_STMT * const stmt) +{ + return stmt->param_count; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::errno */ +static unsigned int +MYSQLND_METHOD(mysqlnd_stmt, errno)(const MYSQLND_STMT * const stmt) +{ + return stmt->error_info.error_no; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::error */ +static const char * +MYSQLND_METHOD(mysqlnd_stmt, error)(const MYSQLND_STMT * const stmt) +{ + return stmt->error_info.error; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::sqlstate */ +static const char * +MYSQLND_METHOD(mysqlnd_stmt, sqlstate)(const MYSQLND_STMT * const stmt) +{ + return 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 stmt, mynd_ulonglong row TSRMLS_DC) +{ + return 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 stmt) +{ + if (!stmt->param_count) { + return NULL; + } + + return NULL; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::result_metadata */ +static MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_stmt, result_metadata)(MYSQLND_STMT * const stmt TSRMLS_DC) +{ + MYSQLND_RES *result; + + DBG_ENTER("mysqlnd_stmt::result_metadata"); + 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); + } + + /* + 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. + */ + result = mysqlnd_result_init(stmt->field_count, NULL TSRMLS_CC); + result->type = MYSQLND_RES_NORMAL; + result->m.fetch_row = result->m.fetch_row_normal_unbuffered; + result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED)); + result->unbuf->eof_reached = TRUE; + result->meta = stmt->result->meta->m->clone_metadata(stmt->result->meta, FALSE TSRMLS_CC); + + DBG_INF_FMT("result=%p", result); + DBG_RETURN(result); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::attr_set */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, attr_set)(MYSQLND_STMT * const stmt, + enum mysqlnd_stmt_attr attr_type, + const void * const value TSRMLS_DC) +{ + unsigned long val = *(unsigned long *) value; + DBG_ENTER("mysqlnd_stmt::attr_set"); + DBG_INF_FMT("stmt=%lu attr_type=%u value=%lu", stmt->stmt_id, attr_type, val); + + switch (attr_type) { + case STMT_ATTR_UPDATE_MAX_LENGTH: + /* + 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 = val? TRUE:FALSE; + break; + case STMT_ATTR_CURSOR_TYPE: { + if (val > (unsigned long) CURSOR_TYPE_READ_ONLY) { + SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented"); + return FAIL; + } + stmt->flags = val; + break; + } + case STMT_ATTR_PREFETCH_ROWS: { + if (val == 0) { + val = MYSQLND_DEFAULT_PREFETCH_ROWS; + } else if (val > 1) { + SET_STMT_ERROR(stmt, CR_NOT_IMPLEMENTED, UNKNOWN_SQLSTATE, "Not implemented"); + return FAIL; + } + stmt->prefetch_rows = val; + 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)(MYSQLND_STMT * const stmt, + enum mysqlnd_stmt_attr attr_type, + void * const value TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_stmt::attr_set"); + 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); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::free_result */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, free_result)(MYSQLND_STMT * const stmt TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_stmt::free_result"); + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + if (!stmt->result) { + DBG_INF("no result"); + DBG_RETURN(PASS); + } + + 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 = stmt->m->use_result; + stmt->default_rset_handler(stmt 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(stmt TSRMLS_CC); + + /* Now we can destroy the result set */ + stmt->result->m.free_result_buffers(stmt->result TSRMLS_CC); + } + + /* As the buffers have been freed, we should go back to PREPARED */ + stmt->state = MYSQLND_STMT_PREPARED; + + /* Line is free! */ + stmt->conn->state = CONN_READY; + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_separate_result_bind */ +void mysqlnd_stmt_separate_result_bind(MYSQLND_STMT * const stmt TSRMLS_DC) +{ + unsigned int i; + + DBG_ENTER("mysqlnd_stmt_separate_result_bind"); + 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("%d has refcount=%u", i, ZVAL_REFCOUNT(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 (ZVAL_REFCOUNT(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); + } + } + } + mnd_efree(stmt->result_bind); + stmt->result_bind = NULL; + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_internal_free_stmt_content */ +static +void mysqlnd_internal_free_stmt_content(MYSQLND_STMT *stmt TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_internal_free_stmt_content"); + 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) { + 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++) { + /* For BLOBs zv is NULL */ + if (stmt->param_bind[i].zv) { + zval_ptr_dtor(&stmt->param_bind[i].zv); + } + } + + mnd_efree(stmt->param_bind); + stmt->param_bind = NULL; + } + + /* + First separate the bound variables, which point to the result set, then + destroy the set. + */ + mysqlnd_stmt_separate_result_bind(stmt 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->cmd_buffer.buffer) { + mnd_efree(stmt->cmd_buffer.buffer); + stmt->cmd_buffer.buffer = NULL; + } + + if (stmt->conn) { + stmt->conn->m->free_reference(stmt->conn TSRMLS_CC); + stmt->conn = NULL; + } + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::close */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_stmt, close)(MYSQLND_STMT * const stmt, zend_bool implicit TSRMLS_DC) +{ + MYSQLND * conn = stmt->conn; + zend_uchar cmd_buf[STMT_ID_LENGTH /* statement id */]; + enum_mysqlnd_collected_stats stat = STAT_LAST; + + DBG_ENTER("mysqlnd_stmt::close"); + DBG_INF_FMT("stmt=%lu", stmt->stmt_id); + + 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. + */ + if (stmt->state == MYSQLND_STMT_WAITING_USE_OR_STORE) { + DBG_INF("fetching result set header"); + stmt->default_rset_handler(stmt 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); + } + /* + 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->state == CONN_READY && + FAIL == mysqlnd_simple_command(conn, COM_STMT_CLOSE, (char *)cmd_buf, sizeof(cmd_buf), + PROT_LAST /* COM_STMT_CLOSE doesn't send an OK packet*/, + FALSE TSRMLS_CC)) { + stmt->error_info = conn->error_info; + DBG_RETURN(FAIL); + } + } + switch (stmt->execute_count) { + case 0: + stat = STAT_PS_PREPARED_NEVER_EXECUTED; + break; + case 1: + stat = STAT_PS_PREPARED_ONCE_USED; + break; + default: + break; + } + if (stat != STAT_LAST) { + MYSQLND_INC_CONN_STATISTIC(&conn->stats, stat); + } + + mysqlnd_internal_free_stmt_content(stmt TSRMLS_CC); + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_stmt::dtor */ +static enum_func_status +MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, dtor)(MYSQLND_STMT * const stmt, zend_bool implicit TSRMLS_DC) +{ + enum_func_status ret; + + DBG_ENTER("mysqlnd_stmt::close"); + DBG_INF_FMT("stmt=%p", stmt); + + MYSQLND_INC_GLOBAL_STATISTIC(implicit == TRUE? STAT_STMT_CLOSE_IMPLICIT: + STAT_STMT_CLOSE_EXPLICIT); + + if (PASS == (ret = stmt->m->close(stmt, implicit TSRMLS_CC))) { + mnd_efree(stmt); + } + + DBG_INF(ret == PASS? "PASS":"FAIL"); + DBG_RETURN(ret); +} +/* }}} */ + + +static +struct st_mysqlnd_stmt_methods mysqlnd_stmt_methods = { + 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, free_result), + MYSQLND_METHOD(mysqlnd_stmt, data_seek), + MYSQLND_METHOD(mysqlnd_stmt, reset), + MYSQLND_METHOD(mysqlnd_stmt, close), + MYSQLND_METHOD_PRIVATE(mysqlnd_stmt, dtor), + + MYSQLND_METHOD(mysqlnd_stmt, fetch), + + MYSQLND_METHOD(mysqlnd_stmt, bind_param), + MYSQLND_METHOD(mysqlnd_stmt, bind_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_stmt_init */ +MYSQLND_STMT * _mysqlnd_stmt_init(MYSQLND * const conn TSRMLS_DC) +{ + MYSQLND_STMT *stmt = mnd_ecalloc(1, sizeof(MYSQLND_STMT)); + + DBG_ENTER("_mysqlnd_stmt_init"); + DBG_INF_FMT("stmt=%p", stmt); + + stmt->m = &mysqlnd_stmt_methods; + stmt->state = MYSQLND_STMT_INITTED; + stmt->cmd_buffer.length = 4096; + stmt->cmd_buffer.buffer = mnd_emalloc(stmt->cmd_buffer.length); + + 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); + + DBG_RETURN(stmt); +} +/* }}} */ + +/* + * 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 0000000000..ea04cfb5fa --- /dev/null +++ b/ext/mysqlnd/mysqlnd_ps_codec.c @@ -0,0 +1,854 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_debug.h" + +#define MYSQLND_SILENT + + + +typedef int8 my_int8; +typedef uint8 my_uint8; + +typedef int16 my_int16; +typedef uint16 my_uint16; + +typedef int32 my_int32; +typedef uint32 my_uint32; + + +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, + uint 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; + if (field->flags & UNSIGNED_FLAG) { + my_uint64 uval = 0; + + switch (byte_count) { + case 8:uval = is_bit? (my_uint64) bit_uint8korr(*row):(my_uint64) 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? (my_uint64) bit_uint4korr(*row):(my_uint64) uint4korr(*row);break; + case 3:uval = is_bit? (my_uint64) bit_uint3korr(*row):(my_uint64) uint3korr(*row);break; + case 2:uval = is_bit? (my_uint64) bit_uint2korr(*row):(my_uint64) uint2korr(*row);break; + case 1:uval = (my_uint64) uint1korr(*row);break; + } + +#if SIZEOF_LONG==4 + if (uval > INT_MAX) { + 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, uval); + } else { + tmp_len = sprintf((char *)&tmp, MYSQLND_LLU_SPEC, uval); + } + } + } else { + /* SIGNED */ + my_int64 lval = 0; + switch (byte_count) { + case 8:lval = (my_int64) sint8korr(*row);break; + /* + 7, 6 and 5 are not possible. + BIT is only unsigned, thus only uint5|6|7 macroses exist + */ + case 4:lval = (my_int64) sint4korr(*row);break; + case 3:lval = (my_int64) sint3korr(*row);break; + case 2:lval = (my_int64) sint2korr(*row);break; + case 1:lval = (my_int64) *(my_int8*)*row;break; + } + +#if SIZEOF_LONG==4 + if ((L64(2147483647) < (my_int64) lval) || (L64(-2147483648) > (my_int64) lval)) { + tmp_len = sprintf((char *)&tmp, MYSQLND_LL_SPEC, lval); + } else +#endif /* SIZEOF */ + { + ZVAL_LONG(zv, lval); + } + } + + if (tmp_len) { +#if PHP_MAJOR_VERSION >= 6 + if (as_unicode) { + ZVAL_UTF8_STRINGL(zv, tmp, tmp_len, ZSTR_DUPLICATE); + } else +#endif + { + ZVAL_STRINGL(zv, tmp, tmp_len, 1); + } + } + (*row)+= byte_count; +} +/* }}} */ + + +/* {{{ ps_fetch_null */ +static +void ps_fetch_null(zval *zv, const MYSQLND_FIELD * const field, + uint 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, + uint 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); +#if 0 + if (field->flags & UNSIGNED_FLAG) { + ZVAL_LONG(zv, *(my_uint8*)*row); + } else { + ZVAL_LONG(zv, *(my_int8*)*row); + } + (*row)++; +#endif +} +/* }}} */ + + +/* {{{ ps_fetch_int16 */ +static +void ps_fetch_int16(zval *zv, const MYSQLND_FIELD * const field, + uint 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); +#if 0 + if (field->flags & UNSIGNED_FLAG) { + ZVAL_LONG(zv, (my_uint16) sint2korr(*row)); + } else { + ZVAL_LONG(zv, (my_int16) sint2korr(*row)); + } + (*row)+= 2; +#endif +} +/* }}} */ + + +/* {{{ ps_fetch_int32 */ +static +void ps_fetch_int32(zval *zv, const MYSQLND_FIELD * const field, + uint 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); +#if 0 + + if (field->flags & UNSIGNED_FLAG) { + my_uint32 uval; + + /* unsigned int (11) */ + uval= (my_uint32) sint4korr(*row); +#if SIZEOF_LONG==4 + if (uval > INT_MAX) { + char *tmp, *p; + int j=10; + tmp= mnd_emalloc(11); + p= &tmp[9]; + do { + *p-- = (uval % 10) + 48; + uval = uval / 10; + } while (--j > 0); + tmp[10]= '\0'; + /* unsigned int > INT_MAX is 10 digits - ALWAYS */ +#if PHP_MAJOR_VERSION >= 6 + if (!as_unicode) { +#endif + ZVAL_STRING(zv, tmp, 0); +#if PHP_MAJOR_VERSION >= 6 + } else { + ZVAL_UTF8_STRINGL(zv, tmp, 10, ZSTR_AUTOFREE); + } +#endif /* PHP_MAJOR_VERSION >= 6 */ + } else +#endif /* #if SIZEOF_LONG==4 */ + { + ZVAL_LONG(zv, uval); + } + } else { + ZVAL_LONG(zv, (my_int32) sint4korr(*row)); + } + (*row)+= 4; +#endif /* 0 */ +} +/* }}} */ + + +/* {{{ ps_fetch_int64 */ +static +void ps_fetch_int64(zval *zv, const MYSQLND_FIELD * const field, + uint 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); +#if 0 + + my_uint64 llval = (my_uint64) sint8korr(*row); + zend_bool uns = field->flags & UNSIGNED_FLAG? TRUE:FALSE; + +#if SIZEOF_LONG==8 + if (uns == TRUE && llval > 9223372036854775807L) { +#elif SIZEOF_LONG==4 + if ((uns == TRUE && llval > L64(2147483647)) || + (uns == FALSE && ((L64( 2147483647) < (my_int64) llval) || + (L64(-2147483648) > (my_int64) llval)))) + { +#endif + char tmp[22]; + /* even though lval is declared as unsigned, the value + * may be negative. Therefor we cannot use MYSQLND_LLU_SPEC and must + * use MYSQLND_LL_SPEC. + */ + sprintf((char *)&tmp, uns == TRUE? MYSQLND_LLU_SPEC : MYSQLND_LL_SPEC, llval); +#if PHP_MAJOR_VERSION >= 6 + if (!as_unicode) { +#endif + ZVAL_STRING(zv, tmp, 1); +#if PHP_MAJOR_VERSION >= 6 + } else { + ZVAL_UTF8_STRING(zv, tmp, ZSTR_DUPLICATE); + } +#endif + } else { + /* This cast is safe, as we have checked the values above */ + ZVAL_LONG(zv, (long) llval); + } + (*row)+= 8; +#endif /* 0 */ +} +/* }}} */ + + +/* {{{ ps_fetch_float */ +static +void ps_fetch_float(zval *zv, const MYSQLND_FIELD * const field, + uint pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + float value; + float4get(value, *row); + ZVAL_DOUBLE(zv, value); + (*row)+= 4; +} +/* }}} */ + + +/* {{{ ps_fetch_double */ +static +void ps_fetch_double(zval *zv, const MYSQLND_FIELD * const field, + uint pack_len, zend_uchar **row, + zend_bool as_unicode TSRMLS_DC) +{ + double value; + float8get(value, *row); + ZVAL_DOUBLE(zv, value); + (*row)+= 8; +} +/* }}} */ + + +/* {{{ ps_fetch_time */ +static +void ps_fetch_time(zval *zv, const MYSQLND_FIELD * const field, + uint 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 *to; + + 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; + } + + /* + QQ : How to make this unicode without copying two times the buffer - + Unicode equivalent of spprintf? + */ + length = spprintf(&to, 0, "%s%02u:%02u:%02u", + (t.neg ? "-" : ""), t.hour, t.minute, t.second); + +#if PHP_MAJOR_VERSION >= 6 + if (!as_unicode) { +#endif + ZVAL_STRINGL(zv, to, length, 1); + mnd_efree(to); +#if PHP_MAJOR_VERSION >= 6 + } else { + ZVAL_UTF8_STRINGL(zv, to, length, ZSTR_AUTOFREE); + } +#endif +} +/* }}} */ + + +/* {{{ ps_fetch_date */ +static +void ps_fetch_date(zval *zv, const MYSQLND_FIELD * const field, + uint 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 *to; + + 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; + } + + /* + QQ : How to make this unicode without copying two times the buffer - + Unicode equivalent of spprintf? + */ + length = spprintf(&to, 0, "%04u-%02u-%02u", t.year, t.month, t.day); + +#if PHP_MAJOR_VERSION >= 6 + if (!as_unicode) { +#endif + ZVAL_STRINGL(zv, to, length, 1); + mnd_efree(to); +#if PHP_MAJOR_VERSION >= 6 + } else { + ZVAL_UTF8_STRINGL(zv, to, length, ZSTR_AUTOFREE); + } +#endif +} +/* }}} */ + + +/* {{{ ps_fetch_datetime */ +static +void ps_fetch_datetime(zval *zv, const MYSQLND_FIELD * const field, + uint 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 *to; + + 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; + } + + /* + QQ : How to make this unicode without copying two times the buffer - + Unicode equivalent of spprintf? + */ + length = spprintf(&to, 0, "%04u-%02u-%02u %02u:%02u:%02u", + t.year, t.month, t.day, t.hour, t.minute, t.second); + +#if PHP_MAJOR_VERSION >= 6 + if (!as_unicode) { +#endif + ZVAL_STRINGL(zv, to, length, 1); + mnd_efree(to); +#if PHP_MAJOR_VERSION >= 6 + } else { + ZVAL_UTF8_STRINGL(zv, to, length, ZSTR_AUTOFREE); + } +#endif +} +/* }}} */ + + +/* {{{ ps_fetch_string */ +static +void ps_fetch_string(zval *zv, const MYSQLND_FIELD * const field, + uint 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); + +#if PHP_MAJOR_VERSION < 6 + ZVAL_STRINGL(zv, (char *)*row, length, 1); +#else + if (field->charsetnr == MYSQLND_BINARY_CHARSET_NR) { + ZVAL_STRINGL(zv, (char *)*row, length, 1); + } else { + ZVAL_UTF8_STRINGL(zv, (char*)*row, length, ZSTR_DUPLICATE); + } +#endif + + (*row) += length; +} +/* }}} */ + + +/* {{{ ps_fetch_bit */ +static +void ps_fetch_bit(zval *zv, const MYSQLND_FIELD * const field, + uint 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_subsystem */ +void _mysqlnd_init_ps_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_date; + 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_execute_store_params */ +void +mysqlnd_stmt_execute_store_params(MYSQLND_STMT *stmt, zend_uchar **buf, zend_uchar **p, + size_t *buf_len, unsigned int null_byte_offset TSRMLS_DC) +{ + unsigned int i = 0; + unsigned left = (*buf_len - (*p - *buf)); + unsigned int data_size = 0; + +/* 1. Store type information */ + 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); + memcpy(tmp_buf, *buf, offset); + *buf = tmp_buf; + + /* Update our pos pointer */ + *p = *buf + offset; + } + for (i = 0; i < stmt->param_count; i++) { + /* our types are not unsigned */ +#if SIZEOF_LONG==8 + if (stmt->param_bind[i].type == MYSQL_TYPE_LONG) { + stmt->param_bind[i].type = MYSQL_TYPE_LONGLONG; + } +#endif + int2store(*p, stmt->param_bind[i].type); + *p+= 2; + } + } + +/* 2. Store data */ + /* 2.1 Calculate how much space we need */ + for (i = 0; i < stmt->param_count; i++) { + if (stmt->param_bind[i].zv && + Z_TYPE_P(stmt->param_bind[i].zv) == IS_NULL) { + continue; + } + + switch (stmt->param_bind[i].type) { + case MYSQL_TYPE_DOUBLE: + data_size += 8; + break; +#if SIZEOF_LONG==8 + case MYSQL_TYPE_LONGLONG: + data_size += 8; + break; +#elif SIZEOF_LONG==4 + case MYSQL_TYPE_LONG: + data_size += 4; + break; +#else +#error "Should not happen" +#endif + 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 follow after it. + */ + data_size++; + } + break; + case MYSQL_TYPE_VAR_STRING: + data_size += 8; /* max 8 bytes for size */ + convert_to_string_ex(&stmt->param_bind[i].zv); + data_size += Z_STRLEN_P(stmt->param_bind[i].zv); + 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); + memcpy(tmp_buf, *buf, offset); + *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 = 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; +#if SIZEOF_LONG==8 + case MYSQL_TYPE_LONGLONG: + convert_to_long_ex(&data); + int8store(*p, Z_LVAL_P(data)); + (*p) += 8; + break; +#elif SIZEOF_LONG==4 + case MYSQL_TYPE_LONG: + convert_to_long_ex(&data); + int4store(*p, Z_LVAL_P(data)); + (*p) += 4; + break; +#else +#error "Should not happen" +#endif + 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: + /* + If the user uses refs, it could be that the type has + has changed and we need to convert, again. Which is noop, + if the type hasn't changed. + */ + convert_to_string_ex(&stmt->param_bind[i].zv); + { + 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; + } + } + } +} +/* }}} */ + + +/* {{{ mysqlnd_stmt_execute_generate_request */ +zend_uchar* mysqlnd_stmt_execute_generate_request(MYSQLND_STMT *stmt, size_t *request_len, + zend_bool *free_buffer TSRMLS_DC) +{ + zend_uchar *p = stmt->cmd_buffer.buffer, + *cmd_buffer = stmt->cmd_buffer.buffer; + size_t cmd_buffer_length = stmt->cmd_buffer.length; + unsigned int null_byte_offset, + null_count= (stmt->param_count + 7) / 8; + + 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; + + + null_byte_offset = p - cmd_buffer; + memset(p, 0, null_count); + p += null_count; + + + int1store(p, stmt->send_types_to_server); + p++; + + mysqlnd_stmt_execute_store_params(stmt, &cmd_buffer, &p, &cmd_buffer_length, null_byte_offset TSRMLS_CC); + + *free_buffer = (cmd_buffer != stmt->cmd_buffer.buffer); + *request_len = (p - cmd_buffer); + return cmd_buffer; +} +/* }}} */ + +/* + * 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_qcache.c b/ext/mysqlnd/mysqlnd_qcache.c new file mode 100644 index 0000000000..a08c7d488d --- /dev/null +++ b/ext/mysqlnd/mysqlnd_qcache.c @@ -0,0 +1,141 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_statistics.h" + +#define MYSQLND_SILENT + +#ifdef ZTS +#define LOCK_QCACHE(cache) tsrm_mutex_lock((cache)->LOCK_access) +#define UNLOCK_QCACHE(cache) tsrm_mutex_unlock((cache)->LOCK_access) +#else +#define LOCK_QCACHE(cache) +#define UNLOCK_QCACHE(cache) +#endif + + +/* {{{ mysqlnd_qcache_init_cache */ +PHPAPI MYSQLND_QCACHE * mysqlnd_qcache_init_cache() +{ + MYSQLND_QCACHE *cache = calloc(1, sizeof(MYSQLND_QCACHE)); +#ifndef MYSQLND_SILENT + php_printf("[mysqlnd_qcache_init_cache %p]\n", cache); +#endif + + cache->references = 1; +#ifdef ZTS + cache->LOCK_access = tsrm_mutex_alloc(); +#endif + cache->ht = malloc(sizeof(HashTable)); + zend_hash_init(cache->ht, 10 /* init_elements */, NULL, NULL, TRUE /*pers*/); + + return cache; +} +/* }}} */ + + +/* {{{ mysqlnd_qcache_get_cache_reference */ +PHPAPI MYSQLND_QCACHE * mysqlnd_qcache_get_cache_reference(MYSQLND_QCACHE * const cache) +{ + if (cache) { +#ifndef MYSQLND_SILENT + php_printf("[mysqlnd_qcache_get_cache_reference %p will become %d]\n", cache, cache->references+1); +#endif + LOCK_QCACHE(cache); + cache->references++; + UNLOCK_QCACHE(cache); + } + return cache; +} +/* }}} */ + + +/* {{{ mysqlnd_qcache_free_cache */ +/* + As this call will happen on MSHUTDOWN(), then we don't need to copy the zvals with + copy_ctor but scrap what they point to with zval_dtor() and then just free our + pre-allocated block. Precondition is that we ZVAL_NULL() the zvals when we put them + to the free list after usage. We ZVAL_NULL() them when we allocate them in the + constructor of the cache. +*/ +static +void mysqlnd_qcache_free_cache(MYSQLND_QCACHE *cache) +{ +#ifndef MYSQLND_SILENT + php_printf("[mysqlnd_qcache_free_cache %p]\n", cache); +#endif + +#ifdef ZTS + tsrm_mutex_free(cache->LOCK_access); +#endif + zend_hash_destroy(cache->ht); + free(cache->ht); + free(cache); +} +/* }}} */ + + +/* {{{ mysqlnd_qcache_free_cache_reference */ +PHPAPI void mysqlnd_qcache_free_cache_reference(MYSQLND_QCACHE **cache) +{ + if (*cache) { + zend_bool to_free; +#ifndef MYSQLND_SILENT + php_printf("[mysqlnd_qcache_free_cache_reference %p] refs=%d\n", *cache, (*cache)->references); +#endif + LOCK_QCACHE(*cache); + to_free = --(*cache)->references == 0; + /* Unlock before destroying */ + UNLOCK_QCACHE(*cache); + if (to_free) { + mysqlnd_qcache_free_cache(*cache); + } + *cache = NULL; + } +} +/* }}} */ + + +/* {{{ mysqlnd_qcache_free_cache_reference */ +PHPAPI void mysqlnd_qcache_stats(const MYSQLND_QCACHE * const cache, zval *return_value) +{ + if (cache) { + LOCK_QCACHE(cache); + array_init(return_value); + add_assoc_long_ex(return_value, "references", sizeof("references"), cache->references); + UNLOCK_QCACHE(cache); + } else { + ZVAL_NULL(return_value); + } +} +/* }}} */ + +/* + * 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 0000000000..2d42d65469 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_result.c @@ -0,0 +1,1194 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_result.h" +#include "mysqlnd_result_meta.h" +#include "mysqlnd_statistics.h" +#include "mysqlnd_charset.h" +#include "mysqlnd_debug.h" +#include "ext/standard/basic_functions.h" + +#define MYSQLND_SILENT + + +/* {{{ mysqlnd_unbuffered_free_last_data */ +void mysqlnd_unbuffered_free_last_data(MYSQLND_RES *result TSRMLS_DC) +{ + MYSQLND_RES_UNBUFFERED *unbuf = result->unbuf; + + DBG_ENTER("mysqlnd_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_palloc_zval_ptr_dtor(&(unbuf->last_row_data[i]), + result->zval_cache, result->type, + ©_ctor_called TSRMLS_CC); + if (copy_ctor_called) { + ctor_called_count++; + } + } + /* By using value3 macros we hold a mutex only once, there is no value2 */ + MYSQLND_INC_CONN_STATISTIC_W_VALUE3(global_stats, + STAT_COPY_ON_WRITE_PERFORMED, + ctor_called_count, + STAT_COPY_ON_WRITE_SAVED, + result->field_count - ctor_called_count, + STAT_COPY_ON_WRITE_PERFORMED, 0); + + /* Free last row's zvals */ + efree(unbuf->last_row_data); + unbuf->last_row_data = NULL; + } + if (unbuf->last_row_buffer) { + /* Nothing points to this buffer now, free it */ + efree(unbuf->last_row_buffer); + unbuf->last_row_buffer = NULL; + } + + DBG_VOID_RETURN; +} +/* }}} */ + +/* {{{ mysqlnd_free_buffered_data */ +void mysqlnd_free_buffered_data(MYSQLND_RES *result TSRMLS_DC) +{ + MYSQLND_THD_ZVAL_PCACHE *zval_cache = result->zval_cache; + MYSQLND_RES_BUFFERED *set = result->data; + unsigned int field_count = result->field_count; + unsigned int row; + + DBG_ENTER("mysqlnd_free_buffered_data"); + DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", result->data->row_count); + + DBG_INF_FMT("before: real_usage=%lu usage=%lu", zend_memory_usage(TRUE TSRMLS_CC), zend_memory_usage(FALSE TSRMLS_CC)); + for (row = 0; row < result->data->row_count; row++) { + unsigned int col; + zval **current_row = current_row = set->data[row]; + zend_uchar *current_buffer = set->row_buffers[row]; + + for (col = 0; col < field_count; col++) { + zend_bool copy_ctor_called; + mysqlnd_palloc_zval_ptr_dtor(&(current_row[col]), zval_cache, + result->type, ©_ctor_called TSRMLS_CC); +#if MYSQLND_DEBUG_MEMORY + DBG_INF_FMT("Copy_ctor_called=%d", copy_ctor_called); +#endif + MYSQLND_INC_GLOBAL_STATISTIC(copy_ctor_called? STAT_COPY_ON_WRITE_PERFORMED: + STAT_COPY_ON_WRITE_SAVED); + } +#if MYSQLND_DEBUG_MEMORY + DBG_INF("Freeing current_row & current_buffer"); +#endif + pefree(current_row, set->persistent); + pefree(current_buffer, set->persistent); + } + DBG_INF("Freeing data & row_buffer"); + pefree(set->data, set->persistent); + pefree(set->row_buffers, set->persistent); + set->data = NULL; + set->row_buffers = NULL; + set->data_cursor = NULL; + set->row_count = 0; + if (set->qcache) { + mysqlnd_qcache_free_cache_reference(&set->qcache); + } + DBG_INF("Freeing set"); + pefree(set, set->persistent); + + DBG_INF_FMT("after: real_usage=%lu usage=%lu", zend_memory_usage(TRUE TSRMLS_CC), zend_memory_usage(FALSE TSRMLS_CC)); + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_res::free_result_buffers */ +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->data? "buffered":"unknown")); + + if (result->unbuf) { + mysqlnd_unbuffered_free_last_data(result TSRMLS_CC); + efree(result->unbuf); + result->unbuf = NULL; + } else if (result->data) { + mysqlnd_free_buffered_data(result TSRMLS_CC); + result->data = NULL; + } + + if (result->lengths) { + efree(result->lengths); + result->lengths = 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->row_packet) { + DBG_INF("Freeing packet"); + PACKET_FREE(result->row_packet); + result->row_packet = NULL; + } + + result->conn = NULL; + + if (result->meta) { + result->meta->m->free_metadata(result->meta, FALSE TSRMLS_CC); + result->meta = NULL; + } + + if (result->zval_cache) { + DBG_INF("Freeing zval cache reference"); + mysqlnd_palloc_free_thd_cache_reference(&result->zval_cache); + result->zval_cache = 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->conn is an address if this is an unbuffered query. + In this case, decrement the reference counter in the connection + object and if needed free the latter. + */ + if (result->conn) { + result->conn->m->free_reference(result->conn TSRMLS_CC); + result->conn = NULL; + } + + result->m.free_result_contents(result TSRMLS_CC); + efree(result); + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_res::read_result_metadata */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_res, read_result_metadata)(MYSQLND_RES *result, MYSQLND *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, FALSE TSRMLS_CC); + result->meta = NULL; + } + + result->meta = mysqlnd_result_meta_init(result->field_count TSRMLS_CC); + + /* 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); + } + + /* + 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, MYSQLND_STMT *stmt TSRMLS_DC) +{ + enum_func_status ret; + php_mysql_packet_rset_header rset_header; + + DBG_ENTER("mysqlnd_query_read_result_set_header"); + DBG_INF_FMT("stmt=%d", stmt? stmt->stmt_id:0); + + ret = FAIL; + PACKET_INIT_ALLOCA(rset_header, PROT_RSET_HEADER_PACKET); + do { + if (FAIL == (ret = PACKET_READ_ALLOCA(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; + conn->upsert_status.affected_rows = -1; + /* + This will copy the error code and the messages, as they + are buffers in the struct + */ + conn->error_info = rset_header.error_info; + ret = FAIL; + 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->state = CONN_SENDING_LOAD_DATA; + ret = mysqlnd_handle_local_infile(conn, rset_header.info_or_local_file, &is_warning TSRMLS_CC); + conn->state = (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->state = CONN_NEXT_RESULT_PENDING; + } else { + conn->state = CONN_READY; + } + ret = PASS; + MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_NON_RSET_QUERY); + break; + default:{ /* Result set */ + php_mysql_packet_eof fields_eof; + MYSQLND_RES *result; + enum_mysqlnd_collected_stats stat = 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)); + conn->last_query_type = QUERY_SELECT; + conn->state = CONN_FETCHING_DATA; + /* PS has already allocated it */ + if (!stmt) { + conn->field_count = rset_header.field_count; + result = + conn->current_result= + mysqlnd_result_init(rset_header.field_count, + mysqlnd_palloc_get_thd_cache_reference(conn->zval_cache) + 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. + */ + conn->field_count = rset_header.field_count; + result = + stmt->result = + mysqlnd_result_init(rset_header.field_count, + mysqlnd_palloc_get_thd_cache_reference(conn->zval_cache) + 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 (FAIL == (ret = result->m.read_result_metadata(result, conn TSRMLS_CC))) { + /* For PS, we leave them in Prepared state */ + if (!stmt) { + efree(conn->current_result); + conn->current_result = NULL; + } + DBG_ERR("Error ocurred while reading metadata"); + break; + } + + /* Check for SERVER_STATUS_MORE_RESULTS if needed */ + PACKET_INIT_ALLOCA(fields_eof, PROT_EOF_PACKET); + if (FAIL == (ret = PACKET_READ_ALLOCA(fields_eof, conn))) { + DBG_ERR("Error ocurred while reading the EOF packet"); + result->m.free_result_contents(result TSRMLS_CC); + efree(result); + if (!stmt) { + conn->current_result = NULL; + } else { + stmt->result = NULL; + memset(stmt, 0, sizeof(MYSQLND_STMT)); + stmt->state = MYSQLND_STMT_INITTED; + } + } else { + DBG_INF_FMT("warns=%u status=%u", fields_eof.warning_count, fields_eof.server_status); + conn->upsert_status.warning_count = fields_eof.warning_count; + conn->upsert_status.server_status = fields_eof.server_status; + if (fields_eof.server_status & MYSQLND_SERVER_QUERY_NO_GOOD_INDEX_USED) { + stat = STAT_BAD_INDEX_USED; + } else if (fields_eof.server_status & MYSQLND_SERVER_QUERY_NO_INDEX_USED) { + stat = STAT_NO_INDEX_USED; + } + if (stat != STAT_LAST) { + char *backtrace = mysqlnd_get_backtrace(TSRMLS_C); +#if A0 + php_log_err(backtrace TSRMLS_CC); +#endif + efree(backtrace); + MYSQLND_INC_CONN_STATISTIC(&conn->stats, stat); + } + } + + PACKET_FREE_ALLOCA(fields_eof); + + break; + } + } + } while (0); + PACKET_FREE_ALLOCA(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) +{ + int i; + zval **previous_row; + + /* + If: + - unbuffered result + - first row has not been read + - last_row has been read + */ + if (result->data->data_cursor == NULL || + result->data->data_cursor == result->data->data || + ((result->data->data_cursor - result->data->data) > result->data->row_count)) + { + return NULL;/* No rows or no more rows */ + } + + previous_row = *(result->data->data_cursor - 1); + for (i = 0; i < result->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) +{ + return result->lengths; +} +/* }}} */ + + +/* {{{ mysqlnd_res::fetch_lengths */ +PHPAPI unsigned long * mysqlnd_fetch_lengths(MYSQLND_RES * const result) +{ + return result->m.fetch_lengths? result->m.fetch_lengths(result):NULL; +} +/* }}} */ + + +/* {{{ 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; + unsigned int i, + field_count = result->field_count; + php_mysql_packet_row *row_packet = result->row_packet; + unsigned long *lengths = result->lengths; + + DBG_ENTER("mysqlnd_fetch_row_unbuffered"); + DBG_INF_FMT("flags=%d", flags); + + if (result->unbuf->eof_reached) { + /* No more rows obviously */ + *fetched_anything = FALSE; + DBG_RETURN(PASS); + } + if (result->conn->state != CONN_FETCHING_DATA) { + SET_CLIENT_ERROR(result->conn->error_info, CR_COMMANDS_OUT_OF_SYNC, + UNKNOWN_SQLSTATE, mysqlnd_out_of_sync); + 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 + mysqlnd_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++; + *fetched_anything = TRUE; + + mysqlnd_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); + + for (i = 0; i < field_count; i++) { + zval *data = result->unbuf->last_row_data[i]; + int len = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data); + MYSQLND_RES_METADATA *meta = result->meta; + + if (lengths) { + lengths[i] = len; + } + + /* Forbid ZE to free it, we will clean it */ + ZVAL_ADDREF(data); + + if ((flags & MYSQLND_FETCH_BOTH) == MYSQLND_FETCH_BOTH) { + ZVAL_ADDREF(data); + } + if (flags & MYSQLND_FETCH_NUM) { + 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. + */ + if (meta->zend_hash_keys[i].is_numeric == FALSE) { +#if PHP_MAJOR_VERSION >= 6 + if (UG(unicode)) { + zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE, + meta->zend_hash_keys[i].ustr, + meta->zend_hash_keys[i].ulen + 1, + meta->zend_hash_keys[i].key, + (void *) &data, sizeof(zval *), NULL); + } else +#endif + { + zend_hash_quick_update(Z_ARRVAL_P(row), + meta->fields[i].name, + meta->fields[i].name_length + 1, + meta->zend_hash_keys[i].key, + (void *) &data, sizeof(zval *), NULL); + } + } else { + zend_hash_index_update(Z_ARRVAL_P(row), + meta->zend_hash_keys[i].key, + (void *) &data, sizeof(zval *), NULL); + } + } + if (meta->fields[i].max_length < len) { + meta->fields[i].max_length = len; + } + } + } + } else if (ret == FAIL) { + if (row_packet->error_info.error_no) { + result->conn->error_info = row_packet->error_info; + DBG_ERR_FMT("errorno=%d error=%s", row_packet->error_info.error_no, row_packet->error_info.error); + } + *fetched_anything = FALSE; + result->conn->state = 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("warns=%u 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) { + result->conn->state = CONN_NEXT_RESULT_PENDING; + } else { + result->conn->state = CONN_READY; + } + mysqlnd_unbuffered_free_last_data(result TSRMLS_CC); + *fetched_anything = FALSE; + } + + DBG_INF_FMT("ret=%s fetched=%d", ret == PASS? "PASS":"FAIL", *fetched_anything); + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_res::use_result */ +MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_res, use_result)(MYSQLND_RES * const result, zend_bool ps TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res::use_result"); + DBG_INF_FMT("ps=%d", ps); + + result->type = MYSQLND_RES_NORMAL; + result->m.fetch_row = result->m.fetch_row_normal_unbuffered; + result->m.fetch_lengths = mysqlnd_fetch_lengths_unbuffered; + result->unbuf = mnd_ecalloc(1, sizeof(MYSQLND_RES_UNBUFFERED)); + + /* + 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. + */ + PACKET_INIT(result->row_packet, PROT_ROW_PACKET, php_mysql_packet_row *); + result->row_packet->field_count = result->field_count; + result->row_packet->binary_protocol = FALSE; + 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; + result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long)); + + /* No multithreading issues as we don't share the connection :) */ + + DBG_RETURN(result); +} +/* }}} */ + + +/* {{{ 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; + + DBG_ENTER("mysqlnd_fetch_row_buffered"); + DBG_INF_FMT("flags=%u row=%p", flags, row); + + /* If we haven't read everything */ + if (result->data->data_cursor && + (result->data->data_cursor - result->data->data) < result->data->row_count) + { + zval **current_row = *result->data->data_cursor; + for (i = 0; i < result->field_count; i++) { + zval *data = current_row[i]; + + /* + Let us later know what to do with this zval. If ref_count > 1, we will just + decrease it, otherwise free it. zval_ptr_dtor() make this very easy job. + */ + ZVAL_ADDREF(data); + + if ((flags & MYSQLND_FETCH_BOTH) == MYSQLND_FETCH_BOTH) { + ZVAL_ADDREF(data); + } + if (flags & MYSQLND_FETCH_NUM) { + 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. + */ + if (result->meta->zend_hash_keys[i].is_numeric == FALSE) { +#if PHP_MAJOR_VERSION >= 6 + if (UG(unicode)) { + zend_u_hash_quick_update(Z_ARRVAL_P(row), IS_UNICODE, + result->meta->zend_hash_keys[i].ustr, + result->meta->zend_hash_keys[i].ulen + 1, + result->meta->zend_hash_keys[i].key, + (void *) &data, sizeof(zval *), NULL); + } else +#endif + { + zend_hash_quick_update(Z_ARRVAL_P(row), + result->meta->fields[i].name, + result->meta->fields[i].name_length + 1, + result->meta->zend_hash_keys[i].key, + (void *) &data, sizeof(zval *), NULL); + } + } else { + zend_hash_index_update(Z_ARRVAL_P(row), + result->meta->zend_hash_keys[i].key, + (void *) &data, sizeof(zval *), NULL); + } + } + } + result->data->data_cursor++; + *fetched_anything = TRUE; + MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF); + } else { + result->data->data_cursor = NULL; + *fetched_anything = FALSE; + DBG_INF("EOF reached"); + } + DBG_INF_FMT("ret=PASS fetched=%d", *fetched_anything); + DBG_RETURN(PASS); +} +/* }}} */ + + +#define STORE_RESULT_PREALLOCATED_SET 32 + +/* {{{ mysqlnd_store_result_fetch_data */ +enum_func_status +mysqlnd_store_result_fetch_data(MYSQLND * const conn, MYSQLND_RES *result, + MYSQLND_RES_METADATA *meta, + zend_bool binary_protocol, + zend_bool update_max_length, + zend_bool to_cache TSRMLS_DC) +{ + enum_func_status ret; + php_mysql_packet_row row_packet; + unsigned int next_extend = STORE_RESULT_PREALLOCATED_SET, free_rows; + MYSQLND_RES_BUFFERED *set; + + DBG_ENTER("mysqlnd_store_result_fetch_data"); + DBG_INF_FMT("conn=%llu binary_proto=%d update_max_len=%d to_cache=%d", + conn->thread_id, binary_protocol, update_max_length, to_cache); + + free_rows = next_extend; + + result->data = set = mnd_pecalloc(1, sizeof(MYSQLND_RES_BUFFERED), to_cache); + set->data = mnd_pemalloc(STORE_RESULT_PREALLOCATED_SET * sizeof(zval **), to_cache); + set->row_buffers= mnd_pemalloc(STORE_RESULT_PREALLOCATED_SET * sizeof(zend_uchar *), to_cache); + set->persistent = to_cache; + set->qcache = to_cache? mysqlnd_qcache_get_cache_reference(conn->qcache):NULL; + set->references = 1; + + PACKET_INIT_ALLOCA(row_packet, PROT_ROW_PACKET); + 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; + /* Let the row packet fill our buffer and skip additional malloc + memcpy */ + while (FAIL != (ret = PACKET_READ_ALLOCA(row_packet, conn)) && !row_packet.eof) { + int i; + zval **current_row; + + if (!free_rows) { + mynd_ulonglong total_rows = free_rows = next_extend = next_extend * 5 / 3; /* extend with 33% */ + total_rows += set->row_count; + set->data = mnd_perealloc(set->data, total_rows * sizeof(zval **), set->persistent); + + set->row_buffers = mnd_perealloc(set->row_buffers, + total_rows * sizeof(zend_uchar *), set->persistent); + } + free_rows--; + current_row = set->data[set->row_count] = row_packet.fields; + 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; + + + if (update_max_length == TRUE) { + for (i = 0; i < row_packet.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; + } + } + } + } + /* + 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. + */ + } + 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) { + set->data = mnd_perealloc(set->data, + (size_t) set->row_count * sizeof(zval **), + set->persistent); + set->row_buffers = mnd_perealloc(set->row_buffers, + (size_t) set->row_count * sizeof(zend_uchar *), + set->persistent); + } + + if (conn->upsert_status.server_status & SERVER_MORE_RESULTS_EXISTS) { + conn->state = CONN_NEXT_RESULT_PENDING; + } else { + conn->state = CONN_READY; + } + + if (ret == FAIL) { + 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 = result->data->row_count; + } + PACKET_FREE_ALLOCA(row_packet); + + DBG_INF_FMT("ret=%s row_count=%u warns=%u status=%u", ret == PASS? "PASS":"FAIL", + set->row_count, conn->upsert_status.warning_count, conn->upsert_status.server_status); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_res::store_result */ +MYSQLND_RES * +MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result, + MYSQLND * const conn, + zend_bool ps_protocol TSRMLS_DC) +{ + enum_func_status ret; + zend_bool to_cache = FALSE; + + DBG_ENTER("mysqlnd_res::store_result"); + DBG_INF_FMT("conn=%d ps_protocol=%d", conn->thread_id, ps_protocol); + + result->conn = NULL; /* store result does not reference the connection */ + result->type = MYSQLND_RES_NORMAL; + result->m.fetch_row = result->m.fetch_row_normal_buffered; + result->m.fetch_lengths = mysqlnd_fetch_lengths_buffered; + + conn->state = CONN_FETCHING_DATA; + + result->lengths = mnd_ecalloc(result->field_count, sizeof(unsigned long)); + + ret = mysqlnd_store_result_fetch_data(conn, result, result->meta, + ps_protocol, TRUE, to_cache TSRMLS_CC); + if (PASS == ret) { + /* libmysql's documentation says it should be so for SELECT statements */ + conn->upsert_status.affected_rows = result->data->row_count; + } else { + conn->error_info = result->data->error_info; + result->m.free_result_internal(result TSRMLS_CC); + result = NULL; + } + + 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->data && result->conn && 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"); + DBG_INF_FMT("implicit=%d", implicit); + + 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, mynd_ulonglong row TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res::data_seek"); + DBG_INF_FMT("row=%lu", row); + + if (!result->data) { + return FAIL; + } + + /* libmysql just moves to the end, it does traversing of a linked list */ + if (row >= result->data->row_count) { + result->data->data_cursor = NULL; + } else { + result->data->data_cursor = result->data->data + row; + } + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_res::num_fields */ +mynd_ulonglong +MYSQLND_METHOD(mysqlnd_res, num_rows)(const MYSQLND_RES * const res) +{ + /* Be compatible with libmysql. We count row_count, but will return 0 */ + return res->data? res->data->row_count:0; +} +/* }}} */ + + +/* {{{ mysqlnd_res::num_fields */ +unsigned int +MYSQLND_METHOD(mysqlnd_res, num_fields)(const MYSQLND_RES * const res) +{ + return res->field_count; +} +/* }}} */ + + +/* {{{ mysqlnd_res::fetch_field */ +static MYSQLND_FIELD * +MYSQLND_METHOD(mysqlnd_res, fetch_field)(MYSQLND_RES * const result TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res::fetch_field"); + DBG_RETURN(result->meta? result->meta->m->fetch_field(result->meta TSRMLS_CC):NULL); +} +/* }}} */ + + +/* {{{ mysqlnd_res::fetch_field_direct */ +static MYSQLND_FIELD * +MYSQLND_METHOD(mysqlnd_res, fetch_field_direct)(const MYSQLND_RES * const result, + MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_res::fetch_field_direct"); + DBG_RETURN(result->meta? result->meta->m->fetch_field_direct(result->meta, fieldnr TSRMLS_CC):NULL); +} +/* }}} */ + + +/* {{{ mysqlnd_res::field_seek */ +static MYSQLND_FIELD_OFFSET +MYSQLND_METHOD(mysqlnd_res, field_seek)(MYSQLND_RES * const result, + MYSQLND_FIELD_OFFSET field_offset) +{ + 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) +{ + return result->meta? result->meta->m->field_tell(result->meta):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"); + DBG_INF_FMT("flags=%u mysqlnd_extension=%d", flags, extension); + + 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_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; + + DBG_ENTER("mysqlnd_res::fetch_all"); + DBG_INF_FMT("flags=%u", flags); + + /* mysqlnd_res::fetch_all works with buffered resultsets only */ + if (result->conn || !result->data || + !result->data->row_count || !result->data->data_cursor || + result->data->data_cursor >= result->data->data + result->data->row_count) + { + RETVAL_NULL(); + DBG_VOID_RETURN; + } + + mysqlnd_array_init(return_value, (uint) result->data->row_count); + + while (result->data->data_cursor && + (result->data->data_cursor - result->data->data) < result->data->row_count) + { + MAKE_STD_ZVAL(row); + mysqlnd_fetch_into(result, flags, row, MYSQLND_MYSQLI); + add_index_zval(return_value, i++, row); + } + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_res::fetch_into */ +static void +MYSQLND_METHOD(mysqlnd_res, fetch_field_data)(MYSQLND_RES *result, unsigned int offset, + zval *return_value TSRMLS_DC) +{ + zval row; + zval **entry; + uint 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); + ZVAL_REFCOUNT(return_value) = 1; + zval_dtor(&row); + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_result_init */ +MYSQLND_RES *mysqlnd_result_init(unsigned int field_count, MYSQLND_THD_ZVAL_PCACHE *cache TSRMLS_DC) +{ + MYSQLND_RES *ret = mnd_ecalloc(1, sizeof(MYSQLND_RES)); + + DBG_ENTER("mysqlnd_result_init"); + DBG_INF_FMT("field_count=%u cache=%p", field_count, cache); + + ret->field_count = field_count; + ret->zval_cache = cache; + + ret->m.use_result = MYSQLND_METHOD(mysqlnd_res, use_result); + ret->m.store_result = MYSQLND_METHOD(mysqlnd_res, store_result); + ret->m.free_result = MYSQLND_METHOD(mysqlnd_res, free_result); + ret->m.seek_data = MYSQLND_METHOD(mysqlnd_res, data_seek); + ret->m.num_rows = MYSQLND_METHOD(mysqlnd_res, num_rows); + ret->m.num_fields = MYSQLND_METHOD(mysqlnd_res, num_fields); + ret->m.fetch_into = MYSQLND_METHOD(mysqlnd_res, fetch_into); + ret->m.fetch_all = MYSQLND_METHOD(mysqlnd_res, fetch_all); + ret->m.fetch_field_data = MYSQLND_METHOD(mysqlnd_res, fetch_field_data); + ret->m.seek_field = MYSQLND_METHOD(mysqlnd_res, field_seek); + ret->m.field_tell = MYSQLND_METHOD(mysqlnd_res, field_tell); + ret->m.fetch_field = MYSQLND_METHOD(mysqlnd_res, fetch_field); + ret->m.fetch_field_direct = MYSQLND_METHOD(mysqlnd_res, fetch_field_direct); + + ret->m.skip_result = MYSQLND_METHOD(mysqlnd_res, skip_result); + ret->m.free_result_buffers = MYSQLND_METHOD(mysqlnd_res, free_result_buffers); + ret->m.free_result_internal = mysqlnd_internal_free_result; + ret->m.free_result_contents = mysqlnd_internal_free_result_contents; + + ret->m.read_result_metadata = MYSQLND_METHOD(mysqlnd_res, read_result_metadata); + ret->m.fetch_row_normal_buffered = mysqlnd_fetch_row_buffered; + ret->m.fetch_row_normal_unbuffered = mysqlnd_fetch_row_unbuffered; + + 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 0000000000..eec92becff --- /dev/null +++ b/ext/mysqlnd/mysqlnd_result.h @@ -0,0 +1,48 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef MYSQLND_RESULT_H +#define MYSQLND_RESULT_H + +MYSQLND_RES *mysqlnd_result_init(unsigned int field_count, MYSQLND_THD_ZVAL_PCACHE *cache TSRMLS_DC); + +void mysqlnd_unbuffered_free_last_data(MYSQLND_RES *result TSRMLS_DC); + +enum_func_status +mysqlnd_store_result_fetch_data(MYSQLND * const conn, MYSQLND_RES *result, + MYSQLND_RES_METADATA *meta, + zend_bool binary_protocol, + zend_bool update_max_length, + zend_bool to_cache TSRMLS_DC); + +enum_func_status mysqlnd_query_read_result_set_header(MYSQLND *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 0000000000..bd80ae24b2 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_result_meta.c @@ -0,0 +1,440 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_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(char *key, size_t length, long *idx) +{ + register char *tmp=key; + + if (*tmp=='-') { + tmp++; + } + if ((*tmp>='0' && *tmp<='9')) { + do { /* possibly a numeric index */ + 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 PHP_MAJOR_VERSION >= 6 +/* {{{ 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 TSRMLS_DC) +{ + int i = 0; + php_mysql_packet_res_field field_packet; + + DBG_ENTER("mysqlnd_res_meta::read_metadata"); + + PACKET_INIT_ALLOCA(field_packet, PROT_RSET_FLD_PACKET); + for (;i < meta->field_count; i++) { + long idx; + + if (meta->fields[i].root) { + /* We re-read metadata for PS */ + mnd_efree(meta->fields[i].root); + meta->fields[i].root = NULL; + } + + field_packet.metadata = &(meta->fields[i]); + if (FAIL == PACKET_READ_ALLOCA(field_packet, conn)) { + PACKET_FREE_ALLOCA(field_packet); + DBG_RETURN(FAIL); + } + if (field_packet.stupid_list_fields_eof == TRUE) { + break; + } + + if (mysqlnd_ps_fetch_functions[meta->fields[i].type].func == NULL) { + DBG_ERR_FMT("Unknown type %d 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 %d sent by the server. " + "Please send a report to the developers", + meta->fields[i].type); + PACKET_FREE_ALLOCA(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 PHP_MAJOR_VERSION >= 6 + if (UG(unicode)) { + UChar *ustr; + int ulen; + 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 +#endif + { + /* 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); + } + } + } + PACKET_FREE_ALLOCA(field_packet); + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ mysqlnd_res_meta::free */ +static void +MYSQLND_METHOD(mysqlnd_res_meta, free)(MYSQLND_RES_METADATA *meta, zend_bool persistent TSRMLS_DC) +{ + int i; + MYSQLND_FIELD *fields; + + DBG_ENTER("mysqlnd_res_meta::free"); + DBG_INF_FMT("persistent=%d", persistent); + + if ((fields = meta->fields)) { + DBG_INF("Freeing fields metadata"); + i = meta->field_count; + while (i--) { + php_mysqlnd_free_field_metadata(fields++, persistent TSRMLS_CC); + } + mnd_pefree(meta->fields, persistent); + meta->fields = NULL; + } + + if (meta->zend_hash_keys) { + DBG_INF("Freeing zend_hash_keys"); +#if PHP_MAJOR_VERSION >= 6 + 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, persistent); + } + } + } +#endif + mnd_pefree(meta->zend_hash_keys, persistent); + meta->zend_hash_keys = NULL; + } + DBG_INF("Freeing metadata structure"); + mnd_pefree(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 = mnd_pemalloc(sizeof(MYSQLND_RES_METADATA), persistent); + MYSQLND_FIELD *new_fields = mnd_pecalloc(meta->field_count + 1, sizeof(MYSQLND_FIELD), persistent); + 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=%d", persistent); + + new_meta->zend_hash_keys = mnd_pemalloc(len, persistent); + memcpy(new_meta->zend_hash_keys, meta->zend_hash_keys, len); + new_meta->m = meta->m; + + /* + 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); + 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); + /* copy the trailing \0 too */ + memcpy(new_fields[i].def, orig_fields[i].def, orig_fields[i].def_length + 1); + } +#if PHP_MAJOR_VERSION >= 6 + 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); + } +#endif + } + new_meta->current_field = 0; + new_meta->field_count = meta->field_count; + + new_meta->fields = new_fields; + + DBG_RETURN(new_meta); +} +/* }}} */ + +/* {{{ mysqlnd_res_meta::fetch_field */ +static 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_RETURN(NULL); + } + DBG_RETURN(&meta->fields[meta->current_field++]); +} +/* }}} */ + + +/* {{{ mysqlnd_res_meta::fetch_field_direct */ +static 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=%d", fieldnr); + DBG_RETURN(&meta->fields[fieldnr]); +} +/* }}} */ + + +/* {{{ mysqlnd_res_meta::field_tell */ +static MYSQLND_FIELD_OFFSET +MYSQLND_METHOD(mysqlnd_res_meta, field_tell)(const MYSQLND_RES_METADATA * const meta) +{ + return meta->current_field; +} +/* }}} */ + + +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, 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 */ +MYSQLND_RES_METADATA *mysqlnd_result_meta_init(unsigned int field_count TSRMLS_DC) +{ + MYSQLND_RES_METADATA *ret; + DBG_ENTER("mysqlnd_result_meta_init"); + + /* +1 is to have empty marker at the end */ + ret = mnd_ecalloc(1, sizeof(MYSQLND_RES_METADATA)); + ret->field_count = field_count; + ret->fields = ecalloc(field_count + 1, sizeof(MYSQLND_FIELD)); + ret->zend_hash_keys = ecalloc(field_count, sizeof(struct mysqlnd_field_hash_key)); + + ret->m = & mysqlnd_mysqlnd_res_meta_methods; + DBG_INF_FMT("meta=%p", ret); + 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_meta.h b/ext/mysqlnd/mysqlnd_result_meta.h new file mode 100644 index 0000000000..f1bfaee681 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_result_meta.h @@ -0,0 +1,40 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef MYSQLND_RESULT_META_H +#define MYSQLND_RESULT_META_H + + +MYSQLND_RES_METADATA *mysqlnd_result_meta_init(unsigned int field_count 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_statistics.c b/ext/mysqlnd/mysqlnd_statistics.c new file mode 100644 index 0000000000..66d9bacd44 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_statistics.c @@ -0,0 +1,155 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ +#include "php.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_statistics.h" +#include "mysqlnd_debug.h" + + +#define STR_W_LEN(str) str, (sizeof(str) - 1) + +/* {{{ mysqlnd_stats_values_names + */ +const MYSQLND_STRING mysqlnd_stats_values_names[STAT_LAST] = +{ + { STR_W_LEN("bytes_sent") }, + { STR_W_LEN("bytes_received") }, + { STR_W_LEN("packets_sent") }, + { STR_W_LEN("packets_received") }, + { STR_W_LEN("protocol_overhead_in") }, + { STR_W_LEN("protocol_overhead_out") }, + { STR_W_LEN("result_set_queries") }, + { STR_W_LEN("non_result_set_queries") }, + { STR_W_LEN("no_index_used") }, + { STR_W_LEN("bad_index_used") }, + { STR_W_LEN("buffered_sets") }, + { STR_W_LEN("unbuffered_sets") }, + { STR_W_LEN("ps_buffered_sets") }, + { STR_W_LEN("ps_unbuffered_sets") }, + { STR_W_LEN("flushed_normal_sets") }, + { STR_W_LEN("flushed_ps_sets") }, + { STR_W_LEN("ps_prepared_never_executed") }, + { STR_W_LEN("ps_prepared_once_executed") }, + { STR_W_LEN("rows_fetched_from_server_normal") }, + { STR_W_LEN("rows_fetched_from_server_ps") }, + { STR_W_LEN("rows_buffered_from_client_normal") }, + { STR_W_LEN("rows_buffered_from_client_ps") }, + { STR_W_LEN("rows_fetched_from_client_normal_buffered") }, + { STR_W_LEN("rows_fetched_from_client_normal_unbuffered") }, + { STR_W_LEN("rows_fetched_from_client_ps_buffered") }, + { STR_W_LEN("rows_fetched_from_client_ps_unbuffered") }, + { STR_W_LEN("rows_fetched_from_client_ps_cursor") }, + { STR_W_LEN("rows_skipped_normal") }, + { STR_W_LEN("rows_skipped_ps") }, + { STR_W_LEN("copy_on_write_saved") }, + { STR_W_LEN("copy_on_write_performed") }, + { STR_W_LEN("command_buffer_too_small") }, + { STR_W_LEN("connect_success") }, + { STR_W_LEN("connect_failure") }, + { STR_W_LEN("connection_reused") }, + { STR_W_LEN("reconnect") }, + { STR_W_LEN("pconnect_success") }, + { STR_W_LEN("active_connections") }, + { STR_W_LEN("active_persistent_connections") }, + { STR_W_LEN("explicit_close") }, + { STR_W_LEN("implicit_close") }, + { STR_W_LEN("disconnect_close") }, + { STR_W_LEN("in_middle_of_command_close") }, + { STR_W_LEN("explicit_free_result") }, + { STR_W_LEN("implicit_free_result") }, + { STR_W_LEN("explicit_stmt_close") }, + { STR_W_LEN("implicit_stmt_close") }, + { STR_W_LEN("mem_emalloc_count") }, + { STR_W_LEN("mem_emalloc_ammount") }, + { STR_W_LEN("mem_ecalloc_count") }, + { STR_W_LEN("mem_ecalloc_ammount") }, + { STR_W_LEN("mem_erealloc_count") }, + { STR_W_LEN("mem_erealloc_ammount") }, + { STR_W_LEN("mem_efree_count") }, + { STR_W_LEN("mem_malloc_count") }, + { STR_W_LEN("mem_malloc_ammount") }, + { STR_W_LEN("mem_calloc_count") }, + { STR_W_LEN("mem_calloc_ammount") }, + { STR_W_LEN("mem_realloc_calloc") }, + { STR_W_LEN("mem_realloc_ammount") }, + { STR_W_LEN("mem_free_count") } +}; +/* }}} */ + + +/* {{{ mysqlnd_fill_stats_hash */ +void +mysqlnd_fill_stats_hash(const MYSQLND_STATS * const stats, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC) +{ + unsigned int i; + + mysqlnd_array_init(return_value, STAT_LAST); + for (i = 0; i < STAT_LAST; i++) { + char tmp[22]; + + sprintf((char *)&tmp, MYSQLND_LLU_SPEC, stats->values[i]); +#if PHP_MAJOR_VERSION >= 6 + if (UG(unicode)) { + UChar *ustr, *tstr; + int ulen, tlen; + + zend_string_to_unicode(UG(utf8_conv), &ustr, &ulen, mysqlnd_stats_values_names[i].s, + mysqlnd_stats_values_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 +#endif + { + add_assoc_string_ex(return_value, mysqlnd_stats_values_names[i].s, + mysqlnd_stats_values_names[i].l + 1, tmp, 1); + } + } +} +/* }}} */ + + +/* {{{ _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, return_value TSRMLS_CC ZEND_FILE_LINE_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_statistics.h b/ext/mysqlnd/mysqlnd_statistics.h new file mode 100644 index 0000000000..2122ac2411 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_statistics.h @@ -0,0 +1,209 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef MYSQLND_STATISTICS_H +#define MYSQLND_STATISTICS_H + + +extern MYSQLND_STATS *mysqlnd_global_stats; + +typedef struct st_mysqlnd_string +{ + char *s; + size_t l; +} MYSQLND_STRING; + +extern const MYSQLND_STRING mysqlnd_stats_values_names[]; + +#ifdef ZTS + +#define MYSQLND_INC_GLOBAL_STATISTIC(statistic) \ + { \ + if (MYSQLND_G(collect_statistics)) { \ + DBG_INF_FMT("Global stat increase [%s]", mysqlnd_stats_values_names[statistic]); \ + tsrm_mutex_lock(mysqlnd_global_stats->LOCK_access); \ + mysqlnd_global_stats->values[(statistic)]++; \ + tsrm_mutex_unlock(mysqlnd_global_stats->LOCK_access); \ + }\ + } + +#define MYSQLND_INC_GLOBAL_STATISTIC2_W_VALUE(statistic1, value1, statistic2, value2) \ + { \ + if (MYSQLND_G(collect_statistics)) { \ + DBG_INF_FMT("Global stats increase w value [%s] [%s]", mysqlnd_stats_values_names[statistic1], mysqlnd_stats_values_names[statistic2]); \ + tsrm_mutex_lock(mysqlnd_global_stats->LOCK_access); \ + mysqlnd_global_stats->values[(statistic1)] += (value1); \ + mysqlnd_global_stats->values[(statistic2)] += (value2); \ + tsrm_mutex_unlock(mysqlnd_global_stats->LOCK_access); \ + }\ + } + +#define MYSQLND_DEC_CONN_STATISTIC(conn_stats, statistic) \ + { \ + if (MYSQLND_G(collect_statistics)) { \ + DBG_INF_FMT("Global&conn stat decrease [%s]", mysqlnd_stats_values_names[statistic]); \ + tsrm_mutex_lock(mysqlnd_global_stats->LOCK_access); \ + mysqlnd_global_stats->values[(statistic)]--; \ + tsrm_mutex_unlock(mysqlnd_global_stats->LOCK_access); \ + if ((conn_stats)) { \ + ((MYSQLND_STATS *) conn_stats)->values[(statistic)]--; \ + } \ + }\ + } + +#define MYSQLND_INC_CONN_STATISTIC(conn_stats, statistic) \ + { \ + if (MYSQLND_G(collect_statistics)) { \ + DBG_INF_FMT("Global&Conn stat increase [%s]", mysqlnd_stats_values_names[statistic]); \ + tsrm_mutex_lock(mysqlnd_global_stats->LOCK_access); \ + mysqlnd_global_stats->values[(statistic)]++; \ + tsrm_mutex_unlock(mysqlnd_global_stats->LOCK_access); \ + if ((conn_stats)) { \ + ((MYSQLND_STATS *) conn_stats)->values[(statistic)]++; \ + } \ + }\ + } + +#define MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn_stats, statistic, value) \ + { \ + if (MYSQLND_G(collect_statistics)) { \ + my_uint64 v = (my_uint64) (value); \ + DBG_INF_FMT("Global&Conn stat increase w value [%s]", mysqlnd_stats_values_names[statistic]); \ + tsrm_mutex_lock(mysqlnd_global_stats->LOCK_access); \ + mysqlnd_global_stats->values[(statistic)] += v; \ + tsrm_mutex_unlock(mysqlnd_global_stats->LOCK_access); \ + if ((conn_stats)) { \ + ((MYSQLND_STATS *) conn_stats)->values[(statistic)]+= v; \ + } \ + }\ + } + +#define MYSQLND_INC_CONN_STATISTIC_W_VALUE3(conn_stats, statistic1, value1, statistic2, value2, statistic3, value3) \ + { \ + if (MYSQLND_G(collect_statistics)) { \ + my_uint64 v1 = (my_uint64) (value1); \ + my_uint64 v2 = (my_uint64) (value2); \ + my_uint64 v3 = (my_uint64) (value3); \ + \ + tsrm_mutex_lock(mysqlnd_global_stats->LOCK_access); \ + mysqlnd_global_stats->values[(statistic1)]+= v1; \ + mysqlnd_global_stats->values[(statistic2)]+= v2; \ + mysqlnd_global_stats->values[(statistic3)]+= v3; \ + tsrm_mutex_unlock(mysqlnd_global_stats->LOCK_access); \ + if ((conn_stats)) { \ + ((MYSQLND_STATS *) conn_stats)->values[(statistic1)]+= v1; \ + ((MYSQLND_STATS *) conn_stats)->values[(statistic2)]+= v2; \ + ((MYSQLND_STATS *) conn_stats)->values[(statistic3)]+= v3; \ + } \ + } \ + } + + +#else /* NON-ZTS */ + +#define MYSQLND_INC_GLOBAL_STATISTIC(statistic) \ + { \ + if (MYSQLND_G(collect_statistics)) { \ + DBG_INF_FMT("Global stat increase [%s]", mysqlnd_stats_values_names[statistic]); \ + mysqlnd_global_stats->values[(statistic)]++; \ + } \ + } + +#define MYSQLND_INC_GLOBAL_STATISTIC2_W_VALUE(statistic1, value1, statistic2, value2) \ + { \ + if (MYSQLND_G(collect_statistics)) { \ + DBG_INF_FMT("Global stats increase w value [%s] [%s]", \ + mysqlnd_stats_values_names[statistic1], mysqlnd_stats_values_names[statistic2]); \ + mysqlnd_global_stats->values[(statistic1)] += (value1); \ + mysqlnd_global_stats->values[(statistic2)] += (value2); \ + }\ + } + + +#define MYSQLND_DEC_CONN_STATISTIC(conn_stats, statistic) \ + { \ + if (MYSQLND_G(collect_statistics)) { \ + DBG_INF_FMT("Global&Conn stat decrease [%s]", mysqlnd_stats_values_names[statistic]); \ + mysqlnd_global_stats->values[(statistic)]--; \ + if ((conn_stats)) { \ + ((MYSQLND_STATS *) conn_stats)->values[(statistic)]--; \ + } \ + } \ + } + +#define MYSQLND_INC_CONN_STATISTIC(conn_stats, statistic) \ + { \ + if (MYSQLND_G(collect_statistics)) { \ + DBG_INF_FMT("Global&Conn stat increase [%s]", mysqlnd_stats_values_names[statistic]); \ + mysqlnd_global_stats->values[(statistic)]++; \ + if ((conn_stats)) { \ + ((MYSQLND_STATS *) conn_stats)->values[(statistic)]++; \ + } \ + } \ + } + +#define MYSQLND_INC_CONN_STATISTIC_W_VALUE(conn_stats, statistic, value) \ + { \ + my_uint64 v = (my_uint64) (value); \ + DBG_INF_FMT("Global&Conn stats increase w value [%s]", mysqlnd_stats_values_names[statistic]); \ + if (MYSQLND_G(collect_statistics)) { \ + mysqlnd_global_stats->values[(statistic)] += v; \ + if ((conn_stats)) { \ + ((MYSQLND_STATS *) conn_stats)->values[(statistic)] += v; \ + } \ + } \ + } + +#define MYSQLND_INC_CONN_STATISTIC_W_VALUE3(conn_stats, statistic1, value1, statistic2, value2, statistic3, value3) \ + { \ + if (MYSQLND_G(collect_statistics)) { \ + my_uint64 v1 = (my_uint64) (value1); \ + my_uint64 v2 = (my_uint64) (value2); \ + my_uint64 v3 = (my_uint64) (value3); \ + \ + mysqlnd_global_stats->values[(statistic1)]+= v1; \ + mysqlnd_global_stats->values[(statistic2)]+= v2; \ + mysqlnd_global_stats->values[(statistic3)]+= v3; \ + if ((conn_stats)) { \ + ((MYSQLND_STATS *) conn_stats)->values[(statistic1)]+= v1; \ + ((MYSQLND_STATS *) conn_stats)->values[(statistic2)]+= v2; \ + ((MYSQLND_STATS *) conn_stats)->values[(statistic3)]+= v3; \ + } \ + } \ + } + +#endif + +void mysqlnd_fill_stats_hash(const MYSQLND_STATS * const stats, zval *return_value + TSRMLS_DC ZEND_FILE_LINE_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 0000000000..31367d53c9 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_structs.h @@ -0,0 +1,540 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef MYSQLND_STRUCTS_H +#define MYSQLND_STRUCTS_H + +typedef struct st_mysqlnd_cmd_buffer +{ + zend_uchar *buffer; + size_t length; +} MYSQLND_CMD_BUFFER; + + +typedef struct st_mysqlnd_field +{ + char *name; /* Name of column */ + char *org_name; /* Original column name, if an alias */ + char *table; /* Table of column if column was a field */ + char *org_table; /* Org table name, if table was an alias */ + char *db; /* Database for table */ + 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; + mynd_ulonglong affected_rows; + mynd_ulonglong 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; +} mysqlnd_error_info; + + +typedef struct st_mysqlnd_zval_pcache MYSQLND_ZVAL_PCACHE; +typedef struct st_mysqlnd_thread_zval_pcache MYSQLND_THD_ZVAL_PCACHE; +typedef struct st_mysqlnd_qcache MYSQLND_QCACHE; + + +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 +{ + uint nr; + char *name; + char *collation; + uint char_minlen; + uint char_maxlen; + uint dangerous_for_escape_backslash; + uint (*mb_charlen)(uint c); + uint (*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, char *buf, uint buf_len TSRMLS_DC); + int (*local_infile_error)(void *ptr, char *error_msg, uint error_msg_len TSRMLS_DC); + void (*local_infile_end)(void *ptr TSRMLS_DC); + zval *callback; + void *userdata; +} MYSQLND_INFILE; + +typedef struct st_mysqlnd_option +{ + /* timeouts */ + uint timeout_connect; + uint timeout_read; + uint timeout_write; + + ulong flags; + + /* init commands - we need to send them to server directly after connect */ + uint num_commands; + char **init_commands; + + /* configuration file information */ + char *cfg_file; + char *cfg_section; + + /* SSL information */ + char *ssl_key; + char *ssl_cert; + char *ssl_ca; + char *ssl_capath; + char *ssl_cipher; + zend_bool use_ssl; + + 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_year_as_int; +#endif + unsigned int net_read_buffer_size; +} MYSQLND_OPTION; + + +typedef struct st_mysqlnd_connection MYSQLND; +typedef struct st_mysqlnd_res MYSQLND_RES; +typedef char** MYSQLND_ROW; /* return data as array of strings */ +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 +{ + my_uint64 values[STAT_LAST]; +#ifdef ZTS + MUTEX_T LOCK_access; +#endif +} MYSQLND_STATS; + + +typedef struct st_mysqlnd_net +{ + php_stream *stream; + /* sequence for simple checking of correct packets */ + zend_uchar packet_no; + +#ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND + zend_uchar last_command; +#endif + + /* cmd buffer */ + MYSQLND_CMD_BUFFER cmd_buffer; +} MYSQLND_NET; + + +struct st_mysqlnd_conn_methods +{ + ulong (*escape_string)(const MYSQLND * const conn, char *newstr, const char *escapestr, int escapestr_len TSRMLS_DC); + enum_func_status (*set_charset)(MYSQLND * const conn, const char * const charset TSRMLS_DC); + enum_func_status (*query)(MYSQLND *conn, const char *query, unsigned int query_len TSRMLS_DC); + MYSQLND_RES * (*use_result)(MYSQLND * const conn TSRMLS_DC); + MYSQLND_RES * (*store_result)(MYSQLND * const conn TSRMLS_DC); + enum_func_status (*next_result)(MYSQLND * const conn TSRMLS_DC); + zend_bool (*more_results)(const MYSQLND * const conn); + + MYSQLND_STMT * (*stmt_init)(MYSQLND * const conn TSRMLS_DC); + + enum_func_status (*shutdown_server)(MYSQLND * const conn, unsigned long level TSRMLS_DC); + enum_func_status (*refresh_server)(MYSQLND * const conn, unsigned long options TSRMLS_DC); + + enum_func_status (*ping)(MYSQLND * const conn TSRMLS_DC); + enum_func_status (*kill_connection)(MYSQLND *conn, unsigned int pid TSRMLS_DC); + enum_func_status (*select_db)(MYSQLND * const conn, const char * const db, unsigned int db_len TSRMLS_DC); + enum_func_status (*server_dump_debug_information)(MYSQLND * const conn TSRMLS_DC); + enum_func_status (*change_user)(MYSQLND * const conn, const char * user, const char * passwd, const char * db TSRMLS_DC); + + unsigned int (*get_error_no)(const MYSQLND * const conn); + const char * (*get_error_str)(const MYSQLND * const conn); + const char * (*get_sqlstate)(const MYSQLND * const conn); + mynd_ulonglong (*get_thread_id)(const MYSQLND * const conn); + void (*get_statistics)(const MYSQLND * const conn, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC); + + unsigned long (*get_server_version)(const MYSQLND * const conn); + const char * (*get_server_information)(const MYSQLND * const conn); + enum_func_status (*get_server_statistics)(MYSQLND *conn, char **message, unsigned int * message_len TSRMLS_DC); + const char * (*get_host_information)(const MYSQLND * const conn); + unsigned int (*get_protocol_information)(const MYSQLND * const conn); + const char * (*get_last_message)(const MYSQLND * const conn); + const char * (*charset_name)(const MYSQLND * const conn); + MYSQLND_RES * (*list_fields)(MYSQLND *conn, const char *table, const char *achtung_wild TSRMLS_DC); + MYSQLND_RES * (*list_method)(MYSQLND *conn, const char *query, const char *achtung_wild, char *par1 TSRMLS_DC); + + mynd_ulonglong (*get_last_insert_id)(const MYSQLND * const conn); + mynd_ulonglong (*get_affected_rows)(const MYSQLND * const conn); + unsigned int (*get_warning_count)(const MYSQLND * const conn); + + unsigned int (*get_field_count)(const MYSQLND * const conn); + + enum_func_status (*set_server_option)(MYSQLND * const conn, enum_mysqlnd_server_option option TSRMLS_DC); + enum_func_status (*set_client_option)(MYSQLND * const conn, enum_mysqlnd_option option, const char * const value TSRMLS_DC); + void (*free_contents)(MYSQLND *conn TSRMLS_DC); /* private */ + enum_func_status (*close)(MYSQLND *conn, enum_connection_close_type close_type TSRMLS_DC); + void (*dtor)(MYSQLND *conn TSRMLS_DC); /* private */ + + MYSQLND * (*get_reference)(MYSQLND * const conn); + enum_func_status (*free_reference)(MYSQLND * const conn 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 */ + + MYSQLND_RES * (*use_result)(MYSQLND_RES * const result, zend_bool ps_protocol TSRMLS_DC); + MYSQLND_RES * (*store_result)(MYSQLND_RES * result, MYSQLND * const conn, zend_bool ps TSRMLS_DC); + void (*fetch_into)(MYSQLND_RES *result, unsigned int flags, zval *return_value, enum_mysqlnd_extension ext TSRMLS_DC ZEND_FILE_LINE_DC); + void (*fetch_all)(MYSQLND_RES *result, unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC); + void (*fetch_field_data)(MYSQLND_RES *result, unsigned int offset, zval *return_value TSRMLS_DC); + mynd_ulonglong (*num_rows)(const MYSQLND_RES * const result); + unsigned int (*num_fields)(const MYSQLND_RES * const result); + enum_func_status (*skip_result)(MYSQLND_RES * const result TSRMLS_DC); + enum_func_status (*seek_data)(MYSQLND_RES * result, mynd_ulonglong row TSRMLS_DC); + MYSQLND_FIELD_OFFSET (*seek_field)(MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET field_offset); + MYSQLND_FIELD_OFFSET (*field_tell)(const MYSQLND_RES * const result); + MYSQLND_FIELD * (*fetch_field)(MYSQLND_RES * const result TSRMLS_DC); + MYSQLND_FIELD * (*fetch_field_direct)(const MYSQLND_RES * const result, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC); + + enum_func_status (*read_result_metadata)(MYSQLND_RES *result, MYSQLND *conn TSRMLS_DC); + unsigned long * (*fetch_lengths)(MYSQLND_RES * const result); + void (*free_result_buffers)(MYSQLND_RES * result TSRMLS_DC); /* private */ + enum_func_status (*free_result)(MYSQLND_RES * result, zend_bool implicit TSRMLS_DC); + void (*free_result_internal)(MYSQLND_RES *result TSRMLS_DC); + void (*free_result_contents)(MYSQLND_RES *result TSRMLS_DC); +}; + + +struct st_mysqlnd_res_meta_methods +{ + MYSQLND_FIELD * (*fetch_field)(MYSQLND_RES_METADATA * const meta TSRMLS_DC); + MYSQLND_FIELD * (*fetch_field_direct)(const MYSQLND_RES_METADATA * const meta, MYSQLND_FIELD_OFFSET fieldnr TSRMLS_DC); + MYSQLND_FIELD_OFFSET (*field_tell)(const MYSQLND_RES_METADATA * const meta); + enum_func_status (*read_metadata)(MYSQLND_RES_METADATA * const meta, MYSQLND *conn TSRMLS_DC); + MYSQLND_RES_METADATA * (*clone_metadata)(const MYSQLND_RES_METADATA * const meta, zend_bool persistent TSRMLS_DC); + void (*free_metadata)(MYSQLND_RES_METADATA *meta, zend_bool persistent TSRMLS_DC); +}; + + +struct st_mysqlnd_stmt_methods +{ + enum_func_status (*prepare)(MYSQLND_STMT * const stmt, const char * const query, unsigned int query_len TSRMLS_DC); + enum_func_status (*execute)(MYSQLND_STMT * const stmt TSRMLS_DC); + MYSQLND_RES * (*use_result)(MYSQLND_STMT * const stmt TSRMLS_DC); + MYSQLND_RES * (*store_result)(MYSQLND_STMT * const stmt TSRMLS_DC); + MYSQLND_RES * (*get_result)(MYSQLND_STMT * const stmt TSRMLS_DC); + enum_func_status (*free_result)(MYSQLND_STMT * const stmt TSRMLS_DC); + enum_func_status (*seek_data)(const MYSQLND_STMT * const stmt, mynd_ulonglong row TSRMLS_DC); + enum_func_status (*reset)(MYSQLND_STMT * const stmt TSRMLS_DC); + enum_func_status (*close)(MYSQLND_STMT * const stmt, zend_bool implicit TSRMLS_DC); /* private */ + enum_func_status (*dtor)(MYSQLND_STMT * const stmt, zend_bool implicit TSRMLS_DC); /* use this for mysqlnd_stmt_close */ + + enum_func_status (*fetch)(MYSQLND_STMT * const stmt, zend_bool * const fetched_anything TSRMLS_DC); + + enum_func_status (*bind_param)(MYSQLND_STMT * const stmt, MYSQLND_PARAM_BIND * const param_bind TSRMLS_DC); + enum_func_status (*bind_result)(MYSQLND_STMT * const stmt, MYSQLND_RESULT_BIND * const result_bind TSRMLS_DC); + enum_func_status (*send_long_data)(MYSQLND_STMT * const stmt, unsigned int param_num, + const char * const data, unsigned long length TSRMLS_DC); + MYSQLND_RES * (*get_parameter_metadata)(MYSQLND_STMT * const stmt); + MYSQLND_RES * (*get_result_metadata)(MYSQLND_STMT * const stmt TSRMLS_DC); + + mynd_ulonglong (*get_last_insert_id)(const MYSQLND_STMT * const stmt); + mynd_ulonglong (*get_affected_rows)(const MYSQLND_STMT * const stmt); + mynd_ulonglong (*get_num_rows)(const MYSQLND_STMT * const stmt); + + unsigned int (*get_param_count)(const MYSQLND_STMT * const stmt); + unsigned int (*get_field_count)(const MYSQLND_STMT * const stmt); + unsigned int (*get_warning_count)(const MYSQLND_STMT * const stmt); + + unsigned int (*get_error_no)(const MYSQLND_STMT * const stmt); + const char * (*get_error_str)(const MYSQLND_STMT * const stmt); + const char * (*get_sqlstate)(const MYSQLND_STMT * const stmt); + + enum_func_status (*get_attribute)(MYSQLND_STMT * const stmt, enum mysqlnd_stmt_attr attr_type, void * const value TSRMLS_DC); + enum_func_status (*set_attribute)(MYSQLND_STMT * const stmt, enum mysqlnd_stmt_attr attr_type, const void * const value TSRMLS_DC); +}; + + +struct st_mysqlnd_connection +{ +/* Operation related */ + MYSQLND_NET net; + +/* Information related */ + char *host; + char *unix_socket; + char *user; + char *passwd; + unsigned int *passwd_len; + char *scheme; + mynd_ulonglong thread_id; + char *server_version; + char *host_info; + unsigned char *scramble; + const MYSQLND_CHARSET *charset; + const MYSQLND_CHARSET *greet_charset; + MYSQLND_INFILE infile; + unsigned int protocol_version; + unsigned long max_packet_size; + unsigned int port; + unsigned long client_flag; + unsigned long server_capabilities; + + int tmp_int; + + + /* For UPSERT queries */ + mysqlnd_upsert_status upsert_status; + char *last_message; + unsigned int last_message_len; + + /* If error packet, we use these */ + mysqlnd_error_info error_info; + + /* + 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_OPTION options; + + /* zval cache */ + MYSQLND_THD_ZVAL_PCACHE *zval_cache; + + /* qcache */ + MYSQLND_QCACHE *qcache; + + /* stats */ + MYSQLND_STATS stats; + + struct st_mysqlnd_conn_methods *m; +}; + +typedef struct st_php_mysql_packet_row php_mysql_packet_row; + + +struct mysqlnd_field_hash_key +{ + zend_bool is_numeric; + unsigned long key; +#if PHP_MAJOR_VERSION >= 6 + 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 */ + + struct st_mysqlnd_res_meta_methods *m; +}; + + +struct st_mysqlnd_buffered_result +{ + zval ***data; + zval ***data_cursor; + zend_uchar **row_buffers; + mynd_ulonglong row_count; + zend_bool persistent; + + MYSQLND_QCACHE *qcache; + unsigned int references; + + zend_bool async_invalid; + mysqlnd_error_info error_info; +}; + + +struct st_mysqlnd_unbuffered_result +{ + /* For unbuffered (both normal and PS) */ + zval **last_row_data; + zend_uchar *last_row_buffer; + + mynd_ulonglong row_count; + zend_bool eof_reached; +}; + + +struct st_mysqlnd_res +{ + struct st_mysqlnd_res_methods m; + + MYSQLND *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 *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; + + php_mysql_packet_row *row_packet; /* Unused for PS */ + + /* zval cache */ + MYSQLND_THD_ZVAL_PCACHE *zval_cache; +}; + + +struct st_mysqlnd_param_bind +{ + zval *zv; + zend_uchar type; + enum_param_bind_flags flags; +}; + +struct st_mysqlnd_result_bind +{ + zval *zv; + zend_uchar original_type; + zend_bool bound; +}; + + +struct st_mysqlnd_stmt +{ + MYSQLND *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; + + mysqlnd_upsert_status upsert_status; + + mysqlnd_error_info error_info; + + 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 cmd_buffer; + unsigned int execute_count;/* count how many times the stmt was executed */ + + struct st_mysqlnd_stmt_methods *m; +}; + +#endif /* MYSQLND_STRUCTS_H */ diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c new file mode 100644 index 0000000000..73179281fa --- /dev/null +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -0,0 +1,1956 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ +#include "php.h" +#include "php_globals.h" +#include "mysqlnd.h" +#include "mysqlnd_priv.h" +#include "mysqlnd_wireprotocol.h" +#include "mysqlnd_statistics.h" +#include "mysqlnd_palloc.h" +#include "mysqlnd_debug.h" +#include "ext/standard/sha1.h" +#include "php_network.h" +#include "zend_ini.h" + +#ifndef PHP_WIN32 +#include <netinet/tcp.h> +#else +#include <winsock.h> +#endif + + +#define USE_CORK 0 + +#define MYSQLND_SILENT 1 + +#define MYSQLND_DUMP_HEADER_N_BODY2 +#define MYSQLND_DUMP_HEADER_N_BODY_FULL2 + +#define MYSQLND_MAX_PACKET_SIZE (256L*256L*256L-1) + +#define PACKET_READ_HEADER_AND_BODY(packet, conn, buf, buf_size, packet_type) \ + { \ + if (FAIL == mysqlnd_read_header((conn), &((packet)->header) TSRMLS_CC)) {\ + conn->state = 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)); \ + DBG_RETURN(FAIL);\ + }\ + if ((buf_size) < (packet)->header.size) { \ + DBG_ERR_FMT("Packet buffer wasn't big enough %u bytes will be unread", \ + (packet)->header.size - (buf_size)); \ + }\ + if (!mysqlnd_read_body((conn), (buf), \ + MIN((buf_size), (packet)->header.size) TSRMLS_CC)) { \ + conn->state = 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)); \ + DBG_RETURN(FAIL);\ + } \ + } + + +extern mysqlnd_packet_methods packet_methods[]; + +static const char *unknown_sqlstate= "HY000"; + +char * const mysqlnd_empty_string = ""; + +/* Used in mysqlnd_debug.c */ +char * mysqlnd_read_header_name = "mysqlnd_read_header"; +char * mysqlnd_read_body_name = "mysqlnd_read_body"; + + +/* {{{ 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" +}; +/* }}} */ + + +/* {{{ 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 */ +mynd_ulonglong php_mysqlnd_net_field_length_ll(zend_uchar **packet) +{ + register zend_uchar *p= (zend_uchar *)*packet; + + if (*p < 251) { + (*packet)++; + return (mynd_ulonglong) *p; + } + + switch (*p) { + case 251: + (*packet)++; + return (mynd_ulonglong) MYSQLND_NULL_LENGTH; + case 252: + (*packet) += 3; + return (mynd_ulonglong) uint2korr(p + 1); + case 253: + (*packet) += 4; + return (mynd_ulonglong) uint3korr(p + 1); + default: + (*packet) += 9; + return (mynd_ulonglong) uint8korr(p + 1); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_net_store_length */ +zend_uchar *php_mysqlnd_net_store_length(zend_uchar *packet, mynd_ulonglong length) +{ + if (length < (mynd_ulonglong) L64(251)) { + *packet = (zend_uchar) length; + return packet + 1; + } + + if (length < (mynd_ulonglong) L64(65536)) { + *packet++ = 252; + int2store(packet,(uint) length); + return packet + 2; + } + + if (length < (mynd_ulonglong) L64(16777216)) { + *packet++ = 253; + int3store(packet,(ulong) length); + return packet + 3; + } + *packet++ = 254; + int8store(packet, length); + return packet + 8; +} +/* }}} */ + + +/* {{{ php_mysqlnd_consume_uneaten_data */ +#ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND +size_t php_mysqlnd_consume_uneaten_data(MYSQLND * const conn, enum php_mysqlnd_server_command cmd TSRMLS_DC) +{ + + /* + 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]; + MYSQLND_NET *net = &conn->net; + 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("php_mysqlnd_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. PID=%d", + bytes_consumed, mysqlnd_command_to_text[net->last_command], getpid()); + } + } + net->last_command = cmd; + + DBG_RETURN(skipped_bytes); +} +#endif +/* }}} */ + + +/* {{{ 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"); + + if (buf_len > 2) { + *error_no = uint2korr(p); + p+= 2; + /* sqlstate is following */ + if (*p == '#') { + memcpy(sqlstate, ++p, MYSQLND_SQLSTATE_LENGTH); + p+= MYSQLND_SQLSTATE_LENGTH; + } + error_msg_len = buf_len - (p - buf); + error_msg_len = MIN(error_msg_len, error_buf_len - 1); + memcpy(error, p, error_msg_len); + } else { + *error_no = CR_UNKNOWN_ERROR; + memcpy(sqlstate, unknown_sqlstate, MYSQLND_SQLSTATE_LENGTH); + } + sqlstate[MYSQLND_SQLSTATE_LENGTH] = '\0'; + error[error_msg_len]= '\0'; + + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ mysqlnd_set_sock_no_delay */ +int mysqlnd_set_sock_no_delay(php_stream *stream) +{ + + 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)); + TSRMLS_FETCH(); + + DBG_ENTER("mysqlnd_set_sock_no_delay"); + + if (result == -1) { + ret = FAILURE; + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* We assume that MYSQLND_HEADER_SIZE is 4 bytes !! */ +#define STORE_HEADER_SIZE(safe_storage, buffer) int4store((safe_storage), (*(uint32 *)(buffer))) +#define RESTORE_HEADER_SIZE(buffer, safe_storage) STORE_HEADER_SIZE((safe_storage), (buffer)) + +/* {{{ mysqlnd_stream_write_w_header */ +/* + IMPORTANT : It's expected that buf 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(buf) (not the pointer but the actual buffer) +*/ +size_t mysqlnd_stream_write_w_header(MYSQLND * const conn, char * const buf, size_t count TSRMLS_DC) +{ + zend_uchar safe_buf[((MYSQLND_HEADER_SIZE) + (sizeof(zend_uchar)) - 1) / (sizeof(zend_uchar))]; + zend_uchar *safe_storage = safe_buf; + MYSQLND_NET *net = &conn->net; + size_t old_chunk_size = net->stream->chunk_size; + size_t ret, left = count, packets_sent = 1; + zend_uchar *p = (zend_uchar *) buf; + + DBG_ENTER("mysqlnd_stream_write_w_header"); + DBG_INF_FMT("conn=%llu count=%lu", conn->thread_id, count); + + net->stream->chunk_size = MYSQLND_MAX_PACKET_SIZE; + + while (left > MYSQLND_MAX_PACKET_SIZE) { + STORE_HEADER_SIZE(safe_storage, p); + int3store(p, MYSQLND_MAX_PACKET_SIZE); + int1store(p + 3, net->packet_no); + net->packet_no++; + ret = php_stream_write(net->stream, (char *)p, MYSQLND_MAX_PACKET_SIZE + MYSQLND_HEADER_SIZE); + RESTORE_HEADER_SIZE(p, safe_storage); + + p += MYSQLND_MAX_PACKET_SIZE; + left -= MYSQLND_MAX_PACKET_SIZE; + + packets_sent++; + } + /* Even for zero size payload we have to send a packet */ + STORE_HEADER_SIZE(safe_storage, p); + int3store(p, left); + int1store(p + 3, net->packet_no); + net->packet_no++; + ret = php_stream_write(net->stream, (char *)p, left + MYSQLND_HEADER_SIZE); + RESTORE_HEADER_SIZE(p, safe_storage); + + if (!ret) { + DBG_ERR_FMT("Can't %u send bytes", count); + conn->state = CONN_QUIT_SENT; + SET_CLIENT_ERROR(conn->error_info, CR_SERVER_GONE_ERROR, UNKNOWN_SQLSTATE, mysqlnd_server_gone); + } + + 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); + + net->stream->chunk_size = old_chunk_size; + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_stream_write_w_command */ +#if USE_CORK && defined(TCP_CORK) +static +size_t mysqlnd_stream_write_w_command(MYSQLND * const conn, enum php_mysqlnd_server_command command, + const char * const buf, size_t count TSRMLS_DC) +{ + zend_uchar safe_buf[((MYSQLND_HEADER_SIZE) + (sizeof(zend_uchar)) - 1) / (sizeof(zend_uchar))]; + zend_uchar *safe_storage = (char *) &safe_buf; + MYSQLND_NET *net = &conn->net; + size_t old_chunk_size = net->stream->chunk_size; + size_t ret, left = count, header_len = MYSQLND_HEADER_SIZE + 1, packets_sent = 1; + const zend_uchar *p = (zend_uchar *) buf; + zend_bool command_sent = FALSE; + int corked = 1; + + DBG_ENTER("mysqlnd_stream_write_w_command"); + + net->stream->chunk_size = MYSQLND_MAX_PACKET_SIZE; + + setsockopt(((php_netstream_data_t*)net->stream->abstract)->socket, + IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked)); + + int1store(safe_storage + MYSQLND_HEADER_SIZE, command); + while (left > MYSQLND_MAX_PACKET_SIZE) { + size_t body_size = MYSQLND_MAX_PACKET_SIZE; + + int3store(safe_storage, MYSQLND_MAX_PACKET_SIZE); + int1store(safe_storage + 3, net->packet_no); + net->packet_no++; + + ret = php_stream_write(net->stream, (char *)safe_storage, header_len); + if (command_sent == FALSE) { + --header_len; + /* Sent one byte less*/ + --body_size; + command_sent = TRUE; + } + + ret = php_stream_write(net->stream, (char *)p, body_size); + + p += body_size; + left -= body_size; + + packets_sent++; + } + /* Even for zero size payload we have to send a packet */ + int3store(safe_storage, header_len == MYSQLND_HEADER_SIZE? left:left+1); + int1store(safe_storage + 3, net->packet_no); + net->packet_no++; + + ret = php_stream_write(net->stream, (char *)safe_storage, header_len); + + if (left) { + ret = php_stream_write(net->stream, (char *)p, left); + } + corked = 0; + setsockopt(((php_netstream_data_t*)net->stream->abstract)->socket, + IPPROTO_TCP, TCP_CORK, &corked, sizeof(corked)); + + 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); + + net->stream->chunk_size = old_chunk_size; + + DBG_RETURN(ret); +} +#endif +/* }}} */ + + +/* {{{ mysqlnd_read_header */ +static enum_func_status +mysqlnd_read_header(MYSQLND *conn, mysqlnd_packet_header *header TSRMLS_DC) +{ + MYSQLND_NET *net = &conn->net; + char buffer[MYSQLND_HEADER_SIZE]; + char *p = buffer; + int to_read = MYSQLND_HEADER_SIZE, ret; + + DBG_ENTER(mysqlnd_read_header_name); + + do { + if (!(ret= php_stream_read(net->stream, p, to_read))) { + DBG_ERR_FMT("Error while reading header from socket"); + return FAIL; + } + p += ret; + to_read -= ret; + } while (to_read); + + header->size = uint3korr(buffer); + header->packet_no = uint1korr(buffer + 3); + + MYSQLND_INC_CONN_STATISTIC_W_VALUE3(&conn->stats, + STAT_BYTES_RECEIVED, MYSQLND_HEADER_SIZE, + STAT_PROTOCOL_OVERHEAD_IN, MYSQLND_HEADER_SIZE, + STAT_PACKETS_RECEIVED, 1); + + if (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++; +#ifdef MYSQLND_DUMP_HEADER_N_BODY + DBG_ERR_FMT("HEADER: packet_no=%d size=%3d", header->packet_no, header->size); +#endif + DBG_RETURN(PASS); + } + +#if !MYSQLND_SILENT + DBG_ERR_FMT("Packets out of order. Expected %d received %d. Packet size=%d", + net->packet_no, header->packet_no, header->size); +#endif + php_error(E_WARNING, "Packets out of order. Expected %d received %d. Packet size=%d. PID=%d", + net->packet_no, header->packet_no, header->size, getpid()); + DBG_RETURN(FAIL); +} +/* }}} */ + + +/* {{{ mysqlnd_read_body */ +static +size_t mysqlnd_read_body(MYSQLND *conn, zend_uchar *buf, size_t size TSRMLS_DC) +{ + size_t ret; + char *p = (char *)buf; + int iter = 0; + MYSQLND_NET *net = &conn->net; + size_t old_chunk_size = net->stream->chunk_size; + + DBG_ENTER(mysqlnd_read_body_name); + DBG_INF_FMT("chunk_size=%d", net->stream->chunk_size); + + net->stream->chunk_size = MIN(size, conn->options.net_read_buffer_size); + do { + size -= (ret = php_stream_read(net->stream, p, size)); + if (size || iter++) { + DBG_INF_FMT("read=%d buf=%p p=%p chunk_size=%d left=%d", + ret, buf, p , net->stream->chunk_size, size); + } + p += ret; + } while (size > 0); + + MYSQLND_INC_CONN_STATISTIC_W_VALUE(&conn->stats, STAT_BYTES_RECEIVED, p - (char*)buf); + net->stream->chunk_size = old_chunk_size; + +#ifdef MYSQLND_DUMP_HEADER_N_BODY_FULL + { + int i; + DBG_INF_FMT("BODY: requested=%d last_read=%3d", p - (char*)buf, ret); + for (i = 0 ; i < p - (char*)buf; i++) { + if (i && (i % 30 == 0)) { + printf("\n\t\t"); + } + printf("[%c] ", *(char *)(&(buf[i]))); + } + for (i = 0 ; i < p - (char*)buf; i++) { + if (i && (i % 30 == 0)) { + printf("\n\t\t"); + } + printf("%.2X ", (int)*((char*)&(buf[i]))); + } + php_printf("\n\t\t\t-=-=-=-=-\n"); + } +#endif + + DBG_RETURN(p - (char*)buf); +} +/* }}} */ + + +/* {{{ php_mysqlnd_greet_read */ +static enum_func_status +php_mysqlnd_greet_read(void *_packet, MYSQLND *conn TSRMLS_DC) +{ + zend_uchar buf[512]; + zend_uchar *p= buf; + zend_uchar *begin = buf; + php_mysql_packet_greet *packet= (php_mysql_packet_greet *) _packet; + + DBG_ENTER("php_mysqlnd_greet_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, sizeof(buf), "greeting"); + + packet->protocol_version = uint1korr(p); + p++; + + if (packet->protocol_version == 0xFF) { + 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 = pestrdup((char *)p, conn->persistent); + p+= strlen(packet->server_version) + 1; /* eat the '\0' */ + + packet->thread_id = uint4korr(p); + p+=4; + + memcpy(packet->scramble_buf, p, SCRAMBLE_LENGTH_323); + p+= 8; + + /* pad1 */ + p++; + + packet->server_capabilities = uint2korr(p); + p+= 2; + + packet->charset_no = uint1korr(p); + p++; + + packet->server_status = uint2korr(p); + p+= 2; + + /* pad2 */ + p+= 13; + + if (p - buf < packet->header.size) { + /* scramble_buf is split into two parts */ + memcpy(packet->scramble_buf + SCRAMBLE_LENGTH_323, + p, SCRAMBLE_LENGTH - SCRAMBLE_LENGTH_323); + } else { + packet->pre41 = TRUE; + } + if (p - begin > packet->header.size) { + 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 %d bytes shorter than expected. PID=%d", + p - begin - packet->header.size, getpid()); + } + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ php_mysqlnd_greet_free_mem */ +static +void php_mysqlnd_greet_free_mem(void *_packet, zend_bool alloca TSRMLS_DC) +{ + php_mysql_packet_greet *p= (php_mysql_packet_greet *) _packet; + if (p->server_version) { + mnd_efree(p->server_version); + p->server_version = NULL; + } + if (!alloca) { + mnd_efree(p); + } +} +/* }}} */ + + +#define MYSQLND_CAPABILITIES (CLIENT_LONG_PASSWORD | CLIENT_LONG_FLAG | CLIENT_TRANSACTIONS | \ + CLIENT_PROTOCOL_41 | CLIENT_SECURE_CONNECTION | \ + CLIENT_MULTI_RESULTS) + + +/* {{{ php_mysqlnd_crypt */ +static +void php_mysqlnd_crypt(zend_uchar *buffer, const zend_uchar *s1, const zend_uchar *s2, size_t len) +{ + const unsigned char *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) +{ + PHP_SHA1_CTX context; + unsigned char sha1[SHA1_MAX_LENGTH]; + unsigned char sha2[SHA1_MAX_LENGTH]; + + + /* Phase 1: hash password */ + PHP_SHA1Init(&context); + PHP_SHA1Update(&context, password, strlen((char *)password)); + PHP_SHA1Final(sha1, &context); + + /* Phase 2: hash sha1 */ + PHP_SHA1Init(&context); + PHP_SHA1Update(&context, (unsigned char*)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, (unsigned char*)sha2, SHA1_MAX_LENGTH); + PHP_SHA1Final(buffer, &context); + + /* let's crypt buffer now */ + php_mysqlnd_crypt(buffer, (const unsigned char *)buffer, (const unsigned char *)sha1, SHA1_MAX_LENGTH); +} +/* }}} */ + + +/* {{{ php_mysqlnd_auth_write */ +static +size_t php_mysqlnd_auth_write(void *_packet, MYSQLND *conn TSRMLS_DC) +{ + char buffer[1024]; + register char *p= buffer + MYSQLND_HEADER_SIZE; /* start after the header */ + int len; + register php_mysql_packet_auth *packet= (php_mysql_packet_auth *) _packet; + + DBG_ENTER("php_mysqlnd_auth_write"); + + packet->client_flags |= MYSQLND_CAPABILITIES; + + if (packet->db) { + packet->client_flags |= CLIENT_CONNECT_WITH_DB; + } + + if (PG(open_basedir) && strlen(PG(open_basedir))) { + packet->client_flags ^= CLIENT_LOCAL_FILES; + } + + /* don't allow multi_queries via connect parameter */ + packet->client_flags ^= CLIENT_MULTI_STATEMENTS; + 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; + + len= strlen(packet->user); + strncpy(p, packet->user, len); + p+= len; + *p++ = '\0'; + + /* copy scrambled pass*/ + if (packet->password && packet->password[0]) { + /* In 4.1 we use CLIENT_SECURE_CONNECTION and thus the len of the buf should be passed */ + int1store(p, 20); + p++; + php_mysqlnd_scramble((unsigned char*)p, packet->server_scramble_buf, + (unsigned char *)packet->password); + p+= 20; + } else { + /* Zero length */ + int1store(p, 0); + p++; + } + + if (packet->db) { + memcpy(p, packet->db, packet->db_len); + p+= packet->db_len; + *p++= '\0'; + } + /* Handle CLIENT_CONNECT_WITH_DB */ + /* no \0 for no DB */ + + DBG_RETURN(mysqlnd_stream_write_w_header(conn, buffer, p - buffer - MYSQLND_HEADER_SIZE TSRMLS_CC)); +} +/* }}} */ + +/* {{{ php_mysqlnd_auth_free_mem */ +static +void php_mysqlnd_auth_free_mem(void *_packet, zend_bool alloca TSRMLS_DC) +{ + if (!alloca) { + mnd_efree((php_mysql_packet_auth *) _packet); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_ok_read */ +static enum_func_status +php_mysqlnd_ok_read(void *_packet, MYSQLND *conn TSRMLS_DC) +{ + zend_uchar buf[1024]; + zend_uchar *p = buf; + zend_uchar *begin = buf; + int i; + register php_mysql_packet_ok *packet= (php_mysql_packet_ok *) _packet; + + DBG_ENTER("php_mysqlnd_ok_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, sizeof(buf), "OK"); + + /* Should be always 0x0 or 0xFF for error */ + packet->field_count= uint1korr(p); + p++; + + if (0xFF == 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); + packet->last_insert_id = php_mysqlnd_net_field_length_ll(&p); + + packet->server_status = uint2korr(p); + p+= 2; + + packet->warning_count = uint2korr(p); + p+= 2; + + /* There is a message */ + if (packet->header.size > p - buf && (i = php_mysqlnd_net_field_length(&p))) { + packet->message = pestrndup((char *)p, MIN(i, sizeof(buf) - (p - buf)), conn->persistent); + packet->message_len = i; + } else { + packet->message = NULL; + } + + DBG_INF_FMT("OK packet: aff_rows=%lld last_ins_id=%ld server_status=%d warnings=%d", + packet->affected_rows, packet->last_insert_id, packet->server_status, + packet->warning_count); + + if (p - begin > packet->header.size) { + 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 %d bytes shorter than expected. PID=%d", + p - begin - packet->header.size, getpid()); + } + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ php_mysqlnd_ok_free_mem */ +static +void php_mysqlnd_ok_free_mem(void *_packet, zend_bool alloca TSRMLS_DC) +{ + php_mysql_packet_ok *p= (php_mysql_packet_ok *) _packet; + if (p->message) { + mnd_efree(p->message); + p->message = NULL; + } + if (!alloca) { + mnd_efree(p); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_eof_read */ +static enum_func_status +php_mysqlnd_eof_read(void *_packet, MYSQLND *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 + */ + php_mysql_packet_eof *packet= (php_mysql_packet_eof *) _packet; + zend_uchar buf[5 + 10 + sizeof(packet->sqlstate) + sizeof(packet->error)]; + zend_uchar *p= buf; + zend_uchar *begin = buf; + + DBG_ENTER("php_mysqlnd_eof_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, sizeof(buf), "EOF"); + + /* Should be always 0xFE */ + packet->field_count= uint1korr(p); + p++; + + if (0xFF == 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; + packet->server_status = uint2korr(p); + p+= 2; + } else { + packet->warning_count = 0; + packet->server_status = 0; + } + + if (p - begin > packet->header.size) { + 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 %d bytes shorter than expected. PID=%d", + p - begin - packet->header.size, getpid()); + } + + DBG_INF_FMT("EOF packet: status=%d warnings=%d", packet->server_status, packet->warning_count); + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ php_mysqlnd_eof_free_mem */ +static +void php_mysqlnd_eof_free_mem(void *_packet, zend_bool alloca TSRMLS_DC) +{ + if (!alloca) { + mnd_efree(_packet); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_cmd_write */ +size_t php_mysqlnd_cmd_write(void *_packet, MYSQLND *conn TSRMLS_DC) +{ + /* Let's have some space, which we can use, if not enough, we will allocate new buffer */ + php_mysql_packet_command *packet= (php_mysql_packet_command *) _packet; + MYSQLND_NET *net = &conn->net; + unsigned int error_reporting = EG(error_reporting); + size_t written; + + 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; + + if (error_reporting) { + EG(error_reporting) = 0; + } + +#ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND + php_mysqlnd_consume_uneaten_data(conn, packet->command TSRMLS_CC); +#endif + + if (!packet->argument || !packet->arg_len) { + char buffer[MYSQLND_HEADER_SIZE + 1]; + + int1store(buffer + MYSQLND_HEADER_SIZE, packet->command); + written = mysqlnd_stream_write_w_header(conn, buffer, 1 TSRMLS_CC); + } else { +#if USE_CORK && defined(TCP_CORK) + written = mysqlnd_stream_write_w_command(conn, packet->command, packet->argument, + packet->arg_len TSRMLS_CC)); +#else + size_t tmp_len = packet->arg_len + 1 + MYSQLND_HEADER_SIZE, ret; + zend_uchar *tmp, *p; + tmp = (tmp_len > net->cmd_buffer.length)? mnd_emalloc(tmp_len):net->cmd_buffer.buffer; + p = tmp + MYSQLND_HEADER_SIZE; /* skip the header */ + + int1store(p, packet->command); + p++; + + memcpy(p, packet->argument, packet->arg_len); + + ret = mysqlnd_stream_write_w_header(conn, (char *)tmp, tmp_len - MYSQLND_HEADER_SIZE TSRMLS_CC); + if (tmp != net->cmd_buffer.buffer) { + MYSQLND_INC_CONN_STATISTIC(&conn->stats, STAT_CMD_BUFFER_TOO_SMALL); + mnd_efree(tmp); + } + written = ret; +#endif + } + if (error_reporting) { + /* restore error reporting */ + EG(error_reporting) = error_reporting; + } + DBG_RETURN(written); +} +/* }}} */ + + +/* {{{ php_mysqlnd_cmd_free_mem */ +static +void php_mysqlnd_cmd_free_mem(void *_packet, zend_bool alloca TSRMLS_DC) +{ + if (!alloca) { + mnd_efree((php_mysql_packet_command *) _packet); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_rset_header_read */ +static enum_func_status +php_mysqlnd_rset_header_read(void *_packet, MYSQLND *conn TSRMLS_DC) +{ + zend_uchar buf[1024]; + zend_uchar *p = buf; + zend_uchar *begin = buf; + size_t len; + php_mysql_packet_rset_header *packet= (php_mysql_packet_rset_header *) _packet; + + DBG_ENTER("php_mysqlnd_rset_header_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, sizeof(buf), "resultset header"); + + /* + Don't increment. First byte is 0xFF on error, but otherwise is starting byte + of encoded sequence for length. + */ + if (*p == 0xFF) { + /* Error */ + p++; + 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); + switch (packet->field_count) { + case MYSQLND_NULL_LENGTH: + /* + First byte in the packet is the field count. + Thus, the name is size - 1. And we add 1 for a trailing \0. + */ + len = packet->header.size - 1; + packet->info_or_local_file = mnd_pemalloc(len + 1, conn->persistent); + memcpy(packet->info_or_local_file, p, len); + packet->info_or_local_file[len] = '\0'; + packet->info_or_local_file_len = len; + break; + case 0x00: + packet->affected_rows = php_mysqlnd_net_field_length_ll(&p); + packet->last_insert_id= php_mysqlnd_net_field_length_ll(&p); + packet->server_status = uint2korr(p); + p+=2; + packet->warning_count = uint2korr(p); + p+=2; + /* Check for additional textual data */ + if (packet->header.size > (p - buf) && (len = php_mysqlnd_net_field_length(&p))) { + packet->info_or_local_file = mnd_pemalloc(len + 1, conn->persistent); + memcpy(packet->info_or_local_file, p, len); + packet->info_or_local_file[len] = '\0'; + packet->info_or_local_file_len = len; + } + break; + default: + /* Result set */ + break; + } + if (p - begin > packet->header.size) { + 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 %d bytes shorter than expected. PID=%d", + p - begin - packet->header.size, getpid()); + } + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ php_mysqlnd_rset_header_free_mem */ +static +void php_mysqlnd_rset_header_free_mem(void *_packet, zend_bool alloca TSRMLS_DC) +{ + php_mysql_packet_rset_header *p= (php_mysql_packet_rset_header *) _packet; + if (p->info_or_local_file) { + mnd_efree(p->info_or_local_file); + p->info_or_local_file = NULL; + } + if (!alloca) { + mnd_efree(p); + } +} +/* }}} */ + +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 TSRMLS_DC) +{ + /* Should be enough for the metadata of a single row */ + php_mysql_packet_res_field *packet= (php_mysql_packet_res_field *) _packet; + zend_uchar *buf = (zend_uchar *) conn->net.cmd_buffer.buffer; + zend_uchar *p = buf; + zend_uchar *begin = buf; + char *root_ptr; + size_t buf_len = conn->net.cmd_buffer.length, len, total_len = 0; + 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"); + + if (packet->skip_parsing) { + DBG_RETURN(PASS); + } + if (*p == 0xFE && 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); + switch ((len)) { + case 0: + *(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_fake; + default: + *(char **)(((char *)meta) + rset_field_offsets[i]) = (char *)p; + *(unsigned int *)(((char*)meta) + rset_field_offsets[i+1]) = len; + p += len; + total_len += len + 1; + break; + } + } + + /* 1 byte filler */ + p++; + + meta->charsetnr = uint2korr(p); + p += 2; + + meta->length = uint4korr(p); + p += 4; + + meta->type = uint1korr(p); + p += 1; + + meta->flags = uint2korr(p); + p += 2; + + meta->decimals = uint2korr(p); + p += 1; + + /* 2 byte filler */ + p +=2; + + /* 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 > (p - buf) && + (len = php_mysqlnd_net_field_length(&p)) && + len != MYSQLND_NULL_LENGTH) + { + DBG_INF_FMT("Def found, length %lu", len); + meta->def = mnd_emalloc(len + 1); + memcpy(meta->def, p, len); + meta->def[len] = '\0'; + meta->def_length = len; + p += len; + } + + if (p - begin > packet->header.size) { + DBG_ERR_FMT("Result set field packet %d bytes shorter than expected", p - begin - packet->header.size); + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Result set field packet %d bytes " + "shorter than expected. PID=%d", p - begin - packet->header.size, getpid()); + } + + root_ptr = meta->root = mnd_emalloc(total_len); + 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'; + } + + 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_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); +} +/* }}} */ + + +/* {{{ php_mysqlnd_rset_field_free_mem */ +static +void php_mysqlnd_rset_field_free_mem(void *_packet, zend_bool alloca TSRMLS_DC) +{ + php_mysql_packet_res_field *p= (php_mysql_packet_res_field *) _packet; + + /* p->metadata was passed to us as temporal buffer */ + if (!alloca) { + mnd_efree(p); + } +} +/* }}} */ + + +static enum_func_status +php_mysqlnd_read_row_ex(MYSQLND *conn, zend_uchar **buf, int buf_size, + 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 *new_buf = NULL, *p = *buf; + 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 , &header TSRMLS_CC)) { + ret = FAIL; + break; + } + + *data_size += header.size; + + if (first_iteration && header.size > buf_size) { + 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. + */ + p = new_buf = mnd_pemalloc(*data_size + 1, persistent_alloc); + } 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. + */ + new_buf = mnd_perealloc(new_buf, *data_size + 1, persistent_alloc); + /* The position could have changed, recalculate */ + p = new_buf + (*data_size - header.size); + } + + if (!mysqlnd_read_body(conn, p, header.size TSRMLS_CC)) { + DBG_ERR("Empty row packet body"); + php_error(E_WARNING, "Empty row packet body. PID=%d", getpid()); + ret = FAIL; + break; + } + + if (header.size < MYSQLND_MAX_PACKET_SIZE) { + break; + } + } + if (ret == PASS && new_buf) { + *buf = new_buf; + } + *data_size -= prealloc_more_bytes; + DBG_RETURN(ret); +} + + +/* {{{ php_mysqlnd_rowp_read_binary_protocol */ +static +void php_mysqlnd_rowp_read_binary_protocol(php_mysql_packet_row *packet, MYSQLND *conn, + zend_uchar *p, size_t data_size TSRMLS_DC) +{ + int i; + zend_uchar *null_ptr, bit; + zval **current_field, **end_field, **start_field; + zend_bool as_unicode = conn->options.numeric_and_datetime_as_unicode; + zend_bool allocated; + void *obj; + + DBG_ENTER("php_mysqlnd_rowp_read_binary_protocol"); + + end_field = (current_field = start_field = packet->fields) + packet->field_count; + + + /* skip the first byte, not 0xFE -> 0x0, status */ + p++; + null_ptr= p; + p += (packet->field_count + 9)/8; /* skip null bits */ + bit = 4; /* first 2 bits are reserved */ + + for (i = 0; current_field < end_field; current_field++, i++) { +#if 1 + obj = mysqlnd_palloc_get_zval(conn->zval_cache, &allocated TSRMLS_CC); + if (allocated) { + *current_field = (zval *) obj; + } else { + /* It's from the cache, so we can upcast here */ + *current_field = &((mysqlnd_zval *) obj)->zv; + ((mysqlnd_zval *) obj)->point_type = MYSQLND_POINTS_EXT_BUFFER; + } +#else + MAKE_STD_ZVAL(*current_field); +#endif + if (*null_ptr & bit) { + ZVAL_NULL(*current_field); + } else { + enum_mysqlnd_field_types type = packet->fields_metadata[i].type; + mysqlnd_ps_fetch_functions[type].func(*current_field, &packet->fields_metadata[i], + 0, &p, as_unicode TSRMLS_CC); + } + if (!((bit<<=1) & 255)) { + bit= 1; /* To next byte */ + null_ptr++; + } + } + /* Normal queries: The buffer has one more byte at the end, because we need it */ + packet->row_buffer[data_size] = '\0'; + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ php_mysqlnd_rowp_read_text_protocol */ +static +void php_mysqlnd_rowp_read_text_protocol(php_mysql_packet_row *packet, MYSQLND *conn, + zend_uchar *p, size_t data_size TSRMLS_DC) +{ + int i; + zend_bool last_field_was_string; + zval **current_field, **end_field, **start_field; + zend_uchar *bit_area = packet->row_buffer + data_size + 1; /* we allocate from here */ + zend_bool as_unicode = conn->options.numeric_and_datetime_as_unicode; +#ifdef MYSQLND_STRING_TO_INT_CONVERSION + zend_bool as_int = conn->options.int_and_year_as_int; +#endif + + DBG_ENTER("php_mysqlnd_rowp_read_text_protocol"); + + end_field = (current_field = start_field = packet->fields) + packet->field_count; + for (i = 0; current_field < end_field; current_field++, i++) { + /* Don't reverse the order. It is significant!*/ + void *obj; + zend_bool allocated; + 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); + + obj = mysqlnd_palloc_get_zval(conn->zval_cache, &allocated TSRMLS_CC); + if (allocated) { + *current_field = (zval *) obj; + } else { + /* It's from the cache, so we can upcast here */ + *current_field = &((mysqlnd_zval *) obj)->zv; + ((mysqlnd_zval *) obj)->point_type = MYSQLND_POINTS_FREE; + } + + 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 PHP_MAJOR_VERSION >= 6 || defined(MYSQLND_STRING_TO_INT_CONVERSION) + struct st_mysqlnd_perm_bind perm_bind = + mysqlnd_ps_fetch_functions[packet->fields_metadata[i].type]; +#endif + +#ifdef MYSQLND_STRING_TO_INT_CONVERSION + if (as_int && perm_bind.php_type == IS_LONG && + perm_bind.pack_len <= SIZEOF_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 */ + my_int64 v = atoll((char *) p); + ZVAL_LONG(*current_field, v); + } else { + my_uint64 v = (my_uint64) atoll((char *) p); + zend_bool uns = packet->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) < (my_int64) v) || + (L64(-2147483648) > (my_int64) v)))) +#endif /* SIZEOF */ + { + ZVAL_STRINGL(*current_field, (char *)p, len, 0); + } else { + ZVAL_LONG(*current_field, (my_int64)v); + } + } + *(p + len) = save; + } else +#endif + if (packet->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, &(packet->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, MYSQLND_LLU_SPEC, + (my_int64) Z_LVAL_PP(current_field)); +#if PHP_MAJOR_VERSION >= 6 + 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); + } + if (allocated == FALSE) { + ((mysqlnd_zval *) obj)->point_type = MYSQLND_POINTS_INT_BUFFER; + } + } 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 PHP_MAJOR_VERSION >= 6 + 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); + } + if (allocated == FALSE) { + ((mysqlnd_zval *) obj)->point_type = MYSQLND_POINTS_INT_BUFFER; + } + } + /* + IS_UNICODE should not be specially handled. In unicode mode + the buffers are not referenced - everything is copied. + */ + } else +#if PHP_MAJOR_VERSION < 6 + { + ZVAL_STRINGL(*current_field, (char *)p, len, 0); + if (allocated == FALSE) { + ((mysqlnd_zval *) obj)->point_type = MYSQLND_POINTS_INT_BUFFER; + } + } +#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. + + I suppose we can use UG(unicode) in mysqlnd.c/mysqlnd_palloc.c when + freeing a result set + to check if we need to call copy_ctor(). + + 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 && + packet->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); + } + if (allocated == FALSE) { + /* + The zval cache will check and see that the type is IS_STRING. + In this case it will call copy_ctor(). This is valid when + allocated == TRUE . In this case we can't upcast. Thus for non-PS + point_type doesn't matter much, as the valuable information is + in the type of result set. Still good to set it. + */ + if (Z_TYPE_P(*current_field) == IS_STRING) { + ((mysqlnd_zval *) obj)->point_type = MYSQLND_POINTS_INT_BUFFER; + } else { + ((mysqlnd_zval *) obj)->point_type = MYSQLND_POINTS_EXT_BUFFER; + } + } +#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 */ + packet->row_buffer[data_size] = '\0'; + } + + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ 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 TSRMLS_DC) +{ + MYSQLND_NET *net = &conn->net; + zend_uchar *p; + enum_func_status ret = PASS; + size_t data_size = 0; + size_t old_chunk_size = net->stream->chunk_size; + php_mysql_packet_row *packet= (php_mysql_packet_row *) _packet; + size_t post_alloc_for_bit_fields = 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->row_buffer, 0, &data_size, + packet->persistent_alloc, post_alloc_for_bit_fields + TSRMLS_CC); + if (FAIL == ret) { + goto end; + } + + /* packet->row_buffer is of size 'data_size + 1' */ + packet->header.size = data_size; + + if ((*(p = packet->row_buffer)) == 0xFF) { + /* + 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 (*p == 0xFE && 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 */ + } + } 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_pemalloc(packet->field_count * sizeof(zval *), + packet->persistent_alloc); + } + + if (packet->binary_protocol) { + php_mysqlnd_rowp_read_binary_protocol(packet, conn, p, data_size TSRMLS_CC); + } else { + php_mysqlnd_rowp_read_text_protocol(packet, conn, p, data_size TSRMLS_CC); + } + } 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 alloca TSRMLS_DC) +{ + php_mysql_packet_row *p= (php_mysql_packet_row *) _packet; + if (p->row_buffer) { + mnd_pefree(p->row_buffer, p->persistent_alloc); + p->row_buffer = NULL; + } + /* + 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 (!alloca) { + mnd_efree(p); + } +} +/* }}} */ + + + +/* {{{ php_mysqlnd_stats_read */ +static enum_func_status +php_mysqlnd_stats_read(void *_packet, MYSQLND *conn TSRMLS_DC) +{ + zend_uchar buf[1024]; + php_mysql_packet_stats *packet= (php_mysql_packet_stats *) _packet; + + DBG_ENTER("php_mysqlnd_stats_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, sizeof(buf), "statistics"); + + packet->message = mnd_pemalloc(packet->header.size + 1, conn->persistent); + 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 alloca TSRMLS_DC) +{ + php_mysql_packet_stats *p= (php_mysql_packet_stats *) _packet; + if (p->message) { + mnd_efree(p->message); + p->message = NULL; + } + if (!alloca) { + mnd_efree(p); + } +} +/* }}} */ + + +/* 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 TSRMLS_DC) +{ + /* In case of an error, we should have place to put it */ + zend_uchar buf[1024]; + zend_uchar *p = buf; + zend_uchar *begin = buf; + unsigned int data_size; + php_mysql_packet_prepare_response *packet= (php_mysql_packet_prepare_response *) _packet; + + DBG_ENTER("php_mysqlnd_prepare_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, sizeof(buf), "prepare"); + + data_size = packet->header.size; + packet->error_code = uint1korr(p); + p++; + + if (0xFF == 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 %d", data_size); + php_error(E_WARNING, "Wrong COM_STMT_PREPARE response size. Received %d. PID=%d", data_size, getpid()); + DBG_RETURN(FAIL); + } + + packet->stmt_id = uint4korr(p); + p += 4; + + /* Number of columns in result set */ + packet->field_count = uint2korr(p); + p += 2; + + packet->param_count = uint2korr(p); + p += 2; + + if (data_size > 9) { + /* 0x0 filler sent by the server for 5.0+ clients */ + p++; + + packet->warning_count = uint2korr(p); + } + + DBG_INF_FMT("Prepare packet read: stmt_id=%d fields=%d params=%d", + packet->stmt_id, packet->field_count, packet->param_count); + + if (p - begin > packet->header.size) { + 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 %d bytes shorter than expected. PID=%d", + p - begin - packet->header.size, getpid()); + } + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ php_mysqlnd_prepare_free_mem */ +static +void php_mysqlnd_prepare_free_mem(void *_packet, zend_bool alloca TSRMLS_DC) +{ + php_mysql_packet_prepare_response *p= (php_mysql_packet_prepare_response *) _packet; + if (!alloca) { + mnd_efree(p); + } +} +/* }}} */ + + +/* {{{ php_mysqlnd_chg_user_read */ +static enum_func_status +php_mysqlnd_chg_user_read(void *_packet, MYSQLND *conn TSRMLS_DC) +{ + /* There could be an error message */ + zend_uchar buf[1024]; + zend_uchar *p = buf; + zend_uchar *begin = buf; + php_mysql_packet_chg_user_resp *packet= (php_mysql_packet_chg_user_resp *) _packet; + + DBG_ENTER("php_mysqlnd_chg_user_read"); + + PACKET_READ_HEADER_AND_BODY(packet, conn, buf, sizeof(buf), "change user response "); + + /* + Don't increment. First byte is 0xFF on error, but otherwise is starting byte + of encoded sequence for length. + */ + + /* Should be always 0x0 or 0xFF for error */ + packet->field_count= uint1korr(p); + p++; + + if (packet->header.size == 1 && buf[0] == 0xFE && + packet->server_capabilities & CLIENT_SECURE_CONNECTION) { + /* We don't handle 3.23 authentication */ + DBG_RETURN(FAIL); + } + + if (0xFF == packet->field_count) { + 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); + } + if (p - begin > packet->header.size) { + 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 %d bytes shorter than expected. PID=%d", + p - begin - packet->header.size, getpid()); + } + + DBG_RETURN(PASS); +} +/* }}} */ + + +/* {{{ php_mysqlnd_chg_user_free_mem */ +static +void php_mysqlnd_chg_user_free_mem(void *_packet, zend_bool alloca TSRMLS_DC) +{ + if (!alloca) { + mnd_efree(_packet); + } +} +/* }}} */ + + +/* {{{ packet_methods + */ +mysqlnd_packet_methods packet_methods[PROT_LAST] = +{ + { + sizeof(php_mysql_packet_greet), + php_mysqlnd_greet_read, + NULL, /* write */ + php_mysqlnd_greet_free_mem, + }, /* PROT_GREET_PACKET */ + { + sizeof(php_mysql_packet_auth), + NULL, /* read */ + php_mysqlnd_auth_write, + php_mysqlnd_auth_free_mem, + }, /* PROT_AUTH_PACKET */ + { + sizeof(php_mysql_packet_ok), + php_mysqlnd_ok_read, /* read */ + NULL, /* write */ + php_mysqlnd_ok_free_mem, + }, /* PROT_OK_PACKET */ + { + sizeof(php_mysql_packet_eof), + php_mysqlnd_eof_read, /* read */ + NULL, /* write */ + php_mysqlnd_eof_free_mem, + }, /* PROT_EOF_PACKET */ + { + sizeof(php_mysql_packet_command), + NULL, /* read */ + php_mysqlnd_cmd_write, /* write */ + php_mysqlnd_cmd_free_mem, + }, /* PROT_CMD_PACKET */ + { + sizeof(php_mysql_packet_rset_header), + php_mysqlnd_rset_header_read, /* read */ + NULL, /* write */ + php_mysqlnd_rset_header_free_mem, + }, /* PROT_RSET_HEADER_PACKET */ + { + sizeof(php_mysql_packet_res_field), + php_mysqlnd_rset_field_read, /* read */ + NULL, /* write */ + php_mysqlnd_rset_field_free_mem, + }, /* PROT_RSET_FLD_PACKET */ + { + sizeof(php_mysql_packet_row), + php_mysqlnd_rowp_read, /* read */ + NULL, /* write */ + php_mysqlnd_rowp_free_mem, + }, /* PROT_ROW_PACKET */ + { + sizeof(php_mysql_packet_stats), + php_mysqlnd_stats_read, /* read */ + NULL, /* write */ + php_mysqlnd_stats_free_mem, + }, /* PROT_STATS_PACKET */ + { + sizeof(php_mysql_packet_prepare_response), + php_mysqlnd_prepare_read, /* read */ + NULL, /* write */ + php_mysqlnd_prepare_free_mem, + }, /* PROT_PREPARE_RESP_PACKET */ + { + sizeof(php_mysql_packet_chg_user_resp), + php_mysqlnd_chg_user_read, /* read */ + NULL, /* write */ + php_mysqlnd_chg_user_free_mem, + } /* PROT_CHG_USER_PACKET */ +}; +/* }}} */ + + +/* + * 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 0000000000..96da8d16a8 --- /dev/null +++ b/ext/mysqlnd/mysqlnd_wireprotocol.h @@ -0,0 +1,335 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 2006-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@mysql.com> | + | Andrey Hristov <andrey@mysql.com> | + | Ulf Wendel <uwendel@mysql.com> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef MYSQLND_WIREPROTOCOL_H +#define MYSQLND_WIREPROTOCOL_H + +#define MYSQLND_HEADER_SIZE 4 + +#define MYSQLND_NULL_LENGTH (unsigned long) ~0 + +typedef zend_uchar mysqlnd_1b; +typedef zend_ushort mysqlnd_2b; +typedef zend_uint mysqlnd_4b; + +/* Used in mysqlnd_debug.c */ +extern char * mysqlnd_read_header_name; +extern char * mysqlnd_read_body_name; + + +/* Packet handling */ +#define PACKET_INIT(packet, enum_type, c_type) \ + { \ + packet = (c_type) ecalloc(1, packet_methods[enum_type].struct_size); \ + ((c_type) (packet))->header.m = &packet_methods[enum_type]; \ + } +#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 { \ + ((packet)->header.m->free_mem((packet), FALSE TSRMLS_CC)); \ + } while (0); + +#define PACKET_INIT_ALLOCA(packet, enum_type) \ + { \ + memset(&(packet), 0, packet_methods[enum_type].struct_size); \ + (packet).header.m = &packet_methods[enum_type]; \ + } +#define PACKET_WRITE_ALLOCA(packet, conn) PACKET_WRITE(&(packet), (conn)) +#define PACKET_READ_ALLOCA(packet, conn) PACKET_READ(&(packet), (conn)) +#define PACKET_FREE_ALLOCA(packet) (packet.header.m->free_mem(&(packet), TRUE TSRMLS_CC)) + +/* Enums */ +enum php_mysql_packet_type +{ + PROT_GREET_PACKET= 0, + PROT_AUTH_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_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 +}; + +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 TSRMLS_DC); + size_t (*write_to_net)(void *packet, MYSQLND *conn TSRMLS_DC); + void (*free_mem)(void *packet, zend_bool alloca TSRMLS_DC); +} mysqlnd_packet_methods; + +extern mysqlnd_packet_methods packet_methods[]; + + +typedef struct st_mysqlnd_packet_header { + size_t size; + zend_uchar packet_no; + mysqlnd_packet_methods *m; +} mysqlnd_packet_header; + +/* Server greets the client */ +typedef struct st_php_mysql_packet_greet { + mysqlnd_packet_header header; + mysqlnd_1b protocol_version; + char *server_version; + mysqlnd_4b thread_id; + zend_uchar scramble_buf[SCRAMBLE_LENGTH]; + /* 1 byte pad */ + mysqlnd_2b server_capabilities; + mysqlnd_1b charset_no; + mysqlnd_2b server_status; + /* 13 byte pad*/ + 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; +} php_mysql_packet_greet; + + +/* Client authenticates */ +typedef struct st_php_mysql_packet_auth { + mysqlnd_packet_header header; + mysqlnd_4b client_flags; + uint32 max_packet_size; + mysqlnd_1b charset_no; + /* 23 byte pad */ + char *user; + /* 8 byte scramble */ + char *db; + /* 12 byte scramble */ + + /* Here the packet ends. This is user supplied data */ + char *password; + /* +1 for \0 because of scramble() */ + unsigned char *server_scramble_buf; + size_t db_len; +} php_mysql_packet_auth; + +/* OK packet */ +typedef struct st_php_mysql_packet_ok { + mysqlnd_packet_header header; + mysqlnd_1b field_count; /* always 0x0 */ + mynd_ulonglong affected_rows; + mynd_ulonglong last_insert_id; + mysqlnd_2b server_status; + mysqlnd_2b 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; +} php_mysql_packet_ok; + + +/* Command packet */ +typedef struct st_php_mysql_packet_command { + mysqlnd_packet_header header; + enum php_mysqlnd_server_command command; + const char *argument; + size_t arg_len; +} php_mysql_packet_command; + + +/* EOF packet */ +typedef struct st_php_mysql_packet_eof { + mysqlnd_packet_header header; + mysqlnd_1b field_count; /* 0xFE */ + mysqlnd_2b warning_count; + mysqlnd_2b server_status; + /* If error packet, we use these */ + char error[MYSQLND_ERRMSG_SIZE+1]; + char sqlstate[MYSQLND_SQLSTATE_LENGTH + 1]; + unsigned int error_no; +} php_mysql_packet_eof; +/* EOF packet */ + + +/* Result Set header*/ +typedef struct st_php_mysql_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. + */ + mysqlnd_2b warning_count; + mysqlnd_2b server_status; + mynd_ulonglong affected_rows; + mynd_ulonglong 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; +} php_mysql_packet_rset_header; + + +/* Result set field packet */ +typedef struct st_php_mysql_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; +} php_mysql_packet_res_field; + + +/* Row packet */ +struct st_php_mysql_packet_row { + mysqlnd_packet_header header; + zval **fields; + mysqlnd_4b field_count; + zend_bool eof; + /* + These are, of course, only for SELECT in the EOF packet, + which is detected by this packet + */ + mysqlnd_2b warning_count; + mysqlnd_2b server_status; + + zend_uchar *row_buffer; + + 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; +}; + + +/* Statistics packet */ +typedef struct st_php_mysql_packet_stats { + mysqlnd_packet_header header; + char *message; + /* message_len is not part of the packet*/ + size_t message_len; +} php_mysql_packet_stats; + + +/* COM_PREPARE response packet */ +typedef struct st_php_mysql_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; +} php_mysql_packet_prepare_response; + + +/* Statistics packet */ +typedef struct st_php_mysql_packet_chg_user_resp { + mysqlnd_packet_header header; + mysqlnd_4b field_count; + + /* message_len is not part of the packet*/ + mysqlnd_2b server_capabilities; + /* If error packet, we use these */ + mysqlnd_error_info error_info; +} php_mysql_packet_chg_user_resp; + + +size_t mysqlnd_stream_write(MYSQLND * const conn, char * const buf, size_t count TSRMLS_DC); +size_t mysqlnd_stream_write_w_header(MYSQLND * const conn, char * const buf, size_t count TSRMLS_DC); + +#ifdef MYSQLND_DO_WIRE_CHECK_BEFORE_COMMAND +size_t php_mysqlnd_consume_uneaten_data(MYSQLND * const conn, enum php_mysqlnd_server_command cmd TSRMLS_DC); +#endif + +void php_mysqlnd_scramble(zend_uchar * const buffer, const zend_uchar * const scramble, const zend_uchar * const pass); + +unsigned long php_mysqlnd_net_field_length(zend_uchar **packet); +zend_uchar * php_mysqlnd_net_store_length(zend_uchar *packet, mynd_ulonglong length); + +extern char * const mysqlnd_empty_string; + +#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.h b/ext/mysqlnd/php_mysqlnd.h new file mode 100644 index 0000000000..0958fcae1f --- /dev/null +++ b/ext/mysqlnd/php_mysqlnd.h @@ -0,0 +1,29 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 6 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2007 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: Georg Richter <georg@php.net> | + | Andrey Hristov <andrey@php.net> | + | Ulf Wendel <uw@php.net> | + +----------------------------------------------------------------------+ + + $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 */ |