Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions ext/pdo_pgsql/pgsql_statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,19 @@ static int pgsql_stmt_dtor(pdo_stmt_t *stmt)
// TODO (??) libpq does not support close statement protocol < postgres 17
// check if we can circumvent this.
char *q = NULL;
spprintf(&q, 0, "DEALLOCATE %s", S->stmt_name);
res = PQexec(H->server, q);
efree(q);
PGTransactionStatusType tstatus = PQtransactionStatus(H->server);

if (tstatus == PQTRANS_IDLE) {
spprintf(&q, 0, "DEALLOCATE %s", S->stmt_name);
res = PQexec(H->server, q);
efree(q);
} else {
/* Inside a transaction, already aborted, or connection is unusable:
* skip DEALLOCATE. On servers that reject it (e.g. Aurora DSQL) it
* would poison the transaction (GH-21869); on any server the server
* frees the prepared statement when the transaction ends. */
res = NULL;
}
#else
res = PQclosePrepared(H->server, S->stmt_name);
#endif
Expand Down
46 changes: 46 additions & 0 deletions ext/pdo_pgsql/tests/gh21869.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
--TEST--
GH-21869 pdo_pgsql: DEALLOCATE failure in destructor must not poison the enclosing transaction
--EXTENSIONS--
pdo
pdo_pgsql
--SKIPIF--
<?php
require __DIR__ . '/config.inc';
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
PDOTest::skip();
$pdo = PDOTest::factory();
if (version_compare($pdo->getAttribute(PDO::ATTR_CLIENT_VERSION), '17', '>=')) {
die('skip libpq >= 17 uses PQclosePrepared instead of the DEALLOCATE query');
}
?>
--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_EMULATE_PREPARES, false);

$pdo->exec('CREATE TABLE gh21869 (a integer not null)');

$pdo->beginTransaction();
$stmt = $pdo->prepare('INSERT INTO gh21869 (a) VALUES (?)');
$stmt->execute([1]);

foreach ($pdo->query('SELECT name FROM pg_prepared_statements') as $row) {
$pdo->exec('DEALLOCATE ' . $row['name']);
}

unset($stmt);

$pdo->commit();

var_dump((int) $pdo->query('SELECT count(*) FROM gh21869')->fetchColumn());
?>
--CLEAN--
<?php
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
$pdo = PDOTest::test_factory(__DIR__ . '/common.phpt');
$pdo->exec('DROP TABLE IF EXISTS gh21869');
?>
--EXPECT--
int(1)
Loading