Commit ec79850c501 for php.net

commit ec79850c501b1abd06ea44f3f9feeee84b5ea438
Author: Thomas Schiet <thomas.schiet@gmail.com>
Date:   Thu Apr 9 12:18:29 2026 +0200

    Fix GH-21683: PDOStatement::fetch() throws on empty result With PDO::ATTR_FETCH attribute.

    close GH-21684.

diff --git a/NEWS b/NEWS
index b79170b9166..8271d5fe138 100644
--- a/NEWS
+++ b/NEWS
@@ -43,6 +43,10 @@ PHP                                                                        NEWS
   . Fix memory leak regression in openssl_pbkdf2(). (ndossche)
   . Fix a bunch of memory leaks and crashes on edge cases. (ndossche)

+- PDO_PGSQL:
+  . Fixed bug GH-21683 (pdo_pgsql throws with ATTR_PREFETCH=0
+    on empty result set). (thomasschiet)
+
 - Random:
   . Fixed bug GH-21731 (Random\Engine\Xoshiro256StarStar::__unserialize()
     accepts all-zero state). (iliaal)
diff --git a/ext/pdo_pgsql/pgsql_statement.c b/ext/pdo_pgsql/pgsql_statement.c
index f9320fd86ea..89f713ffcbf 100644
--- a/ext/pdo_pgsql/pgsql_statement.c
+++ b/ext/pdo_pgsql/pgsql_statement.c
@@ -573,6 +573,12 @@ static int pgsql_stmt_fetch(pdo_stmt_t *stmt,
 			}

 			S->result = PQgetResult(S->H->server);
+			if (!S->result) {
+				S->is_running_unbuffered = false;
+				stmt->row_count = 0;
+				S->current_row = 0;
+				return 0;
+			}
 			status = PQresultStatus(S->result);

 			if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK && status != PGRES_SINGLE_TUPLE) {
diff --git a/ext/pdo_pgsql/tests/gh21683.phpt b/ext/pdo_pgsql/tests/gh21683.phpt
new file mode 100644
index 00000000000..bd941511767
--- /dev/null
+++ b/ext/pdo_pgsql/tests/gh21683.phpt
@@ -0,0 +1,48 @@
+--TEST--
+PDO PgSQL single-row mode (ATTR_PREFETCH=0) on empty result set
+--EXTENSIONS--
+pdo
+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);
+$pdo->setAttribute(PDO::ATTR_PREFETCH, 0);
+
+$pdo->exec("CREATE TEMP TABLE empty_t (id int, val text)");
+
+echo "=== query / fetch ===\n";
+$stmt = $pdo->query("SELECT * FROM empty_t");
+var_dump($stmt->fetch());
+
+echo "=== prepare / fetchAll ===\n";
+$stmt = $pdo->prepare("SELECT * FROM empty_t");
+$stmt->execute();
+var_dump($stmt->fetchAll());
+
+echo "=== connection still works ===\n";
+$stmt = $pdo->query("SELECT 1 AS alive");
+var_dump($stmt->fetch(PDO::FETCH_ASSOC));
+
+echo "Done\n";
+?>
+--EXPECT--
+=== query / fetch ===
+bool(false)
+=== prepare / fetchAll ===
+array(0) {
+}
+=== connection still works ===
+array(1) {
+  ["alive"]=>
+  string(1) "1"
+}
+Done