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)