Skip to content

Commit bfa69d2

Browse files
committed
Handle column count change in PDO MySQL
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.
1 parent 713ef85 commit bfa69d2

File tree

5 files changed

+108
-77
lines changed

5 files changed

+108
-77
lines changed

ext/pdo/pdo_stmt.c

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,45 @@ int pdo_stmt_describe_columns(pdo_stmt_t *stmt) /* {{{ */
173173
}
174174
/* }}} */
175175

176+
static void pdo_stmt_reset_columns(pdo_stmt_t *stmt) {
177+
if (stmt->columns) {
178+
int i;
179+
struct pdo_column_data *cols = stmt->columns;
180+
181+
for (i = 0; i < stmt->column_count; i++) {
182+
if (cols[i].name) {
183+
zend_string_release_ex(cols[i].name, 0);
184+
}
185+
}
186+
efree(stmt->columns);
187+
}
188+
stmt->columns = NULL;
189+
stmt->column_count = 0;
190+
}
191+
192+
/**
193+
* Change the column count on the statement. If it differs from the previous one,
194+
* discard existing columns information.
195+
*/
196+
PDO_API void php_pdo_stmt_set_column_count(pdo_stmt_t *stmt, int new_count)
197+
{
198+
/* Columns not yet "described". */
199+
if (!stmt->columns) {
200+
stmt->column_count = new_count;
201+
return;
202+
}
203+
204+
/* The column count has not changed: No need to reload columns description.
205+
* Note: Do not handle attribute name change, without column count change. */
206+
if (new_count == stmt->column_count) {
207+
return;
208+
}
209+
210+
/* Free previous columns to force reload description. */
211+
pdo_stmt_reset_columns(stmt);
212+
stmt->column_count = new_count;
213+
}
214+
176215
static void get_lazy_object(pdo_stmt_t *stmt, zval *return_value) /* {{{ */
177216
{
178217
if (Z_ISUNDEF(stmt->lazy_object_ref)) {
@@ -1910,20 +1949,7 @@ PHP_METHOD(PDOStatement, setFetchMode)
19101949

19111950
static bool pdo_stmt_do_next_rowset(pdo_stmt_t *stmt)
19121951
{
1913-
/* un-describe */
1914-
if (stmt->columns) {
1915-
int i;
1916-
struct pdo_column_data *cols = stmt->columns;
1917-
1918-
for (i = 0; i < stmt->column_count; i++) {
1919-
if (cols[i].name) {
1920-
zend_string_release_ex(cols[i].name, 0);
1921-
}
1922-
}
1923-
efree(stmt->columns);
1924-
stmt->columns = NULL;
1925-
stmt->column_count = 0;
1926-
}
1952+
pdo_stmt_reset_columns(stmt);
19271953

19281954
if (!stmt->methods->next_rowset(stmt)) {
19291955
/* 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)
21562182
efree(stmt->query_string);
21572183
}
21582184

2159-
if (stmt->columns) {
2160-
int i;
2161-
struct pdo_column_data *cols = stmt->columns;
2162-
2163-
for (i = 0; i < stmt->column_count; i++) {
2164-
if (cols[i].name) {
2165-
zend_string_release_ex(cols[i].name, 0);
2166-
cols[i].name = NULL;
2167-
}
2168-
}
2169-
efree(stmt->columns);
2170-
stmt->columns = NULL;
2171-
}
2185+
pdo_stmt_reset_columns(stmt);
21722186

21732187
if (!Z_ISUNDEF(stmt->fetch.into) && stmt->default_fetch_type == PDO_FETCH_INTO) {
21742188
zval_ptr_dtor(&stmt->fetch.into);

ext/pdo/php_pdo_driver.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ PDO_API void php_pdo_dbh_addref(pdo_dbh_t *dbh);
690690
PDO_API void php_pdo_dbh_delref(pdo_dbh_t *dbh);
691691

692692
PDO_API void php_pdo_free_statement(pdo_stmt_t *stmt);
693-
693+
PDO_API void php_pdo_stmt_set_column_count(pdo_stmt_t *stmt, int new_count);
694694

695695
PDO_API void pdo_throw_exception(unsigned int driver_errcode, char *driver_errmsg, pdo_error_type *pdo_error);
696696
#endif /* PHP_PDO_DRIVER_H */

ext/pdo_mysql/mysql_statement.c

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ static int pdo_mysql_fill_stmt_from_result(pdo_stmt_t *stmt) /* {{{ */
144144
}
145145

146146
stmt->row_count = (zend_long) mysql_num_rows(S->result);
147-
stmt->column_count = (int) mysql_num_fields(S->result);
147+
php_pdo_stmt_set_column_count(stmt, (int) mysql_num_fields(S->result));
148148
S->fields = mysql_fetch_fields(S->result);
149149
} else {
150150
/* 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) /* {{{ */
194194
efree(S->out_length);
195195
}
196196

197-
stmt->column_count = (int)mysql_num_fields(S->result);
197+
php_pdo_stmt_set_column_count(stmt, (int)mysql_num_fields(S->result));
198198
S->bound_result = ecalloc(stmt->column_count, sizeof(MYSQL_BIND));
199199
S->out_null = ecalloc(stmt->column_count, sizeof(my_bool));
200200
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) /* {{{ */
290290
}
291291

