ext/pdo_pgsql: Fixed PDO::CURSOR_SCROLL statements closing a cursor that does not exist - #23490
Open
KentarouTakeda wants to merge 1 commit into
Open
ext/pdo_pgsql: Fixed PDO::CURSOR_SCROLL statements closing a cursor that does not exist#23490KentarouTakeda wants to merge 1 commit into
KentarouTakeda wants to merge 1 commit into
Conversation
KentarouTakeda
requested review from
SakiTakamachi and
devnexen
as code owners
August 29, 2026 05:00
devnexen
requested changes
Aug 29, 2026
Member
There was a problem hiding this comment.
--- /dev/null
+++ b/ext/pdo_pgsql/tests/cursor_scroll_failed_redeclare.phpt
@@ -0,0 +1,37 @@
+--TEST--
+PDO PgSQL PDO::CURSOR_SCROLL sends no CLOSE after a failed re-declare
+--EXTENSIONS--
+pdo_pgsql
+--SKIPIF--
+<?php
+require __DIR__ . '/config.inc';
+require dirname(__DIR__, 2) . '/pdo/tests/pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+
+require_once __DIR__ . "/config.inc";
+
+$db = Pdo::connect($config['ENV']['PDOTEST_DSN']);
+
+$stmt = $db->prepare('SELECT CAST(:v AS int)', [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
+$stmt->execute([':v' => '1']);
+
+try {
+ $stmt->execute([':v' => 'not an int']);
+} catch (PDOException $e) {
+ echo $e::class, ': ', $e->getCode(), PHP_EOL;
+}
+
+$db->beginTransaction();
+unset($stmt);
+
+$db->exec('SELECT 2');
+
+echo 'Done', PHP_EOL;
+
+?>
+--EXPECT--
+PDOException: 22P02
+Done
Contributor
Author
There was a problem hiding this comment.
Thanks, your test failed as expected. I've changed the approach. is_prepared and is_cursor_declared now track the two states separately.
KentarouTakeda
force-pushed
the
fix-pdo-pgsql-cursor-dtor
branch
from
August 30, 2026 11:46
b5a49d1 to
0366dee
Compare
…hat does not exist
KentarouTakeda
force-pushed
the
fix-pdo-pgsql-cursor-dtor
branch
from
August 30, 2026 11:53
0366dee to
fa968a0
Compare
Contributor
Author
|
I had the wrong target branch. This bug is not new, so it needs to go to |
Member
|
Some more tests :) --TEST--
PDO PgSQL PDO::CURSOR_SCROLL keeps track of a held cursor when the CLOSE before a re-declare fails
--EXTENSIONS--
pdo_pgsql
--SKIPIF--
<?php
require __DIR__ . '/config.inc';
require dirname(__DIR__, 2) . '/pdo/tests/pdo_test.inc';
PDOTest::skip();
?>
--FILE--
<?php
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
$db = PDOTest::test_factory(__DIR__ . '/common.phpt');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $db->prepare('SELECT CAST(:v AS int)', [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
$stmt->execute([':v' => '1']);
$db->beginTransaction();
try {
$db->exec('SELECT 1 / 0');
} catch (PDOException $e) {
echo $e::class, ': ', $e->getCode(), PHP_EOL;
}
try {
$stmt->execute([':v' => '2']);
} catch (PDOException $e) {
echo $e::class, ': ', $e->getCode(), PHP_EOL;
}
$db->rollBack();
unset($stmt);
var_dump($db->query("SELECT count(*) FROM pg_cursors WHERE name LIKE 'pdo\_crsr\_%'")->fetchColumn());
?>
--EXPECT--
PDOException: 22012
PDOException: 25P02
string(1) "0"--TEST--
PDO PgSQL PDO::CURSOR_SCROLL sends no CLOSE for a cursor a rollback already destroyed
--EXTENSIONS--
pdo_pgsql
--SKIPIF--
<?php
require __DIR__ . '/config.inc';
require dirname(__DIR__, 2) . '/pdo/tests/pdo_test.inc';
PDOTest::skip();
?>
--FILE--
<?php
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
$db = PDOTest::test_factory(__DIR__ . '/common.phpt');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->beginTransaction();
$stmt = $db->prepare('SELECT 1', [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]);
$stmt->execute();
$db->rollBack();
$db->beginTransaction();
unset($stmt);
$db->exec('SELECT 2');
echo 'Done', PHP_EOL;
?>
--EXPECT--
Done |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The destructor of a statement created with
[PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL]tries to close the cursor even when it does not exist. This fixes that.The attempted close causes an error on the database server, but the destructor discards its result, so the error cannot be observed by the user. Apart from polluting the server's log, this is mostly harmless, but when it happens inside a transaction, it causes a strange situation where subsequent statements fail for a reason that cannot be observed.
is_preparedwas overloaded to also mean "cursor declared" and was never reset, so the cursor state now has its own flag.