Repository navigation
Fix crash when re-executing a statement after closeCursor() - #46
Conversation
closeCursor() clears stmt->executed, so the next execute() reallocates S->cols, but PDO core keeps its column descriptions and skips describing them again. The OCI defines then keep writing into the freed buffers and the next fetch crashes. Drop PDO's column descriptions when reallocating so they are described into the new buffers. The same path is reached on any re-execute with PHP 8.4.27RC1, 8.5.12RC1 and 8.6.0RC3, see php#44.
|
The 8.4/8.5/master failures are the php-src common PDO tests run from the branch head against the released setup-php binary (8.5.11 here); those tests assert fixes newer than that release, so |
|
Thanks for this. With this branch on PHP 8.4.27 the SELECT crash is gone for us (Debian 13, Instant Client 19.12, Oracle 11.2.0.4), but re-executing a non-query statement still fails. pdo_oci has no $st = $dbh->prepare("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'");
$st->execute();
$st->execute(); // ORA-01002: fetch out of sequenceyiisoft/yii2#21122 reports Would you consider adding a cursor closer that cancels the cursor of a query only, with the same zero-row fetch @@ -1020,6 +1020,20 @@ static int oci_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *return_val
return SUCCESS;
} /* }}} */
+static int oci_stmt_cursor_closer(pdo_stmt_t *stmt) /* {{{ */
+{
+ pdo_oci_stmt *S = (pdo_oci_stmt*)stmt->driver_data;
+
+ /* Without a closer, PDO drains the previous result with the fetcher, even on
+ * DML / DDL handles (ORA-01002, ORA-03137 on PHP 8.4.27 / 8.5.12: GH-24372).
+ * Only a query has a cursor: cancel it, as oci_stmt_execute() does. */
+ if (S && S->stmt && S->stmt_type == OCI_STMT_SELECT) {
+ OCIStmtFetch(S->stmt, S->err, 0, OCI_FETCH_NEXT, OCI_DEFAULT);
+ }
+
+ return 1;
+} /* }}} */
+
const struct pdo_stmt_methods oci_stmt_methods = {
oci_stmt_dtor,
oci_stmt_execute,
@@ -1031,5 +1045,5 @@ const struct pdo_stmt_methods oci_stmt_methods = {
NULL, /* get_attr */
oci_stmt_col_meta,
NULL,
- NULL
+ oci_stmt_cursor_closer
};With the core fix (d5696b8) the closer is also what PDO calls on re-execute, and Tested on PHP 8.4.27 with this branch + the closer: SELECT re-executed with binds, partially fetched SELECT re-executed, SELECT with a BLOB column, I could not run DML on a real table here, so I added a test for CI. On 8.4.27 its tests/pdo_oci_reexecute_dml.phpt--TEST--
PDO_OCI: Re-executing DML and DDL statements
--EXTENSIONS--
pdo
pdo_oci
--SKIPIF--
<?php
require(getenv('PDO_TEST_DIR').'/pdo_test.inc');
PDOTest::skip();
?>
--FILE--
<?php
require_once(getenv('PDO_TEST_DIR').'/pdo_test.inc');
$dbh = PDOTest::factory();
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$dbh->exec("begin
execute immediate 'drop table test_reexecute_dml';
exception when others then
if sqlcode <> -942 then
raise;
end if;
end;");
$dbh->exec('CREATE TABLE test_reexecute_dml (id NUMBER NOT NULL PRIMARY KEY, name VARCHAR2(20))');
$insert = $dbh->prepare('INSERT INTO test_reexecute_dml (id, name) VALUES (:id, :name)');
foreach ([1 => 'a', 2 => 'b', 3 => 'c'] as $id => $name) {
$insert->execute([':id' => $id, ':name' => $name]);
}
$update = $dbh->prepare('UPDATE test_reexecute_dml SET name = :name WHERE id = :id');
$update->execute([':id' => 1, ':name' => 'a1']);
$update->execute([':id' => 2, ':name' => 'b1']);
$update->execute([':id' => 2, ':name' => 'b2']);
var_dump($update->rowCount());
$alter = $dbh->prepare("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'");
$alter->execute();
$alter->execute();
$alter->execute();
echo "alter session: ok\n";
$select = $dbh->prepare('SELECT name FROM test_reexecute_dml WHERE id = :id');
foreach ([1, 2, 3] as $id) {
$select->execute([':id' => $id]);
echo $select->fetchColumn(), "\n";
}
?>
--CLEAN--
<?php
require_once(getenv('PDO_TEST_DIR').'/pdo_test.inc');
$dbh = PDOTest::factory();
$dbh->exec('DROP TABLE test_reexecute_dml');
?>
--EXPECT--
int(1)
alter session: ok
a1
b2
cThe commit (closer + test + |
…acos-unit][win-unit] Reset PDO column descriptions before replacing OCI define buffers, adapting php/pecl-database-pdo_oci#46. Provide an OCI cursor closer so PDO does not drain DML/RETURNING statements with a SELECT fetch before re-execution on PHP 8.4.27. Cover exhausted and partially read cursor reuse in blocking and coroutine modes. Exercise explicit cursor closing between LOB executions and isolate fixture tables so crashes do not poison retries. Validation: reproduced SIGSEGV and both ORA-03137 LOB failures before the fix; all four focused Oracle tests pass after the fix. The full directory has 33 passes, 4 version skips, and 3 pre-existing client error-format mismatches, verified identical with the original driver.
Register a cursor closer that cancels SELECT cursors with a zero-row fetch. PDO's fallback fetches from DML, DDL, and PL/SQL handles, which can make the next execute fail with ORA-01002 or ORA-03137. This affects explicit closeCursor() and automatic cleanup in PHP 8.4.27 and 8.5.12. Cover non-query re-execution with and without closeCursor(), and extend query coverage to partial fetches, changed binds, repeated closure, and scrollable cursors. Based on m3ct0n's suggestion and regression test.
|
Added the query-only closer and regression coverage for DML, ALTER SESSION, PL/SQL, and partially fetched queries. Verified on PHP 8.4.26 and 8.4.27, including explicit closeCursor(), repeated closure, and scrollable cursors. Thanks for the reproducer and proposed fix. |
|
Thks 👍 |
closeCursor() clears
stmt->executed, so the next execute() reallocatesS->colswhile PDO core keeps its column descriptions and never describes them again; the OCI defines keep writing into the freed buffers and the next fetch crashes. Dropping PDO's descriptions with php_pdo_stmt_set_column_count(stmt, 0), as pdo_sqlsrv does, makes core describe the columns into the new buffers. PHP 8.4.27RC1, 8.5.12RC1 and 8.6.0RC3 reach the same branch on every re-execute (#44, php/php-src#24372), so this covers that too; the core side is fixed separately.