[php-src] Issue #21869: `pdo_pgsql`: `DEALLOCATE` on statement destruct silently aborts enclosing transaction if it fails
[email protected] (Benjscho)
| Newsgroups | php.bugs |
|---|---|
| Message-ID | <[email protected]> |
Issue: https://github.com/php/php-src/issues/21869
Author: Benjscho
### Description
## Description
When a `PDOStatement` backed by a server-side prepared statement is destroyed inside an open transaction, `pdo_pgsql` issues `PQexec("DEALLOCATE <name>")` unconditionally. If that `DEALLOCATE` fails for any reason, the enclosing transaction is moved to the failed state by the server, and the user's subsequent `COMMIT` is silently returned as `ROLLBACK`. No exception is raised to PHP because the failing call is pdo_pgsql-internal, not user-initiated.
On stock PostgreSQL this is latent — `DEALLOCATE <name>` effectively never fails once you've successfully `PREPARE`d. But the code is not defensive about it, and on Postgres-compatible servers where `DEALLOCATE <name>` is not supported (Aurora DSQL, and plausibly others in the future), every prepared-statement user inside a transaction silently loses their data.
## Reproduction
Any PDO code doing:
```php
$pdo = new PDO("pgsql:host=...;dbname=...", $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
// ATTR_EMULATE_PREPARES defaults to false for pgsql
]);
$pdo->exec("BEGIN");
$stmt = $pdo->prepare("INSERT INTO t VALUES (?)");
$stmt->execute([1]);
unset($stmt); // <-- pdo_pgsql sends DEALLOCATE here
$pdo->exec("COMMIT"); // <-- returns success; row is NOT persisted
```
will silently lose the row if the `DEALLOCATE` fails.
### Wire-level evidence
Captured with `PQtrace` against Aurora DSQL (PostgreSQL 16-compatible server that rejects `DEALLOCATE <name>`):
```
F Query "BEGIN"
B CommandComplete "BEGIN"
B ReadyForQuery T ; in transaction
F Parse "pdo_stmt_00000001" "INSERT INTO t VALUES ($1)"
F Bind / Describe / Execute / Sync
B ParseComplete / BindComplete / CommandComplete "INSERT 0 1"
B ReadyForQuery T
F Query "DEALLOCATE pdo_stmt_00000001" ; pdo_pgsql destructor
B ErrorResponse C "0A000" M "DEALLOCATE <name> not supported"
B ReadyForQuery E ; TRANSACTION NOW FAILED
F Query "COMMIT"
B CommandComplete "ROLLBACK" ; PG semantics: commit-of-failed-tx = rollback
B ReadyForQuery I
```
From PHP's point of view `execute()` succeeded, `COMMIT` succeeded, `errorInfo()` is `["00000", null, null]`.
## Environment I observed it in
- PHP 8.2.30
- `pdo_pgsql` compiled against libpq 15 (glibc, Debian bookworm)
- Aurora DSQL (PostgreSQL 16-compatible)
### PHP Version
```plain
PHP 8.2.30 (cli) (built: Apr 22 2026 01:33:58) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.30, Copyright (c) Zend Technologies
with Zend OPcache v8.2.30, Copyright (c), by Zend Technologies
```
### Operating System
_No response_