Commit 11690db1787 for php
commit 11690db1787e9b3ee61138dc7e06d82e025f1b3a
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Sat Sep 26 18:15:09 2026 -0400
ext/pdo: Drop the previous result when execute() fails
PDOStatement::execute() returned false on a bind, parse, or driver
failure without clearing stmt->executed, so a later fetch returned rows
from the previous success. Close the previous result on entry, before
the error state is cleared, so a cursor closer that records an error
cannot replace the SQLSTATE of the failure execute() reports.
Closes GH-23938
diff --git a/NEWS b/NEWS
index 3e045040774..77ed82419ae 100644
--- a/NEWS
+++ b/NEWS
@@ -119,6 +119,8 @@ PHP NEWS
column index. (Ilia Alshanetsky)
. Fixed PDOStatement::bindColumn() registering a binding for a column name
that is not in the result set. (Ilia Alshanetsky)
+ . Fixed PDOStatement::execute() leaving the previous result available
+ after a failed execution. (Ilia Alshanetsky)
. Fixed bug GH-23962 (Destroying a persistent PDO instance rolls back a
transaction still in use by another instance). (Lazizbek Ergashev)
. Fixed PDO::setAttribute() installing a PDO::ATTR_STATEMENT_CLASS class
diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c
index c4abbfe4536..5fbfb0d220f 100644
--- a/ext/pdo/pdo_stmt.c
+++ b/ext/pdo/pdo_stmt.c
@@ -380,6 +380,29 @@ static bool really_register_bound_param(struct pdo_bound_param_data *param, pdo_
}
/* }}} */
+static bool pdo_stmt_do_next_rowset(pdo_stmt_t *stmt);
+
+static void pdo_stmt_invalidate_result(pdo_stmt_t *stmt)
+{
+ if (stmt->methods->cursor_closer) {
+ stmt->methods->cursor_closer(stmt);
+ } else {
+ do {
+ while (stmt->methods->fetcher(stmt, PDO_FETCH_ORI_NEXT, 0))
+ ;
+ if (!stmt->methods->next_rowset) {
+ break;
+ }
+
+ if (!pdo_stmt_do_next_rowset(stmt)) {
+ break;
+ }
+ } while (1);
+ }
+
+ stmt->executed = 0;
+}
+
/* {{{ Execute a prepared statement, optionally binding parameters */
PHP_METHOD(PDOStatement, execute)
{
@@ -392,6 +415,10 @@ PHP_METHOD(PDOStatement, execute)
ZEND_PARSE_PARAMETERS_END();
PHP_STMT_GET_OBJ;
+
+ if (stmt->executed) {
+ pdo_stmt_invalidate_result(stmt);
+ }
PDO_STMT_CLEAR_ERR();
if (input_params) {
diff --git a/ext/pdo_sqlite/tests/pdo_sqlite_failed_execute.phpt b/ext/pdo_sqlite/tests/pdo_sqlite_failed_execute.phpt
new file mode 100644
index 00000000000..c483c6dd959
--- /dev/null
+++ b/ext/pdo_sqlite/tests/pdo_sqlite_failed_execute.phpt
@@ -0,0 +1,83 @@
+--TEST--
+PDO SQLite: failed execute invalidates the previous result
+--EXTENSIONS--
+pdo_sqlite
+--FILE--
+<?php
+
+$db = new PDO('sqlite::memory:');
+
+$stmt = $db->prepare('SELECT ? AS value UNION ALL SELECT 2');
+$stmt->execute(['first']);
+var_dump($stmt->fetchColumn());
+
+try {
+ $stmt->execute([new stdClass()]);
+} catch (Error $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+var_dump($stmt->fetchColumn());
+
+$stmt->execute(['third']);
+var_dump($stmt->fetchAll(PDO::FETCH_COLUMN));
+
+$db->sqliteCreateFunction('fail_execute', function (bool $fail): string {
+ if ($fail) {
+ throw new RuntimeException('execution failed');
+ }
+ return 'first';
+});
+$stmt = $db->prepare('SELECT fail_execute(?) AS value UNION ALL SELECT 2');
+$stmt->execute([false]);
+var_dump($stmt->fetchColumn());
+
+try {
+ $stmt->execute([true]);
+} catch (RuntimeException $e) {
+ echo $e::class, ': ', $e->getMessage(), PHP_EOL;
+}
+var_dump($stmt->fetchColumn());
+
+$stmt->execute([false]);
+var_dump($stmt->fetchAll(PDO::FETCH_COLUMN));
+
+$db->exec('CREATE TABLE failed_execute (value INTEGER UNIQUE)');
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
+$stmt = $db->prepare('INSERT INTO failed_execute VALUES (?)');
+$stmt->execute([1]);
+$warnings = 0;
+set_error_handler(function () use (&$warnings): bool {
+ $warnings++;
+ return true;
+});
+try {
+ var_dump($stmt->execute([1]));
+} finally {
+ restore_error_handler();
+}
+echo "driver warnings: $warnings\n";
+var_dump($stmt->fetchColumn());
+
+?>
+--EXPECT--
+string(5) "first"
+Error: Object of class stdClass could not be converted to string
+bool(false)
+array(2) {
+ [0]=>
+ string(5) "third"
+ [1]=>
+ int(2)
+}
+string(5) "first"
+RuntimeException: execution failed
+bool(false)
+array(2) {
+ [0]=>
+ string(5) "first"
+ [1]=>
+ int(2)
+}
+bool(false)
+driver warnings: 1
+bool(false)