From 68c76eb425936d95f6321dfe95e23ebb4b1c0b3a Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Mon, 20 Apr 2026 17:46:52 +0000 Subject: [PATCH] fix(pgsql): mark transaction state unknown when backend connection is broken When a backend connection breaks with an error (e.g. postmaster SIGKILL mid-query), libpq's PQtransactionStatus() returns PQTRANS_UNKNOWN rather than PQTRANS_INTRANS, so IsActiveTransaction() reports false for a connection that was in a transaction moments earlier. The retry path in PgSQL_Session::handler_minus1_ClientLibraryError() then replays the failed statement on a fresh backend connection as autocommit, breaking transactional semantics: the client's next COMMIT lands on a connection with no open transaction and PostgreSQL emits "WARNING: there is no transaction in progress". MySQL does not have this issue because mysql->server_status caches SERVER_STATUS_IN_TRANS from the last OK packet and survives the connection breaking. Fix compute_unknown_transaction_status() to force unknown_transaction_status=true when the backend is disconnected, so the retry guard in handler_minus1_ClientLibraryError() refuses to replay statements that were running inside an explicit transaction. --- lib/PgSQL_Connection.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/PgSQL_Connection.cpp b/lib/PgSQL_Connection.cpp index 6fc0a1025..dab5ed3d4 100644 --- a/lib/PgSQL_Connection.cpp +++ b/lib/PgSQL_Connection.cpp @@ -1308,10 +1308,16 @@ void PgSQL_Connection::compute_unknown_transaction_status() { return; } - /*if (is_connected() == false) { + // On a broken backend, PQtransactionStatus() returns PQTRANS_UNKNOWN + // even if a transaction was active — libpq has no cached INTRANS bit + // equivalent to MySQL's server_status & SERVER_STATUS_IN_TRANS. Force + // unknown_transaction_status=true so IsActiveTransaction() still + // reports true and the retry path does not replay inside-tx statements + // on a fresh connection (which would run them as autocommit). + if (is_connected() == false) { unknown_transaction_status = true; return; - }*/ + } switch (PQtransactionStatus(pgsql_conn)) { case PQTRANS_INTRANS: