Skip to content

Raise when COMMIT is answered with ROLLBACK - #1401

Open
soujasK wants to merge 1 commit into
MagicStack:masterfrom
soujasK:fix/commit-rolled-back
Open

soujasK wants to merge 1 commit into
MagicStack:masterfrom
soujasK:fix/commit-rolled-back

Conversation

@soujasK

@soujasK soujasK commented Oct 27, 2026

Copy link
Copy Markdown

Fixes #1400.

When a statement fails inside a transaction and the application catches the error without rolling back to a savepoint, PostgreSQL answers the later COMMIT with the command tag ROLLBACK and discards the transaction. Transaction.commit() ignored the tag, so both await tr.commit() and async with con.transaction(): returned normally and the transaction was marked as committed, although its changes were lost. A later tr.rollback() then failed with "the transaction is already committed".

__commit() now checks the command tag of a top-level COMMIT. If it is ROLLBACK, the transaction is marked as rolled back and InFailedSQLTransactionError (SQLSTATE 25P02) is raised, which is the error PostgreSQL itself uses for statements sent to an aborted transaction. pgjdbc reports this case with the same SQLSTATE. Savepoints are not affected: RELEASE SAVEPOINT in an aborted transaction already raises on the server.

This changes behaviour for code that catches an error inside a transaction and still expects the block to commit. That code was already losing its writes silently, so it now gets an exception instead.

Testing

test_transaction_commit_rolled_back_by_server covers both the explicit commit() and the async with block, and checks that the rows are gone and the transaction state is rolledback. It fails without the change (InFailedSQLTransactionError not raised) and passes with it. I ran test_transaction, test_pool, test_execute and test_cursor against PostgreSQL 16 on Windows; all pass.

When a statement fails inside a transaction and the error is caught
without rolling back to a savepoint, PostgreSQL answers the following
COMMIT with the ROLLBACK command tag and discards the transaction.
Transaction.commit() and `async with con.transaction()` ignored the tag,
so they returned normally and marked the transaction as committed even
though its changes were lost.

Check the command tag and raise InFailedSQLTransactionError (25P02) in
that case, and mark the transaction as rolled back.

Fixes MagicStack#1400.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Transaction.commit() returns normally when PostgreSQL answers COMMIT with ROLLBACK

1 participant