Skip to content

Commit

Permalink
Handle column count change in PDO MySQL
Browse files Browse the repository at this point in the history
This has been fixed for PDO SQlite by GH-4313, however the same
issue also applied to PDO MySQL.

Move the column count setting function into the main PDO layer
(and export it) and then use it in both PDO SQLite and PDO MySQL.
  • Loading branch information
nikic committed Dec 8, 2020
1 parent 713ef85 commit bfa69d2
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 77 deletions.
68 changes: 41 additions & 27 deletions ext/pdo/pdo_stmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion ext/pdo/php_pdo_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,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 */
11 changes: 4 additions & 7 deletions ext/pdo_mysql/mysql_statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -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, ... */
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
Expand Down
60 changes: 60 additions & 0 deletions ext/pdo_mysql/tests/change_column_count.phpt
Original file line number Diff line number Diff line change
@@ -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
}
}
44 changes: 2 additions & 42 deletions ext/pdo_sqlite/sqlite_statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down

0 comments on commit bfa69d2

Please sign in to comment.