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.