Commit 197bdc0a405 for php.net
commit 197bdc0a40586be9583c5b77de35e9900b6a912e
Author: 武田 憲太郎 <takeda@youmind.jp>
Date: Tue Aug 25 23:00:34 2026 +0000
ext/pdo_pgsql: Fix PDO::CURSOR_SCROLL statements failing under lazy fetching
A statement prepared with PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL and lazy
fetching (PDO::ATTR_PREFETCH => 0) failed at execute() with
SQLSTATE[HY000]: General error: 7 and no message. Either option alone
worked.
A cursor does not stream its result, but S->is_unbuffered stayed set, so
execute() called PQgetResult() with nothing in flight. Clear the flag when
the statement has a cursor.
Close GH-23471
diff --git a/NEWS b/NEWS
index 149fea34707..466321cf102 100644
--- a/NEWS
+++ b/NEWS
@@ -48,6 +48,10 @@ PHP NEWS
. Fixed a leak when a persistent connection failed a liveness check
with no other live PDO handle. (iliaal)
+- PDO_PGSQL:
+ . Fixed PDO::CURSOR_SCROLL statements failing under lazy fetching
+ (PDO::ATTR_PREFETCH => 0). (KentarouTakeda)
+
- Phar:
. Fixed bug GH-23418 (Use-after-free when looking up mounted directories).
(Weilin Du)
diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c
index 54b2e25f72f..060b8d68776 100644
--- a/ext/pdo_pgsql/pgsql_driver.c
+++ b/ext/pdo_pgsql/pgsql_driver.c
@@ -287,7 +287,16 @@ static bool pgsql_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *
scrollable = pdo_attr_lval(driver_options, PDO_ATTR_CURSOR,
PDO_CURSOR_FWDONLY) == PDO_CURSOR_SCROLL;
+ S->is_unbuffered =
+ driver_options
+ && (val = zend_hash_index_find(Z_ARRVAL_P(driver_options), PDO_ATTR_PREFETCH))
+ && pdo_get_long_param(&lval, val)
+ ? !lval
+ : H->default_fetching_laziness
+ ;
+
if (scrollable) {
+ S->is_unbuffered = false;
if (S->cursor_name) {
efree(S->cursor_name);
}
@@ -312,14 +321,6 @@ static bool pgsql_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *
stmt->named_rewrite_template = "$%d";
}
- S->is_unbuffered =
- driver_options
- && (val = zend_hash_index_find(Z_ARRVAL_P(driver_options), PDO_ATTR_PREFETCH))
- && pdo_get_long_param(&lval, val)
- ? !lval
- : H->default_fetching_laziness
- ;
-
ret = pdo_parse_params(stmt, sql, &nsql);
if (ret == -1) {
diff --git a/ext/pdo_pgsql/tests/cursor_scroll_lazy_fetch.phpt b/ext/pdo_pgsql/tests/cursor_scroll_lazy_fetch.phpt
new file mode 100644
index 00000000000..df4be41b853
--- /dev/null
+++ b/ext/pdo_pgsql/tests/cursor_scroll_lazy_fetch.phpt
@@ -0,0 +1,33 @@
+--TEST--
+PDO PgSQL a scrollable cursor is unaffected by lazy fetching
+--EXTENSIONS--
+pdo_pgsql
+--SKIPIF--
+<?php
+require __DIR__ . '/config.inc';
+require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+
+require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
+$pdo = PDOTest::test_factory(__DIR__ . '/common.phpt');
+$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+
+$sql = "SELECT * FROM generate_series(1, 3)";
+$scrollable = [PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL];
+
+$stmt = $pdo->prepare($sql, $scrollable + [PDO::ATTR_PREFETCH => 0]);
+$stmt->execute();
+echo 'lazy on the statement: ', implode(',', $stmt->fetchAll(PDO::FETCH_COLUMN)), PHP_EOL;
+
+$pdo->setAttribute(PDO::ATTR_PREFETCH, 0);
+$stmt = $pdo->prepare($sql, $scrollable);
+$stmt->execute();
+echo 'lazy on the connection: ', implode(',', $stmt->fetchAll(PDO::FETCH_COLUMN)), PHP_EOL;
+
+?>
+--EXPECT--
+lazy on the statement: 1,2,3
+lazy on the connection: 1,2,3