diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 559eb898a9..bea1d4fc1a 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8880,6 +8880,51 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
+
+ nested_transactions (enum)
+
+ nested transactions
+
+
+ nested_transactions configuration parameter
+
+
+
+
+ This parameter controls the behavior of transactions when
+ BEGIN, COMMIT or
+ ROLLBACK are received when already in a transaction
+ block.
+
+
+ The default is off
, indicating
+ that when a transaction block is already in progress, a
+ BEGIN will throw a WARNING
+ but otherwise do nothing. This is the historic behavior of
+ PostgreSQL.
+
+
+ A setting of all
will cause a nested
+ BEGIN to start a subtransaction, which will end when
+ a COMMIT or ROLLBACK is
+ received. In that case a ROLLBACK will only
+ abort the subtransaction. Once we reach the top-level transaction,
+ the final COMMIT will end the transction.
+ This ensures that commands at each transaction nesting level are atomic.
+
+
+ A setting of outer
will cause a nested
+ BEGIN to be remembered, so that an equal number
+ of COMMIT or ROLLBACK commands
+ are required to end the nesting. In that case a ROLLBACK
+ at any level will be abort the entire outer transaction.
+ Once we reach the top-level transaction,
+ the final COMMIT will end the transction.
+ This ensures that all commands within the outer transaction are atomic.
+
+
+
+
transaction_isolation (enum)
diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README
index 72af656060..50ccb0daca 100644
--- a/src/backend/access/transam/README
+++ b/src/backend/access/transam/README
@@ -894,3 +894,79 @@ yet simplifies emulation of subtransactions considerably.
Further details on locking mechanics in recovery are given in comments
with the Lock rmgr code.
+
+Nested Transactions
+-------------------
+
+Nested BEGIN/COMMIT statements can be confusing for developers. In this
+example, the first BEGIN and the first COMMIT match, leaving the commands
+between the first and second COMMIT outside of a transaction block,
+allowing them to commit separately from each other. The second BEGIN and
+second COMMIT throw WARNING messages but if an ERROR occurs, the result
+is not atomic and can be hard to manually undo the damage.
+
+BEGIN
+INSERT
+BEGIN
+WARNING: there is already a transaction in progress
+INSERT
+COMMIT
+INSERT
+ERROR: syntax error
+COMMIT
+WARNING: there is no transaction in progress
+SELECT
+val
+---
+1
+2
+(2 rows)
+
+Using SET nested_transactions = 'outer', we get simpler and more understandable
+behavior, with all commands now within the outer transaction block.
+In this example, the ERROR causes rollback of all commands because nested
+begin and commit commands are ignored, while we track the nesting level.
+
+BEGIN
+INSERT
+BEGIN
+NOTICE: nested BEGIN, level 1
+INSERT
+COMMIT
+NOTICE: nested COMMIT, level 1
+INSERT
+ERROR: syntax error
+ROLLBACK
+SELECT
+val
+--
+(0 rows)
+
+Notices are shown, so that developers can be certain of the behavior.
+
+Nested COMMIT/ROLLBACK affects all subtransactions under the transaction
+that issued BEGIN, irrespective of subsequent SAVEPOINT/ROLLBACK/RELEASE.
+Nesting can occur to any level.
+
+An alternative approach is to use SET nested_transactions = 'all',
+which changes nested BEGINs into subtransactions and COMMITs into subcommits.
+This allows us to trap nested errors and yet continue the main transaction.
+In this example, a nested ERROR aborts the nested subtransaction, but not
+the main transaction.
+
+BEGIN
+INSERT
+BEGIN
+NOTICE: BEGIN starts nested subtransaction, level 1
+INSERT
+ERROR: syntax error
+COMMIT
+NOTICE: COMMIT will rollback nested subtransaction, level 1
+INSERT
+ROLLBACK
+SELECT
+val
+--
+(0 rows)
+
+The default behavior is equivalent to using SET nested_transactions = 'off'.
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index fd5103a78e..7c049b49f5 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -83,6 +83,12 @@ bool XactReadOnly;
bool DefaultXactDeferrable = false;
bool XactDeferrable;
+int DefaultXactNesting;
+int XactNesting = XACT_NEST_OFF;
+int XactNestingLevel = 0;
+
+#define NESTED_XACT_NAME "_internal_nested_xact"
+
int synchronous_commit = SYNCHRONOUS_COMMIT_ON;
/*
@@ -2049,6 +2055,8 @@ StartTransaction(void)
}
XactDeferrable = DefaultXactDeferrable;
XactIsoLevel = DefaultXactIsoLevel;
+ XactNesting = DefaultXactNesting;
+ XactNestingLevel = 0;
forceSyncCommit = false;
MyXactFlags = 0;
@@ -2992,7 +3000,7 @@ StartTransactionCommand(void)
/*
* Simple system for saving and restoring transaction characteristics
- * (isolation level, read only, deferrable). We need this for transaction
+ * (isolation level, read only, deferrable, nesting). We need this for transaction
* chaining, so that we can set the characteristics of the new transaction to
* be the same as the previous one. (We need something like this because the
* GUC system resets the characteristics at transaction end, so for example
@@ -3004,6 +3012,7 @@ SaveTransactionCharacteristics(SavedTransactionCharacteristics *s)
s->save_XactIsoLevel = XactIsoLevel;
s->save_XactReadOnly = XactReadOnly;
s->save_XactDeferrable = XactDeferrable;
+ s->save_XactNesting = XactNesting;
}
void
@@ -3012,6 +3021,7 @@ RestoreTransactionCharacteristics(const SavedTransactionCharacteristics *s)
XactIsoLevel = s->save_XactIsoLevel;
XactReadOnly = s->save_XactReadOnly;
XactDeferrable = s->save_XactDeferrable;
+ XactNesting = s->save_XactNesting;
}
@@ -3182,7 +3192,10 @@ CommitTransactionCommand(void)
CommitSubTransaction();
s = CurrentTransactionState; /* changed by pop */
} while (s->blockState == TBLOCK_SUBCOMMIT);
- /* If we had a COMMIT command, finish off the main xact too */
+ /*
+ * If we had a COMMIT command, finish off the main xact too,
+ * unless this is a nested transaction.
+ */
if (s->blockState == TBLOCK_END)
{
Assert(s->parent == NULL);
@@ -3202,7 +3215,7 @@ CommitTransactionCommand(void)
PrepareTransaction();
s->blockState = TBLOCK_DEFAULT;
}
- else
+ else if (XactNesting == XACT_NEST_OFF)
elog(ERROR, "CommitTransactionCommand: unexpected state %s",
BlockStateAsString(s->blockState));
break;
@@ -3765,13 +3778,37 @@ BeginTransactionBlock(void)
* Already a transaction block in progress.
*/
case TBLOCK_INPROGRESS:
- case TBLOCK_PARALLEL_INPROGRESS:
case TBLOCK_SUBINPROGRESS:
+ if (XactNesting == XACT_NEST_ALL)
+ {
+ /*
+ * BEGIN starts a nested subtransaction.
+ */
+ XactNestingLevel++;
+ ereport(NOTICE,
+ (errmsg("BEGIN starts nested subtransaction, level %u", XactNestingLevel)));
+ BeginInternalSubTransaction(NESTED_XACT_NAME);
+ break;
+ }
+ else if (XactNesting == XACT_NEST_OUTER)
+ {
+ XactNestingLevel++;
+ ereport(NOTICE,
+ (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
+ errmsg("nested BEGIN, level %u", XactNestingLevel)));
+ break;
+ }
+ /* else drop thru */
+
+ case TBLOCK_PARALLEL_INPROGRESS:
case TBLOCK_ABORT:
case TBLOCK_SUBABORT:
ereport(WARNING,
(errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
errmsg("there is already a transaction in progress")));
+ if (XactNesting == XACT_NEST_OUTER)
+ XactNestingLevel++;
+
break;
/* These cases are invalid. */
@@ -3815,6 +3852,10 @@ PrepareTransactionBlock(const char *gid)
/* Set up to commit the current transaction */
result = EndTransactionBlock(false);
+ /* Don't allow prepare until we are back to an unnested state at level 0 */
+ if (XactNestingLevel > 0)
+ return false;
+
/* If successful, change outer tblock state to PREPARE */
if (result)
{
@@ -3863,6 +3904,7 @@ EndTransactionBlock(bool chain)
{
TransactionState s = CurrentTransactionState;
bool result = false;
+ bool found_subxact = false;
switch (s->blockState)
{
@@ -3871,7 +3913,19 @@ EndTransactionBlock(bool chain)
* to COMMIT.
*/
case TBLOCK_INPROGRESS:
- s->blockState = TBLOCK_END;
+ if (XactNesting == XACT_NEST_OUTER)
+ {
+ if (XactNestingLevel <= 0)
+ s->blockState = TBLOCK_END;
+ else
+ ereport(NOTICE,
+ (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
+ errmsg("nested COMMIT, level %u", XactNestingLevel)));
+ XactNestingLevel--;
+ return true;
+ }
+ else
+ s->blockState = TBLOCK_END;
result = true;
break;
@@ -3900,6 +3954,16 @@ EndTransactionBlock(bool chain)
* CommitTransactionCommand it's time to exit the block.
*/
case TBLOCK_ABORT:
+ if (XactNesting == XACT_NEST_OUTER)
+ {
+ if (XactNestingLevel > 0)
+ {
+ ereport(NOTICE,
+ (errmsg("nested COMMIT, level %u in aborted transaction", XactNestingLevel)));
+ XactNestingLevel--;
+ return false;
+ }
+ }
s->blockState = TBLOCK_ABORT_END;
break;
@@ -3908,8 +3972,14 @@ EndTransactionBlock(bool chain)
* open subtransactions and then commit the main transaction.
*/
case TBLOCK_SUBINPROGRESS:
- while (s->parent != NULL)
+ while (s->parent != NULL && !found_subxact)
{
+ if (XactNesting == XACT_NEST_ALL &&
+ XactNestingLevel > 0 &&
+ PointerIsValid(s->name) &&
+ strcmp(s->name, NESTED_XACT_NAME) == 0)
+ found_subxact = true;
+
if (s->blockState == TBLOCK_SUBINPROGRESS)
s->blockState = TBLOCK_SUBCOMMIT;
else
@@ -3917,7 +3987,26 @@ EndTransactionBlock(bool chain)
BlockStateAsString(s->blockState));
s = s->parent;
}
- if (s->blockState == TBLOCK_INPROGRESS)
+ if (XactNesting == XACT_NEST_ALL && XactNestingLevel > 0)
+ {
+ if (s->parent == NULL && !found_subxact)
+ ereport(ERROR,
+ (errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+ errmsg("nested transaction does not exist")));
+
+ /*
+ * With transaction nesting, the COMMIT command no
+ * longer forces main transaction abort, only the
+ * subcommit of subxacts up to the last nested BEGIN.
+ * We only mark state here; CommitTransactionCommand()
+ * will release portals and resources.
+ */
+ ereport(NOTICE,
+ (errmsg("COMMIT will commit nested subtransaction, level %u", XactNestingLevel)));
+ XactNestingLevel--;
+ return true;
+ }
+ else if (s->blockState == TBLOCK_INPROGRESS)
s->blockState = TBLOCK_END;
else
elog(FATAL, "EndTransactionBlock: unexpected state %s",
@@ -3931,8 +4020,14 @@ EndTransactionBlock(bool chain)
* transaction.
*/
case TBLOCK_SUBABORT:
- while (s->parent != NULL)
+ while (s->parent != NULL && !found_subxact)
{
+ if (XactNesting == XACT_NEST_ALL &&
+ XactNestingLevel > 0 &&
+ PointerIsValid(s->name) &&
+ strcmp(s->name, NESTED_XACT_NAME) == 0)
+ found_subxact = true;
+
if (s->blockState == TBLOCK_SUBINPROGRESS)
s->blockState = TBLOCK_SUBABORT_PENDING;
else if (s->blockState == TBLOCK_SUBABORT)
@@ -3942,6 +4037,18 @@ EndTransactionBlock(bool chain)
BlockStateAsString(s->blockState));
s = s->parent;
}
+ if (XactNesting == XACT_NEST_ALL && XactNestingLevel > 0)
+ {
+ if (!found_subxact)
+ ereport(ERROR,
+ (errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+ errmsg("nested transaction does not exist")));
+
+ ereport(NOTICE,
+ (errmsg("COMMIT will rollback nested subtransaction, level %u", XactNestingLevel)));
+ XactNestingLevel--;
+ return false;
+ }
if (s->blockState == TBLOCK_INPROGRESS)
s->blockState = TBLOCK_ABORT_PENDING;
else if (s->blockState == TBLOCK_ABORT)
@@ -4022,6 +4129,7 @@ void
UserAbortTransactionBlock(bool chain)
{
TransactionState s = CurrentTransactionState;
+ bool found_subxact = false;
switch (s->blockState)
{
@@ -4031,7 +4139,14 @@ UserAbortTransactionBlock(bool chain)
* exit the transaction block.
*/
case TBLOCK_INPROGRESS:
- s->blockState = TBLOCK_ABORT_PENDING;
+ if (XactNesting == XACT_NEST_OUTER && XactNestingLevel > 0)
+ {
+ /* Throw ERROR */
+ ereport(ERROR,
+ (errmsg("nested ROLLBACK, level %u aborts outer transaction", XactNestingLevel--)));
+ }
+ else
+ s->blockState = TBLOCK_ABORT_PENDING;
break;
/*
@@ -4041,7 +4156,19 @@ UserAbortTransactionBlock(bool chain)
* idle state.
*/
case TBLOCK_ABORT:
- s->blockState = TBLOCK_ABORT_END;
+ if (XactNesting == XACT_NEST_OUTER)
+ {
+ if (XactNestingLevel <= 0)
+ s->blockState = TBLOCK_ABORT_END;
+ else
+ {
+ ereport(NOTICE,
+ (errmsg("nested ROLLBACK, level %u in aborted transaction", XactNestingLevel--)));
+ }
+ return;
+ }
+ else
+ s->blockState = TBLOCK_ABORT_END;
break;
/*
@@ -4050,8 +4177,14 @@ UserAbortTransactionBlock(bool chain)
*/
case TBLOCK_SUBINPROGRESS:
case TBLOCK_SUBABORT:
- while (s->parent != NULL)
+ while (s->parent != NULL && !found_subxact)
{
+ if (XactNesting == XACT_NEST_ALL &&
+ XactNestingLevel > 0 &&
+ PointerIsValid(s->name) &&
+ strcmp(s->name, NESTED_XACT_NAME) == 0)
+ found_subxact = true;
+
if (s->blockState == TBLOCK_SUBINPROGRESS)
s->blockState = TBLOCK_SUBABORT_PENDING;
else if (s->blockState == TBLOCK_SUBABORT)
@@ -4061,6 +4194,18 @@ UserAbortTransactionBlock(bool chain)
BlockStateAsString(s->blockState));
s = s->parent;
}
+ if (XactNesting == XACT_NEST_ALL && XactNestingLevel > 0)
+ {
+ if (!found_subxact)
+ ereport(ERROR,
+ (errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+ errmsg("nested transaction does not exist")));
+
+ ereport(NOTICE,
+ (errmsg("ROLLBACK will rollback nested subtransaction, level %u", XactNestingLevel)));
+ XactNestingLevel--;
+ return;
+ }
if (s->blockState == TBLOCK_INPROGRESS)
s->blockState = TBLOCK_ABORT_PENDING;
else if (s->blockState == TBLOCK_ABORT)
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 05ab087934..1ddf1d9792 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -195,6 +195,13 @@ static const struct config_enum_entry isolation_level_options[] = {
{NULL, 0}
};
+static const struct config_enum_entry xact_nesting_options[] = {
+ {"off", XACT_NEST_OFF, false},
+ {"all", XACT_NEST_ALL, false},
+ {"outer", XACT_NEST_OUTER, false},
+ {NULL, 0, false}
+};
+
static const struct config_enum_entry session_replication_role_options[] = {
{"origin", SESSION_REPLICATION_ROLE_ORIGIN, false},
{"replica", SESSION_REPLICATION_ROLE_REPLICA, false},
@@ -4533,6 +4540,16 @@ struct config_enum ConfigureNamesEnum[] =
NULL, NULL, NULL
},
+ {
+ {"nested_transactions", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Sets the transaction nesting behavior for each new transaction."),
+ NULL
+ },
+ &DefaultXactNesting,
+ XACT_NEST_OFF, xact_nesting_options,
+ NULL, NULL, NULL
+ },
+
{
{"default_transaction_isolation", PGC_USERSET, CLIENT_CONN_STATEMENT,
gettext_noop("Sets the transaction isolation level of each new transaction."),
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index c604ee11f8..1cdc697fe1 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -41,6 +41,15 @@
extern PGDLLIMPORT int DefaultXactIsoLevel;
extern PGDLLIMPORT int XactIsoLevel;
+/*
+ * Xact nesting
+ */
+#define XACT_NEST_OFF 0
+#define XACT_NEST_ALL 1
+#define XACT_NEST_OUTER 2
+
+extern PGDLLIMPORT int DefaultXactNesting;
+
/*
* We implement three isolation levels internally.
* The two stronger ones use one snapshot per database transaction;
@@ -147,6 +156,7 @@ typedef struct SavedTransactionCharacteristics
int save_XactIsoLevel;
bool save_XactReadOnly;
bool save_XactDeferrable;
+ int save_XactNesting;
} SavedTransactionCharacteristics;
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 2b2cff7d91..cf153a3393 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -1147,6 +1147,229 @@ SELECT * FROM abc ORDER BY 1;
17
(3 rows)
+SET nested_transactions = 'off';
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+WARNING: there is already a transaction in progress
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+WARNING: there is no transaction in progress
+SELECT * FROM abc ORDER BY 1;
+ a
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+WARNING: there is already a transaction in progress
+INSERT INTO abc VALUES (2);
+ROLLBACK;
+INSERT INTO abc VALUES (3);
+COMMIT;
+WARNING: there is no transaction in progress
+SELECT * FROM abc ORDER BY 1;
+ a
+---
+ 3
+(1 row)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+WARNING: there is already a transaction in progress
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3))); --error
+ERROR: syntax error at or near ")"
+LINE 1: INSERT INTO abc VALUES (3)));
+ ^
+COMMIT;
+WARNING: there is no transaction in progress
+SELECT * FROM abc ORDER BY 1;
+ a
+---
+ 1
+ 2
+(2 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+WARNING: there is already a transaction in progress
+INSERT INTO abc VALUES (2))); --error
+ERROR: syntax error at or near ")"
+LINE 1: INSERT INTO abc VALUES (2)));
+ ^
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+WARNING: there is no transaction in progress
+SELECT * FROM abc ORDER BY 1;
+ a
+---
+ 3
+(1 row)
+
+SET nested_transactions = 'all';
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE: BEGIN starts nested subtransaction, level 1
+INSERT INTO abc VALUES (2);
+COMMIT;
+NOTICE: COMMIT will commit nested subtransaction, level 1
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE: BEGIN starts nested subtransaction, level 1
+INSERT INTO abc VALUES (2);
+ROLLBACK;
+NOTICE: ROLLBACK will rollback nested subtransaction, level 1
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a
+---
+ 1
+ 3
+(2 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE: BEGIN starts nested subtransaction, level 1
+INSERT INTO abc VALUES (2);
+COMMIT;
+NOTICE: COMMIT will commit nested subtransaction, level 1
+INSERT INTO abc VALUES (3))); --error
+ERROR: syntax error at or near ")"
+LINE 1: INSERT INTO abc VALUES (3)));
+ ^
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a
+---
+(0 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE: BEGIN starts nested subtransaction, level 1
+INSERT INTO abc VALUES (2))); --error
+ERROR: syntax error at or near ")"
+LINE 1: INSERT INTO abc VALUES (2)));
+ ^
+COMMIT;
+NOTICE: COMMIT will rollback nested subtransaction, level 1
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a
+---
+ 1
+ 3
+(2 rows)
+
+SET nested_transactions = 'outer';
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE: nested BEGIN, level 1
+INSERT INTO abc VALUES (2);
+COMMIT;
+NOTICE: nested COMMIT, level 1
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE: nested BEGIN, level 1
+INSERT INTO abc VALUES (2);
+ROLLBACK;
+ERROR: nested ROLLBACK, level 1 aborts outer transaction
+INSERT INTO abc VALUES (3);
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a
+---
+(0 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE: nested BEGIN, level 1
+INSERT INTO abc VALUES (2);
+COMMIT;
+NOTICE: nested COMMIT, level 1
+INSERT INTO abc VALUES (3))); --error
+ERROR: syntax error at or near ")"
+LINE 1: INSERT INTO abc VALUES (3)));
+ ^
+INSERT INTO abc VALUES (4);
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a
+---
+(0 rows)
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+NOTICE: nested BEGIN, level 1
+INSERT INTO abc VALUES (2))); --error
+ERROR: syntax error at or near ")"
+LINE 1: INSERT INTO abc VALUES (2)));
+ ^
+COMMIT;
+NOTICE: nested COMMIT, level 1 in aborted transaction
+INSERT INTO abc VALUES (3);
+ERROR: current transaction is aborted, commands ignored until end of transaction block
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+ a
+---
+(0 rows)
+
+RESET nested_transactions;
DROP TABLE abc;
-- Test for successful cleanup of an aborted transaction at session exit.
-- THIS MUST BE THE LAST TEST IN THIS FILE.
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 7ee5f6aaa5..4df2d4d382 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -611,8 +611,136 @@ RESET default_transaction_isolation;
SELECT * FROM abc ORDER BY 1;
-DROP TABLE abc;
+SET nested_transactions = 'off';
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+ROLLBACK;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3))); --error
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2))); --error
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+SET nested_transactions = 'all';
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+ROLLBACK;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3))); --error
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2))); --error
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+SET nested_transactions = 'outer';
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+ROLLBACK;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2);
+COMMIT;
+INSERT INTO abc VALUES (3))); --error
+INSERT INTO abc VALUES (4);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+TRUNCATE abc;
+BEGIN;
+INSERT INTO abc VALUES (1);
+BEGIN;
+INSERT INTO abc VALUES (2))); --error
+COMMIT;
+INSERT INTO abc VALUES (3);
+COMMIT;
+SELECT * FROM abc ORDER BY 1;
+
+RESET nested_transactions;
+
+DROP TABLE abc;
-- Test for successful cleanup of an aborted transaction at session exit.
-- THIS MUST BE THE LAST TEST IN THIS FILE.