From b9656afc64d02ee20c4d5a69c619add9c1867454 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Thu, 12 Nov 2026 13:55:39 +0900 Subject: [PATCH v2] Fix some edge cases with characteristics after COMMIT/ROLLBACK AND CHAIN Certain combinations of transaction sequences with AND CHAIN failed to preserve the top-transaction characteristics, in some cases: * When a transaction block failed, the chained transaction used the default characteristics, not the characteristics of the transaction chaining from. * When COMMIT AND CHAIN was issued in a subtransaction that had the idea to switch read_only, the chained transaction inherited the read-only state of the subtransaction, not of the top transaction. The state is reset when the subtransaction ends, like RELEASE. The characteristics are now saved once all the subtransactions are committed. Narek has reported the first problem. I have discovered the second pattern while digging through this area of the code. Tests are added for all the patterns spotted. Author: Narek Galstyan Author: Michael Paquier Discussion: https://postgr.es/m/CAHsn6_z-ewqXWe2h--SK93AdnPSOvjvF9KLodWckFZKAjxcONw@mail.gmail.com Backpatch-through: 14 --- src/backend/access/transam/xact.c | 18 +++++ src/test/regress/expected/transactions.out | 76 ++++++++++++++++++++++ src/test/regress/sql/transactions.sql | 34 ++++++++++ 3 files changed, 128 insertions(+) diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 49e649396673..1227e3fbe242 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -3417,6 +3417,14 @@ CommitTransactionCommandInternal(void) if (s->blockState == TBLOCK_END) { Assert(s->parent == NULL); + + /* + * Save the characteristics again, now that the + * subtransactions are committed. A subtransaction may have + * switched to read-only, which should not be inherited by the + * next AND CHAIN, if any. + */ + SaveTransactionCharacteristics(&savetc); CommitTransaction(); s->blockState = TBLOCK_DEFAULT; if (s->chain) @@ -3547,6 +3555,10 @@ static bool AbortCurrentTransactionInternal(void) { TransactionState s = CurrentTransactionState; + SavedTransactionCharacteristics savetc; + + /* Must save in case we need to restore below */ + SaveTransactionCharacteristics(&savetc); switch (s->blockState) { @@ -3604,6 +3616,12 @@ AbortCurrentTransactionInternal(void) case TBLOCK_INPROGRESS: case TBLOCK_PARALLEL_INPROGRESS: AbortTransaction(); + + /* + * AbortTransaction() has reset the transaction characteristics; + * restore them for AND CHAIN. + */ + RestoreTransactionCharacteristics(&savetc); s->blockState = TBLOCK_ABORT; /* CleanupTransaction happens when we exit TBLOCK_ABORT_END */ break; diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out index 7f5757e89c42..3b2fc5cc939b 100644 --- a/src/test/regress/expected/transactions.out +++ b/src/test/regress/expected/transactions.out @@ -956,6 +956,82 @@ SHOW transaction_deferrable; off (1 row) +ROLLBACK; +-- failed transaction with characteristics set by START TRANSACTION. +START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; +INSERT INTO trans_abc VALUES ('error'); +ERROR: invalid input syntax for type integer: "error" +LINE 1: INSERT INTO trans_abc VALUES ('error'); + ^ +COMMIT AND CHAIN; -- TBLOCK_ABORT_END +SHOW transaction_isolation; + transaction_isolation +----------------------- + repeatable read +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + on +(1 row) + +ROLLBACK; +-- failed transaction with characteristics set by SET TRANSACTION. +START TRANSACTION; +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ WRITE, DEFERRABLE; +INSERT INTO trans_abc VALUES ('error'); +ERROR: invalid input syntax for type integer: "error" +LINE 1: INSERT INTO trans_abc VALUES ('error'); + ^ +ROLLBACK AND CHAIN; -- TBLOCK_ABORT_END +SHOW transaction_isolation; + transaction_isolation +----------------------- + serializable +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + on +(1 row) + +ROLLBACK; +-- read-only set in a subtransaction, not inherited by the next transaction +START TRANSACTION READ WRITE; +SAVEPOINT x; +SET TRANSACTION READ ONLY; +COMMIT AND CHAIN; -- TBLOCK_SUBCOMMIT +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +ROLLBACK; +START TRANSACTION READ WRITE; +SAVEPOINT x; +SET TRANSACTION READ ONLY; +ROLLBACK AND CHAIN; -- TBLOCK_ABORT_PENDING +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + ROLLBACK; -- not allowed outside a transaction block COMMIT AND CHAIN; -- error diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql index 51ae1b31b30b..f43d6122f881 100644 --- a/src/test/regress/sql/transactions.sql +++ b/src/test/regress/sql/transactions.sql @@ -517,6 +517,40 @@ SHOW transaction_read_only; SHOW transaction_deferrable; ROLLBACK; +-- failed transaction with characteristics set by START TRANSACTION. +START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; +INSERT INTO trans_abc VALUES ('error'); +COMMIT AND CHAIN; -- TBLOCK_ABORT_END +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +ROLLBACK; + +-- failed transaction with characteristics set by SET TRANSACTION. +START TRANSACTION; +SET TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ WRITE, DEFERRABLE; +INSERT INTO trans_abc VALUES ('error'); +ROLLBACK AND CHAIN; -- TBLOCK_ABORT_END +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +ROLLBACK; + +-- read-only set in a subtransaction, not inherited by the next transaction +START TRANSACTION READ WRITE; +SAVEPOINT x; +SET TRANSACTION READ ONLY; +COMMIT AND CHAIN; -- TBLOCK_SUBCOMMIT +SHOW transaction_read_only; +ROLLBACK; + +START TRANSACTION READ WRITE; +SAVEPOINT x; +SET TRANSACTION READ ONLY; +ROLLBACK AND CHAIN; -- TBLOCK_ABORT_PENDING +SHOW transaction_read_only; +ROLLBACK; + -- not allowed outside a transaction block COMMIT AND CHAIN; -- error ROLLBACK AND CHAIN; -- error -- 2.56.0