diff options
44 files changed, 1810 insertions, 380 deletions
diff --git a/ext/mysql/php_mysql.c b/ext/mysql/php_mysql.c index 3d092b2d6a..ff4de0f5a8 100644 --- a/ext/mysql/php_mysql.c +++ b/ext/mysql/php_mysql.c @@ -877,7 +877,7 @@ static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) #ifndef MYSQL_USE_MYSQLND mysql->conn = mysql_init(NULL); #else - mysql->conn = mysql_init(persistent); + mysql->conn = mysqlnd_init(MYSQLND_CLIENT_KNOWS_RSET_COPY_DATA, persistent); #endif if (connect_timeout != -1) { @@ -886,7 +886,7 @@ static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) #ifndef MYSQL_USE_MYSQLND if (mysql_real_connect(mysql->conn, host, user, passwd, NULL, port, socket, client_flags)==NULL) #else - if (mysqlnd_connect(mysql->conn, host, user, passwd, passwd_len, NULL, 0, port, socket, client_flags TSRMLS_CC) == NULL) + if (mysqlnd_connect(mysql->conn, host, user, passwd, passwd_len, NULL, 0, port, socket, client_flags, MYSQLND_CLIENT_KNOWS_RSET_COPY_DATA TSRMLS_CC) == NULL) #endif { /* Populate connect error globals so that the error functions can read them */ @@ -934,7 +934,7 @@ static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) #ifndef MYSQL_USE_MYSQLND if (mysql_real_connect(mysql->conn, host, user, passwd, NULL, port, socket, client_flags)==NULL) #else - if (mysqlnd_connect(mysql->conn, host, user, passwd, passwd_len, NULL, 0, port, socket, client_flags TSRMLS_CC) == NULL) + if (mysqlnd_connect(mysql->conn, host, user, passwd, passwd_len, NULL, 0, port, socket, client_flags, MYSQLND_CLIENT_KNOWS_RSET_COPY_DATA TSRMLS_CC) == NULL) #endif { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Link to server lost, unable to reconnect"); @@ -996,7 +996,7 @@ static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) #ifndef MYSQL_USE_MYSQLND mysql->conn = mysql_init(NULL); #else - mysql->conn = mysql_init(persistent); + mysql->conn = mysqlnd_init(MYSQLND_CLIENT_KNOWS_RSET_COPY_DATA, persistent); #endif if (!mysql->conn) { MySG(connect_error) = estrdup("OOM"); @@ -1013,7 +1013,7 @@ static void php_mysql_do_connect(INTERNAL_FUNCTION_PARAMETERS, int persistent) #ifndef MYSQL_USE_MYSQLND if (mysql_real_connect(mysql->conn, host, user, passwd, NULL, port, socket, client_flags)==NULL) #else - if (mysqlnd_connect(mysql->conn, host, user, passwd, passwd_len, NULL, 0, port, socket, client_flags TSRMLS_CC) == NULL) + if (mysqlnd_connect(mysql->conn, host, user, passwd, passwd_len, NULL, 0, port, socket, client_flags, MYSQLND_CLIENT_KNOWS_RSET_COPY_DATA TSRMLS_CC) == NULL) #endif { /* Populate connect error globals so that the error functions can read them */ diff --git a/ext/mysqli/mysqli.c b/ext/mysqli/mysqli.c index 0cea68a33f..120c194964 100644 --- a/ext/mysqli/mysqli.c +++ b/ext/mysqli/mysqli.c @@ -721,6 +721,7 @@ PHP_MINIT_FUNCTION(mysqli) REGISTER_LONG_CONSTANT("MYSQLI_USE_RESULT", MYSQLI_USE_RESULT, CONST_CS | CONST_PERSISTENT); #if defined (MYSQLI_USE_MYSQLND) REGISTER_LONG_CONSTANT("MYSQLI_ASYNC", MYSQLI_ASYNC, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("MYSQLI_STORE_RESULT_COPY_DATA", MYSQLI_STORE_RESULT_COPY_DATA, CONST_CS | CONST_PERSISTENT); #endif /* for mysqli_fetch_assoc */ diff --git a/ext/mysqli/mysqli_api.c b/ext/mysqli/mysqli_api.c index 53639a0670..d14625e0f7 100644 --- a/ext/mysqli/mysqli_api.c +++ b/ext/mysqli/mysqli_api.c @@ -1477,7 +1477,7 @@ void php_mysqli_init(INTERNAL_FUNCTION_PARAMETERS) We create always persistent, as if the user want to connecto to p:somehost, we can't convert the handle then */ - if (!(mysql->mysql = mysql_init(TRUE))) + if (!(mysql->mysql = mysqlnd_init(MYSQLND_CLIENT_KNOWS_RSET_COPY_DATA, TRUE))) #endif { efree(mysql); @@ -2557,7 +2557,7 @@ PHP_FUNCTION(mysqli_stmt_sqlstate) } /* }}} */ -/* {{{ proto object mysqli_store_result(object link) +/* {{{ proto object mysqli_store_result(object link [, flags]) Buffer result set on client */ PHP_FUNCTION(mysqli_store_result) { @@ -2565,13 +2565,19 @@ PHP_FUNCTION(mysqli_store_result) MYSQL_RES *result; zval *mysql_link; MYSQLI_RESOURCE *mysqli_resource; + long flags = 0; - if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O", &mysql_link, mysqli_link_class_entry) == FAILURE) { + + if (zend_parse_method_parameters(ZEND_NUM_ARGS() TSRMLS_CC, getThis(), "O|l", &mysql_link, mysqli_link_class_entry, &flags) == FAILURE) { return; } MYSQLI_FETCH_RESOURCE_CONN(mysql, &mysql_link, MYSQLI_STATUS_VALID); - - if (!(result = mysql_store_result(mysql->mysql))) { +#if MYSQLI_USE_MYSQLND + result = flags & MYSQLI_STORE_RESULT_COPY_DATA? mysqlnd_store_result_ofs(mysql->mysql) : mysqlnd_store_result(mysql->mysql); +#else + result = mysql_store_result(mysql->mysql); +#endif + if (!result) { MYSQLI_REPORT_MYSQL_ERROR(mysql->mysql); RETURN_FALSE; } diff --git a/ext/mysqli/mysqli_fe.c b/ext/mysqli/mysqli_fe.c index 3d31b8183c..e099fe7194 100644 --- a/ext/mysqli/mysqli_fe.c +++ b/ext/mysqli/mysqli_fe.c @@ -142,6 +142,17 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_mysqli_rollback, 0, 0, 0) ZEND_ARG_INFO(0, name) ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_mysqli_store_result, 0, 0, 1) + MYSQLI_ZEND_ARG_OBJ_INFO_LINK() + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_store_result, 0, 0, 0) + ZEND_ARG_INFO(0, flags) +ZEND_END_ARG_INFO() + + ZEND_BEGIN_ARG_INFO_EX(arginfo_mysqli_change_user, 0, 0, 4) MYSQLI_ZEND_ARG_OBJ_INFO_LINK() ZEND_ARG_INFO(0, user) @@ -498,7 +509,7 @@ const zend_function_entry mysqli_functions[] = { PHP_FE(mysqli_sqlstate, arginfo_mysqli_only_link) PHP_FE(mysqli_ssl_set, arginfo_mysqli_ssl_set) PHP_FE(mysqli_stat, arginfo_mysqli_only_link) - PHP_FE(mysqli_store_result, arginfo_mysqli_only_link) + PHP_FE(mysqli_store_result, arginfo_mysqli_store_result) PHP_FE(mysqli_thread_id, arginfo_mysqli_only_link) PHP_FE(mysqli_thread_safe, arginfo_mysqli_no_params) PHP_FE(mysqli_use_result, arginfo_mysqli_only_link) @@ -568,7 +579,7 @@ const zend_function_entry mysqli_link_methods[] = { PHP_FALIAS(ssl_set, mysqli_ssl_set, arginfo_class_mysqli_ssl_set) PHP_FALIAS(stat, mysqli_stat, arginfo_mysqli_no_params) PHP_FALIAS(stmt_init, mysqli_stmt_init, arginfo_mysqli_no_params) - PHP_FALIAS(store_result, mysqli_store_result, arginfo_mysqli_no_params) + PHP_FALIAS(store_result, mysqli_store_result, arginfo_class_store_result) PHP_FALIAS(thread_safe, mysqli_thread_safe, arginfo_mysqli_no_params) PHP_FALIAS(use_result, mysqli_use_result, arginfo_mysqli_no_params) PHP_FALIAS(refresh,mysqli_refresh, arginfo_class_mysqli_refresh) diff --git a/ext/mysqli/mysqli_nonapi.c b/ext/mysqli/mysqli_nonapi.c index 312f2806ce..25a88c0984 100644 --- a/ext/mysqli/mysqli_nonapi.c +++ b/ext/mysqli/mysqli_nonapi.c @@ -217,7 +217,7 @@ void mysqli_common_connect(INTERNAL_FUNCTION_PARAMETERS, zend_bool is_real_conne #if !defined(MYSQLI_USE_MYSQLND) if (!(mysql->mysql = mysql_init(NULL))) { #else - if (!(mysql->mysql = mysqlnd_init(persistent))) { + if (!(mysql->mysql = mysqlnd_init(MYSQLND_CLIENT_KNOWS_RSET_COPY_DATA, persistent))) { #endif goto err; } @@ -240,7 +240,7 @@ void mysqli_common_connect(INTERNAL_FUNCTION_PARAMETERS, zend_bool is_real_conne if (mysql_real_connect(mysql->mysql, hostname, username, passwd, dbname, port, socket, flags) == NULL) #else if (mysqlnd_connect(mysql->mysql, hostname, username, passwd, passwd_len, dbname, dbname_len, - port, socket, flags TSRMLS_CC) == NULL) + port, socket, flags, MYSQLND_CLIENT_KNOWS_RSET_COPY_DATA TSRMLS_CC) == NULL) #endif { /* Save error messages - for mysqli_connect_error() & mysqli_connect_errno() */ @@ -575,7 +575,7 @@ PHP_FUNCTION(mysqli_query) php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty query"); RETURN_FALSE; } - if ((resultmode & ~MYSQLI_ASYNC) != MYSQLI_USE_RESULT && (resultmode & ~MYSQLI_ASYNC) != MYSQLI_STORE_RESULT) { + if ((resultmode & ~MYSQLI_ASYNC) != MYSQLI_USE_RESULT && (resultmode & ~(MYSQLI_ASYNC | MYSQLI_STORE_RESULT_COPY_DATA)) != MYSQLI_STORE_RESULT) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid value for resultmode"); RETURN_FALSE; } @@ -609,9 +609,14 @@ PHP_FUNCTION(mysqli_query) RETURN_TRUE; } - switch (resultmode) { + switch (resultmode & ~(MYSQLI_ASYNC | MYSQLI_STORE_RESULT_COPY_DATA)) { case MYSQLI_STORE_RESULT: - result = mysql_store_result(mysql->mysql); +#ifdef MYSQLI_USE_MYSQLND + if (resultmode & MYSQLI_STORE_RESULT_COPY_DATA) { + result = mysqlnd_store_result_ofs(mysql->mysql); + } else +#endif + result = mysql_store_result(mysql->mysql); break; case MYSQLI_USE_RESULT: result = mysql_use_result(mysql->mysql); diff --git a/ext/mysqli/mysqli_priv.h b/ext/mysqli/mysqli_priv.h index 190572b689..e28caebf92 100644 --- a/ext/mysqli/mysqli_priv.h +++ b/ext/mysqli/mysqli_priv.h @@ -114,9 +114,11 @@ PHP_MYSQLI_EXPORT(zend_object_value) mysqli_objects_new(zend_class_entry * TSRML #define MYSQLI_USE_RESULT 1 #ifdef MYSQLI_USE_MYSQLND #define MYSQLI_ASYNC 8 +#define MYSQLI_STORE_RESULT_COPY_DATA 16 #else /* libmysql */ #define MYSQLI_ASYNC 0 +#define MYSQLI_STORE_RESULT_OFS 0 #endif /* for mysqli_fetch_assoc */ diff --git a/ext/mysqli/tests/bug66043.phpt b/ext/mysqli/tests/bug66043.phpt index d0e8b1c3d3..52e42b6177 100644 --- a/ext/mysqli/tests/bug66043.phpt +++ b/ext/mysqli/tests/bug66043.phpt @@ -12,7 +12,9 @@ require_once('skipifconnectfailure.inc'); --FILE-- <?php require 'connect.inc'; -$db = new mysqli($host, $user, $passwd, 'mysql'); +if (!$db = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) { + printf("[001] Connect failed, [%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()); +} $stmt = $db->stmt_init(); $stmt->prepare("SELECT User FROM user WHERE password=\"\""); diff --git a/ext/mysqli/tests/bug66762.phpt b/ext/mysqli/tests/bug66762.phpt index 2b8a92c7fd..cf1309e5a2 100644 --- a/ext/mysqli/tests/bug66762.phpt +++ b/ext/mysqli/tests/bug66762.phpt @@ -9,7 +9,9 @@ require_once('skipifconnectfailure.inc'); <?php require_once("connect.inc"); - $mysqli = new mysqli($host, $user, $passwd, $db); + if (!$mysqli = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) { + printf("[001] Connect failed, [%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()); + } $read_stmt = $mysqli->prepare("SELECT 1"); @@ -17,7 +19,6 @@ require_once('skipifconnectfailure.inc'); unset($mysqli); var_dump($read_stmt->bind_result($data)); - ?> done! --EXPECT-- diff --git a/ext/mysqli/tests/mysqli_begin_transaction.phpt b/ext/mysqli/tests/mysqli_begin_transaction.phpt new file mode 100644 index 0000000000..7e708316b4 --- /dev/null +++ b/ext/mysqli/tests/mysqli_begin_transaction.phpt @@ -0,0 +1,140 @@ +--TEST-- +mysqli_begin_transaction() +--SKIPIF-- +<?php +require_once('skipif.inc'); +require_once('skipifemb.inc'); +require_once('skipifconnectfailure.inc'); + +require_once('connect.inc'); +if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + die(sprintf("Cannot connect, [%d] %s", mysqli_connect_errno(), mysqli_connect_error())); + +if (!have_innodb($link)) + die(sprintf("Needs InnoDB support, [%d] %s", $link->errno, $link->error)); +?> +--FILE-- +<?php + require_once("connect.inc"); + /* {{{ proto bool mysqli_begin_transaction(object link, [int flags [, string name]]) */ + $tmp = NULL; + $link = NULL; + + if (!is_null($tmp = @mysqli_begin_transaction())) + printf("[001] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (!is_null($tmp = @mysqli_begin_transaction($link))) + printf("[002] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (!is_null($tmp = @mysqli_begin_transaction($link, $link))) + printf("[003] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + printf("[004] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + + if (!is_null($tmp = @mysqli_begin_transaction($link, $link))) + printf("[005] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (!is_null($tmp = @mysqli_begin_transaction($link, 0, $link))) + printf("[006] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (!is_null($tmp = @mysqli_begin_transaction($link, 0, "mytrx", $link))) + printf("[007] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (!mysqli_query($link, 'DROP TABLE IF EXISTS test')) + printf("[008] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (!mysqli_query($link, 'CREATE TABLE test(id INT) ENGINE = InnoDB')) + printf("[009] Cannot create test table, [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (true !== ($tmp = mysqli_autocommit($link, true))) + printf("[010] Cannot turn on autocommit, expecting true, got %s/%s\n", gettype($tmp), $tmp); + + /* overrule autocommit */ + if (true !== ($tmp = mysqli_begin_transaction($link))) + printf("[011] Got %s - [%d] %s\n", var_dump($tmp, true), mysqli_errno($link), mysqli_error($link)); + + if (!mysqli_query($link, 'INSERT INTO test(id) VALUES (1)')) + printf("[012] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + $tmp = mysqli_rollback($link); + if ($tmp !== true) + printf("[013] Expecting boolean/true, got %s/%s\n", gettype($tmp), $tmp); + + /* empty */ + $res = mysqli_query($link, "SELECT * FROM test"); + var_dump($res->fetch_assoc()); + + /* valid flags */ + $flags = array( + MYSQLI_TRANS_START_WITH_CONSISTENT_SNAPSHOT, + MYSQLI_TRANS_START_READ_WRITE, + MYSQLI_TRANS_START_READ_ONLY, + MYSQLI_TRANS_COR_AND_CHAIN, + MYSQLI_TRANS_COR_AND_NO_CHAIN, + MYSQLI_TRANS_COR_RELEASE, + MYSQLI_TRANS_COR_NO_RELEASE); + + /* just coverage */ + foreach ($flags as $flag) { + if (!mysqli_begin_transaction($link, $flag, sprintf("flag %d", $flag))) { + printf("[014] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + } + if (!mysqli_query($link, 'SELECT * FROM test') || + !mysqli_rollback($link)) { + printf("[015] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + } + } + + /* does it really set a flag? */ + if (mysqli_get_server_version($link) >= 50600) { + if (!mysqli_begin_transaction($link, MYSQLI_TRANS_START_READ_ONLY, sprintf("flag %d", $flag))) { + printf("[016] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + } + if (!mysqli_query($link, "INSERT INTO test(id) VALUES (2)") || + !mysqli_commit($link)) { + printf("[017] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + } else { + $res = mysqli_query($link, "SELECT id FROM test WHERE id = 2"); + var_dump($res->fetch_assoc()); + } + } + + /* invalid flag */ + do { + $invalid_flag = mt_rand(0, 10000); + } while (isset(array_flip($flags)[$invalid_flag])); + /* we may or may not hit an invalid combination provoking a SQL error */ + if (!mysqli_begin_transaction($link, $invalid_flag, sprintf("flag %d", $invalid_flag))) { + printf("[018] invalid_flag = %d [%d] %s\n", $invalid_flag, mysqli_errno($link), mysqli_error($link)); + } else { + printf("[018] invalid_flag = %d [%d] %s\n", $invalid_flag, mysqli_errno($link), mysqli_error($link)); + } + if (!mysqli_begin_transaction($link, -1)) { + printf("[019] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + } + + /* does it like stupid names? */ + if (!$link->begin_transaction(MYSQLI_TRANS_START_READ_WRITE, "*/trick me?\n\0")) + printf("[020] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + /* does it like stupid names? */ + if (!$link->begin_transaction(MYSQLI_TRANS_START_READ_WRITE, "az09")) + printf("[021] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + print "done!"; +?> +--CLEAN-- +<?php + require_once("clean_table.inc"); +?> +--EXPECTF-- +NULL +[017] [1792] %s +[018] invalid_flag = %d [%d]%A + +Warning: mysqli_begin_transaction(): Invalid value for parameter flags (-1) in %s on line %d +[019] [%d]%A +[020] [%d]%A +done!
\ No newline at end of file diff --git a/ext/mysqli/tests/mysqli_change_user.phpt b/ext/mysqli/tests/mysqli_change_user.phpt index bfea423c9e..7a4530f0d5 100644 --- a/ext/mysqli/tests/mysqli_change_user.phpt +++ b/ext/mysqli/tests/mysqli_change_user.phpt @@ -32,79 +32,91 @@ require_once('skipifconnectfailure.inc'); printf("[006] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", $host, $user, $db, $port, $socket); - if (false !== ($tmp = mysqli_change_user($link, $user . '_unknown_really', $passwd . 'non_empty', $db))) + if (false !== ($tmp = @mysqli_change_user($link, $user . '_unknown_really', $passwd . 'non_empty', $db))) printf("[007] Expecting false, got %s/%s\n", gettype($tmp), $tmp); - if (false !== ($tmp = mysqli_change_user($link, $user, $passwd . '_unknown_really', $db))) - printf("[008] Expecting false, got %s/%s\n", gettype($tmp), $tmp); + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + printf("[008] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); - if (false !== ($tmp = mysqli_change_user($link, $user, $passwd, $db . '_unknown_really'))) + if (false !== ($tmp = @mysqli_change_user($link, $user, $passwd . '_unknown_really', $db))) printf("[009] Expecting false, got %s/%s\n", gettype($tmp), $tmp); - if (!mysqli_query($link, 'SET @mysqli_change_user_test_var=1')) - printf("[010] Failed to set test variable: [%d] %s\n", mysqli_errno($link), mysqli_error($link)); - - if (!$res = mysqli_query($link, 'SELECT @mysqli_change_user_test_var AS test_var')) - printf("[011] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); - $tmp = mysqli_fetch_assoc($res); - mysqli_free_result($res); - if (1 != $tmp['test_var']) - printf("[012] Cannot set test variable\n"); - - if (true !== ($tmp = mysqli_change_user($link, $user, $passwd, $db))) - printf("[013] Expecting true, got %s/%s\n", gettype($tmp), $tmp); - - if (!$res = mysqli_query($link, 'SELECT database() AS dbname, user() AS user')) - printf("[014] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); - $tmp = mysqli_fetch_assoc($res); - mysqli_free_result($res); - - if (substr($tmp['user'], 0, strlen($user)) !== $user) - printf("[015] Expecting user %s, got user() %s\n", $user, $tmp['user']); - if ($tmp['dbname'] != $db) - printf("[016] Expecting database %s, got database() %s\n", $db, $tmp['dbname']); + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + printf("[010] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); - if (!$res = mysqli_query($link, 'SELECT @mysqli_change_user_test_var AS test_var')) - printf("[017] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); - $tmp = mysqli_fetch_assoc($res); - mysqli_free_result($res); - if (NULL !== $tmp['test_var']) - printf("[019] Test variable is still set!\n"); + if (false !== ($tmp = @mysqli_change_user($link, $user, $passwd, $db . '_unknown_really'))) + printf("[011] Expecting false, got %s/%s\n", gettype($tmp), $tmp); mysqli_close($link); if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) { - printf("[020] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + printf("[012] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", $host, $user, $db, $port, $socket); } - if (false !== ($tmp = mysqli_change_user($link, str_repeat('user', 16384), str_repeat('pass', 16384), str_repeat('dbase', 16384)))) - printf("[021] Expecting false, got %s/%s\n", gettype($tmp), $tmp); + if (false !== ($tmp = @mysqli_change_user($link, str_repeat('user', 16384), str_repeat('pass', 16384), str_repeat('dbase', 16384)))) + printf("[013] Expecting false, got %s/%s\n", gettype($tmp), $tmp); mysqli_close($link); if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) { - printf("[022] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + printf("[014] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", $host, $user, $db, $port, $socket); } /* silent protocol change if no db which requires workaround in mysqlnd/libmysql (empty db = no db send with COM_CHANGE_USER) */ if (true !== ($tmp = mysqli_change_user($link, $user, $passwd, ""))) - printf("[023] Expecting true, got %s/%s\n", gettype($tmp), $tmp); + printf("[015] Expecting true, got %s/%s\n", gettype($tmp), $tmp); if (!$res = mysqli_query($link, 'SELECT database() AS dbname, user() AS user')) - printf("[024] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + printf("[016] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); $tmp = mysqli_fetch_assoc($res); mysqli_free_result($res); if ($tmp['dbname'] != "") - printf("[025] Expecting database '', got database() '%s'\n", $tmp['dbname']); + printf("[017] Expecting database '', got database() '%s'\n", $tmp['dbname']); mysqli_close($link); if (NULL !== ($tmp = @mysqli_change_user($link, $user, $passwd, $db))) - printf("[026] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + printf("[018] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + printf("[019] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + + if (!mysqli_query($link, 'SET @mysqli_change_user_test_var=1')) + printf("[020] Failed to set test variable: [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (!$res = mysqli_query($link, 'SELECT @mysqli_change_user_test_var AS test_var')) + printf("[021] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + $tmp = mysqli_fetch_assoc($res); + mysqli_free_result($res); + if (1 != $tmp['test_var']) + printf("[022] Cannot set test variable\n"); + + if (true !== ($tmp = mysqli_change_user($link, $user, $passwd, $db))) + printf("[023] Expecting true, got %s/%s\n", gettype($tmp), $tmp); + + if (!$res = mysqli_query($link, 'SELECT database() AS dbname, user() AS user')) + printf("[024] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + $tmp = mysqli_fetch_assoc($res); + mysqli_free_result($res); + + if (substr($tmp['user'], 0, strlen($user)) !== $user) + printf("[025] Expecting user %s, got user() %s\n", $user, $tmp['user']); + if ($tmp['dbname'] != $db) + printf("[026] Expecting database %s, got database() %s\n", $db, $tmp['dbname']); + + if (!$res = mysqli_query($link, 'SELECT @mysqli_change_user_test_var AS test_var')) + printf("[027] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + $tmp = mysqli_fetch_assoc($res); + mysqli_free_result($res); + if (NULL !== $tmp['test_var']) + printf("[028] Test variable is still set!\n"); print "done!"; ?> diff --git a/ext/mysqli/tests/mysqli_change_user_new.phpt b/ext/mysqli/tests/mysqli_change_user_new.phpt new file mode 100644 index 0000000000..a87afa84a3 --- /dev/null +++ b/ext/mysqli/tests/mysqli_change_user_new.phpt @@ -0,0 +1,44 @@ +--TEST-- +mysqli_change_user(), MySQL 5.6+ +--SKIPIF-- +<?php +require_once('skipif.inc'); +require_once('skipifemb.inc'); +require_once('skipifconnectfailure.inc'); + +if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + die(sprintf("SKIP Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket)); + +if (mysqli_get_server_version($link) < 50600) + die("SKIP For MySQL >= 5.6.0"); +?> +--FILE-- +<?php + require_once("connect.inc"); + + $tmp = NULL; + $link = NULL; + + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + printf("[001] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + + /* Pre 5.6: link remains useable */ + if (false !== ($tmp = @mysqli_change_user($link, $user . '_unknown_really', $passwd . 'non_empty', $db))) + printf("[002] Expecting false, got %s/%s\n", gettype($tmp), $tmp); + + if (!$res = mysqli_query($link, 'SELECT 1 AS _one')) + printf("[003] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + var_dump($res->fetch_assoc()); + + print "done!"; +?> +--EXPECTF-- +Warning: mysqli_query(): MySQL server has gone away in %s on line %d + +Warning: mysqli_query(): Error reading result set's header in %s on line %d +[003] [2006] MySQL server has gone away + +Fatal error: Call to a member function fetch_assoc() on a non-object in %s on line %d
\ No newline at end of file diff --git a/ext/mysqli/tests/mysqli_change_user_old.phpt b/ext/mysqli/tests/mysqli_change_user_old.phpt new file mode 100644 index 0000000000..370bb344da --- /dev/null +++ b/ext/mysqli/tests/mysqli_change_user_old.phpt @@ -0,0 +1,39 @@ +--TEST-- +mysqli_change_user(), MySQL < 5.6 +--SKIPIF-- +<?php +require_once('skipif.inc'); +require_once('skipifemb.inc'); +require_once('skipifconnectfailure.inc'); + +if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + die(sprintf("SKIP Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket)); + +if (mysqli_get_server_version($link) >= 50600) + die("SKIP For MySQL < 5.6.0"); +?> +--FILE-- +<?php + require_once("connect.inc"); + + $tmp = NULL; + $link = NULL; + + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + printf("[001] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + + /* Pre 5.6: link remains useable */ + if (false !== ($tmp = mysqli_change_user($link, $user . '_unknown_really', $passwd . 'non_empty', $db))) + printf("[011] Expecting false, got %s/%s\n", gettype($tmp), $tmp); + + if (!$res = mysqli_query($link, 'SELECT 1 AS _one')) + printf("[012] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + var_dump($res->fetch_assoc()); + + print "done!"; +?> +--EXPECTF-- +done!
\ No newline at end of file diff --git a/ext/mysqli/tests/mysqli_change_user_oo.phpt b/ext/mysqli/tests/mysqli_change_user_oo.phpt index 61444ae235..7ed2d08933 100644 --- a/ext/mysqli/tests/mysqli_change_user_oo.phpt +++ b/ext/mysqli/tests/mysqli_change_user_oo.phpt @@ -10,6 +10,9 @@ require_once('table.inc'); if (!$IS_MYSQLND && (mysqli_get_server_version($link) < 50118 && mysqli_get_server_version($link) > 50100)) { die("skip Your MySQL Server version has a known bug that will cause a crash"); } + +if (mysqli_get_server_version($link) >= 50600) + die("SKIP For MySQL < 5.6.0"); ?> --FILE-- <?php diff --git a/ext/mysqli/tests/mysqli_class_mysqli_reflection.phpt b/ext/mysqli/tests/mysqli_class_mysqli_reflection.phpt index 63ec7ca3c0..8f6c24900a 100644 --- a/ext/mysqli/tests/mysqli_class_mysqli_reflection.phpt +++ b/ext/mysqli/tests/mysqli_class_mysqli_reflection.phpt @@ -1140,9 +1140,16 @@ isInternal: yes isUserDefined: no returnsReference: no Modifiers: 256 -Number of Parameters: 0 +Number of Parameters: 1 Number of Required Parameters: 0 +Inspecting parameter 'flags' of method 'store_result' +isArray: no +allowsNull: no +isPassedByReference: no +isOptional: yes +isDefaultValueAvailable: no + Inspecting method 'thread_safe' isFinal: no isAbstract: no diff --git a/ext/mysqli/tests/mysqli_constants.phpt b/ext/mysqli/tests/mysqli_constants.phpt index 7c6dacd393..bed9d53419 100644 --- a/ext/mysqli/tests/mysqli_constants.phpt +++ b/ext/mysqli/tests/mysqli_constants.phpt @@ -108,6 +108,10 @@ require_once('skipifconnectfailure.inc'); $expected_constants['MYSQLI_OPT_INT_AND_FLOAT_NATIVE'] = true; } + if ($IS_MYSQLND && defined('MYSQLI_STORE_RESULT_COPY_DATA')) { + $expected_constants['MYSQLI_STORE_RESULT_COPY_DATA'] = true; + } + if ($IS_MYSQLND || defined('MYSQLI_REFRESH_BACKUP_LOG')) { $expected_constants['MYSQLI_REFRESH_BACKUP_LOG'] = true; } diff --git a/ext/mysqli/tests/mysqli_fetch_all.phpt b/ext/mysqli/tests/mysqli_fetch_all.phpt index 63b6ad2848..854b8160f0 100644 --- a/ext/mysqli/tests/mysqli_fetch_all.phpt +++ b/ext/mysqli/tests/mysqli_fetch_all.phpt @@ -299,6 +299,26 @@ if (!function_exists('mysqli_fetch_all')) if (null !== ($tmp = mysqli_fetch_array($res, MYSQLI_ASSOC))) printf("[015] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) { + printf("[016] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + } + + if (!$res = mysqli_real_query($link, "SELECT 1 AS _one")) + printf("[017] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + /* on mysqlnd level this would not be allowed */ + if (!is_object($res = mysqli_use_result($link))) + printf("[018] Expecting object, got %s/%s. [%d] %s\n", + gettype($res), $res, mysqli_errno($link), mysqli_error($link)); + + $rows = mysqli_fetch_all($res, MYSQLI_ASSOC); + if (!is_array($rows) || (count($rows) > 1) || !isset($rows[0]['_one']) || ($rows[0]['_one'] != 1)) { + printf("[019] Results seem wrong, dumping\n"); + var_dump($rows); + } + + print "done!"; ?> --CLEAN-- diff --git a/ext/mysqli/tests/mysqli_fetch_lengths.phpt b/ext/mysqli/tests/mysqli_fetch_lengths.phpt index 1abc61170e..6d0b698ee7 100644 --- a/ext/mysqli/tests/mysqli_fetch_lengths.phpt +++ b/ext/mysqli/tests/mysqli_fetch_lengths.phpt @@ -10,8 +10,10 @@ require_once('skipifconnectfailure.inc'); <?php require_once("connect.inc"); - if (!$mysqli = new mysqli($host, $user, $passwd, $db, $port, $socket)) - printf("[001] Cannot connect\n"); + if (!$mysqli = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) { + printf("[001] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + } if (!is_null($tmp = @mysqli_fetch_lengths())) printf("[001] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); diff --git a/ext/mysqli/tests/mysqli_poll.phpt b/ext/mysqli/tests/mysqli_poll.phpt index dd4f9b9710..8a49ba3f82 100644 --- a/ext/mysqli/tests/mysqli_poll.phpt +++ b/ext/mysqli/tests/mysqli_poll.phpt @@ -52,6 +52,13 @@ if (!$IS_MYSQLND) if (0 !== ($tmp = (mysqli_poll($read, $error, $reject, 0, 1)))) printf("[009] Expecting int/0 got %s/%s\n", gettype($tmp), var_export($tmp, true)); + $read = $error = $reject = array($link); + if (false !== ($tmp = (mysqli_poll($read, $error, $reject, -1, 1)))) + printf("[010] Expecting false got %s/%s\n", gettype($tmp), var_export($tmp, true)); + + $read = $error = $reject = array($link); + if (false !== ($tmp = (mysqli_poll($read, $error, $reject, 0, -1)))) + printf("[011] Expecting false got %s/%s\n", gettype($tmp), var_export($tmp, true)); function poll_async($offset, $link, $links, $errors, $reject, $exp_ready, $use_oo_syntax) { @@ -90,14 +97,14 @@ if (!$IS_MYSQLND) $links = array($link); $errors = array($link); $reject = array($link); - poll_async(10, $link, $links, $errors, $reject, 0, false); + poll_async(12, $link, $links, $errors, $reject, 0, false); mysqli_close($link); $link = get_connection(); $links = array($link); $errors = array($link); $reject = array($link); - poll_async(11, $link, $links, $errors, $reject, 0, true); + poll_async(13, $link, $links, $errors, $reject, 0, true); mysqli_close($link); // Connections on which no query has been send - 2 @@ -106,7 +113,7 @@ if (!$IS_MYSQLND) $links = array($link, $link); $errors = array($link, $link); $reject = array(); - poll_async(12, $link, $links, $errors, $reject, 0, false); + poll_async(14, $link, $links, $errors, $reject, 0, false); // Connections on which no query has been send - 3 // Difference: pass two connections @@ -114,7 +121,7 @@ if (!$IS_MYSQLND) $links = array($link, get_connection()); $errors = array($link, $link); $reject = array(); - poll_async(13, $link, $links, $errors, $reject, 0, false); + poll_async(15, $link, $links, $errors, $reject, 0, false); // Reference mess... $link = get_connection(); @@ -122,16 +129,20 @@ if (!$IS_MYSQLND) $errors = array($link); $ref_errors =& $errors; $reject = array(); - poll_async(14, $link, $links, $ref_errors, $reject, 0, false); + poll_async(16, $link, $links, $ref_errors, $reject, 0, false); print "done!"; ?> --EXPECTF-- -[010 + 6] Rejecting thread %d: 0/ -[011 + 6] Rejecting thread %d: 0/ -[012 + 6] Rejecting thread %d: 0/ + +Warning: mysqli_poll(): Negative values passed for sec and/or usec in %s on line %d + +Warning: mysqli_poll(): Negative values passed for sec and/or usec in %s on line %d [012 + 6] Rejecting thread %d: 0/ [013 + 6] Rejecting thread %d: 0/ -[013 + 6] Rejecting thread %d: 0/ [014 + 6] Rejecting thread %d: 0/ -done! +[014 + 6] Rejecting thread %d: 0/ +[015 + 6] Rejecting thread %d: 0/ +[015 + 6] Rejecting thread %d: 0/ +[016 + 6] Rejecting thread %d: 0/ +done!
\ No newline at end of file diff --git a/ext/mysqli/tests/mysqli_poll_kill.phpt b/ext/mysqli/tests/mysqli_poll_kill.phpt index b068d64e8f..c69a251111 100644 --- a/ext/mysqli/tests/mysqli_poll_kill.phpt +++ b/ext/mysqli/tests/mysqli_poll_kill.phpt @@ -90,6 +90,7 @@ if (!$IS_MYSQLND) // Yes, 1 - fetch OK packet of kill! $processed = 0; + $begin = microtime(true); do { $links = array($link, $link); $errors = array($link, $link); @@ -106,9 +107,14 @@ if (!$IS_MYSQLND) break; } + if (FALSE === $ready) { + printf("[013] MySQLi indicates some error\n"); + break; + } + if (!empty($reject)) { foreach ($reject as $mysqli) { - printf("[013] Rejecting thread %d: %s/%s\n", + printf("[014] Rejecting thread %d: %s/%s\n", mysqli_thread_id($mysqli), mysqli_errno($mysqli), mysqli_error($mysqli)); @@ -121,11 +127,16 @@ if (!$IS_MYSQLND) printf("Fetching from thread %d...\n", mysqli_thread_id($mysqli)); var_dump(mysqli_fetch_assoc($res)); } else if (mysqli_errno($mysqli) > 0) { - printf("[014] %d/%s\n", mysqli_errno($mysqli), mysqli_error($mysqli)); + printf("[015] %d/%s\n", mysqli_errno($mysqli), mysqli_error($mysqli)); } $processed++; } + if ((microtime(true) - $begin) > 5) { + printf("[016] Pulling the emergency break after 5s, something is wrong...\n"); + break; + } + } while ($processed < 2); @@ -137,17 +148,17 @@ if (!$IS_MYSQLND) // Sleep 0.1s to ensure the KILL gets recognized usleep(100000); if (false !== ($tmp = mysqli_query($link, "SELECT 1 AS 'processed before killed'", MYSQLI_ASYNC | MYSQLI_USE_RESULT))) - printf("[015] Expecting boolean/false got %s/%s\n", gettype($tmp), var_export($tmp, true)); + printf("[017] Expecting boolean/false got %s/%s\n", gettype($tmp), var_export($tmp, true)); $links = array($link); $errors = array($link); $reject = array($link); if (0 !== ($tmp = (mysqli_poll($links, $errors, $reject, 0, 10000)))) - printf("[016] Expecting int/0 got %s/%s\n", gettype($tmp), var_export($tmp, true)); + printf("[018] Expecting int/0 got %s/%s\n", gettype($tmp), var_export($tmp, true)); if (!is_array($links) || empty($links)) - printf("[017] Expecting non-empty array got %s/%s\n", gettype($links), var_export($links, true)); + printf("[019] Expecting non-empty array got %s/%s\n", gettype($links), var_export($links, true)); else foreach ($links as $link) { if (is_object($res = mysqli_reap_async_query($link))) { @@ -156,16 +167,16 @@ if (!$IS_MYSQLND) mysqli_free_result($res); } else if ($link->errno > 0) { // But you are supposed to handle the error the way its shown here! - printf("[018] Error: %d/%s\n", $link->errno, $link->error); + printf("[020] Error: %d/%s\n", $link->errno, $link->error); } } // None of these will indicate an error, check errno on the list of returned connections! if (!is_array($errors) || !empty($errors)) - printf("[019] Expecting non-empty array got %s/%s\n", gettype($errors), var_export($errors, true)); + printf("[021] Expecting non-empty array got %s/%s\n", gettype($errors), var_export($errors, true)); if (!is_array($reject) || !empty($reject)) - printf("[020] Expecting empty array got %s/%s\n", gettype($reject), var_export($reject, true)); + printf("[021] Expecting empty array got %s/%s\n", gettype($reject), var_export($reject, true)); mysqli_close($link); diff --git a/ext/mysqli/tests/mysqli_poll_mixing_insert_select.phpt b/ext/mysqli/tests/mysqli_poll_mixing_insert_select.phpt index 9c02cf9760..9068f6f708 100644 --- a/ext/mysqli/tests/mysqli_poll_mixing_insert_select.phpt +++ b/ext/mysqli/tests/mysqli_poll_mixing_insert_select.phpt @@ -80,13 +80,17 @@ if (!$IS_MYSQLND) if (0 == count($poll_links)) break; - if (0 == ($num_ready = mysqli_poll($poll_links, $poll_errors, $poll_reject, 0, 200000))) + if (0 === ($num_ready = mysqli_poll($poll_links, $poll_errors, $poll_reject, 0, 200000))) continue; if (!empty($poll_errors)) { die(var_dump($poll_errors)); } + if (FALSE === $num_ready) { + die("Some mysqli indicated error"); + } + foreach ($poll_links as $link) { $thread_id = mysqli_thread_id($link); $links[$thread_id]['processed'] = true; diff --git a/ext/mysqli/tests/mysqli_reap_async_query.phpt b/ext/mysqli/tests/mysqli_reap_async_query.phpt new file mode 100644 index 0000000000..e3858e6172 --- /dev/null +++ b/ext/mysqli/tests/mysqli_reap_async_query.phpt @@ -0,0 +1,97 @@ +--TEST-- +mysqli_reap_async_query() +--SKIPIF-- +<?php +require_once('skipif.inc'); +require_once('skipifemb.inc'); +require_once('connect.inc'); +require_once('skipifconnectfailure.inc'); + +if (!$IS_MYSQLND) + die("skip mysqlnd only feature, compile PHP using --with-mysqli=mysqlnd"); +?> +--FILE-- +<?php + require_once('connect.inc'); + + function get_connection() { + global $host, $user, $passwd, $db, $port, $socket; + + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + printf("[001] [%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()); + return $link; + } + + if (!$link = get_connection()) + printf("[001] [%d] %s\n", mysqli_connect_errno(), mysqli_connect_error()); + + if (NULL !== ($tmp = @mysqli_reap_async_query())) + printf("[002] Expecting NULL got %s\n", var_export($tmp, true)); + + $l = array($link); + if (NULL !== ($tmp = @mysqli_reap_async_query($l))) + printf("[003] Expecting NULL got %s\n", var_export($tmp, true)); + + if (NULL !== ($tmp = @mysqli_reap_async_query($link, $link))) + printf("[004] Expecting NULL got %s\n", var_export($tmp, true)); + + + function poll_async($offset, $link, $links, $errors, $reject, $exp_ready, $use_oo_syntax) { + + if ($exp_ready !== ($tmp = mysqli_poll($links, $errors, $reject, 0, 1000))) + printf("[%03d + 1] There should be %d links ready to read from, %d ready\n", + $offset, $exp_ready, $tmp); + + foreach ($links as $mysqli) { + if ($use_oo_syntax) { + $res = $mysqli->reap_async_query(); + } else { + $res = mysqli_reap_async_query($mysqli); + } + if (is_object($res)) { + printf("[%03d + 2] %s\n", $offset, var_export($res->fetch_assoc(), true)); + } else if (mysqli_errno($mysqli) > 0) { + printf("[%03d + 3] Error indicated through links array: %d/%s", + $offset, mysqli_errno($mysqli), mysqli_error($mysqli)); + } else { + printf("[%03d + 4] Cannot fetch and no error set - non resultset query (no SELECT)!\n", $offset); + } + } + + foreach ($errors as $mysqli) + printf("[%03d + 5] Error on %d: %d/%s\n", + $offset, mysqli_thread_id($mysqli), mysqli_errno($mysqli), mysqli_error($mysqli)); + + foreach ($reject as $mysqli) + printf("[%03d + 6] Rejecting thread %d: %d/%s\n", + $offset, mysqli_thread_id($mysqli), mysqli_errno($mysqli), mysqli_error($mysqli)); + + } + + // Connections on which no query has been send - 1 + $link = get_connection(); + $link->query("SELECT 1 AS _one", MYSQLI_ASYNC | MYSQLI_STORE_RESULT); + $links = array($link); + $errors = array($link); + $reject = array($link); + poll_async(12, $link, $links, $errors, $reject, 1, false); + mysqli_close($link); + + $link = get_connection(); + $link->query("SELECT 2 AS _two", MYSQLI_ASYNC | MYSQLI_USE_RESULT); + $links = array($link); + $errors = array($link); + $reject = array($link); + poll_async(13, $link, $links, $errors, $reject, 1, true); + mysqli_close($link); + + print "done!"; +?> +--EXPECTF-- +[012 + 2] array ( + '_one' => '1', +) +[013 + 2] array ( + '_two' => '2', +) +done! diff --git a/ext/mysqli/tests/mysqli_release_savepoint.phpt b/ext/mysqli/tests/mysqli_release_savepoint.phpt new file mode 100644 index 0000000000..ba4c564385 --- /dev/null +++ b/ext/mysqli/tests/mysqli_release_savepoint.phpt @@ -0,0 +1,84 @@ +--TEST-- +mysqli_release_savepoint() +--SKIPIF-- +<?php +require_once('skipif.inc'); +require_once('skipifemb.inc'); +require_once('skipifconnectfailure.inc'); + +require_once('connect.inc'); +if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + die(sprintf("Cannot connect, [%d] %s", mysqli_connect_errno(), mysqli_connect_error())); + +if (!have_innodb($link)) + die(sprintf("Needs InnoDB support, [%d] %s", $link->errno, $link->error)); +?> +--FILE-- +<?php + require_once("connect.inc"); + /* {{{ proto bool mysqli_release_savepoint(object link, string name) */ + $tmp = NULL; + $link = NULL; + + if (!is_null($tmp = @mysqli_release_savepoint())) + printf("[001] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (!is_null($tmp = @mysqli_release_savepoint($link))) + printf("[002] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + printf("[003] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + + $name = array(); + if (!is_null($tmp = @mysqli_release_savepoint($link, $name))) + printf("[004] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (!is_null($tmp = @mysqli_release_savepoint($link, 'foo', $link))) + printf("[005] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (false !== ($tmp = mysqli_release_savepoint($link, ''))) + printf("[006] Expecting false, got %s/%s\n", gettype($tmp), $tmp); + + if (!mysqli_query($link, 'DROP TABLE IF EXISTS test')) + printf("[007] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (!mysqli_query($link, 'CREATE TABLE test(id INT) ENGINE = InnoDB')) + printf("[008] Cannot create test table, [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (true !== ($tmp = mysqli_autocommit($link, false))) + printf("[009] Cannot turn off autocommit, expecting true, got %s/%s\n", gettype($tmp), $tmp); + + /* note that there is no savepoint my... */ + if (true !== ($tmp = mysqli_release_savepoint($link, 'my'))) + printf("[010] Got %s - [%d] %s\n", var_dump($tmp, true), mysqli_errno($link), mysqli_error($link)); + + if (!mysqli_query($link, 'INSERT INTO test(id) VALUES (1)')) + printf("[011] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + $tmp = mysqli_commit($link); + if ($tmp !== true) + printf("[012] Expecting boolean/true, got %s/%s\n", gettype($tmp), $tmp); + + if (true !== ($tmp = mysqli_savepoint($link, 'my'))) + printf("[013] Got %s - [%d] %s\n", var_dump($tmp, true), mysqli_errno($link), mysqli_error($link)); + + $res = mysqli_query($link, "SELECT * FROM test"); + var_dump($res->fetch_assoc()); + + if (true !== ($tmp = mysqli_release_savepoint($link, 'my'))) + printf("[014] Got %s - [%d] %s\n", var_dump($tmp, true), mysqli_errno($link), mysqli_error($link)); + + print "done!"; +?> +--CLEAN-- +<?php + require_once("clean_table.inc"); +?> +--EXPECTF-- +Warning: mysqli_release_savepoint(): Savepoint name cannot be empty in %s on line %d +array(1) { + ["id"]=> + string(1) "1" +} +done!
\ No newline at end of file diff --git a/ext/mysqli/tests/mysqli_report.phpt b/ext/mysqli/tests/mysqli_report.phpt index f5d77e38bc..4d2d3553d1 100644 --- a/ext/mysqli/tests/mysqli_report.phpt +++ b/ext/mysqli/tests/mysqli_report.phpt @@ -43,8 +43,6 @@ require_once('skipifconnectfailure.inc'); mysqli_multi_query($link, "BAR; FOO;"); mysqli_query($link, "FOO"); - /* This might work if you accept anonymous users in your setup */ - mysqli_change_user($link, "0123456789-10-456789-20-456789-30-456789-40-456789-50-456789-60-456789-70-456789-80-456789-90-456789", "password", $db); mysqli_kill($link, -1); // mysqli_ping() cannot be tested, because one would need to cause an error inside the C function to test it @@ -61,7 +59,6 @@ require_once('skipifconnectfailure.inc'); mysqli_multi_query($link, "BAR; FOO;"); mysqli_query($link, "FOO"); - mysqli_change_user($link, "This might work if you accept anonymous users in your setup", "password", $db); mysqli_kill($link, -1); mysqli_prepare($link, "FOO"); mysqli_real_query($link, "FOO"); @@ -291,8 +288,6 @@ Warning: mysqli_multi_query(): (%d/%d): You have an error in your SQL syntax; ch Warning: mysqli_query(): (%d/%d): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'FOO' at line 1 in %s on line %d -Warning: mysqli_change_user(): (%d/%d): Access denied for user '%s'@'%s' (using password: %s) in %s on line %d - Warning: mysqli_kill(): processid should have positive value in %s on line %d Warning: mysqli_prepare(): (%d/%d): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'FOO' at line 1 in %s on line %d diff --git a/ext/mysqli/tests/mysqli_report_new.phpt b/ext/mysqli/tests/mysqli_report_new.phpt new file mode 100644 index 0000000000..5cf5ca8e3b --- /dev/null +++ b/ext/mysqli/tests/mysqli_report_new.phpt @@ -0,0 +1,50 @@ +--TEST-- +mysqli_report(), change user, MySQL 5.6+ +--SKIPIF-- +<?php +require_once('skipif.inc'); +require_once('skipifemb.inc'); +require_once('skipifconnectfailure.inc'); + +if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + die(sprintf("SKIP Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket)); + +if (mysqli_get_server_version($link) < 50600) + die("SKIP For MySQL >= 5.6.0"); + +?> +--FILE-- +<?php + require_once("connect.inc"); + + $tmp = NULL; + $link = NULL; + + require('table.inc'); + + /* + Internal macro MYSQL_REPORT_ERROR + */ + mysqli_report(MYSQLI_REPORT_ERROR); + + mysqli_change_user($link, "0123456789-10-456789-20-456789-30-456789-40-456789-50-456789-60-456789-70-456789-80-456789-90-456789", "password", $db); + + mysqli_report(MYSQLI_REPORT_OFF); + + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + printf("[001] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + + mysqli_change_user($link, "This might work if you accept anonymous users in your setup", "password", $db); + + print "done!"; +?> +--CLEAN-- +<?php + require_once("clean_table.inc"); +?> +--EXPECTF-- + +Warning: mysqli_change_user(): (%d/%d): Access denied for user '%s'@'%s' (using password: %s) in %s on line %d +done!
\ No newline at end of file diff --git a/ext/mysqli/tests/mysqli_report_wo_ps.phpt b/ext/mysqli/tests/mysqli_report_wo_ps.phpt index cc57511b5b..dae81b21cc 100644 --- a/ext/mysqli/tests/mysqli_report_wo_ps.phpt +++ b/ext/mysqli/tests/mysqli_report_wo_ps.phpt @@ -1,10 +1,17 @@ --TEST-- -mysqli_report() +mysqli_report(), MySQL < 5.6 --SKIPIF-- <?php require_once('skipif.inc'); require_once('skipifemb.inc'); require_once('skipifconnectfailure.inc'); + +if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + die(sprintf("SKIP Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket)); + +if (mysqli_get_server_version($link) >= 50600) + die("SKIP For MySQL < 5.6.0"); ?> --FILE-- <?php diff --git a/ext/mysqli/tests/mysqli_savepoint.phpt b/ext/mysqli/tests/mysqli_savepoint.phpt new file mode 100644 index 0000000000..5775b0eaec --- /dev/null +++ b/ext/mysqli/tests/mysqli_savepoint.phpt @@ -0,0 +1,72 @@ +--TEST-- +mysqli_savepoint() +--SKIPIF-- +<?php +require_once('skipif.inc'); +require_once('skipifemb.inc'); +require_once('skipifconnectfailure.inc'); + +require_once('connect.inc'); +if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + die(sprintf("Cannot connect, [%d] %s", mysqli_connect_errno(), mysqli_connect_error())); + +if (!have_innodb($link)) + die(sprintf("Needs InnoDB support, [%d] %s", $link->errno, $link->error)); +?> +--FILE-- +<?php + require_once("connect.inc"); + /* {{{ proto bool mysqli_savepoint(object link, string name) */ + $tmp = NULL; + $link = NULL; + + if (!is_null($tmp = @mysqli_savepoint())) + printf("[001] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (!is_null($tmp = @mysqli_savepoint($link))) + printf("[002] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) + printf("[003] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + + $name = array(); + if (!is_null($tmp = @mysqli_savepoint($link, $name))) + printf("[004] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (!is_null($tmp = @mysqli_savepoint($link, 'foo', $link))) + printf("[005] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + + if (false !== ($tmp = mysqli_savepoint($link, ''))) + printf("[006] Expecting false, got %s/%s\n", gettype($tmp), $tmp); + + if (!mysqli_query($link, 'DROP TABLE IF EXISTS test')) + printf("[007] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (!mysqli_query($link, 'CREATE TABLE test(id INT) ENGINE = InnoDB')) + printf("[008] Cannot create test table, [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (true !== ($tmp = mysqli_autocommit($link, false))) + printf("[009] Cannot turn off autocommit, expecting true, got %s/%s\n", gettype($tmp), $tmp); + + /* overrule autocommit */ + if (true !== ($tmp = mysqli_savepoint($link, 'my'))) + printf("[010] Got %s - [%d] %s\n", var_dump($tmp, true), mysqli_errno($link), mysqli_error($link)); + + if (!mysqli_query($link, 'INSERT INTO test(id) VALUES (1)')) + printf("[011] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + $tmp = mysqli_rollback($link); + if ($tmp !== true) + printf("[012] Expecting boolean/true, got %s/%s\n", gettype($tmp), $tmp); + + print "done!"; +?> +--CLEAN-- +<?php + require_once("clean_table.inc"); +?> +--EXPECTF-- + +Warning: mysqli_savepoint(): Savepoint name cannot be empty in %s on line %d +done! diff --git a/ext/mysqli/tests/mysqli_stmt_get_warnings.phpt b/ext/mysqli/tests/mysqli_stmt_get_warnings.phpt index 2b0e150ded..69755865e9 100644 --- a/ext/mysqli/tests/mysqli_stmt_get_warnings.phpt +++ b/ext/mysqli/tests/mysqli_stmt_get_warnings.phpt @@ -17,7 +17,7 @@ if (!mysqli_query($link, "DROP TABLE IF EXISTS test") || !mysqli_query($link, "CREATE TABLE test(id SMALLINT)")) die(sprintf("skip [%d] %s\n", $link->errno, $link->error)); -if (!@mysqli_query("INSERT INTO test(id) VALUES (100001)")) +if (!@mysqli_query($link, "SET sql_mode=''") || !@mysqli_query($link, "INSERT INTO test(id) VALUES (100001)")) die("skip Strict sql mode seems to be active. We won't get a warning to check for."); mysqli_query($link, "DROP TABLE IF EXISTS test"); @@ -43,54 +43,57 @@ mysqli_query($link, "DROP TABLE IF EXISTS test"); if (NULL !== ($tmp = mysqli_stmt_get_warnings($stmt))) printf("[004] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); - if (!mysqli_stmt_prepare($stmt, "DROP TABLE IF EXISTS test") || !mysqli_stmt_execute($stmt)) + if (!mysqli_stmt_prepare($stmt, "SET sql_mode=''") || !mysqli_stmt_execute($stmt)) printf("[005] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt)); + if (!mysqli_stmt_prepare($stmt, "DROP TABLE IF EXISTS test") || !mysqli_stmt_execute($stmt)) + printf("[006] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt)); + if (false !== ($tmp = mysqli_stmt_get_warnings($stmt))) - printf("[006] Expecting boolean/false, got %s/%s\n", gettype($tmp), $tmp); + printf("[007] Expecting boolean/false, got %s/%s\n", gettype($tmp), $tmp); if (!mysqli_stmt_prepare($stmt, "CREATE TABLE test(id SMALLINT, label CHAR(1))") || !mysqli_stmt_execute($stmt)) - printf("[007] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt)); + printf("[008] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt)); if (false !== ($tmp = mysqli_stmt_get_warnings($stmt))) - printf("[008] Expecting boolean/false, got %s/%s\n", gettype($tmp), $tmp); + printf("[009] Expecting boolean/false, got %s/%s\n", gettype($tmp), $tmp); if (!mysqli_stmt_prepare($stmt, "INSERT INTO test(id, label) VALUES (100000, 'a'), (100001, 'b')") || !mysqli_stmt_execute($stmt)) - printf("[009] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt)); + printf("[010] [%d] %s\n", mysqli_stmt_errno($stmt), mysqli_stmt_error($stmt)); if (!is_object($warning = mysqli_stmt_get_warnings($stmt))) - printf("[010] Expecting mysqli_warning object, got %s/%s\n", gettype($warning), $warning); + printf("[011] Expecting mysqli_warning object, got %s/%s\n", gettype($warning), $warning); if ('mysqli_warning' !== get_class($warning)) - printf("[011] Expecting object of type mysqli_warning got type '%s'", get_class($warning)); + printf("[012] Expecting object of type mysqli_warning got type '%s'", get_class($warning)); if (!method_exists($warning, 'next')) - printf("[012] Object mysqli_warning seems to lack method next()\n"); + printf("[013] Object mysqli_warning seems to lack method next()\n"); $i = 0; do { if ('' == $warning->message) - printf("[013 - %d] Message should not be empty\n", $i); + printf("[014 - %d] Message should not be empty\n", $i); if ('' == $warning->sqlstate) - printf("[014 - %d] SQL State should not be empty\n", $i); + printf("[015 - %d] SQL State should not be empty\n", $i); if (0 == $warning->errno) - printf("[015 - %d] Error number should not be zero\n", $i); + printf("[016 - %d] Error number should not be zero\n", $i); $i++; } while ($warning->next()); if (2 != $i) - printf("[016] Expected 2 warnings, got %d warnings\n", $i); + printf("[017] Expected 2 warnings, got %d warnings\n", $i); mysqli_stmt_close($stmt); if (NULL !== ($tmp = mysqli_stmt_get_warnings($stmt))) - printf("[015] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); + printf("[018] Expecting NULL, got %s/%s\n", gettype($tmp), $tmp); mysqli_close($link); print "done!"; diff --git a/ext/mysqli/tests/mysqli_store_result_buffered_c.phpt b/ext/mysqli/tests/mysqli_store_result_buffered_c.phpt new file mode 100644 index 0000000000..d580ec430f --- /dev/null +++ b/ext/mysqli/tests/mysqli_store_result_buffered_c.phpt @@ -0,0 +1,42 @@ +--TEST-- +mysqli_store_result() +--SKIPIF-- +<?php +require_once('skipif.inc'); +require_once('skipifemb.inc'); +require_once('skipifconnectfailure.inc'); +?> +--INI-- +mysqlnd.debug=d:t:O,/tmp/mysqlnd.trace +--FILE-- +<?php + require_once("connect.inc"); + + $tmp = NULL; + $link = NULL; + + + require('table.inc'); + + if (!$res = mysqli_real_query($link, "SELECT id, label FROM test ORDER BY id")) + printf("[003] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (!is_object($res = mysqli_store_result($link, MYSQLI_STORE_RESULT_COPY_DATA))) + printf("[004] Expecting object, got %s/%s. [%d] %s\n", + gettype($res), $res, mysqli_errno($link), mysqli_error($link)); + + if (true !== ($tmp = mysqli_data_seek($res, 2))) + printf("[005] Expecting boolean/true, got %s/%s. [%d] %s\n", + gettype($tmp), $tmp, mysqli_errno($link), mysqli_error($link)); + + mysqli_free_result($res); + + print "done!"; +?> +--CLEAN-- +<?php + require_once("clean_table.inc"); +?> +--EXPECTF-- +Warning: mysqli_store_result(): Couldn't fetch mysqli in %s on line %d +done!
\ No newline at end of file diff --git a/ext/mysqli/tests/mysqli_store_result_copy.phpt b/ext/mysqli/tests/mysqli_store_result_copy.phpt new file mode 100644 index 0000000000..304300459b --- /dev/null +++ b/ext/mysqli/tests/mysqli_store_result_copy.phpt @@ -0,0 +1,285 @@ +--TEST-- +mysqli_store_result() +--SKIPIF-- +<?php +require_once('skipif.inc'); +require_once('skipifemb.inc'); +require_once('skipifconnectfailure.inc'); +if (!$IS_MYSQLND) { + die("SKIP mysqlnd only test"); +} +?> +--INI-- +mysqlnd.debug=d:t:O,/tmp/mysqlnd.trace +mysqlnd.net_read_buffer_size=1 +mysqlnd.mempool_default_size=1 +mysqlnd.fetch_data_copy=0 +--FILE-- +<?php + require_once("connect.inc"); + + $tmp = NULL; + $link = NULL; + + require('table.inc'); + + if (!$res = mysqli_real_query($link, "SELECT id, label FROM test ORDER BY id")) + printf("[003] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (!is_object($res = mysqli_store_result($link, MYSQLI_STORE_RESULT_COPY_DATA))) + printf("[004] Expecting object, got %s/%s. [%d] %s\n", + gettype($res), $res, mysqli_errno($link), mysqli_error($link)); + + if (true !== ($tmp = mysqli_data_seek($res, 2))) + printf("[005] Expecting boolean/true, got %s/%s. [%d] %s\n", + gettype($tmp), $tmp, mysqli_errno($link), mysqli_error($link)); + + var_dump($res->fetch_assoc()); + + if (true !== ($tmp = mysqli_data_seek($res, 0))) + printf("[006] Expecting boolean/true, got %s/%s. [%d] %s\n", + gettype($tmp), $tmp, mysqli_errno($link), mysqli_error($link)); + + while ($row = $res->fetch_assoc()) { + printf("id = %d, label = %s\n", $row['id'], $row['label']); + } + + mysqli_free_result($res); + mysqli_close($link); + + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) { + printf("[007] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + } + + + if (!$res = mysqli_real_query($link, "SELECT id, label FROM test ORDER BY id")) + printf("[008] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (!is_object($res = mysqli_store_result($link, MYSQLI_STORE_RESULT_COPY_DATA))) + printf("[009] Expecting object, got %s/%s. [%d] %s\n", + gettype($res), $res, mysqli_errno($link), mysqli_error($link)); + + $no_result = 0; + for ($i = 0; $i < 1000; $i++) { + $idx = mt_rand(-100, 100); + if (true === @mysqli_data_seek($res, $idx)) { + $row = $res->fetch_assoc(); + if (!isset($row['id']) || !isset($row['label'])) { + printf("[010] Brute force seek %d returned %d\n", $idx, var_export($row, true)); + } + } else { + $no_result++; + } + } + printf("No result: %d\n", $no_result); + + /* implicit free, implicit store */ + /* meta and fetch lenghts code */ + if (!$res = mysqli_query($link, "SELECT CONCAT(id, id) AS _c, label FROM test ORDER BY id DESC LIMIT 2")) + printf("[011] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + printf("Default\n"); + var_dump(mysqli_fetch_lengths($res)); + $fields = $res->fetch_fields(); + while ($row = $res->fetch_assoc()) { + var_dump(mysqli_fetch_lengths($res)); + } + var_dump(mysqli_fetch_lengths($res)); + + if (!$res = mysqli_real_query($link, "SELECT CONCAT(id, id) AS _c, label FROM test ORDER BY id DESC LIMIT 2")) + printf("[012] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (!is_object($res = mysqli_store_result($link, MYSQLI_STORE_RESULT_COPY_DATA))) + printf("[013] Expecting object, got %s/%s. [%d] %s\n", + gettype($res), $res, mysqli_errno($link), mysqli_error($link)); + + printf("Copy\n"); + var_dump(mysqli_fetch_lengths($res)); + $fields_copy = $res->fetch_fields(); + while ($row = $res->fetch_assoc()) { + var_dump(mysqli_fetch_lengths($res)); + } + var_dump(mysqli_fetch_lengths($res)); + + /* There's no need for in-depth testing here. If you want in-depth switch mysqlnd + globally to copy mode and run all the tests */ + foreach ($fields as $k => $field_info) { + if ($fields_copy[$k] != $field_info) { + printf("[014] Metadata seems to differ, dumping\n"); + var_dump($field_info); + var_dump($fields_copy[$k]); + } + } + + /* fetch all */ + + if (!$res = mysqli_real_query($link, "SELECT id, label FROM test ORDER BY id DESC LIMIT 2")) + printf("[015] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (!is_object($res = mysqli_store_result($link, MYSQLI_STORE_RESULT_COPY_DATA))) + printf("[016] Expecting object, got %s/%s. [%d] %s\n", + gettype($res), $res, mysqli_errno($link), mysqli_error($link)); + + foreach (mysqli_fetch_all($res, MYSQLI_ASSOC) as $row) { + printf("id = %d label = %s\n", $row['id'], $row['label']); + } + + if (!$res = mysqli_real_query($link, "SELECT id, label FROM test ORDER BY id DESC LIMIT 2")) + printf("[017] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (!is_object($res = mysqli_store_result($link, MYSQLI_STORE_RESULT_COPY_DATA))) + printf("[018] Expecting object, got %s/%s. [%d] %s\n", + gettype($res), $res, mysqli_errno($link), mysqli_error($link)); + + /* provoke out of sync */ + if (!mysqli_real_query($link, "SELECT id, label FROM test ORDER BY id DESC LIMIT 2")) + printf("[019] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + var_dump($res->fetch_assoc()); + + if (!$res = mysqli_real_query($link, "SELECT id, label FROM test ORDER BY id ASC LIMIT 2")) + printf("[020] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + if (!is_object($res = mysqli_store_result($link, MYSQLI_STORE_RESULT_COPY_DATA))) + printf("[021] Expecting object, got %s/%s. [%d] %s\n", + gettype($res), $res, mysqli_errno($link), mysqli_error($link)); + + /* user conn killed, res associated with conn, fetch from res */ + unset($link); + var_dump($res->fetch_assoc()); + + + if (!$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket)) { + printf("[022] Cannot connect to the server using host=%s, user=%s, passwd=***, dbname=%s, port=%s, socket=%s\n", + $host, $user, $db, $port, $socket); + } + + if (!$res = mysqli_real_query($link, "INSERT INTO test(id, label) VALUES (100, 'z')")) + printf("[023] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + + mysqli_store_result($link, MYSQLI_STORE_RESULT_COPY_DATA); + + if (mysqli_get_server_version($link) > 50000) { + // let's try to play with stored procedures + mysqli_real_query($link, 'DROP PROCEDURE IF EXISTS p'); + if (mysqli_real_query($link, 'CREATE PROCEDURE p(OUT ver_param VARCHAR(25)) READS SQL DATA BEGIN SELECT id FROM test WHERE id >= 100 ORDER BY id; SELECT id + 1, label FROM test WHERE id > 0 AND id < 3 ORDER BY id; SELECT VERSION() INTO ver_param; +END;')) { + mysqli_multi_query($link, "CALL p(@version)"); + do { + if ($res = $link->store_result(MYSQLI_STORE_RESULT_COPY_DATA)) { + printf("---\n"); + var_dump($res->fetch_all()); + $res->free(); + } else { + if ($link->errno) { + echo "Store failed: (" . $link->errno . ") " . $link->error; + } + } + } while ($link->more_results() && $link->next_result()); + mysqli_real_query($link, 'SELECT @version AS p_version'); + $res = mysqli_store_result($link, MYSQLI_STORE_RESULT_COPY_DATA); + + $tmp = mysqli_fetch_assoc($res); + if (!is_array($tmp) || empty($tmp) || !isset($tmp['p_version']) || ('' == $tmp['p_version'])) { + printf("[024] Expecting array [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + var_dump($tmp); + } + + mysqli_free_result($res); + } else { + printf("[025] [%d] %s\n", mysqli_errno($link), mysqli_error($link)); + } + } + + print "done!"; +?> +--CLEAN-- +<?php + require_once("clean_table.inc"); +?> +--EXPECTF-- +array(2) { + ["id"]=> + string(1) "3" + ["label"]=> + string(1) "c" +} +id = 1, label = a +id = 2, label = b +id = 3, label = c +id = 4, label = d +id = 5, label = e +id = 6, label = f +No result: %d +Default +bool(false) +array(2) { + [0]=> + int(2) + [1]=> + int(1) +} +array(2) { + [0]=> + int(2) + [1]=> + int(1) +} +bool(false) +Copy +bool(false) +array(2) { + [0]=> + int(2) + [1]=> + int(1) +} +array(2) { + [0]=> + int(2) + [1]=> + int(1) +} +bool(false) +id = 6 label = f +id = 5 label = e +array(2) { + ["id"]=> + string(1) "6" + ["label"]=> + string(1) "f" +} +[020] [2014] %s +array(2) { + ["id"]=> + string(1) "6" + ["label"]=> + string(1) "f" +} +--- +array(1) { + [0]=> + array(1) { + [0]=> + string(3) "100" + } +} +--- +array(2) { + [0]=> + array(2) { + [0]=> + string(1) "2" + [1]=> + string(1) "a" + } + [1]=> + array(2) { + [0]=> + string(1) "3" + [1]=> + string(1) "b" + } +} +done!
\ No newline at end of file diff --git a/ext/mysqlnd/mysqlnd.c b/ext/mysqlnd/mysqlnd.c index 3c1bc82f44..e64efe9122 100644 --- a/ext/mysqlnd/mysqlnd.c +++ b/ext/mysqlnd/mysqlnd.c @@ -1111,7 +1111,8 @@ PHPAPI MYSQLND * mysqlnd_connect(MYSQLND * conn_handle, const char * db, unsigned int db_len, unsigned int port, const char * socket_or_pipe, - unsigned int mysql_flags + unsigned int mysql_flags, + unsigned int client_api_flags TSRMLS_DC) { enum_func_status ret = FAIL; @@ -1122,7 +1123,7 @@ PHPAPI MYSQLND * mysqlnd_connect(MYSQLND * conn_handle, if (!conn_handle) { self_alloced = TRUE; - if (!(conn_handle = mysqlnd_init(FALSE))) { + if (!(conn_handle = mysqlnd_init(client_api_flags, FALSE))) { /* OOM */ DBG_RETURN(NULL); } @@ -2570,6 +2571,7 @@ MYSQLND_METHOD(mysqlnd_conn_data, store_result)(MYSQLND_CONN_DATA * const conn, if (PASS == conn->m->local_tx_start(conn, this_func TSRMLS_CC)) { do { + unsigned int f = flags; if (!conn->current_result) { break; } @@ -2583,7 +2585,24 @@ MYSQLND_METHOD(mysqlnd_conn_data, store_result)(MYSQLND_CONN_DATA * const conn, MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_BUFFERED_SETS); - result = conn->current_result->m.store_result(conn->current_result, conn, 0 TSRMLS_CC); + /* overwrite */ + if ((conn->m->get_client_api_capabilities(conn TSRMLS_CC) & MYSQLND_CLIENT_KNOWS_RSET_COPY_DATA)) { + if (MYSQLND_G(fetch_data_copy)) { + f &= ~MYSQLND_STORE_NO_COPY; + f |= MYSQLND_STORE_COPY; + } + } else { + /* if for some reason PDO borks something */ + if (!(f & (MYSQLND_STORE_NO_COPY | MYSQLND_STORE_COPY))) { + f |= MYSQLND_STORE_COPY; + } + } + if (!(f & (MYSQLND_STORE_NO_COPY | MYSQLND_STORE_COPY))) { + SET_CLIENT_ERROR(*conn->error_info, CR_UNKNOWN_ERROR, UNKNOWN_SQLSTATE, "Unknown fetch mode"); + DBG_ERR("Unknown fetch mode"); + break; + } + result = conn->current_result->m.store_result(conn->current_result, conn, f TSRMLS_CC); if (!result) { conn->current_result->m.free_result(conn->current_result, TRUE TSRMLS_CC); } @@ -2839,6 +2858,32 @@ MYSQLND_METHOD(mysqlnd_conn_data, tx_savepoint_release)(MYSQLND_CONN_DATA * conn /* }}} */ +/* {{{ mysqlnd_conn_data::negotiate_client_api_capabilities */ +static unsigned int +MYSQLND_METHOD(mysqlnd_conn_data, negotiate_client_api_capabilities)(MYSQLND_CONN_DATA * const conn, const unsigned int flags TSRMLS_DC) +{ + unsigned int ret = 0; + DBG_ENTER("mysqlnd_conn_data::negotiate_client_api_capabilities"); + if (conn) { + ret = conn->client_api_capabilities; + conn->client_api_capabilities = flags; + } + + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_conn_data::get_client_api_capabilities */ +static unsigned int +MYSQLND_METHOD(mysqlnd_conn_data, get_client_api_capabilities)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_conn_data::get_client_api_capabilities"); + DBG_RETURN(conn? conn->client_api_capabilities : 0); +} +/* }}} */ + + /* {{{ mysqlnd_conn_data::local_tx_start */ static enum_func_status MYSQLND_METHOD(mysqlnd_conn_data, local_tx_start)(MYSQLND_CONN_DATA * conn, size_t this_func TSRMLS_DC) @@ -2967,7 +3012,10 @@ MYSQLND_CLASS_METHODS_START(mysqlnd_conn_data) MYSQLND_METHOD(mysqlnd_conn_data, simple_command_send_request), MYSQLND_METHOD(mysqlnd_conn_data, fetch_auth_plugin_by_name), - MYSQLND_METHOD(mysqlnd_conn_data, set_client_option_2d) + MYSQLND_METHOD(mysqlnd_conn_data, set_client_option_2d), + + MYSQLND_METHOD(mysqlnd_conn_data, negotiate_client_api_capabilities), + MYSQLND_METHOD(mysqlnd_conn_data, get_client_api_capabilities) MYSQLND_CLASS_METHODS_END; @@ -3046,11 +3094,14 @@ MYSQLND_CLASS_METHODS_END; /* {{{ _mysqlnd_init */ PHPAPI MYSQLND * -_mysqlnd_init(zend_bool persistent TSRMLS_DC) +_mysqlnd_init(unsigned int flags, zend_bool persistent TSRMLS_DC) { MYSQLND * ret; DBG_ENTER("mysqlnd_init"); ret = MYSQLND_CLASS_METHOD_TABLE_NAME(mysqlnd_object_factory).get_connection(persistent TSRMLS_CC); + if (ret && ret->data) { + ret->data->m->negotiate_client_api_capabilities(ret->data, flags TSRMLS_CC); + } DBG_RETURN(ret); } /* }}} */ diff --git a/ext/mysqlnd/mysqlnd.h b/ext/mysqlnd/mysqlnd.h index 551c5f34ac..f373ea5dd5 100644 --- a/ext/mysqlnd/mysqlnd.h +++ b/ext/mysqlnd/mysqlnd.h @@ -85,15 +85,16 @@ PHPAPI const MYSQLND_CHARSET * mysqlnd_find_charset_name(const char * const char /* Connect */ -#define mysqlnd_init(persistent) _mysqlnd_init((persistent) TSRMLS_CC) -PHPAPI MYSQLND * _mysqlnd_init(zend_bool persistent TSRMLS_DC); +#define mysqlnd_init(client_flags, persistent) _mysqlnd_init((client_flags), (persistent) TSRMLS_CC) +PHPAPI MYSQLND * _mysqlnd_init(unsigned int client_flags, zend_bool persistent TSRMLS_DC); PHPAPI MYSQLND * mysqlnd_connect(MYSQLND * conn, const char * host, const char * user, const char * passwd, unsigned int passwd_len, const char * db, unsigned int db_len, unsigned int port, const char * socket_or_pipe, - unsigned int mysql_flags + unsigned int mysql_flags, + unsigned int client_api_flags TSRMLS_DC); #define mysqlnd_change_user(conn, user, passwd, db, silent) ((conn)->data)->m->change_user((conn)->data, (user), (passwd), (db), (silent), strlen((passwd)) TSRMLS_CC) @@ -282,6 +283,7 @@ ZEND_BEGIN_MODULE_GLOBALS(mysqlnd) long debug_calloc_fail_threshold; long debug_realloc_fail_threshold; char * sha256_server_public_key; + zend_bool fetch_data_copy; ZEND_END_MODULE_GLOBALS(mysqlnd) PHPAPI ZEND_EXTERN_MODULE_GLOBALS(mysqlnd) diff --git a/ext/mysqlnd/mysqlnd_enum_n_def.h b/ext/mysqlnd/mysqlnd_enum_n_def.h index d771998577..e1fc5f8f01 100644 --- a/ext/mysqlnd/mysqlnd_enum_n_def.h +++ b/ext/mysqlnd/mysqlnd_enum_n_def.h @@ -620,6 +620,16 @@ enum php_mysqlnd_server_command #define MYSQLND_STORE_NO_COPY 2 #define MYSQLND_STORE_COPY 4 +enum mysqlnd_buffered_type +{ + MYSQLND_BUFFERED_TYPE_ZVAL = 1, + MYSQLND_BUFFERED_TYPE_C +}; + + +#define MYSQLND_CLIENT_NO_FLAG 0 +#define MYSQLND_CLIENT_KNOWS_RSET_COPY_DATA 1 + #endif /* MYSQLND_ENUM_N_DEF_H */ diff --git a/ext/mysqlnd/mysqlnd_ext_plugin.c b/ext/mysqlnd/mysqlnd_ext_plugin.c index 706111346f..13f1e294dc 100644 --- a/ext/mysqlnd/mysqlnd_ext_plugin.c +++ b/ext/mysqlnd/mysqlnd_ext_plugin.c @@ -84,14 +84,26 @@ PHPAPI void ** _mysqlnd_plugin_get_plugin_result_unbuffered_data(const MYSQLND_R /* {{{ _mysqlnd_plugin_get_plugin_result_buffered_data */ -PHPAPI void ** _mysqlnd_plugin_get_plugin_result_buffered_data(const MYSQLND_RES_BUFFERED * result, unsigned int plugin_id TSRMLS_DC) +PHPAPI void ** _mysqlnd_plugin_get_plugin_result_buffered_data_zval(const MYSQLND_RES_BUFFERED_ZVAL * result, unsigned int plugin_id TSRMLS_DC) { DBG_ENTER("_mysqlnd_plugin_get_plugin_result_data"); DBG_INF_FMT("plugin_id=%u", plugin_id); if (!result || plugin_id >= mysqlnd_plugin_count()) { return NULL; } - DBG_RETURN((void *)((char *)result + sizeof(MYSQLND_RES_BUFFERED) + plugin_id * sizeof(void *))); + DBG_RETURN((void *)((char *)result + sizeof(MYSQLND_RES_BUFFERED_ZVAL) + plugin_id * sizeof(void *))); +} +/* }}} */ + +/* {{{ _mysqlnd_plugin_get_plugin_result_buffered_data */ +PHPAPI void ** _mysqlnd_plugin_get_plugin_result_buffered_data_c(const MYSQLND_RES_BUFFERED_C * result, unsigned int plugin_id TSRMLS_DC) +{ + DBG_ENTER("_mysqlnd_plugin_get_plugin_result_data"); + DBG_INF_FMT("plugin_id=%u", plugin_id); + if (!result || plugin_id >= mysqlnd_plugin_count()) { + return NULL; + } + DBG_RETURN((void *)((char *)result + sizeof(MYSQLND_RES_BUFFERED_C) + plugin_id * sizeof(void *))); } /* }}} */ diff --git a/ext/mysqlnd/mysqlnd_ext_plugin.h b/ext/mysqlnd/mysqlnd_ext_plugin.h index 6f15eee8ce..d4a9d6cfc0 100644 --- a/ext/mysqlnd/mysqlnd_ext_plugin.h +++ b/ext/mysqlnd/mysqlnd_ext_plugin.h @@ -34,9 +34,11 @@ PHPAPI void ** _mysqlnd_plugin_get_plugin_result_data(const MYSQLND_RES * result PHPAPI void ** _mysqlnd_plugin_get_plugin_result_unbuffered_data(const MYSQLND_RES_UNBUFFERED * result, unsigned int plugin_id TSRMLS_DC); #define mysqlnd_plugin_get_plugin_result_unbuffered_data(r, p_id) _mysqlnd_plugin_get_plugin_result_unbuffered_data((r), (p_id) TSRMLS_CC) -PHPAPI void ** _mysqlnd_plugin_get_plugin_result_buffered_data(const MYSQLND_RES_BUFFERED * result, unsigned int plugin_id TSRMLS_DC); -#define mysqlnd_plugin_get_plugin_result_buffered_data(r, p_id) _mysqlnd_plugin_get_plugin_result_buffered_data((r), (p_id) TSRMLS_CC) +PHPAPI void ** _mysqlnd_plugin_get_plugin_result_buffered_data_zval(const MYSQLND_RES_BUFFERED_ZVAL * result, unsigned int plugin_id TSRMLS_DC); +#define mysqlnd_plugin_get_plugin_result_buffered_data_zval(r, p_id) _mysqlnd_plugin_get_plugin_result_buffered_data_zval((r), (p_id) TSRMLS_CC) +PHPAPI void ** _mysqlnd_plugin_get_plugin_result_buffered_data_c(const MYSQLND_RES_BUFFERED_C * result, unsigned int plugin_id TSRMLS_DC); +#define mysqlnd_plugin_get_plugin_result_buffered_data_c(r, p_id) _mysqlnd_plugin_get_plugin_result_buffered_data_c((r), (p_id) TSRMLS_CC) PHPAPI void ** _mysqlnd_plugin_get_plugin_stmt_data(const MYSQLND_STMT * stmt, unsigned int plugin_id TSRMLS_DC); #define mysqlnd_plugin_get_plugin_stmt_data(s, p_id) _mysqlnd_plugin_get_plugin_stmt_data((s), (p_id) TSRMLS_CC) diff --git a/ext/mysqlnd/mysqlnd_ps.c b/ext/mysqlnd/mysqlnd_ps.c index fc30bddcb5..bee8e1d0ee 100644 --- a/ext/mysqlnd/mysqlnd_ps.c +++ b/ext/mysqlnd/mysqlnd_ps.c @@ -89,36 +89,39 @@ MYSQLND_METHOD(mysqlnd_stmt, store_result)(MYSQLND_STMT * const s TSRMLS_DC) result->type = MYSQLND_RES_PS_BUF; /* result->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; */ - result->stored_data = mysqlnd_result_buffered_init(result->field_count, TRUE, result->persistent TSRMLS_CC); + result->stored_data = (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_zval_init(result->field_count, TRUE, result->persistent TSRMLS_CC); if (!result->stored_data) { SET_OOM_ERROR(*conn->error_info); DBG_RETURN(NULL); } - ret = result->m.store_result_fetch_data(conn, result, result->meta, TRUE TSRMLS_CC); + ret = result->m.store_result_fetch_data(conn, result, result->meta, &result->stored_data->row_buffers, TRUE TSRMLS_CC); result->stored_data->m.fetch_row = mysqlnd_stmt_fetch_row_buffered; if (PASS == ret) { /* Overflow ? */ - MYSQLND_RES_BUFFERED * set = result->stored_data; - if (set->row_count) { - /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */ - if (set->row_count * result->meta->field_count * sizeof(zval *) > SIZE_MAX) { - SET_OOM_ERROR(*conn->error_info); - DBG_RETURN(NULL); - } - /* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */ - set->data = mnd_emalloc((size_t)(set->row_count * result->meta->field_count * sizeof(zval *))); - if (!set->data) { - SET_OOM_ERROR(*conn->error_info); - DBG_RETURN(NULL); + if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) { + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data; + if (result->stored_data->row_count) { + /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */ + if (result->stored_data->row_count * result->meta->field_count * sizeof(zval *) > SIZE_MAX) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + /* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */ + set->data = mnd_emalloc((size_t)(result->stored_data->row_count * result->meta->field_count * sizeof(zval *))); + if (!set->data) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + memset(set->data, 0, (size_t)(result->stored_data->row_count * result->meta->field_count * sizeof(zval *))); } - memset(set->data, 0, (size_t)(set->row_count * result->meta->field_count * sizeof(zval *))); + /* Position at the first row */ + set->data_cursor = set->data; + } else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) { + /*TODO*/ } - /* Position at the first row */ - set->data_cursor = set->data; - /* libmysql API docs say it should be so for SELECT statements */ stmt->upsert_status->affected_rows = stmt->result->stored_data->row_count; @@ -187,7 +190,7 @@ MYSQLND_METHOD(mysqlnd_stmt, get_result)(MYSQLND_STMT * const s TSRMLS_DC) break; } - if ((result = result->m.store_result(result, conn, TRUE TSRMLS_CC))) { + if ((result = result->m.store_result(result, conn, MYSQLND_STORE_PS | MYSQLND_STORE_NO_COPY TSRMLS_CC))) { stmt->upsert_status->affected_rows = result->stored_data->row_count; stmt->state = MYSQLND_STMT_PREPARED; result->type = MYSQLND_RES_PS_BUF; @@ -733,7 +736,6 @@ mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES * result, void * param, unsigned int { MYSQLND_STMT * s = (MYSQLND_STMT *) param; MYSQLND_STMT_DATA * stmt = s? s->data:NULL; - MYSQLND_RES_BUFFERED *set = result->stored_data; const MYSQLND_RES_METADATA * const meta = result->meta; unsigned int field_count = meta->field_count; @@ -742,81 +744,86 @@ mysqlnd_stmt_fetch_row_buffered(MYSQLND_RES * result, void * param, unsigned int DBG_INF_FMT("stmt=%lu", stmt != NULL ? stmt->stmt_id : 0L); /* If we haven't read everything */ - if (set->data_cursor && - (set->data_cursor - set->data) < (set->row_count * field_count)) - { - /* The user could have skipped binding - don't crash*/ - if (stmt->result_bind) { - unsigned int i; - zval **current_row = set->data_cursor; - - if (NULL == current_row[0]) { - uint64_t row_num = (set->data_cursor - set->data) / field_count; - enum_func_status rc = result->stored_data->m.row_decoder(set->row_buffers[row_num], - current_row, - meta->field_count, - meta->fields, - result->conn->options->int_and_float_native, - result->conn->stats TSRMLS_CC); - if (PASS != rc) { - DBG_RETURN(FAIL); - } - set->initialized_rows++; - if (stmt->update_max_length) { - for (i = 0; i < result->field_count; i++) { - /* - NULL fields are 0 length, 0 is not more than 0 - String of zero size, definitely can't be the next max_length. - Thus for NULL and zero-length we are quite efficient. - */ - if (Z_TYPE_P(current_row[i]) >= IS_STRING) { - unsigned long len = Z_STRLEN_P(current_row[i]); - if (meta->fields[i].max_length < len) { - meta->fields[i].max_length = len; + if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) { + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data; + if (set->data_cursor && + (set->data_cursor - set->data) < (result->stored_data->row_count * field_count)) + { + /* The user could have skipped binding - don't crash*/ + if (stmt->result_bind) { + unsigned int i; + zval **current_row = set->data_cursor; + + if (NULL == current_row[0]) { + uint64_t row_num = (set->data_cursor - set->data) / field_count; + enum_func_status rc = result->stored_data->m.row_decoder(result->stored_data->row_buffers[row_num], + current_row, + meta->field_count, + meta->fields, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); + if (PASS != rc) { + DBG_RETURN(FAIL); + } + result->stored_data->initialized_rows++; + if (stmt->update_max_length) { + for (i = 0; i < result->field_count; i++) { + /* + NULL fields are 0 length, 0 is not more than 0 + String of zero size, definitely can't be the next max_length. + Thus for NULL and zero-length we are quite efficient. + */ + if (Z_TYPE_P(current_row[i]) >= IS_STRING) { + unsigned long len = Z_STRLEN_P(current_row[i]); + if (meta->fields[i].max_length < len) { + meta->fields[i].max_length = len; + } } } } } - } - for (i = 0; i < result->field_count; i++) { - /* Clean what we copied last time */ + for (i = 0; i < result->field_count; i++) { + /* Clean what we copied last time */ #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF - if (stmt->result_bind[i].zv) { - zval_dtor(stmt->result_bind[i].zv); - } + if (stmt->result_bind[i].zv) { + zval_dtor(stmt->result_bind[i].zv); + } #endif - /* copy the type */ - if (stmt->result_bind[i].bound == TRUE) { - DBG_INF_FMT("i=%u type=%u", i, Z_TYPE_P(current_row[i])); - if (Z_TYPE_P(current_row[i]) != IS_NULL) { - /* - Copy the value. - Pre-condition is that the zvals in the result_bind buffer - have been ZVAL_NULL()-ed or to another simple type - (int, double, bool but not string). Because of the reference - counting the user can't delete the strings the variables point to. - */ - - Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(current_row[i]); - stmt->result_bind[i].zv->value = current_row[i]->value; + /* copy the type */ + if (stmt->result_bind[i].bound == TRUE) { + DBG_INF_FMT("i=%u type=%u", i, Z_TYPE_P(current_row[i])); + if (Z_TYPE_P(current_row[i]) != IS_NULL) { + /* + Copy the value. + Pre-condition is that the zvals in the result_bind buffer + have been ZVAL_NULL()-ed or to another simple type + (int, double, bool but not string). Because of the reference + counting the user can't delete the strings the variables point to. + */ + + Z_TYPE_P(stmt->result_bind[i].zv) = Z_TYPE_P(current_row[i]); + stmt->result_bind[i].zv->value = current_row[i]->value; #ifndef WE_DONT_COPY_IN_BUFFERED_AND_UNBUFFERED_BECAUSEOF_IS_REF - zval_copy_ctor(stmt->result_bind[i].zv); + zval_copy_ctor(stmt->result_bind[i].zv); #endif - } else { - ZVAL_NULL(stmt->result_bind[i].zv); + } else { + ZVAL_NULL(stmt->result_bind[i].zv); + } } } } + set->data_cursor += field_count; + *fetched_anything = TRUE; + /* buffered result sets don't have a connection */ + MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF); + DBG_INF("row fetched"); + } else { + set->data_cursor = NULL; + DBG_INF("no more data"); } - set->data_cursor += field_count; - *fetched_anything = TRUE; - /* buffered result sets don't have a connection */ - MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_PS_BUF); - DBG_INF("row fetched"); - } else { - set->data_cursor = NULL; - DBG_INF("no more data"); + } else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_C) { + /*TODO*/ } DBG_INF("PASS"); DBG_RETURN(PASS); diff --git a/ext/mysqlnd/mysqlnd_result.c b/ext/mysqlnd/mysqlnd_result.c index fc6f48581c..0112a1394d 100644 --- a/ext/mysqlnd/mysqlnd_result.c +++ b/ext/mysqlnd/mysqlnd_result.c @@ -32,29 +32,30 @@ #define MYSQLND_SILENT - -/* {{{ mysqlnd_result_buffered::initialize_result_set_rest */ +/* {{{ mysqlnd_result_buffered_zval::initialize_result_set_rest */ static enum_func_status -MYSQLND_METHOD(mysqlnd_result_buffered, initialize_result_set_rest)(MYSQLND_RES_BUFFERED * const result, MYSQLND_RES_METADATA * const meta, - MYSQLND_STATS * stats, zend_bool int_and_float_native TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_result_buffered_zval, initialize_result_set_rest)(MYSQLND_RES_BUFFERED * const result, MYSQLND_RES_METADATA * const meta, + MYSQLND_STATS * stats, zend_bool int_and_float_native TSRMLS_DC) { unsigned int i; - zval **data_cursor = result->data; - zval **data_begin = result->data; - unsigned int field_count = meta->field_count; - uint64_t row_count = result->row_count; enum_func_status ret = PASS; - DBG_ENTER("mysqlnd_result_buffered::initialize_result_set_rest"); + const unsigned int field_count = meta->field_count; + const uint64_t row_count = result->row_count; + enum_func_status rc; + + zval **data_begin = ((MYSQLND_RES_BUFFERED_ZVAL *) result)->data; + zval **data_cursor = data_begin; + + DBG_ENTER("mysqlnd_result_buffered_zval::initialize_result_set_rest"); if (!data_cursor || row_count == result->initialized_rows) { DBG_RETURN(ret); } while ((data_cursor - data_begin) < (int)(row_count * field_count)) { if (NULL == data_cursor[0]) { - enum_func_status rc = result->m.row_decoder( - result->row_buffers[(data_cursor - data_begin) / field_count], + rc = result->m.row_decoder(result->row_buffers[(data_cursor - data_begin) / field_count], data_cursor, - meta->field_count, + field_count, meta->fields, int_and_float_native, stats TSRMLS_CC); @@ -63,7 +64,7 @@ MYSQLND_METHOD(mysqlnd_result_buffered, initialize_result_set_rest)(MYSQLND_RES_ break; } result->initialized_rows++; - for (i = 0; i < meta->field_count; i++) { + for (i = 0; i < 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. @@ -84,6 +85,62 @@ MYSQLND_METHOD(mysqlnd_result_buffered, initialize_result_set_rest)(MYSQLND_RES_ /* }}} */ +/* {{{ mysqlnd_result_buffered_c::initialize_result_set_rest */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_result_buffered_c, initialize_result_set_rest)(MYSQLND_RES_BUFFERED * const result, MYSQLND_RES_METADATA * const meta, + MYSQLND_STATS * stats, zend_bool int_and_float_native TSRMLS_DC) +{ + unsigned int i; + enum_func_status ret = PASS; + const unsigned int field_count = meta->field_count; + const uint64_t row_count = result->row_count; + enum_func_status rc; + DBG_ENTER("mysqlnd_result_buffered_c::initialize_result_set_rest"); + + if (result->initialized_rows < row_count) { + zend_uchar * initialized = ((MYSQLND_RES_BUFFERED_C *) result)->initialized; + zval ** current_row = mnd_emalloc(field_count * sizeof(zval *)); + + if (!current_row) { + DBG_RETURN(FAIL); + } + + for (i = 0; i < result->row_count; i++) { + /* (i / 8) & the_bit_for_i*/ + if (initialized[i >> 3] & (1 << (i & 7))) { + continue; + } + + rc = result->m.row_decoder(result->row_buffers[i], current_row, field_count, meta->fields, int_and_float_native, stats TSRMLS_CC); + + if (rc != PASS) { + ret = FAIL; + break; + } + result->initialized_rows++; + initialized[i >> 3] |= (1 << (i & 7)); + for (i = 0; i < 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; + } + } + zval_ptr_dtor(¤t_row[i]); + } + } + mnd_efree(current_row); + } + DBG_RETURN(ret); +} +/* }}} */ + + /* {{{ mysqlnd_rset_zval_ptr_dtor */ static void mysqlnd_rset_zval_ptr_dtor(zval **zv, enum_mysqlnd_res_type type, zend_bool * copy_ctor_called TSRMLS_DC) @@ -205,25 +262,23 @@ MYSQLND_METHOD(mysqlnd_result_unbuffered, free_result)(MYSQLND_RES_UNBUFFERED * /* }}} */ -/* {{{ mysqlnd_result_buffered::free_result */ +/* {{{ mysqlnd_result_buffered_zval::free_result */ static void -MYSQLND_METHOD(mysqlnd_result_buffered, free_result)(MYSQLND_RES_BUFFERED * const set TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_result_buffered_zval, free_result)(MYSQLND_RES_BUFFERED_ZVAL * const set TSRMLS_DC) { - unsigned int field_count = set->field_count; - int64_t row; + zval ** data = set->data; - DBG_ENTER("mysqlnd_res::free_buffered_data"); - DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", set->row_count); + DBG_ENTER("mysqlnd_result_buffered_zval::free_result"); - if (set->data) { + set->data = NULL; /* prevent double free if following loop is interrupted */ + if (data) { unsigned int copy_on_write_performed = 0; unsigned int copy_on_write_saved = 0; - zval **data = set->data; - set->data = NULL; /* prevent double free if following loop is interrupted */ - + unsigned int field_count = set->field_count; + int64_t row; + for (row = set->row_count - 1; row >= 0; row--) { zval **current_row = data + row * field_count; - MYSQLND_MEMORY_POOL_CHUNK *current_buffer = set->row_buffers[row]; int64_t col; if (current_row != NULL) { @@ -239,13 +294,48 @@ MYSQLND_METHOD(mysqlnd_result_buffered, free_result)(MYSQLND_RES_BUFFERED * cons } } } - current_buffer->free_chunk(current_buffer TSRMLS_CC); } - MYSQLND_INC_GLOBAL_STATISTIC_W_VALUE2(STAT_COPY_ON_WRITE_PERFORMED, copy_on_write_performed, STAT_COPY_ON_WRITE_SAVED, copy_on_write_saved); mnd_efree(data); } + set->data_cursor = NULL; + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_result_buffered_c::free_result */ +static void +MYSQLND_METHOD(mysqlnd_result_buffered_c, free_result)(MYSQLND_RES_BUFFERED_C * const set TSRMLS_DC) +{ + DBG_ENTER("mysqlnd_result_buffered_c::free_result"); + mnd_pefree(set->initialized, set->persistent); + set->initialized = NULL; + DBG_VOID_RETURN; +} +/* }}} */ + + +/* {{{ mysqlnd_result_buffered::free_result */ +static void +MYSQLND_METHOD(mysqlnd_result_buffered, free_result)(MYSQLND_RES_BUFFERED * const set TSRMLS_DC) +{ + int64_t row; + + DBG_ENTER("mysqlnd_result_buffered::free_result"); + DBG_INF_FMT("Freeing "MYSQLND_LLU_SPEC" row(s)", set->row_count); + + if (set->type == MYSQLND_BUFFERED_TYPE_ZVAL) { + MYSQLND_METHOD(mysqlnd_result_buffered_zval, free_result)((MYSQLND_RES_BUFFERED_ZVAL *) set TSRMLS_CC); + } if (set->type == MYSQLND_BUFFERED_TYPE_C) { + MYSQLND_METHOD(mysqlnd_result_buffered_c, free_result)((MYSQLND_RES_BUFFERED_C *) set TSRMLS_CC); + } + + for (row = set->row_count - 1; row >= 0; row--) { + MYSQLND_MEMORY_POOL_CHUNK *current_buffer = set->row_buffers[row]; + current_buffer->free_chunk(current_buffer TSRMLS_CC); + } if (set->lengths) { mnd_pefree(set->lengths, set->persistent); @@ -253,8 +343,8 @@ MYSQLND_METHOD(mysqlnd_result_buffered, free_result)(MYSQLND_RES_BUFFERED * cons } if (set->row_buffers) { - mnd_efree(set->row_buffers); - set->row_buffers = NULL; + mnd_pefree(set->row_buffers, 0); + set->row_buffers = NULL; } if (set->result_set_memory_pool) { @@ -263,7 +353,6 @@ MYSQLND_METHOD(mysqlnd_result_buffered, free_result)(MYSQLND_RES_BUFFERED * cons } - set->data_cursor = NULL; set->row_count = 0; mnd_pefree(set, set->persistent); @@ -590,31 +679,49 @@ mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * s completeness. */ static unsigned long * -MYSQLND_METHOD(mysqlnd_result_buffered, fetch_lengths)(MYSQLND_RES_BUFFERED * const result TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_lengths)(MYSQLND_RES_BUFFERED * const result TSRMLS_DC) { - unsigned int i; - zval **previous_row; - MYSQLND_RES_BUFFERED *set = result; - + const MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result; /* If: - unbuffered result - first row has not been read - last_row has been read */ + DBG_ENTER("mysqlnd_result_buffered_zval::fetch_lengths"); + if (set->data_cursor == NULL || set->data_cursor == set->data || - ((set->data_cursor - set->data) > (set->row_count * result->field_count) )) + ((set->data_cursor - set->data) > (result->row_count * result->field_count) )) { - return NULL;/* No rows or no more rows */ + DBG_INF("EOF"); + DBG_RETURN(NULL);/* No rows or no more rows */ } + DBG_INF("non NULL"); + DBG_RETURN(result->lengths); +} +/* }}} */ - previous_row = set->data_cursor - result->field_count; - 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_result_buffered_c::fetch_lengths */ +/* + 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_METHOD(mysqlnd_result_buffered_c, fetch_lengths)(MYSQLND_RES_BUFFERED * const result TSRMLS_DC) +{ + const MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result; + DBG_ENTER("mysqlnd_result_buffered_c::fetch_lengths"); + + if (set->current_row > set->row_count || set->current_row == 0) { + DBG_INF("EOF"); + DBG_RETURN(NULL); /* No more rows, or no fetched row */ + } + DBG_INF("non NULL"); + DBG_RETURN(result->lengths); } /* }}} */ @@ -767,7 +874,7 @@ MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row_c)(MYSQLND_RES * result, voi /* {{{ mysqlnd_result_unbuffered::fetch_row */ static enum_func_status -MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row)(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) { enum_func_status ret; zval *row = (zval *) param; @@ -944,20 +1051,111 @@ static enum_func_status MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row_c)(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) { MYSQLND_ROW_C * row = (MYSQLND_ROW_C *) param; - MYSQLND_RES_BUFFERED * set = result->stored_data; const MYSQLND_RES_METADATA * const meta = result->meta; unsigned int field_count = meta->field_count; enum_func_status ret = FAIL; - DBG_ENTER("mysqlnd_result_buffered::fetch_row_c"); + if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_ZVAL) { + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data; + + /* If we haven't read everything */ + if (set->data_cursor && + (set->data_cursor - set->data) < (result->stored_data->row_count * field_count)) + { + zval **current_row = set->data_cursor; + unsigned int i; + + if (NULL == current_row[0]) { + uint64_t row_num = (set->data_cursor - set->data) / field_count; + enum_func_status rc = set->m.row_decoder(set->row_buffers[row_num], + current_row, + field_count, + meta->fields, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); + if (rc != PASS) { + DBG_RETURN(FAIL); + } + set->initialized_rows++; + for (i = 0; i < 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; + } + } + } + } + +/* BEGIN difference between normal normal fetch and _c */ + /* there is no conn handle in this function thus we can't set OOM in error_info */ + *row = mnd_malloc(field_count * sizeof(char *)); + if (*row) { + for (i = 0; i < field_count; i++) { + zval * data = current_row[i]; + + set->lengths[i] = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data); + + if (Z_TYPE_P(data) != IS_NULL) { + convert_to_string(data); + (*row)[i] = Z_STRVAL_P(data); + } else { + (*row)[i] = NULL; + } + } + set->data_cursor += field_count; + MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF); + } else { + SET_OOM_ERROR(*result->conn->error_info); + } +/* END difference between normal normal fetch and _c */ + + *fetched_anything = *row? TRUE:FALSE; + ret = *row? PASS:FAIL; + } else { + set->data_cursor = NULL; + DBG_INF("EOF reached"); + *fetched_anything = FALSE; + ret = PASS; + } + } else if (result->stored_data->type == MYSQLND_BUFFERED_TYPE_C) { + /* + We don't support _C with pdo because it uses the data in a different way - just references it. + We will either leak or give nirvana pointers + */ + *fetched_anything = FALSE; + DBG_RETURN(FAIL); + } + DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_result_buffered_zval::fetch_row */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) +{ + zval * row = (zval *) param; + const MYSQLND_RES_METADATA * const meta = result->meta; + unsigned int field_count = meta->field_count; + enum_func_status ret = FAIL; + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data; + + DBG_ENTER("mysqlnd_result_buffered_zval::fetch_row"); + /* If we haven't read everything */ if (set->data_cursor && (set->data_cursor - set->data) < (set->row_count * field_count)) { - zval **current_row = set->data_cursor; - MYSQLND_FIELD * field = meta->fields; unsigned int i; + zval **current_row = set->data_cursor; if (NULL == current_row[0]) { uint64_t row_num = (set->data_cursor - set->data) / field_count; @@ -979,30 +1177,44 @@ MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row_c)(MYSQLND_RES * result, void */ if (Z_TYPE_P(current_row[i]) >= IS_STRING) { unsigned long len = Z_STRLEN_P(current_row[i]); - if (field->max_length < len) { - field->max_length = len; + if (meta->fields[i].max_length < len) { + meta->fields[i].max_length = len; } } } } -/* BEGIN difference between normal normal fetch and _c */ - /* there is no conn handle in this function thus we can't set OOM in error_info */ - *row = mnd_malloc(field_count * sizeof(char *)); - if (ret) { - for (i = 0; i < field_count; i++) { - zval * data = current_row[i]; + for (i = 0; i < field_count; i++) { + zval * data = current_row[i]; - if (Z_TYPE_P(data) != IS_NULL) { - convert_to_string(data); - (*row)[i] = Z_STRVAL_P(data); + set->lengths[i] = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data); + + if (flags & MYSQLND_FETCH_NUM) { + Z_ADDREF_P(data); + zend_hash_next_index_insert(Z_ARRVAL_P(row), &data, sizeof(zval *), NULL); + } + if (flags & MYSQLND_FETCH_ASSOC) { + /* zend_hash_quick_update needs length + trailing zero */ + /* QQ: Error handling ? */ + /* + zend_hash_quick_update does not check, as add_assoc_zval_ex do, whether + the index is a numeric and convert it to it. This however means constant + hashing of the column name, which is not needed as it can be precomputed. + */ + Z_ADDREF_P(data); + if (meta->zend_hash_keys[i].is_numeric == FALSE) { + 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 { - (*row)[i] = NULL; + zend_hash_index_update(Z_ARRVAL_P(row), + meta->zend_hash_keys[i].key, + (void *) &data, sizeof(zval *), NULL); } } } -/* END difference between normal normal fetch and _c */ - set->data_cursor += field_count; MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF); *fetched_anything = TRUE; @@ -1019,38 +1231,45 @@ MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row_c)(MYSQLND_RES * result, void /* }}} */ -/* {{{ mysqlnd_result_buffered::fetch_row */ +/* {{{ mysqlnd_result_buffered_c::fetch_row */ static enum_func_status -MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row)(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool * fetched_anything TSRMLS_DC) { zval * row = (zval *) param; - MYSQLND_RES_BUFFERED * set = result->stored_data; const MYSQLND_RES_METADATA * const meta = result->meta; unsigned int field_count = meta->field_count; enum_func_status ret = FAIL; - DBG_ENTER("mysqlnd_result_buffered::fetch_row"); + MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result->stored_data; + + DBG_ENTER("mysqlnd_result_buffered_c::fetch_row"); /* If we haven't read everything */ - if (set->data_cursor && - (set->data_cursor - set->data) < (set->row_count * field_count)) - { - zval **current_row = set->data_cursor; - MYSQLND_FIELD * field = meta->fields; + if (set->current_row < set->row_count) { + zval **current_row; + enum_func_status rc; unsigned int i; - if (NULL == current_row[0]) { - uint64_t row_num = (set->data_cursor - set->data) / field_count; - enum_func_status rc = set->m.row_decoder(set->row_buffers[row_num], - current_row, - field_count, - meta->fields, - result->conn->options->int_and_float_native, - result->conn->stats TSRMLS_CC); - if (rc != PASS) { - DBG_RETURN(FAIL); - } + current_row = mnd_emalloc(field_count * sizeof(zval *)); + if (!current_row) { + SET_OOM_ERROR(*result->conn->error_info); + DBG_RETURN(FAIL); + } + + rc = result->stored_data->m.row_decoder(result->stored_data->row_buffers[set->current_row], + current_row, + field_count, + meta->fields, + result->conn->options->int_and_float_native, + result->conn->stats TSRMLS_CC); + if (rc != PASS) { + DBG_RETURN(FAIL); + } + if (!(set->initialized[set->current_row >> 3] & (1 << (set->current_row & 7)))) { + set->initialized[set->current_row >> 3] |= (1 << (set->current_row & 7)); /* mark initialized */ + set->initialized_rows++; + for (i = 0; i < field_count; i++) { /* NULL fields are 0 length, 0 is not more than 0 @@ -1059,16 +1278,18 @@ MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row)(MYSQLND_RES * result, void * */ if (Z_TYPE_P(current_row[i]) >= IS_STRING) { unsigned long len = Z_STRLEN_P(current_row[i]); - if (field->max_length < len) { - field->max_length = len; + if (meta->fields[i].max_length < len) { + meta->fields[i].max_length = len; } } } } - for (i = 0; i < field_count; i++, field++) { + for (i = 0; i < field_count; i++) { zval * data = current_row[i]; + set->lengths[i] = (Z_TYPE_P(data) == IS_NULL)? 0:Z_STRLEN_P(data); + if (flags & MYSQLND_FETCH_NUM) { Z_ADDREF_P(data); zend_hash_next_index_insert(Z_ARRVAL_P(row), &data, sizeof(zval *), NULL); @@ -1084,8 +1305,8 @@ MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row)(MYSQLND_RES * result, void * Z_ADDREF_P(data); if (meta->zend_hash_keys[i].is_numeric == FALSE) { zend_hash_quick_update(Z_ARRVAL_P(row), - field->name, - field->name_length + 1, + meta->fields[i].name, + meta->fields[i].name_length + 1, meta->zend_hash_keys[i].key, (void *) &data, sizeof(zval *), NULL); } else { @@ -1094,17 +1315,28 @@ MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row)(MYSQLND_RES * result, void * (void *) &data, sizeof(zval *), NULL); } } + /* + This will usually not destroy anything but decref. + However, if neither NUM nor ASSOC is set we will free memory cleanly and won't leak. + It also simplifies the handling of Z_ADDREF_P because we don't need to check if only + either NUM or ASSOC is set but not both. + */ + zval_ptr_dtor(&data); } - set->data_cursor += field_count; + mnd_efree(current_row); + set->current_row++; MYSQLND_INC_GLOBAL_STATISTIC(STAT_ROWS_FETCHED_FROM_CLIENT_NORMAL_BUF); *fetched_anything = TRUE; ret = PASS; } else { - set->data_cursor = NULL; - DBG_INF("EOF reached"); + if (set->current_row == set->row_count) { + set->current_row = set->row_count + 1; + } + DBG_INF_FMT("EOF reached. current_row=%llu", (unsigned long long) set->current_row); *fetched_anything = FALSE; ret = PASS; } + DBG_INF_FMT("ret=PASS fetched=%u", *fetched_anything); DBG_RETURN(ret); } @@ -1113,7 +1345,7 @@ MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row)(MYSQLND_RES * result, void * /* {{{ mysqlnd_res::fetch_row */ static enum_func_status -MYSQLND_METHOD(mysqlnd_res, fetch_row)(MYSQLND_RES * result, void * param, unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_res, fetch_row)(MYSQLND_RES * result, void * param, const unsigned int flags, zend_bool *fetched_anything TSRMLS_DC) { const mysqlnd_fetch_row_func f = result->stored_data? result->stored_data->m.fetch_row:(result->unbuf? result->unbuf->m.fetch_row:NULL); if (f) { @@ -1131,6 +1363,7 @@ MYSQLND_METHOD(mysqlnd_res, fetch_row)(MYSQLND_RES * result, void * param, unsig enum_func_status MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result, MYSQLND_RES_METADATA * meta, + MYSQLND_MEMORY_POOL_CHUNK ***row_buffers, zend_bool binary_protocol TSRMLS_DC) { enum_func_status ret; @@ -1142,13 +1375,13 @@ MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const c set = result->stored_data; - if (!set) { + if (!set || !row_buffers) { ret = FAIL; goto end; } if (free_rows) { - set->row_buffers = mnd_emalloc((size_t)(free_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *))); - if (!set->row_buffers) { + *row_buffers = mnd_pemalloc((size_t)(free_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)), 0); + if (!*row_buffers) { SET_OOM_ERROR(*conn->error_info); ret = FAIL; goto end; @@ -1184,16 +1417,16 @@ MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const c ret = FAIL; goto end; } - new_row_buffers = mnd_erealloc(set->row_buffers, (size_t)(total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *))); + new_row_buffers = mnd_perealloc(*row_buffers, (size_t)(total_allocated_rows * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)), 0); if (!new_row_buffers) { SET_OOM_ERROR(*conn->error_info); ret = FAIL; goto end; } - set->row_buffers = new_row_buffers; + *row_buffers = new_row_buffers; } free_rows--; - set->row_buffers[set->row_count] = row_packet->row_buffer; + (*row_buffers)[set->row_count] = row_packet->row_buffer; set->row_count++; @@ -1228,7 +1461,7 @@ MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const c ret = FAIL; goto end; } - set->row_buffers = mnd_erealloc(set->row_buffers, (size_t) (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *))); + *row_buffers = mnd_perealloc(*row_buffers, (size_t) (set->row_count * sizeof(MYSQLND_MEMORY_POOL_CHUNK *)), 0); } if (conn->upsert_status->server_status & SERVER_MORE_RESULTS_EXISTS) { @@ -1247,7 +1480,7 @@ MYSQLND_METHOD(mysqlnd_res, store_result_fetch_data)(MYSQLND_CONN_DATA * const c ret == PASS? "PASS":"FAIL", (uint) set->row_count, conn->upsert_status->warning_count, conn->upsert_status->server_status); end: PACKET_FREE(row_packet); - + DBG_INF_FMT("rows=%llu", (unsigned long long)result->stored_data->row_count); DBG_RETURN(ret); } /* }}} */ @@ -1260,6 +1493,7 @@ MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result, const unsigned int flags TSRMLS_DC) { enum_func_status ret; + MYSQLND_MEMORY_POOL_CHUNK ***row_buffers = NULL; DBG_ENTER("mysqlnd_res::store_result"); @@ -1270,13 +1504,23 @@ MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result, CONN_SET_STATE(conn, CONN_FETCHING_DATA); - result->stored_data = mysqlnd_result_buffered_init(result->field_count, flags & MYSQLND_STORE_PS, result->persistent TSRMLS_CC); - if (!result->stored_data) { - SET_OOM_ERROR(*conn->error_info); - DBG_RETURN(NULL); + if (flags & MYSQLND_STORE_NO_COPY) { + result->stored_data = (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_zval_init(result->field_count, flags & MYSQLND_STORE_PS, result->persistent TSRMLS_CC); + if (!result->stored_data) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + row_buffers = &result->stored_data->row_buffers; + } else if (flags & MYSQLND_STORE_COPY) { + result->stored_data = (MYSQLND_RES_BUFFERED *) mysqlnd_result_buffered_c_init(result->field_count, flags & MYSQLND_STORE_PS, result->persistent TSRMLS_CC); + if (!result->stored_data) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + row_buffers = &result->stored_data->row_buffers; } + ret = result->m.store_result_fetch_data(conn, result, result->meta, row_buffers, flags & MYSQLND_STORE_PS TSRMLS_CC); - ret = result->m.store_result_fetch_data(conn, result, result->meta, flags & MYSQLND_STORE_PS TSRMLS_CC); if (FAIL == ret) { if (result->stored_data) { COPY_CLIENT_ERROR(*conn->error_info, result->stored_data->error_info); @@ -1286,24 +1530,30 @@ MYSQLND_METHOD(mysqlnd_res, store_result)(MYSQLND_RES * result, DBG_RETURN(NULL); } else { /* Overflow ? */ - MYSQLND_RES_METADATA * meta = result->meta; - MYSQLND_RES_BUFFERED * set = result->stored_data; - if (set->row_count) { - /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */ - if (set->row_count * meta->field_count * sizeof(zval *) > SIZE_MAX) { - SET_OOM_ERROR(*conn->error_info); - DBG_RETURN(NULL); - } - /* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */ - set->data = mnd_emalloc((size_t)(set->row_count * meta->field_count * sizeof(zval *))); - if (!set->data) { - SET_OOM_ERROR(*conn->error_info); - DBG_RETURN(NULL); + if (flags & MYSQLND_STORE_NO_COPY) { + MYSQLND_RES_METADATA * meta = result->meta; + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result->stored_data; + if (set->row_count) { + /* don't try to allocate more than possible - mnd_XXalloc expects size_t, and it can have narrower range than uint64_t */ + if (set->row_count * meta->field_count * sizeof(zval *) > SIZE_MAX) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + /* if pecalloc is used valgrind barks gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux) */ + set->data = mnd_emalloc((size_t)(set->row_count * meta->field_count * sizeof(zval *))); + if (!set->data) { + SET_OOM_ERROR(*conn->error_info); + DBG_RETURN(NULL); + } + memset(set->data, 0, (size_t)(set->row_count * meta->field_count * sizeof(zval *))); } - memset(set->data, 0, (size_t)(set->row_count * meta->field_count * sizeof(zval *))); + /* Position at the first row */ + set->data_cursor = set->data; + } else if (flags & MYSQLND_STORE_COPY) { + MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result->stored_data; + set->current_row = 0; + set->initialized = mnd_pecalloc((set->row_count / 8) + 1, sizeof(zend_uchar), set->persistent); /* +1 for safety */ } - /* Position at the first row */ - set->data_cursor = set->data; } /* libmysql's documentation says it should be so for SELECT statements */ @@ -1370,19 +1620,37 @@ MYSQLND_METHOD(mysqlnd_res, data_seek)(MYSQLND_RES * const result, const uint64_ /* }}} */ -/* {{{ mysqlnd_result_buffered::data_seek */ +/* {{{ mysqlnd_result_buffered_zval::data_seek */ static enum_func_status -MYSQLND_METHOD(mysqlnd_result_buffered, data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row TSRMLS_DC) +MYSQLND_METHOD(mysqlnd_result_buffered_zval, data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row TSRMLS_DC) { - DBG_ENTER("mysqlnd_result_buffered::data_seek"); + MYSQLND_RES_BUFFERED_ZVAL * set = (MYSQLND_RES_BUFFERED_ZVAL *) result; + DBG_ENTER("mysqlnd_result_buffered_zval::data_seek"); /* libmysql just moves to the end, it does traversing of a linked list */ - if (row >= result->row_count) { - result->data_cursor = NULL; + if (row >= set->row_count) { + set->data_cursor = NULL; } else { - result->data_cursor = result->data + row * result->field_count; + set->data_cursor = set->data + row * result->field_count; } + DBG_RETURN(PASS); +} +/* }}} */ + +/* {{{ mysqlnd_result_buffered_c::data_seek */ +static enum_func_status +MYSQLND_METHOD(mysqlnd_result_buffered_c, data_seek)(MYSQLND_RES_BUFFERED * const result, const uint64_t row TSRMLS_DC) +{ + MYSQLND_RES_BUFFERED_C * set = (MYSQLND_RES_BUFFERED_C *) result; + DBG_ENTER("mysqlnd_result_buffered_c::data_seek"); + + /* libmysql just moves to the end, it does traversing of a linked list */ + if (row >= set->row_count) { + set->current_row = set->row_count; + } else { + set->current_row = row; + } DBG_RETURN(PASS); } /* }}} */ @@ -1539,7 +1807,7 @@ MYSQLND_METHOD(mysqlnd_res, field_tell)(const MYSQLND_RES * const result TSRMLS_ /* {{{ mysqlnd_res::fetch_into */ static void -MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, unsigned int flags, +MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, const unsigned int flags, zval *return_value, enum_mysqlnd_extension extension TSRMLS_DC ZEND_FILE_LINE_DC) { @@ -1585,7 +1853,7 @@ MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result TSRMLS_DC) MYSQLND_ROW_C ret = NULL; DBG_ENTER("mysqlnd_res::fetch_row_c"); - if (result->stored_data && result->stored_data->m.fetch_row == MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row)) { + if (result->stored_data && result->stored_data->m.fetch_row == MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row)) { MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row_c)(result, (void *) &ret, 0, &fetched_anything TSRMLS_CC); } else if (result->unbuf && result->unbuf->m.fetch_row == MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row)) { MYSQLND_METHOD(mysqlnd_result_unbuffered, fetch_row_c)(result, (void *) &ret, 0, &fetched_anything TSRMLS_CC); @@ -1600,7 +1868,7 @@ MYSQLND_METHOD(mysqlnd_res, fetch_row_c)(MYSQLND_RES * result TSRMLS_DC) /* {{{ 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) +MYSQLND_METHOD(mysqlnd_res, fetch_all)(MYSQLND_RES * result, const unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC) { zval *row; ulong i = 0; @@ -1713,12 +1981,12 @@ MYSQLND_CLASS_METHODS_END; MYSQLND_CLASS_METHODS_START(mysqlnd_result_buffered) - MYSQLND_METHOD(mysqlnd_result_buffered, fetch_row), + NULL, /* fetch_row */ NULL, /* row_decoder */ MYSQLND_METHOD(mysqlnd_result_buffered, num_rows), - MYSQLND_METHOD(mysqlnd_result_buffered, fetch_lengths), - MYSQLND_METHOD(mysqlnd_result_buffered, data_seek), - MYSQLND_METHOD(mysqlnd_result_buffered, initialize_result_set_rest), + NULL, /* fetch_lengths */ + NULL, /* data_seek */ + NULL, /* initialize_result_set_rest */ MYSQLND_METHOD(mysqlnd_result_buffered, free_result) MYSQLND_CLASS_METHODS_END; @@ -1778,7 +2046,7 @@ mysqlnd_result_unbuffered_init(unsigned int field_count, zend_bool ps, zend_bool ret->m.fetch_lengths = NULL; /* makes no sense */ ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; } else { - ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol; + ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol_zval; } DBG_RETURN(ret); @@ -1786,14 +2054,57 @@ mysqlnd_result_unbuffered_init(unsigned int field_count, zend_bool ps, zend_bool /* }}} */ -/* {{{ mysqlnd_result_buffered_init */ -PHPAPI MYSQLND_RES_BUFFERED * -mysqlnd_result_buffered_init(unsigned int field_count, zend_bool ps, zend_bool persistent TSRMLS_DC) +/* {{{ mysqlnd_result_buffered_zval_init */ +PHPAPI MYSQLND_RES_BUFFERED_ZVAL * +mysqlnd_result_buffered_zval_init(unsigned int field_count, zend_bool ps, zend_bool persistent TSRMLS_DC) +{ + size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED_ZVAL) + mysqlnd_plugin_count() * sizeof(void *); + MYSQLND_RES_BUFFERED_ZVAL * ret = mnd_pecalloc(1, alloc_size, persistent); + + DBG_ENTER("mysqlnd_result_buffered_zval_init"); + + if (!ret) { + DBG_RETURN(NULL); + } + if (!(ret->lengths = mnd_pecalloc(field_count, sizeof(unsigned long), persistent))) { + mnd_pefree(ret, persistent); + DBG_RETURN(NULL); + } + if (!(ret->result_set_memory_pool = mysqlnd_mempool_create(MYSQLND_G(mempool_default_size) TSRMLS_CC))) { + mnd_efree(ret->lengths); + mnd_pefree(ret, persistent); + DBG_RETURN(NULL); + } + + ret->persistent = persistent; + ret->field_count= field_count; + ret->ps = ps; + ret->m = *mysqlnd_result_buffered_get_methods(); + ret->type = MYSQLND_BUFFERED_TYPE_ZVAL; + + if (ps) { + ret->m.fetch_lengths = NULL; /* makes no sense */ + ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; + } else { + ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol_zval; + } + ret->m.fetch_row = MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_row); + ret->m.fetch_lengths = MYSQLND_METHOD(mysqlnd_result_buffered_zval, fetch_lengths); + ret->m.data_seek = MYSQLND_METHOD(mysqlnd_result_buffered_zval, data_seek); + ret->m.initialize_result_set_rest = MYSQLND_METHOD(mysqlnd_result_buffered_zval, initialize_result_set_rest); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ mysqlnd_result_buffered_c_init */ +PHPAPI MYSQLND_RES_BUFFERED_C * +mysqlnd_result_buffered_c_init(unsigned int field_count, zend_bool ps, zend_bool persistent TSRMLS_DC) { - size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED) + mysqlnd_plugin_count() * sizeof(void *); - MYSQLND_RES_BUFFERED * ret = mnd_pecalloc(1, alloc_size, persistent); + size_t alloc_size = sizeof(MYSQLND_RES_BUFFERED_C) + mysqlnd_plugin_count() * sizeof(void *); + MYSQLND_RES_BUFFERED_C * ret = mnd_pecalloc(1, alloc_size, persistent); - DBG_ENTER("mysqlnd_result_buffered_init"); + DBG_ENTER("mysqlnd_result_buffered_c_init"); if (!ret) { DBG_RETURN(NULL); @@ -1812,13 +2123,18 @@ mysqlnd_result_buffered_init(unsigned int field_count, zend_bool ps, zend_bool p ret->field_count= field_count; ret->ps = ps; ret->m = *mysqlnd_result_buffered_get_methods(); + ret->type = MYSQLND_BUFFERED_TYPE_C; if (ps) { ret->m.fetch_lengths = NULL; /* makes no sense */ ret->m.row_decoder = php_mysqlnd_rowp_read_binary_protocol; } else { - ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol; + ret->m.row_decoder = php_mysqlnd_rowp_read_text_protocol_c; } + ret->m.fetch_row = MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_row); + ret->m.fetch_lengths = MYSQLND_METHOD(mysqlnd_result_buffered_c, fetch_lengths); + ret->m.data_seek = MYSQLND_METHOD(mysqlnd_result_buffered_c, data_seek); + ret->m.initialize_result_set_rest = MYSQLND_METHOD(mysqlnd_result_buffered_c, initialize_result_set_rest); DBG_RETURN(ret); } diff --git a/ext/mysqlnd/mysqlnd_result.h b/ext/mysqlnd/mysqlnd_result.h index 0a54ea0454..f7e22a1d87 100644 --- a/ext/mysqlnd/mysqlnd_result.h +++ b/ext/mysqlnd/mysqlnd_result.h @@ -25,7 +25,8 @@ PHPAPI MYSQLND_RES * mysqlnd_result_init(unsigned int field_count, zend_bool persistent TSRMLS_DC); PHPAPI MYSQLND_RES_UNBUFFERED * mysqlnd_result_unbuffered_init(unsigned int field_count, zend_bool ps, zend_bool persistent TSRMLS_DC); -PHPAPI MYSQLND_RES_BUFFERED * mysqlnd_result_buffered_init(unsigned int field_count, zend_bool ps, zend_bool persistent TSRMLS_DC); +PHPAPI MYSQLND_RES_BUFFERED_ZVAL * mysqlnd_result_buffered_zval_init(unsigned int field_count, zend_bool ps, zend_bool persistent TSRMLS_DC); +PHPAPI MYSQLND_RES_BUFFERED_C * mysqlnd_result_buffered_c_init(unsigned int field_count, zend_bool ps, zend_bool persistent TSRMLS_DC); enum_func_status mysqlnd_query_read_result_set_header(MYSQLND_CONN_DATA * conn, MYSQLND_STMT * stmt TSRMLS_DC); diff --git a/ext/mysqlnd/mysqlnd_reverse_api.c b/ext/mysqlnd/mysqlnd_reverse_api.c index cd490fa62c..669b99bf0e 100644 --- a/ext/mysqlnd/mysqlnd_reverse_api.c +++ b/ext/mysqlnd/mysqlnd_reverse_api.c @@ -68,7 +68,7 @@ mysqlnd_reverse_api_register_api(MYSQLND_REVERSE_API * apiext TSRMLS_DC) /* {{{ zval_to_mysqlnd */ PHPAPI MYSQLND * -zval_to_mysqlnd(zval * zv TSRMLS_DC) +zval_to_mysqlnd(zval * zv, const unsigned int client_api_capabilities, unsigned int * save_client_api_capabilities TSRMLS_DC) { MYSQLND * retval; MYSQLND_REVERSE_API ** elem; @@ -80,6 +80,9 @@ zval_to_mysqlnd(zval * zv TSRMLS_DC) if ((*elem)->conversion_cb) { retval = (*elem)->conversion_cb(zv TSRMLS_CC); if (retval) { + if (retval->data) { + *save_client_api_capabilities = retval->data->m->negotiate_client_api_capabilities(retval->data, client_api_capabilities TSRMLS_CC); + } return retval; } } diff --git a/ext/mysqlnd/mysqlnd_reverse_api.h b/ext/mysqlnd/mysqlnd_reverse_api.h index 028a1f6b13..79c0feb60a 100644 --- a/ext/mysqlnd/mysqlnd_reverse_api.h +++ b/ext/mysqlnd/mysqlnd_reverse_api.h @@ -34,8 +34,7 @@ PHPAPI void mysqlnd_reverse_api_end(TSRMLS_D); PHPAPI HashTable * mysqlnd_reverse_api_get_api_list(TSRMLS_D); PHPAPI void mysqlnd_reverse_api_register_api(MYSQLND_REVERSE_API * apiext TSRMLS_DC); - -PHPAPI MYSQLND * zval_to_mysqlnd(zval * zv TSRMLS_DC); +PHPAPI MYSQLND * zval_to_mysqlnd(zval * zv, const unsigned int client_api_capabilities, unsigned int * save_client_api_capabilities TSRMLS_DC); #endif /* MYSQLND_REVERSE_API_H */ diff --git a/ext/mysqlnd/mysqlnd_structs.h b/ext/mysqlnd/mysqlnd_structs.h index 65f1c57a00..4d4538da35 100644 --- a/ext/mysqlnd/mysqlnd_structs.h +++ b/ext/mysqlnd/mysqlnd_structs.h @@ -235,7 +235,9 @@ 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_buffered_result_parent MYSQLND_RES_BUFFERED; +typedef struct st_mysqlnd_buffered_result_zval MYSQLND_RES_BUFFERED_ZVAL; +typedef struct st_mysqlnd_buffered_result_c MYSQLND_RES_BUFFERED_C; typedef struct st_mysqlnd_unbuffered_result MYSQLND_RES_UNBUFFERED; typedef struct st_mysqlnd_debug MYSQLND_DEBUG; @@ -244,7 +246,7 @@ 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, + const unsigned int flags, zend_bool * fetched_anything TSRMLS_DC); @@ -500,6 +502,11 @@ typedef struct st_mysqlnd_authentication_plugin * (*func_mysqlnd_conn_data__fetc typedef enum_func_status (*func_mysqlnd_conn_data__set_client_option_2d)(MYSQLND_CONN_DATA * const conn, enum mysqlnd_option option, const char * const key, const char * const value TSRMLS_DC); + +typedef unsigned int (*func_mysqlnd_conn_data__negotiate_client_api_capabilities)(MYSQLND_CONN_DATA * const conn, const unsigned int flags TSRMLS_DC); +typedef unsigned int (*func_mysqlnd_conn_data__get_client_api_capabilities)(const MYSQLND_CONN_DATA * const conn TSRMLS_DC); + + struct st_mysqlnd_conn_data_methods { func_mysqlnd_conn_data__init init; @@ -591,6 +598,9 @@ struct st_mysqlnd_conn_data_methods func_mysqlnd_conn_data__fetch_auth_plugin_by_name fetch_auth_plugin_by_name; func_mysqlnd_conn_data__set_client_option_2d set_client_option_2d; + + func_mysqlnd_conn_data__negotiate_client_api_capabilities negotiate_client_api_capabilities; + func_mysqlnd_conn_data__get_client_api_capabilities get_client_api_capabilities; }; @@ -615,9 +625,9 @@ typedef enum_func_status (*func_mysqlnd_res__row_decoder)(MYSQLND_MEMORY_POOL_CH typedef MYSQLND_RES * (*func_mysqlnd_res__use_result)(MYSQLND_RES * const result, zend_bool ps_protocol TSRMLS_DC); typedef MYSQLND_RES * (*func_mysqlnd_res__store_result)(MYSQLND_RES * result, MYSQLND_CONN_DATA * const conn, const unsigned int flags TSRMLS_DC); -typedef void (*func_mysqlnd_res__fetch_into)(MYSQLND_RES *result, unsigned int flags, zval *return_value, enum_mysqlnd_extension ext TSRMLS_DC ZEND_FILE_LINE_DC); +typedef void (*func_mysqlnd_res__fetch_into)(MYSQLND_RES *result, const unsigned int flags, zval *return_value, enum_mysqlnd_extension ext TSRMLS_DC ZEND_FILE_LINE_DC); typedef MYSQLND_ROW_C (*func_mysqlnd_res__fetch_row_c)(MYSQLND_RES *result TSRMLS_DC); -typedef void (*func_mysqlnd_res__fetch_all)(MYSQLND_RES *result, unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC); +typedef void (*func_mysqlnd_res__fetch_all)(MYSQLND_RES *result, const unsigned int flags, zval *return_value TSRMLS_DC ZEND_FILE_LINE_DC); typedef void (*func_mysqlnd_res__fetch_field_data)(MYSQLND_RES *result, unsigned int offset, zval *return_value TSRMLS_DC); typedef uint64_t (*func_mysqlnd_res__num_rows)(const MYSQLND_RES * const result TSRMLS_DC); typedef unsigned int (*func_mysqlnd_res__num_fields)(const MYSQLND_RES * const result TSRMLS_DC); @@ -631,7 +641,7 @@ typedef const MYSQLND_FIELD *(*func_mysqlnd_res__fetch_fields)(MYSQLND_RES * con typedef enum_func_status (*func_mysqlnd_res__read_result_metadata)(MYSQLND_RES * result, MYSQLND_CONN_DATA * conn TSRMLS_DC); typedef unsigned long * (*func_mysqlnd_res__fetch_lengths)(MYSQLND_RES * const result TSRMLS_DC); -typedef enum_func_status (*func_mysqlnd_res__store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result, MYSQLND_RES_METADATA *meta, zend_bool binary_protocol TSRMLS_DC); +typedef enum_func_status (*func_mysqlnd_res__store_result_fetch_data)(MYSQLND_CONN_DATA * const conn, MYSQLND_RES * result, MYSQLND_RES_METADATA * meta, MYSQLND_MEMORY_POOL_CHUNK *** row_buffers, zend_bool binary_protocol TSRMLS_DC); typedef void (*func_mysqlnd_res__free_result_buffers)(MYSQLND_RES * result TSRMLS_DC); /* private */ typedef enum_func_status (*func_mysqlnd_res__free_result)(MYSQLND_RES * result, zend_bool implicit TSRMLS_DC); @@ -947,9 +957,6 @@ struct st_mysqlnd_connection_data /* Temporal storage for mysql_query */ unsigned int field_count; - /* persistent connection */ - zend_bool persistent; - /* options */ MYSQLND_OPTIONS * options; MYSQLND_OPTIONS options_impl; @@ -957,7 +964,12 @@ struct st_mysqlnd_connection_data /* stats */ MYSQLND_STATS * stats; + unsigned int client_api_capabilities; + struct st_mysqlnd_conn_data_methods * m; + + /* persistent connection */ + zend_bool persistent; }; @@ -991,31 +1003,51 @@ struct st_mysqlnd_result_metadata }; -struct st_mysqlnd_buffered_result +#define def_mysqlnd_buffered_result_parent \ + MYSQLND_MEMORY_POOL_CHUNK **row_buffers; \ + uint64_t row_count; \ + uint64_t initialized_rows; \ + \ + /* Column lengths of current row - both buffered and unbuffered. For buffered results it duplicates the data found in **data */ \ + unsigned long *lengths; \ + \ + MYSQLND_MEMORY_POOL *result_set_memory_pool; \ + \ + unsigned int references; \ + \ + MYSQLND_ERROR_INFO error_info; \ + \ + unsigned int field_count; \ + zend_bool ps; \ + zend_bool persistent; \ + struct st_mysqlnd_result_buffered_methods m; \ + enum mysqlnd_buffered_type type; \ + void * unused1; \ + void * unused2; \ + void * unused3; \ + + +struct st_mysqlnd_buffered_result_parent { - zval **data; - zval **data_cursor; - MYSQLND_MEMORY_POOL_CHUNK **row_buffers; - uint64_t row_count; - uint64_t initialized_rows; + def_mysqlnd_buffered_result_parent; +}; - /* - Column lengths of current row - both buffered and unbuffered. - For buffered results it duplicates the data found in **data - */ - unsigned long *lengths; - MYSQLND_MEMORY_POOL *result_set_memory_pool; +struct st_mysqlnd_buffered_result_zval +{ + def_mysqlnd_buffered_result_parent; - unsigned int references; + zval **data; + zval **data_cursor; +}; - MYSQLND_ERROR_INFO error_info; - unsigned int field_count; - zend_bool ps; - zend_bool persistent; +struct st_mysqlnd_buffered_result_c +{ + def_mysqlnd_buffered_result_parent; - struct st_mysqlnd_result_buffered_methods m; + zend_uchar *initialized; /* every row is a single bit */ + uint64_t current_row; }; diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.c b/ext/mysqlnd/mysqlnd_wireprotocol.c index dfdba4084e..ecce71d9c2 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.c +++ b/ext/mysqlnd/mysqlnd_wireprotocol.c @@ -1572,13 +1572,13 @@ php_mysqlnd_rowp_read_binary_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zv } /* }}} */ - /* {{{ php_mysqlnd_rowp_read_text_protocol */ enum_func_status -php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, +php_mysqlnd_rowp_read_text_protocol_aux(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, unsigned int field_count, const MYSQLND_FIELD * fields_metadata, - zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC) + zend_bool as_int_or_float, zend_bool copy_data, MYSQLND_STATS * stats TSRMLS_DC) { + unsigned int i; zend_bool last_field_was_string = FALSE; zval **current_field, **end_field, **start_field; @@ -1586,7 +1586,7 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval size_t data_size = row_buffer->app; zend_uchar * bit_area = (zend_uchar*) row_buffer->ptr + data_size + 1; /* we allocate from here */ - DBG_ENTER("php_mysqlnd_rowp_read_text_protocol"); + DBG_ENTER("php_mysqlnd_rowp_read_text_protocol_aux"); if (!fields) { DBG_RETURN(FAIL); @@ -1608,7 +1608,7 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval /* php_mysqlnd_net_field_length() call should be after *this_field_len_pos = p; */ unsigned long len = php_mysqlnd_net_field_length(&p); - if (current_field > start_field && last_field_was_string) { + if (copy_data == FALSE && 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 @@ -1733,22 +1733,22 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval p -= len; if (Z_TYPE_PP(current_field) == IS_LONG) { bit_area += 1 + sprintf((char *)start, "%ld", Z_LVAL_PP(current_field)); - ZVAL_STRINGL(*current_field, (char *) start, bit_area - start - 1, 0); + ZVAL_STRINGL(*current_field, (char *) start, bit_area - start - 1, copy_data); } 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); - ZVAL_STRINGL(*current_field, (char *) start, bit_area - start - 1, 0); + ZVAL_STRINGL(*current_field, (char *) start, bit_area - start - 1, copy_data); } } else { - ZVAL_STRINGL(*current_field, (char *)p, len, 0); + ZVAL_STRINGL(*current_field, (char *)p, len, copy_data); } p += len; last_field_was_string = TRUE; } } - if (last_field_was_string) { + if (copy_data == FALSE && last_field_was_string) { /* Normal queries: The buffer has one more byte at the end, because we need it */ row_buffer->ptr[data_size] = '\0'; } @@ -1758,6 +1758,36 @@ php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval /* }}} */ +/* {{{ php_mysqlnd_rowp_read_text_protocol_zval */ +enum_func_status +php_mysqlnd_rowp_read_text_protocol_zval(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, + unsigned int field_count, const MYSQLND_FIELD * fields_metadata, + zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC) +{ + enum_func_status ret; + DBG_ENTER("php_mysqlnd_rowp_read_text_protocol_zval"); + ret = php_mysqlnd_rowp_read_text_protocol_aux(row_buffer, fields, field_count, fields_metadata, as_int_or_float, FALSE, stats TSRMLS_CC); + DBG_RETURN(ret); +} +/* }}} */ + + +/* {{{ php_mysqlnd_rowp_read_text_protocol_c */ +enum_func_status +php_mysqlnd_rowp_read_text_protocol_c(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, + unsigned int field_count, const MYSQLND_FIELD * fields_metadata, + zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC) +{ + enum_func_status ret; + DBG_ENTER("php_mysqlnd_rowp_read_text_protocol_c"); + ret = php_mysqlnd_rowp_read_text_protocol_aux(row_buffer, fields, field_count, fields_metadata, as_int_or_float, TRUE, stats TSRMLS_CC); + DBG_RETURN(ret); +} +/* }}} */ + + + + /* {{{ php_mysqlnd_rowp_read */ /* if normal statements => packet->fields is created by this function, diff --git a/ext/mysqlnd/mysqlnd_wireprotocol.h b/ext/mysqlnd/mysqlnd_wireprotocol.h index be71d06508..7e8b9624ac 100644 --- a/ext/mysqlnd/mysqlnd_wireprotocol.h +++ b/ext/mysqlnd/mysqlnd_wireprotocol.h @@ -312,7 +312,11 @@ enum_func_status php_mysqlnd_rowp_read_binary_protocol(MYSQLND_MEMORY_POOL_CHUNK zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC); -enum_func_status php_mysqlnd_rowp_read_text_protocol(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, +enum_func_status php_mysqlnd_rowp_read_text_protocol_zval(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, + unsigned int field_count, const MYSQLND_FIELD * fields_metadata, + zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC); + +enum_func_status php_mysqlnd_rowp_read_text_protocol_c(MYSQLND_MEMORY_POOL_CHUNK * row_buffer, zval ** fields, unsigned int field_count, const MYSQLND_FIELD * fields_metadata, zend_bool as_int_or_float, MYSQLND_STATS * stats TSRMLS_DC); diff --git a/ext/mysqlnd/php_mysqlnd.c b/ext/mysqlnd/php_mysqlnd.c index 7712f1ecb8..913574e2e9 100644 --- a/ext/mysqlnd/php_mysqlnd.c +++ b/ext/mysqlnd/php_mysqlnd.c @@ -209,6 +209,7 @@ static PHP_GINIT_FUNCTION(mysqlnd) mysqlnd_globals->debug_calloc_fail_threshold = -1; mysqlnd_globals->debug_realloc_fail_threshold = -1; mysqlnd_globals->sha256_server_public_key = NULL; + mysqlnd_globals->fetch_data_copy = FALSE; } /* }}} */ @@ -227,8 +228,8 @@ static PHP_INI_MH(OnUpdateNetCmdBufferSize) /* {{{ 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_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.trace_alloc", NULL, PHP_INI_SYSTEM, OnUpdateString, trace_alloc_settings, zend_mysqlnd_globals, mysqlnd_globals) STD_PHP_INI_ENTRY("mysqlnd.net_cmd_buffer_size", MYSQLND_NET_CMD_BUFFER_MIN_SIZE_STR, PHP_INI_ALL, OnUpdateNetCmdBufferSize, net_cmd_buffer_size, zend_mysqlnd_globals, mysqlnd_globals) @@ -237,7 +238,7 @@ PHP_INI_BEGIN() STD_PHP_INI_ENTRY("mysqlnd.log_mask", "0", PHP_INI_ALL, OnUpdateLong, log_mask, zend_mysqlnd_globals, mysqlnd_globals) STD_PHP_INI_ENTRY("mysqlnd.mempool_default_size","16000", PHP_INI_ALL, OnUpdateLong, mempool_default_size, zend_mysqlnd_globals, mysqlnd_globals) STD_PHP_INI_ENTRY("mysqlnd.sha256_server_public_key",NULL, PHP_INI_PERDIR, OnUpdateString, sha256_server_public_key, zend_mysqlnd_globals, mysqlnd_globals) - + STD_PHP_INI_BOOLEAN("mysqlnd.fetch_data_copy", "0", PHP_INI_ALL, OnUpdateBool, fetch_data_copy, zend_mysqlnd_globals, mysqlnd_globals) #if PHP_DEBUG STD_PHP_INI_ENTRY("mysqlnd.debug_emalloc_fail_threshold","-1", PHP_INI_SYSTEM, OnUpdateLong, debug_emalloc_fail_threshold, zend_mysqlnd_globals, mysqlnd_globals) STD_PHP_INI_ENTRY("mysqlnd.debug_ecalloc_fail_threshold","-1", PHP_INI_SYSTEM, OnUpdateLong, debug_ecalloc_fail_threshold, zend_mysqlnd_globals, mysqlnd_globals) diff --git a/ext/pdo_mysql/mysql_driver.c b/ext/pdo_mysql/mysql_driver.c index 825fe2647f..581766b38a 100644 --- a/ext/pdo_mysql/mysql_driver.c +++ b/ext/pdo_mysql/mysql_driver.c @@ -37,7 +37,7 @@ #include "zend_exceptions.h" #if defined(PDO_USE_MYSQLND) -# define pdo_mysql_init(persistent) mysqlnd_init(persistent) +# define pdo_mysql_init(persistent) mysqlnd_init(MYSQLND_CLIENT_NO_FLAG, persistent) #else # define pdo_mysql_init(persistent) mysql_init(NULL) #endif @@ -752,7 +752,7 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options TSRMLS_ } if (mysqlnd_connect(H->server, host, dbh->username, dbh->password, password_len, dbname, dbname_len, - port, unix_socket, connect_opts TSRMLS_CC) == NULL) { + port, unix_socket, connect_opts, MYSQLND_CLIENT_NO_FLAG TSRMLS_CC) == NULL) { #else if (mysql_real_connect(H->server, host, dbh->username, dbh->password, dbname, port, unix_socket, connect_opts) == NULL) { #endif |