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
15 changes: 12 additions & 3 deletions ext/pdo_pgsql/pgsql_statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,18 @@ 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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if this errors it should get bubbled up

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dtor path, no return channel to userland. The pre-patch code already discarded the result the same way. Surfacing dtor cleanup failures across pdo_pgsql might be worth
doing, but as a separate PR.

efree(q);
} else {
/* Outside PQTRANS_IDLE, skip DEALLOCATE: on libpq < 17 some servers
* (e.g. Aurora DSQL) reject it and poison the tx (GH-21869). The
* statement is session-scoped, so it's freed on disconnect. */
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