From ec79850c501b1abd06ea44f3f9feeee84b5ea438 Mon Sep 17 00:00:00 2001 From: Thomas Schiet Date: Thu, 9 Apr 2026 12:18:29 +0200 Subject: [PATCH] Fix GH-21683: PDOStatement::fetch() throws on empty result With PDO::ATTR_FETCH attribute. close GH-21684. --- NEWS | 4 +++ ext/pdo_pgsql/pgsql_statement.c | 6 ++++ ext/pdo_pgsql/tests/gh21683.phpt | 48 ++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 ext/pdo_pgsql/tests/gh21683.phpt diff --git a/NEWS b/NEWS index b79170b91663..8271d5fe1389 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 f9320fd86ea8..89f713ffcbff 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 000000000000..bd941511767a --- /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-- + +--FILE-- +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