Merge branch 'PHP-8.0'

* PHP-8.0:
  Handle column count change in PDO MySQL
This commit is contained in:
Nikita Popov 2020-12-08 16:42:54 +01:00
commit 0ac8518d76
5 changed files with 108 additions and 77 deletions

View file

@ -173,6 +173,45 @@ int pdo_stmt_describe_columns(pdo_stmt_t *stmt) /* {{{ */
}
/* }}} */
static void pdo_stmt_reset_columns(pdo_stmt_t *stmt) {
if (stmt->columns) {
int i;
struct pdo_column_data *cols = stmt->columns;
for (i = 0; i < stmt->column_count; i++) {
if (cols[i].name) {
zend_string_release_ex(cols[i].name, 0);
}
}
efree(stmt->columns);
}
stmt->columns = NULL;
stmt->column_count = 0;
}
/**
* Change the column count on the statement. If it differs from the previous one,
* discard existing columns information.
*/
PDO_API void php_pdo_stmt_set_column_count(pdo_stmt_t *stmt, int new_count)
{
/* Columns not yet "described". */
if (!stmt->columns) {
stmt->column_count = new_count;
return;
}
/* The column count has not changed: No need to reload columns description.
* Note: Do not handle attribute name change, without column count change. */
if (new_count == stmt->column_count) {
return;
}
/* Free previous columns to force reload description. */
pdo_stmt_reset_columns(stmt);
stmt->column_count = new_count;
}
static void get_lazy_object(pdo_stmt_t *stmt, zval *return_value) /* {{{ */
{
if (Z_ISUNDEF(stmt->lazy_object_ref)) {
@ -1910,20 +1949,7 @@ PHP_METHOD(PDOStatement, setFetchMode)
static bool pdo_stmt_do_next_rowset(pdo_stmt_t *stmt)
{
/* un-describe */
if (stmt->columns) {
int i;
struct pdo_column_data *cols = stmt->columns;
for (i = 0; i < stmt->column_count; i++) {
if (cols[i].name) {
zend_string_release_ex(cols[i].name, 0);
}
}
efree(stmt->columns);
stmt->columns = NULL;
stmt->column_count = 0;
}
pdo_stmt_reset_columns(stmt);
if (!stmt->methods->next_rowset(stmt)) {
/* Set the executed flag to 0 to reallocate columns on next execute */
@ -2156,19 +2182,7 @@ PDO_API void php_pdo_free_statement(pdo_stmt_t *stmt)
efree(stmt->query_string);
}
if (stmt->columns) {
int i;
struct pdo_column_data *cols = stmt->columns;
for (i = 0; i < stmt->column_count; i++) {
if (cols[i].name) {
zend_string_release_ex(cols[i].name, 0);
cols[i].name = NULL;
}
}
efree(stmt->columns);
stmt->columns = NULL;
}
pdo_stmt_reset_columns(stmt);
if (!Z_ISUNDEF(stmt->fetch.into) && stmt->default_fetch_type == PDO_FETCH_INTO) {
zval_ptr_dtor(&stmt->fetch.into);

View file

@ -695,7 +695,7 @@ PDO_API void php_pdo_dbh_addref(pdo_dbh_t *dbh);
PDO_API void php_pdo_dbh_delref(pdo_dbh_t *dbh);
PDO_API void php_pdo_free_statement(pdo_stmt_t *stmt);
PDO_API void php_pdo_stmt_set_column_count(pdo_stmt_t *stmt, int new_count);
PDO_API void pdo_throw_exception(unsigned int driver_errcode, char *driver_errmsg, pdo_error_type *pdo_error);
#endif /* PHP_PDO_DRIVER_H */

View file

@ -144,7 +144,7 @@ static int pdo_mysql_fill_stmt_from_result(pdo_stmt_t *stmt) /* {{{ */
}
stmt->row_count = (zend_long) mysql_num_rows(S->result);
stmt->column_count = (int) mysql_num_fields(S->result);
php_pdo_stmt_set_column_count(stmt, (int) mysql_num_fields(S->result));
S->fields = mysql_fetch_fields(S->result);
} else {
/* this was a DML or DDL query (INSERT, UPDATE, DELETE, ... */
@ -194,7 +194,7 @@ static int pdo_mysql_stmt_execute_prepared_libmysql(pdo_stmt_t *stmt) /* {{{ */
efree(S->out_length);
}
stmt->column_count = (int)mysql_num_fields(S->result);
php_pdo_stmt_set_column_count(stmt, (int)mysql_num_fields(S->result));
S->bound_result = ecalloc(stmt->column_count, sizeof(MYSQL_BIND));
S->out_null = ecalloc(stmt->column_count, sizeof(my_bool));
S->out_length = ecalloc(stmt->column_count, sizeof(zend_ulong));
@ -290,7 +290,7 @@ static int pdo_mysql_stmt_execute_prepared_mysqlnd(pdo_stmt_t *stmt) /* {{{ */
}
/* for SHOW/DESCRIBE and others the column/field count is not available before execute */
stmt->column_count = mysql_stmt_field_count(S->stmt);
php_pdo_stmt_set_column_count(stmt, mysql_stmt_field_count(S->stmt));
for (i = 0; i < stmt->column_count; i++) {
mysqlnd_stmt_bind_one_result(S->stmt, i);
}
@ -378,7 +378,7 @@ static int pdo_mysql_stmt_next_rowset(pdo_stmt_t *stmt) /* {{{ */
/* for SHOW/DESCRIBE and others the column/field count is not available before execute */
int i;
stmt->column_count = mysql_stmt_field_count(S->stmt);
php_pdo_stmt_set_column_count(stmt, mysql_stmt_field_count(S->stmt));
for (i = 0; i < stmt->column_count; i++) {
mysqlnd_stmt_bind_one_result(S->stmt, i);
}
@ -407,9 +407,6 @@ static int pdo_mysql_stmt_next_rowset(pdo_stmt_t *stmt) /* {{{ */
/* ensure that we free any previous unfetched results */
#ifndef PDO_USE_MYSQLND
if (S->stmt) {
if (S->result) {
stmt->column_count = (int)mysql_num_fields(S->result);
}
mysql_stmt_free_result(S->stmt);
}
#endif

View file

@ -0,0 +1,60 @@
--TEST--
Change column count after statement has been prepared
--SKIPIF--
<?php
if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'skipif.inc');
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
MySQLPDOTest::skip();
?>
--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 (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(255) NOT NULL)');
$stmt = $db->prepare('INSERT INTO test (id, name) VALUES(:id, :name)');
$stmt->execute([
'id' => 10,
'name' => 'test',
]);
$stmt = $db->prepare('SELECT * FROM test WHERE id = :id');
$stmt->execute(['id' => 10]);
var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC));
$db->exec('ALTER TABLE test ADD new_col VARCHAR(255)');
$stmt->execute(['id' => 10]);
var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC));
?>
--CLEAN--
<?php
require __DIR__ . '/mysql_pdo_test.inc';
MySQLPDOTest::dropTestTable();
?>
--EXPECT--
array(1) {
[0]=>
array(2) {
["id"]=>
string(2) "10"
["name"]=>
string(4) "test"
}
}
array(1) {
[0]=>
array(3) {
["id"]=>
string(2) "10"
["name"]=>
string(4) "test"
["new_col"]=>
NULL
}
}

