}
/* }}} */
+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)) {
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 */
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);
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 */
}
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, ... */
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));
}
/* 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);
}
/* 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);
}
/* 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
--- /dev/null
+--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
+ }
+}
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;
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;