diff options
author | Andrey Hristov <andrey@php.net> | 2007-10-05 21:23:56 +0000 |
---|---|---|
committer | Andrey Hristov <andrey@php.net> | 2007-10-05 21:23:56 +0000 |
commit | 8b9b553aa264d3c0afe611f49499e787004983c7 (patch) | |
tree | 840ea2b1127ec97a63f36ffda689a1a1fe5fb975 /ext/mysqlnd | |
parent | 9f9495a48483aac9f6bc92ceea5841a8d243e2aa (diff) | |
download | php-git-8b9b553aa264d3c0afe611f49499e787004983c7.tar.gz |
Import mysqlnd
Patch ext/mysql and ext/mysqli to support mysqlnd
Diffstat (limited to 'ext/mysqlnd')
31 files changed, 14811 insertions, 0 deletions
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 */ |