View file

@ -39,46 +39,6 @@ static int pdo_sqlite_stmt_dtor(pdo_stmt_t *stmt)
return 1;
}
/**
* Change the column count on the statement.
*
* Since PHP 7.2 sqlite3_prepare_v2 is used which auto recompile prepared statement on schema change.
* Instead of raise an error on schema change, the result set will change, and the statement's columns must be updated.
*
* See bug #78192
*/
static void pdo_sqlite_stmt_set_column_count(pdo_stmt_t *stmt, int new_count)
{
/* Columns not yet "described" */
if (!stmt->columns) {
stmt->column_count = new_count;
return;
}
/*
* The column count has not changed : no need to reload columns description
* Note: Do not handle attribute name change, without column count change
*/
if (new_count == stmt->column_count) {
return;
}
/* Free previous columns to force reload description */
int i;
for (i = 0; i < stmt->column_count; i++) {
if (stmt->columns[i].name) {
zend_string_release(stmt->columns[i].name);
stmt->columns[i].name = NULL;
}
}
efree(stmt->columns);
stmt->columns = NULL;
stmt->column_count = new_count;
}
static int pdo_sqlite_stmt_execute(pdo_stmt_t *stmt)
{
pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data;
@ -91,11 +51,11 @@ static int pdo_sqlite_stmt_execute(pdo_stmt_t *stmt)
switch (sqlite3_step(S->stmt)) {
case SQLITE_ROW:
S->pre_fetched = 1;
pdo_sqlite_stmt_set_column_count(stmt, sqlite3_data_count(S->stmt));
php_pdo_stmt_set_column_count(stmt, sqlite3_data_count(S->stmt));
return 1;
case SQLITE_DONE:
pdo_sqlite_stmt_set_column_count(stmt, sqlite3_column_count(S->stmt));
php_pdo_stmt_set_column_count(stmt, sqlite3_column_count(S->stmt));
stmt->row_count = sqlite3_changes(S->H->db);
sqlite3_reset(S->stmt);
S->done = 1;