From d63a9d27f618b3fce8f565a58410f385366c6f06 Mon Sep 17 00:00:00 2001 From: Guillaume Outters Date: Sat, 5 Oct 2024 15:47:14 +0200 Subject: [PATCH 1/2] ext/pdo_pgsql: avoid getColumnMeta() interfering with ATTR_PREFETCH = 0 doing an (internal) query to fetch metadata from the server broke the currently-running (user) query --- ext/pdo_pgsql/pgsql_statement.c | 10 ++++++++++ ext/pdo_pgsql/tests/gh15287.phpt | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/ext/pdo_pgsql/pgsql_statement.c b/ext/pdo_pgsql/pgsql_statement.c index b2af41a339b1..e9d549d6e3dd 100644 --- a/ext/pdo_pgsql/pgsql_statement.c +++ b/ext/pdo_pgsql/pgsql_statement.c @@ -799,6 +799,12 @@ static zend_always_inline char * pdo_pgsql_translate_oid_to_table(Oid oid, PGcon PGresult *tmp_res; char *querystr = NULL; + if (H->running_stmt && H->running_stmt->is_unbuffered) { + /* in single-row mode, libpq forbids passing a new query + * while we're still flushing the current one's result */ + return NULL; + } + spprintf(&querystr, 0, "SELECT RELNAME FROM PG_CLASS WHERE OID=%d", oid); if ((tmp_res = PQexec(conn, querystr)) == NULL || PQresultStatus(tmp_res) != PGRES_TUPLES_OK) { @@ -885,6 +891,10 @@ static int pgsql_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *r break; default: /* Fetch metadata from Postgres system catalogue */ + if (S->H->running_stmt && S->H->running_stmt->is_unbuffered) { + /* libpq forbids calling a query while we're still reading the preceding one's */ + break; + } spprintf(&q, 0, "SELECT TYPNAME FROM PG_TYPE WHERE OID=%u", S->cols[colno].pgsql_type); res = PQexec(S->H->server, q); efree(q); diff --git a/ext/pdo_pgsql/tests/gh15287.phpt b/ext/pdo_pgsql/tests/gh15287.phpt index 821c22a8f1c5..44cf9fea1d40 100644 --- a/ext/pdo_pgsql/tests/gh15287.phpt +++ b/ext/pdo_pgsql/tests/gh15287.phpt @@ -132,6 +132,17 @@ $res = []; while (($re = $stmt->fetch())) $res[] = $re; display($res); $stmt->execute([ 0 ]); $res = []; for ($i = -1; ++$i < 2;) $res[] = $stmt->fetch(); display($res); display($pdo->query("select * from t2")->fetchAll()); + +// Metadata calls the server for some operations (notably table oid-to-name conversion). +// This will break libpq (that forbids a second PQexec before we consumed the first one). +// Instead of either letting libpq return an error, or blindly forbid this call, we expect +// being transparently provided at least attributes which do not require a server roundtrip. +// And good news: column name is one of those "local" attributes. +echo "=== meta ===\n"; +$stmt = $pdo->query("select * from t limit 2"); +echo "Starting with column " . $stmt->getColumnMeta(0)['name'] . ":\n"; +display($stmt->fetchAll()); + ?> --EXPECTF-- === non regression === @@ -181,3 +192,7 @@ multiple calls to the same prepared statement, some interrupted before having re 0 1 678 ok +=== meta === +Starting with column n: +0 original +1 non original From 90af79a45a7efdc1cc71740cb4d7a2c5fa55eec7 Mon Sep 17 00:00:00 2001 From: Guillaume Outters Date: Fri, 25 Sep 2026 22:31:58 +0200 Subject: [PATCH 2/2] ext/pdo_pgsql: Fix syntax for PHP-8.5 backport the parts of #23920 that we relied upon (this PR being a split of #23892) kept it synchronized with the source, to ease later merging --- ext/pdo_pgsql/pgsql_statement.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/pdo_pgsql/pgsql_statement.c b/ext/pdo_pgsql/pgsql_statement.c index e9d549d6e3dd..59b5780262fe 100644 --- a/ext/pdo_pgsql/pgsql_statement.c +++ b/ext/pdo_pgsql/pgsql_statement.c @@ -793,8 +793,9 @@ static int pgsql_stmt_get_col(pdo_stmt_t *stmt, int colno, zval *result, enum pd return 1; } -static zend_always_inline char * pdo_pgsql_translate_oid_to_table(Oid oid, PGconn *conn) +static zend_always_inline char * pdo_pgsql_translate_oid_to_table(Oid oid, pdo_pgsql_db_handle *H) { + PGconn *conn = H->server; char *table_name = NULL; PGresult *tmp_res; char *querystr = NULL; @@ -849,7 +850,7 @@ static int pgsql_stmt_get_column_meta(pdo_stmt_t *stmt, zend_long colno, zval *r table_oid = PQftable(S->result, colno); add_assoc_long(return_value, "pgsql:table_oid", table_oid); - table_name = pdo_pgsql_translate_oid_to_table(table_oid, S->H->server); + table_name = pdo_pgsql_translate_oid_to_table(table_oid, S->H); if (table_name) { add_assoc_string(return_value, "table", table_name); efree(table_name);