292292
/* for SHOW/DESCRIBE and others the column/field count is not available before execute */
293-
stmt->column_count = mysql_stmt_field_count(S->stmt);
293+
php_pdo_stmt_set_column_count(stmt, mysql_stmt_field_count(S->stmt));
294294
for (i = 0; i < stmt->column_count; i++) {
295295
mysqlnd_stmt_bind_one_result(S->stmt, i);
296296
}
@@ -378,7 +378,7 @@ static int pdo_mysql_stmt_next_rowset(pdo_stmt_t *stmt) /* {{{ */
378378
/* for SHOW/DESCRIBE and others the column/field count is not available before execute */
379379
int i;
380380

381-
stmt->column_count = mysql_stmt_field_count(S->stmt);
381+
php_pdo_stmt_set_column_count(stmt, mysql_stmt_field_count(S->stmt));
382382
for (i = 0; i < stmt->column_count; i++) {
383383
mysqlnd_stmt_bind_one_result(S->stmt, i);
384384
}
@@ -407,9 +407,6 @@ static int pdo_mysql_stmt_next_rowset(pdo_stmt_t *stmt) /* {{{ */
407407
/* ensure that we free any previous unfetched results */
408408
#ifndef PDO_USE_MYSQLND
409409
if (S->stmt) {
410-
if (S->result) {
411-
stmt->column_count = (int)mysql_num_fields(S->result);
412-
}
413410
mysql_stmt_free_result(S->stmt);
414411
}
415412
#endif
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
--TEST--
2+
Change column count after statement has been prepared
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
6+
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'skipif.inc');
7+
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
8+
MySQLPDOTest::skip();
9+
?>
10+
--FILE--
11+
<?php
12+
require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
13+
14+
$db = MySQLPDOTest::factory();
15+
$db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
16+
17+
$db->exec('DROP TABLE IF EXISTS test');
18+
$db->exec('CREATE TABLE test (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(255) NOT NULL)');
19+
20+
$stmt = $db->prepare('INSERT INTO test (id, name) VALUES(:id, :name)');
21+
$stmt->execute([
22+
'id' => 10,
23+
'name' => 'test',
24+
]);
25+
26+
$stmt = $db->prepare('SELECT * FROM test WHERE id = :id');
27+
$stmt->execute(['id' => 10]);
28+
var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC));
29+
30+
$db->exec('ALTER TABLE test ADD new_col VARCHAR(255)');
31+
$stmt->execute(['id' => 10]);
32+
var_dump($stmt->fetchAll(\PDO::FETCH_ASSOC));
33+
34+
?>
35+
--CLEAN--
36+
<?php
37+
require __DIR__ . '/mysql_pdo_test.inc';
38+
MySQLPDOTest::dropTestTable();
39+
?>
40+
--EXPECT--
41+
array(1) {
42+
[0]=>
43+
array(2) {
44+
["id"]=>
45+
string(2) "10"
46+
["name"]=>
47+
string(4) "test"
48+
}
49+
}
50+
array(1) {
51+
[0]=>
52+
array(3) {
53+
["id"]=>
54+
string(2) "10"
55+
["name"]=>
56+
string(4) "test"
57+
["new_col"]=>
58+
NULL
59+
}
60+
}

ext/pdo_sqlite/sqlite_statement.c

Lines changed: 2 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -39,46 +39,6 @@ static int pdo_sqlite_stmt_dtor(pdo_stmt_t *stmt)
3939
return 1;
4040
}
4141

42-
/**
43-
* Change the column count on the statement.
44-
*
45-
* Since PHP 7.2 sqlite3_prepare_v2 is used which auto recompile prepared statement on schema change.
46-
* Instead of raise an error on schema change, the result set will change, and the statement's columns must be updated.
47-
*
48-
* See bug #78192
49-
*/
50-
static void pdo_sqlite_stmt_set_column_count(pdo_stmt_t *stmt, int new_count)
51-
{
52-
/* Columns not yet "described" */
53-
if (!stmt->columns) {
54-
stmt->column_count = new_count;
55-
56-
return;
57-
}
58-
59-
/*
60-
* The column count has not changed : no need to reload columns description
61-
* Note: Do not handle attribute name change, without column count change
62-
*/
63-
if (new_count == stmt->column_count) {
64-
return;
65-
}
66-
67-
/* Free previous columns to force reload description */
68-
int i;
69-
70-
for (i = 0; i < stmt->column_count; i++) {
71-
if (stmt->columns[i].name) {
72-
zend_string_release(stmt->columns[i].name);
73-
stmt->columns[i].name = NULL;
74-
}
75-
}
76-
77-
efree(stmt->columns);
78-
stmt->columns = NULL;
79-
stmt->column_count = new_count;
80-
}
81-
8242
static int pdo_sqlite_stmt_execute(pdo_stmt_t *stmt)
8343
{
8444
pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data;
@@ -91,11 +51,11 @@ static int pdo_sqlite_stmt_execute(pdo_stmt_t *stmt)
9151
switch (sqlite3_step(S->stmt)) {
9252
case SQLITE_ROW:
9353
S->pre_fetched = 1;
94-
pdo_sqlite_stmt_set_column_count(stmt, sqlite3_data_count(S->stmt));
54+
php_pdo_stmt_set_column_count(stmt, sqlite3_data_count(S->stmt));
9555
return 1;
9656

9757
case SQLITE_DONE:
98-
pdo_sqlite_stmt_set_column_count(stmt, sqlite3_column_count(S->stmt));
58+
php_pdo_stmt_set_column_count(stmt, sqlite3_column_count(S->stmt));
9959
stmt->row_count = sqlite3_changes(S->H->db);
10060
sqlite3_reset(S->stmt);
10161
S->done = 1;

0 commit comments

Comments
 (0)