. The mysqlnd.fetch_copy_data ini setting has been removed. However, this
should not result in user-visible behavior changes.
+- PDO MySQL:
+ . Integers and floats in result sets will now be returned using native PHP
+ types instead of strings when using emulated prepared statements. This
+ matches the behavior of native prepared statements. You can restore the
+ previous behavior by enabling the PDO::ATTR_STRINGIFY_FETCHES option.
+
- Standard:
. version_compare() no longer accepts undocumented operator abbreviations.
/* Query */
#define mysqlnd_fetch_into(result, flags, ret_val) (result)->m.fetch_into((result), (flags), (ret_val) ZEND_FILE_LINE_CC)
#define mysqlnd_fetch_row_c(result) (result)->m.fetch_row_c((result))
+#define mysqlnd_fetch_row_zval(result, row_ptr, fetched) \
+ (result)->m.fetch_row((result), (row_ptr), 0, (fetched))
#define mysqlnd_fetch_all(result, flags, return_value) (result)->m.fetch_all((result), (flags), (return_value) ZEND_FILE_LINE_CC)
#define mysqlnd_get_connection_stats(conn, values) ((conn)->data)->m->get_statistics((conn)->data, (values) ZEND_FILE_LINE_CC)
#define mysqlnd_get_client_stats(values) _mysqlnd_get_client_stats(mysqlnd_global_stats, (values) ZEND_FILE_LINE_CC)
goto cleanup;
}
+#ifdef PDO_USE_MYSQLND
+ bool int_and_float_native = true;
+ if (mysql_options(H->server, MYSQLND_OPT_INT_AND_FLOAT_NATIVE, (const char *) &int_and_float_native)) {
+ pdo_mysql_error(dbh);
+ goto cleanup;
+ }
+#endif
+
if (vars[0].optval && mysql_options(H->server, MYSQL_SET_CHARSET_NAME, vars[0].optval)) {
pdo_mysql_error(dbh);
goto cleanup;
# define pdo_mysql_stmt_execute_prepared(stmt) pdo_mysql_stmt_execute_prepared_libmysql(stmt)
#endif
-
static void pdo_mysql_free_result(pdo_mysql_stmt *S)
{
if (S->result) {
efree(S->out_length);
S->bound_result = NULL;
}
+#else
+ if (S->current_row) {
+ unsigned column_count = mysql_num_fields(S->result);
+ for (unsigned i = 0; i < column_count; i++) {
+ zval_ptr_dtor_nogc(&S->current_row[i]);
+ }
+ efree(S->current_row);
+ S->current_row = NULL;
+ }
#endif
-
mysql_free_result(S->result);
S->result = NULL;
}
}
}
-#ifdef PDO_USE_MYSQLND
- if (!S->stmt && S->current_data) {
- mnd_efree(S->current_data);
- }
-#endif /* PDO_USE_MYSQLND */
-
efree(S);
PDO_DBG_RETURN(1);
}
PDO_DBG_RETURN(1);
}
- if (!S->stmt && S->current_data) {
- mnd_efree(S->current_data);
+ zval *row_data;
+ if (mysqlnd_fetch_row_zval(S->result, &row_data, &fetched_anything) == FAIL) {
+ pdo_mysql_error_stmt(stmt);
+ PDO_DBG_RETURN(0);
+ }
+
+ if (!fetched_anything) {
+ PDO_DBG_RETURN(0);
+ }
+
+ if (!S->current_row) {
+ S->current_row = ecalloc(sizeof(zval), stmt->column_count);
}
+ for (unsigned i = 0; i < stmt->column_count; i++) {
+ zval_ptr_dtor_nogc(&S->current_row[i]);
+ ZVAL_COPY_VALUE(&S->current_row[i], &row_data[i]);
+ }
+ PDO_DBG_RETURN(1);
#else
int ret;
PDO_DBG_RETURN(1);
}
-#endif /* PDO_USE_MYSQLND */
if ((S->current_data = mysql_fetch_row(S->result)) == NULL) {
if (!S->H->buffered && mysql_errno(S->H->server)) {
S->current_lengths = mysql_fetch_lengths(S->result);
PDO_DBG_RETURN(1);
+#endif /* PDO_USE_MYSQLND */
}
/* }}} */
cols[i].maxlen = S->fields[i].length;
#ifdef PDO_USE_MYSQLND
- if (S->stmt) {
- cols[i].param_type = PDO_PARAM_ZVAL;
- } else
+ cols[i].param_type = PDO_PARAM_ZVAL;
+#else
+ cols[i].param_type = PDO_PARAM_STR;
#endif
- {
- cols[i].param_type = PDO_PARAM_STR;
- }
}
PDO_DBG_RETURN(1);
}
PDO_DBG_RETURN(0);
}
- /* With mysqlnd data is stored inside mysqlnd, not S->current_data */
- if (!S->stmt) {
- if (S->current_data == NULL || !S->result) {
- PDO_DBG_RETURN(0);
- }
- }
-
if (colno >= stmt->column_count) {
/* error invalid column */
PDO_DBG_RETURN(0);
if (S->stmt) {
Z_TRY_ADDREF(S->stmt->data->result_bind[colno].zv);
*ptr = (char*)&S->stmt->data->result_bind[colno].zv;
- *len = sizeof(zval);
- PDO_DBG_RETURN(1);
+ } else {
+ Z_TRY_ADDREF(S->current_row[colno]);
+ *ptr = (char*)&S->current_row[colno];
}
+ *len = sizeof(zval);
+ PDO_DBG_RETURN(1);
#else
if (S->stmt) {
if (S->out_null[colno]) {
*len = S->out_length[colno];
PDO_DBG_RETURN(1);
}
-#endif
+
+ if (S->current_data == NULL) {
+ PDO_DBG_RETURN(0);
+ }
*ptr = S->current_data[colno];
*len = S->current_lengths[colno];
PDO_DBG_RETURN(1);
+#endif
} /* }}} */
static char *type_to_name_native(int type) /* {{{ */
pdo_mysql_db_handle *H;
MYSQL_RES *result;
const MYSQL_FIELD *fields;
- MYSQL_ROW current_data;
-#ifdef PDO_USE_MYSQLND
- const size_t *current_lengths;
-#else
- unsigned long *current_lengths;
-#endif
pdo_mysql_error_info einfo;
#ifdef PDO_USE_MYSQLND
MYSQLND_STMT *stmt;
#ifndef PDO_USE_MYSQLND
my_bool *in_null;
zend_ulong *in_length;
-#endif
PDO_MYSQL_PARAM_BIND *bound_result;
my_bool *out_null;
zend_ulong *out_length;
+ MYSQL_ROW current_data;
+ unsigned long *current_lengths;
+#else
+ zval *current_row;
+#endif
unsigned max_length:1;
/* Whether all result sets have been fully consumed.
* If this flag is not set, they need to be consumed during destruction. */
<?php
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
$db = MySQLPDOTest::factory();
+ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
$stmt = $db->prepare("SELECT 1 AS \"one\"");
$stmt->execute();
string(1) "1"
string(17) "SELECT 1 AS "one""
----------------------------------
-object(PDORow)#%d (2) {
+object(PDORow)#5 (2) {
["queryString"]=>
string(19) "SELECT id FROM test"
["id"]=>
$attr = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci; SET SESSION sql_mode=traditional',
+ PDO::ATTR_STRINGIFY_FETCHES => true,
);
putenv('PDOTEST_ATTR=' . serialize($attr));
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'skipif.inc');
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
MySQLPDOTest::skip();
+if (!MySQLPDOTest::isPDOMySQLnd()) die('skip only for mysqlnd');
?>
--FILE--
<?php
$pdo->query("INSERT INTO $tbl (`bit`) VALUES (0b011)");
$pdo->query("INSERT INTO $tbl (`bit`) VALUES (0b01100)");
+$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
$ret = $pdo->query("SELECT * FROM $tbl")->fetchAll();
+foreach ($ret as $i) {
+ var_dump($i["bit"]);
+}
+$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+$ret = $pdo->query("SELECT * FROM $tbl")->fetchAll();
foreach ($ret as $i) {
var_dump($i["bit"]);
}
?>
--EXPECT--
-string(1) "1"
-string(1) "3"
-string(2) "12"
+int(1)
+int(3)
+int(12)
+int(1)
+int(3)
+int(12)
$db = MySQLPDOTest::factory();
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
$db->query('DROP TABLE IF EXISTS test');
$db->query('CREATE TABLE test (first int) ENGINE = InnoDB');
[0]=>
array(2) {
["first"]=>
- int(5)
+ string(1) "5"
[0]=>
- int(5)
+ string(1) "5"
}
}
array(0) {
[0]=>
array(2) {
["first"]=>
- int(7)
+ string(1) "7"
[0]=>
- int(7)
+ string(1) "7"
}
}
array(0) {
[0]=>
array(2) {
["first"]=>
- int(16)
+ string(2) "16"
[0]=>
- int(16)
+ string(2) "16"
}
}
$tmp = $stmt->getColumnMeta(0);
// libmysql and mysqlnd will show the pdo_type entry at a different position in the hash
-if (!isset($tmp['pdo_type']) || (isset($tmp['pdo_type']) && $tmp['pdo_type'] != 2))
- printf("Expecting pdo_type = 2 got %s\n", $tmp['pdo_type']);
+if (!isset($tmp['pdo_type']) || (isset($tmp['pdo_type']) && $tmp['pdo_type'] != 1))
+ printf("Expecting pdo_type = 1 got %s\n", $tmp['pdo_type']);
else
unset($tmp['pdo_type']);
<?php
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
$db->exec("DROP TABLE IF EXISTS test");
// And now allow the evil to do his work
<?php
require __DIR__ . '/mysql_pdo_test.inc';
$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
$db->exec('DROP PROCEDURE IF EXISTS p');
$db->exec('CREATE PROCEDURE p() BEGIN SELECT 1 AS "one"; END');
}
$attr[PDO::ATTR_PERSISTENT] = true;
$attr[PDO::ATTR_EMULATE_PREPARES] = false;
+$attr[PDO::ATTR_STRINGIFY_FETCHES] = true;
putenv('PDOTEST_ATTR='.serialize($attr));
$db = MySQLPDOTest::factory();
--EXPECT--
array(2) {
[1]=>
- int(1)
+ string(1) "1"
[0]=>
- int(1)
+ string(1) "1"
}
done!
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
$db = MySQLPDOTest::factory();
-$db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
$db->exec('DROP TABLE IF EXISTS test');
$db->exec('CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(255) NOT NULL)');
--- /dev/null
+--TEST--
+PDO MySQL should use native types if ATTR_STRINGIFY_FETCHES is not enabled
+--SKIPIF--
+<?php
+require_once(__DIR__ . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+if (!MySQLPDOTest::isPDOMySQLnd()) die('skip mysqlnd only');
+?>
+--FILE--
+<?php
+require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+$db->exec('DROP TABLE IF EXISTS test');
+$db->exec('CREATE TABLE test (i INT, f FLOAT)');
+$db->exec('INSERT INTO test VALUES (42, 42.5)');
+
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+var_dump($db->query('SELECT * FROM test')->fetchAll(PDO::FETCH_ASSOC));
+
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
+var_dump($db->query('SELECT * FROM test')->fetchAll(PDO::FETCH_ASSOC));
+?>
+--CLEAN--
+<?php
+require __DIR__ . '/mysql_pdo_test.inc';
+MySQLPDOTest::dropTestTable();
+?>
+--EXPECT--
+array(1) {
+ [0]=>
+ array(2) {
+ ["i"]=>
+ int(42)
+ ["f"]=>
+ float(42.5)
+ }
+}
+array(1) {
+ [0]=>
+ array(2) {
+ ["i"]=>
+ int(42)
+ ["f"]=>
+ float(42.5)
+ }
+}
<?php
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
$db = MySQLPDOTest::factory();
+ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
MySQLPDOTest::createTestTable($db);
$default = $db->getAttribute(PDO::ATTR_CASE);
$create = sprintf('CREATE TABLE %s(id INT)', $table);
var_dump($create);
$db = new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_INIT_COMMAND => $create));
+ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
$info = $db->errorInfo();
var_dump($info[0]);
$table = sprintf("test_%s", md5(mt_rand(0, PHP_INT_MAX)));
$db = new PDO($dsn, $user, $pass);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
+ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
$db->exec(sprintf('DROP TABLE IF EXISTS %s', $table));
$create = sprintf('CREATE TABLE %s(id INT)', $table);
$db->exec($create);
// New connection, does not allow multiple statements.
$db = new PDO($dsn, $user, $pass, array(PDO::MYSQL_ATTR_MULTI_STATEMENTS => false));
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
+ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
$stmt = $db->query(sprintf('SELECT * FROM %s; INSERT INTO %s(id) VALUES (3)', $table, $table));
var_dump($stmt);
$info = $db->errorInfo();
<?php
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
$db = MySQLPDOTest::factory();
+ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
MySQLPDOTest::createTestTable($db);
$default = $db->getAttribute(PDO::ATTR_STATEMENT_CLASS);
<?php
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
$db = MySQLPDOTest::factory();
+
MySQLPDOTest::createTestTable($db, MySQLPDOTest::detect_transactional_mysql_engine($db));
if (1 !== $db->getAttribute(PDO::ATTR_AUTOCOMMIT))
/* This is the PDO way to close a connection */
$db = null;
$db = MySQLPDOTest::factory();
+ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
/* Autocommit was off - by definition. Commit was not issued. DELETE should have been rolled back. */
if (!($stmt = $db->query('SELECT id, label FROM test ORDER BY id ASC')))
<?php
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
$stmt = $db->query('SELECT 1; SELECT x FROM does_not_exist');
var_dump($stmt->fetchAll());
$db1->exec('SET @pdo_persistent_connection=1');
$stmt = $db2->query('SELECT @pdo_persistent_connection as _pers');
$tmp = $stmt->fetch(PDO::FETCH_ASSOC);
- if ($tmp['_pers'] !== '1')
+ if ($tmp['_pers'] != 1)
printf("[001] Both handles should use the same connection.");
$stmt = $db1->query('SELECT CONNECTION_ID() as _con1');
<?php
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
$db = MySQLPDOTest::factory();
+ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
function prepex($offset, &$db, $query, $input_params = null, $error_info = null) {
<?php
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
$db = MySQLPDOTest::factory();
+ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
try {
<?php
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
$db = MySQLPDOTest::factory();
+ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
try {
<?php
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
$db = MySQLPDOTest::factory();
+ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
+
try {
$db->setAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY, 1);
if (1 != $db->getAttribute(PDO::MYSQL_ATTR_DIRECT_QUERY))
var_export($stmt->errorCode(), true),
var_export($stmt->errorInfo(), true));
- $tmp = $stmt->fetchAll(PDO::FETCH_ASSOC);
- if (!MySQLPDOTest::isPDOMySQLnd()) {
- if (isset($tmp[0]['id'])) {
- // libmysql should return a string here whereas mysqlnd returns a native int
- if (gettype($tmp[0]['id']) == 'string')
- // convert to int for the test output...
- settype($tmp[0]['id'], 'integer');
- }
- }
- var_dump($tmp);
+ var_dump($stmt->fetchAll(PDO::FETCH_ASSOC));
} catch (PDOException $e) {
printf("[001] %s [%s] %s\n",
["?"]=>
string(2) "id"
["id"]=>
- int(1)
+ string(1) "1"
["label"]=>
string(4) "row1"
}
<?php
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
MySQLPDOTest::createTestTable($db);
try {
}
$db = new MyPDO(PDO_MYSQL_TEST_DSN, PDO_MYSQL_TEST_USER, PDO_MYSQL_TEST_PASS);
+ $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
$db->exec('DROP TABLE IF EXISTS test');
$db->exec('CREATE TABLE test(id INT)');
$db->exec('INSERT INTO test(id) VALUES (1), (2)');