diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index f8c6435c50e..50f1884afec 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -3490,6 +3490,20 @@ CALL transaction_test1(); + + chained transactions + in PL/pgSQL + + + + A new transaction starts out with default transaction characteristics such + as transaction isolation level. In cases where transactions are committed + in a loop, it might be desirable to start new transactions automatically + with the same characteristics as the previous one. The commands + COMMIT AND CHAIN and ROLLBACK AND + CHAIN accomplish this. + + Transaction control is only possible in CALL or DO invocations from the top level or nested diff --git a/doc/src/sgml/ref/abort.sgml b/doc/src/sgml/ref/abort.sgml index 21799d2a83f..03729133651 100644 --- a/doc/src/sgml/ref/abort.sgml +++ b/doc/src/sgml/ref/abort.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -ABORT [ WORK | TRANSACTION ] +ABORT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ] @@ -51,6 +51,18 @@ ABORT [ WORK | TRANSACTION ] + + + AND CHAIN + + + If AND CHAIN is specified, a new transaction is + immediately started with the same transaction characteristics (see ) as the just finished one. Otherwise, + no new transaction is started. + + + diff --git a/doc/src/sgml/ref/commit.sgml b/doc/src/sgml/ref/commit.sgml index b2e8d5d1807..e4169cd2c62 100644 --- a/doc/src/sgml/ref/commit.sgml +++ b/doc/src/sgml/ref/commit.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -COMMIT [ WORK | TRANSACTION ] +COMMIT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ] @@ -38,6 +38,10 @@ COMMIT [ WORK | TRANSACTION ] Parameters + + chained transactions + + WORK @@ -48,6 +52,18 @@ COMMIT [ WORK | TRANSACTION ] + + + AND CHAIN + + + If AND CHAIN is specified, a new transaction is + immediately started with the same transaction characteristics (see ) as the just finished one. Otherwise, + no new transaction is started. + + + @@ -79,9 +95,8 @@ COMMIT; Compatibility - The SQL standard only specifies the two forms - COMMIT and COMMIT - WORK. Otherwise, this command is fully conforming. + The command COMMIT conforms to the SQL standard. The + form COMMIT TRANSACTION is a PostgreSQL extension. diff --git a/doc/src/sgml/ref/end.sgml b/doc/src/sgml/ref/end.sgml index 7523315f344..8b8f4f0dbb9 100644 --- a/doc/src/sgml/ref/end.sgml +++ b/doc/src/sgml/ref/end.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -END [ WORK | TRANSACTION ] +END [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ] @@ -50,6 +50,18 @@ END [ WORK | TRANSACTION ] + + + AND CHAIN + + + If AND CHAIN is specified, a new transaction is + immediately started with the same transaction characteristics (see ) as the just finished one. Otherwise, + no new transaction is started. + + + diff --git a/doc/src/sgml/ref/rollback.sgml b/doc/src/sgml/ref/rollback.sgml index 3cafb848a9a..a5bbf25221c 100644 --- a/doc/src/sgml/ref/rollback.sgml +++ b/doc/src/sgml/ref/rollback.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -ROLLBACK [ WORK | TRANSACTION ] +ROLLBACK [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ] @@ -37,6 +37,10 @@ ROLLBACK [ WORK | TRANSACTION ] Parameters + + chained transactions + + WORK @@ -47,6 +51,18 @@ ROLLBACK [ WORK | TRANSACTION ] + + + AND CHAIN + + + If AND CHAIN is specified, a new transaction is + immediately started with the same transaction characteristics (see ) as the just finished one. Otherwise, + no new transaction is started. + + + @@ -78,9 +94,8 @@ ROLLBACK; Compatibility - The SQL standard only specifies the two forms - ROLLBACK and ROLLBACK - WORK. Otherwise, this command is fully conforming. + The command ROLLBACK conforms to the SQL standard. The + form ROLLBACK TRANSACTION is a PostgreSQL extension. diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml index 6f4f3bae6ff..9b2f5169421 100644 --- a/doc/src/sgml/spi.sgml +++ b/doc/src/sgml/spi.sgml @@ -4376,6 +4376,7 @@ int SPI_freeplan(SPIPlanPtr plan) SPI_commit + SPI_commit_and_chain SPI_commit @@ -4384,12 +4385,17 @@ int SPI_freeplan(SPIPlanPtr plan) SPI_commit + SPI_commit_and_chain commit the current transaction void SPI_commit(void) + + + +void SPI_commit_and_chain(void) @@ -4406,7 +4412,14 @@ void SPI_commit(void) - This function can only be executed if the SPI connection has been set as + SPI_commit_and_chain is the same, but a new + transaction is immediately started with the same transaction + characteristics as the just finished one, like with the SQL command + COMMIT AND CHAIN. + + + + These functions can only be executed if the SPI connection has been set as nonatomic in the call to SPI_connect_ext. @@ -4416,6 +4429,7 @@ void SPI_commit(void) SPI_rollback + SPI_rollback_and_chain SPI_rollback @@ -4424,12 +4438,17 @@ void SPI_commit(void) SPI_rollback + SPI_rollback_and_chain abort the current transaction void SPI_rollback(void) + + + +void SPI_rollback_and_chain(void) @@ -4444,9 +4463,15 @@ void SPI_rollback(void) using SPI_start_transaction before further database actions can be executed. + + SPI_rollback_and_chain is the same, but a new + transaction is immediately started with the same transaction + characteristics as the just finished one, like with the SQL command + ROLLBACK AND CHAIN. + - This function can only be executed if the SPI connection has been set as + These functions can only be executed if the SPI connection has been set as nonatomic in the call to SPI_connect_ext. diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 6e5891749b4..c3214d4f4d8 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -190,6 +190,7 @@ typedef struct TransactionStateData bool startedInRecovery; /* did we start in recovery? */ bool didLogXid; /* has xid been included in WAL record? */ int parallelModeLevel; /* Enter/ExitParallelMode counter */ + bool chain; /* start a new block after this one */ struct TransactionStateData *parent; /* back link to parent */ } TransactionStateData; @@ -2775,6 +2776,36 @@ StartTransactionCommand(void) MemoryContextSwitchTo(CurTransactionContext); } + +/* + * Simple system for saving and restoring transaction characteristics + * (isolation level, read only, deferrable). 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 + * just skipping the reset in StartTransaction() won't work.) + */ +static int save_XactIsoLevel; +static bool save_XactReadOnly; +static bool save_XactDeferrable; + +void +SaveTransactionCharacteristics(void) +{ + save_XactIsoLevel = XactIsoLevel; + save_XactReadOnly = XactReadOnly; + save_XactDeferrable = XactDeferrable; +} + +void +RestoreTransactionCharacteristics(void) +{ + XactIsoLevel = save_XactIsoLevel; + XactReadOnly = save_XactReadOnly; + XactDeferrable = save_XactDeferrable; +} + + /* * CommitTransactionCommand */ @@ -2783,6 +2814,9 @@ CommitTransactionCommand(void) { TransactionState s = CurrentTransactionState; + if (s->chain) + SaveTransactionCharacteristics(); + switch (s->blockState) { /* @@ -2834,6 +2868,13 @@ CommitTransactionCommand(void) case TBLOCK_END: CommitTransaction(); s->blockState = TBLOCK_DEFAULT; + if (s->chain) + { + StartTransaction(); + s->blockState = TBLOCK_INPROGRESS; + s->chain = false; + RestoreTransactionCharacteristics(); + } break; /* @@ -2853,6 +2894,13 @@ CommitTransactionCommand(void) case TBLOCK_ABORT_END: CleanupTransaction(); s->blockState = TBLOCK_DEFAULT; + if (s->chain) + { + StartTransaction(); + s->blockState = TBLOCK_INPROGRESS; + s->chain = false; + RestoreTransactionCharacteristics(); + } break; /* @@ -2864,6 +2912,13 @@ CommitTransactionCommand(void) AbortTransaction(); CleanupTransaction(); s->blockState = TBLOCK_DEFAULT; + if (s->chain) + { + StartTransaction(); + s->blockState = TBLOCK_INPROGRESS; + s->chain = false; + RestoreTransactionCharacteristics(); + } break; /* @@ -3521,7 +3576,7 @@ PrepareTransactionBlock(const char *gid) bool result; /* Set up to commit the current transaction */ - result = EndTransactionBlock(); + result = EndTransactionBlock(false); /* If successful, change outer tblock state to PREPARE */ if (result) @@ -3567,7 +3622,7 @@ PrepareTransactionBlock(const char *gid) * resource owner, etc while executing inside a Portal. */ bool -EndTransactionBlock(void) +EndTransactionBlock(bool chain) { TransactionState s = CurrentTransactionState; bool result = false; @@ -3693,6 +3748,13 @@ EndTransactionBlock(void) break; } + Assert(s->blockState == TBLOCK_STARTED || + s->blockState == TBLOCK_END || + s->blockState == TBLOCK_ABORT_END || + s->blockState == TBLOCK_ABORT_PENDING); + + s->chain = chain; + return result; } @@ -3703,7 +3765,7 @@ EndTransactionBlock(void) * As above, we don't actually do anything here except change blockState. */ void -UserAbortTransactionBlock(void) +UserAbortTransactionBlock(bool chain) { TransactionState s = CurrentTransactionState; @@ -3801,6 +3863,11 @@ UserAbortTransactionBlock(void) BlockStateAsString(s->blockState)); break; } + + Assert(s->blockState == TBLOCK_ABORT_END || + s->blockState == TBLOCK_ABORT_PENDING); + + s->chain = chain; } /* diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt index bade0fe9aeb..6b23163929d 100644 --- a/src/backend/catalog/sql_features.txt +++ b/src/backend/catalog/sql_features.txt @@ -443,7 +443,7 @@ T213 INSTEAD OF triggers YES T231 Sensitive cursors YES T241 START TRANSACTION statement YES T251 SET TRANSACTION statement: LOCAL option NO -T261 Chained transactions NO +T261 Chained transactions YES T271 Savepoints YES T272 Enhanced savepoint management NO T281 SELECT privilege with column granularity YES diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index d898f4ca78d..6e262d1a3ad 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -217,8 +217,8 @@ SPI_start_transaction(void) MemoryContextSwitchTo(oldcontext); } -void -SPI_commit(void) +static void +_SPI_commit(bool chain) { MemoryContext oldcontext = CurrentMemoryContext; @@ -250,14 +250,36 @@ SPI_commit(void) while (ActiveSnapshotSet()) PopActiveSnapshot(); + if (chain) + SaveTransactionCharacteristics(); + CommitTransactionCommand(); + + if (chain) + { + StartTransactionCommand(); + RestoreTransactionCharacteristics(); + } + MemoryContextSwitchTo(oldcontext); _SPI_current->internal_xact = false; } void -SPI_rollback(void) +SPI_commit(void) +{ + _SPI_commit(false); +} + +void +SPI_commit_and_chain(void) +{ + _SPI_commit(true); +} + +static void +_SPI_rollback(bool chain) { MemoryContext oldcontext = CurrentMemoryContext; @@ -274,12 +296,34 @@ SPI_rollback(void) _SPI_current->internal_xact = true; + if (chain) + SaveTransactionCharacteristics(); + AbortCurrentTransaction(); + + if (chain) + { + StartTransactionCommand(); + RestoreTransactionCharacteristics(); + } + MemoryContextSwitchTo(oldcontext); _SPI_current->internal_xact = false; } +void +SPI_rollback(void) +{ + _SPI_rollback(false); +} + +void +SPI_rollback_and_chain(void) +{ + _SPI_rollback(true); +} + /* * Clean up SPI state. Called on transaction end (of non-SPI-internal * transactions) and when returning to the main loop on error. diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 1ea6b845616..d97781e1cbe 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3666,6 +3666,7 @@ _copyTransactionStmt(const TransactionStmt *from) COPY_NODE_FIELD(options); COPY_STRING_FIELD(savepoint_name); COPY_STRING_FIELD(gid); + COPY_SCALAR_FIELD(chain); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 886e96c9b61..91c007ad5b0 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1510,6 +1510,7 @@ _equalTransactionStmt(const TransactionStmt *a, const TransactionStmt *b) COMPARE_NODE_FIELD(options); COMPARE_STRING_FIELD(savepoint_name); COMPARE_STRING_FIELD(gid); + COMPARE_SCALAR_FIELD(chain); return true; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 502e51bb0e1..0a4822829a5 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -312,6 +312,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query); %type opt_or_replace opt_grant_grant_option opt_grant_admin_option opt_nowait opt_if_exists opt_with_data + opt_transaction_chain %type opt_nowait_or_skip %type OptRoleList AlterOptRoleList @@ -9792,11 +9793,12 @@ UnlistenStmt: *****************************************************************************/ TransactionStmt: - ABORT_P opt_transaction + ABORT_P opt_transaction opt_transaction_chain { TransactionStmt *n = makeNode(TransactionStmt); n->kind = TRANS_STMT_ROLLBACK; n->options = NIL; + n->chain = $3; $$ = (Node *)n; } | BEGIN_P opt_transaction transaction_mode_list_or_empty @@ -9813,25 +9815,28 @@ TransactionStmt: n->options = $3; $$ = (Node *)n; } - | COMMIT opt_transaction + | COMMIT opt_transaction opt_transaction_chain { TransactionStmt *n = makeNode(TransactionStmt); n->kind = TRANS_STMT_COMMIT; n->options = NIL; + n->chain = $3; $$ = (Node *)n; } - | END_P opt_transaction + | END_P opt_transaction opt_transaction_chain { TransactionStmt *n = makeNode(TransactionStmt); n->kind = TRANS_STMT_COMMIT; n->options = NIL; + n->chain = $3; $$ = (Node *)n; } - | ROLLBACK opt_transaction + | ROLLBACK opt_transaction opt_transaction_chain { TransactionStmt *n = makeNode(TransactionStmt); n->kind = TRANS_STMT_ROLLBACK; n->options = NIL; + n->chain = $3; $$ = (Node *)n; } | SAVEPOINT ColId @@ -9931,6 +9936,12 @@ transaction_mode_list_or_empty: { $$ = NIL; } ; +opt_transaction_chain: + AND CHAIN { $$ = true; } + | AND NO CHAIN { $$ = false; } + | /* EMPTY */ { $$ = false; } + ; + /***************************************************************************** * diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 5053ef05eff..857b7a8b43f 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -440,7 +440,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, break; case TRANS_STMT_COMMIT: - if (!EndTransactionBlock()) + if (!EndTransactionBlock(stmt->chain)) { /* report unsuccessful commit in completionTag */ if (completionTag) @@ -471,7 +471,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, break; case TRANS_STMT_ROLLBACK: - UserAbortTransactionBlock(); + UserAbortTransactionBlock(stmt->chain); break; case TRANS_STMT_SAVEPOINT: diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 10ae21cc613..3ba3498496e 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -2091,16 +2091,18 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("WORK", "TRANSACTION", "ISOLATION LEVEL", "READ", "DEFERRABLE", "NOT DEFERRABLE"); /* END, ABORT */ else if (Matches("END|ABORT")) - COMPLETE_WITH("WORK", "TRANSACTION"); + COMPLETE_WITH("AND", "WORK", "TRANSACTION"); /* COMMIT */ else if (Matches("COMMIT")) - COMPLETE_WITH("WORK", "TRANSACTION", "PREPARED"); + COMPLETE_WITH("AND", "WORK", "TRANSACTION", "PREPARED"); /* RELEASE SAVEPOINT */ else if (Matches("RELEASE")) COMPLETE_WITH("SAVEPOINT"); /* ROLLBACK */ else if (Matches("ROLLBACK")) - COMPLETE_WITH("WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED"); + COMPLETE_WITH("AND", "WORK", "TRANSACTION", "TO SAVEPOINT", "PREPARED"); + else if (Matches("ABORT|END|COMMIT|ROLLBACK", "AND")) + COMPLETE_WITH("CHAIN"); /* CALL */ else if (Matches("CALL")) COMPLETE_WITH_VERSIONED_SCHEMA_QUERY(Query_for_list_of_procedures, NULL); diff --git a/src/include/access/xact.h b/src/include/access/xact.h index 426e77846f9..e8579dcd478 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -368,12 +368,14 @@ extern bool TransactionIdIsCurrentTransactionId(TransactionId xid); extern void CommandCounterIncrement(void); extern void ForceSyncCommit(void); extern void StartTransactionCommand(void); +extern void SaveTransactionCharacteristics(void); +extern void RestoreTransactionCharacteristics(void); extern void CommitTransactionCommand(void); extern void AbortCurrentTransaction(void); extern void BeginTransactionBlock(void); -extern bool EndTransactionBlock(void); +extern bool EndTransactionBlock(bool chain); extern bool PrepareTransactionBlock(const char *gid); -extern void UserAbortTransactionBlock(void); +extern void UserAbortTransactionBlock(bool chain); extern void BeginImplicitTransactionBlock(void); extern void EndImplicitTransactionBlock(void); extern void ReleaseSavepoint(const char *name); diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h index eafcc7a4e4d..83c28b79bfa 100644 --- a/src/include/executor/spi.h +++ b/src/include/executor/spi.h @@ -160,7 +160,9 @@ extern int SPI_register_trigger_data(TriggerData *tdata); extern void SPI_start_transaction(void); extern void SPI_commit(void); +extern void SPI_commit_and_chain(void); extern void SPI_rollback(void); +extern void SPI_rollback_and_chain(void); extern void SPICleanup(void); extern void AtEOXact_SPI(bool isCommit); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 81278e40197..bdd2bd2fd9c 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2998,6 +2998,7 @@ typedef struct TransactionStmt List *options; /* for BEGIN/START commands */ char *savepoint_name; /* for savepoint commands */ char *gid; /* for two-phase-commit related commands */ + bool chain; /* AND CHAIN option */ } TransactionStmt; /* ---------------------- diff --git a/src/pl/plpgsql/src/expected/plpgsql_transaction.out b/src/pl/plpgsql/src/expected/plpgsql_transaction.out index 6eedb215a44..ba0745326af 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_transaction.out +++ b/src/pl/plpgsql/src/expected/plpgsql_transaction.out @@ -523,6 +523,34 @@ BEGIN END; $$; CALL transaction_test11(); +-- transaction chain +TRUNCATE test1; +DO LANGUAGE plpgsql $$ +BEGIN + ROLLBACK; + SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; + FOR i IN 0..3 LOOP + RAISE INFO 'transaction_isolation = %', current_setting('transaction_isolation'); + INSERT INTO test1 (a) VALUES (i); + IF i % 2 = 0 THEN + COMMIT AND CHAIN; + ELSE + ROLLBACK AND CHAIN; + END IF; + END LOOP; +END +$$; +INFO: transaction_isolation = repeatable read +INFO: transaction_isolation = repeatable read +INFO: transaction_isolation = repeatable read +INFO: transaction_isolation = repeatable read +SELECT * FROM test1; + a | b +---+--- + 0 | + 2 | +(2 rows) + DROP TABLE test1; DROP TABLE test2; DROP TABLE test3; diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 6dfcd1611a8..527cada4feb 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -4773,8 +4773,13 @@ exec_stmt_commit(PLpgSQL_execstate *estate, PLpgSQL_stmt_commit *stmt) { HoldPinnedPortals(); - SPI_commit(); - SPI_start_transaction(); + if (stmt->chain) + SPI_commit_and_chain(); + else + { + SPI_commit(); + SPI_start_transaction(); + } estate->simple_eval_estate = NULL; plpgsql_create_econtext(estate); @@ -4792,8 +4797,13 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt) { HoldPinnedPortals(); - SPI_rollback(); - SPI_start_transaction(); + if (stmt->chain) + SPI_rollback_and_chain(); + else + { + SPI_rollback(); + SPI_start_transaction(); + } estate->simple_eval_estate = NULL; plpgsql_create_econtext(estate); diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index 0b63da2b4a0..053f83dc746 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -1318,14 +1318,20 @@ static void dump_commit(PLpgSQL_stmt_commit *stmt) { dump_ind(); - printf("COMMIT\n"); + if (stmt->chain) + printf("COMMIT AND CHAIN\n"); + else + printf("COMMIT\n"); } static void dump_rollback(PLpgSQL_stmt_rollback *stmt) { dump_ind(); - printf("ROLLBACK\n"); + if (stmt->chain) + printf("ROLLBACK AND CHAIN\n"); + else + printf("ROLLBACK\n"); } static void diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index 03f7cdce8cd..dea95f42308 100644 --- a/src/pl/plpgsql/src/pl_gram.y +++ b/src/pl/plpgsql/src/pl_gram.y @@ -219,6 +219,8 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %type opt_scrollable %type opt_fetch_direction +%type opt_transaction_chain + %type unreserved_keyword @@ -252,6 +254,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %token K_ABSOLUTE %token K_ALIAS %token K_ALL +%token K_AND %token K_ARRAY %token K_ASSERT %token K_BACKWARD @@ -259,6 +262,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt); %token K_BY %token K_CALL %token K_CASE +%token K_CHAIN %token K_CLOSE %token K_COLLATE %token K_COLUMN @@ -2199,7 +2203,7 @@ stmt_null : K_NULL ';' } ; -stmt_commit : K_COMMIT ';' +stmt_commit : K_COMMIT opt_transaction_chain ';' { PLpgSQL_stmt_commit *new; @@ -2207,12 +2211,13 @@ stmt_commit : K_COMMIT ';' new->cmd_type = PLPGSQL_STMT_COMMIT; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->chain = $2; $$ = (PLpgSQL_stmt *)new; } ; -stmt_rollback : K_ROLLBACK ';' +stmt_rollback : K_ROLLBACK opt_transaction_chain ';' { PLpgSQL_stmt_rollback *new; @@ -2220,11 +2225,18 @@ stmt_rollback : K_ROLLBACK ';' new->cmd_type = PLPGSQL_STMT_ROLLBACK; new->lineno = plpgsql_location_to_lineno(@1); new->stmtid = ++plpgsql_curr_compile->nstatements; + new->chain = $2; $$ = (PLpgSQL_stmt *)new; } ; +opt_transaction_chain: + K_AND K_CHAIN { $$ = true; } + | K_AND K_NO K_CHAIN { $$ = false; } + | /* EMPTY */ { $$ = false; } + ; + stmt_set : K_SET { PLpgSQL_stmt_set *new; @@ -2482,10 +2494,12 @@ any_identifier : T_WORD unreserved_keyword : K_ABSOLUTE | K_ALIAS + | K_AND | K_ARRAY | K_ASSERT | K_BACKWARD | K_CALL + | K_CHAIN | K_CLOSE | K_COLLATE | K_COLUMN diff --git a/src/pl/plpgsql/src/pl_unreserved_kwlist.h b/src/pl/plpgsql/src/pl_unreserved_kwlist.h index ce4be81dd88..6d85f9396ea 100644 --- a/src/pl/plpgsql/src/pl_unreserved_kwlist.h +++ b/src/pl/plpgsql/src/pl_unreserved_kwlist.h @@ -29,10 +29,12 @@ /* name, value */ PG_KEYWORD("absolute", K_ABSOLUTE) PG_KEYWORD("alias", K_ALIAS) +PG_KEYWORD("and", K_AND) PG_KEYWORD("array", K_ARRAY) PG_KEYWORD("assert", K_ASSERT) PG_KEYWORD("backward", K_BACKWARD) PG_KEYWORD("call", K_CALL) +PG_KEYWORD("chain", K_CHAIN) PG_KEYWORD("close", K_CLOSE) PG_KEYWORD("collate", K_COLLATE) PG_KEYWORD("column", K_COLUMN) diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 0a5fbfa9d68..4eff62e8e5e 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -545,6 +545,7 @@ typedef struct PLpgSQL_stmt_commit PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + bool chain; } PLpgSQL_stmt_commit; /* @@ -555,6 +556,7 @@ typedef struct PLpgSQL_stmt_rollback PLpgSQL_stmt_type cmd_type; int lineno; unsigned int stmtid; + bool chain; } PLpgSQL_stmt_rollback; /* diff --git a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql index ac1361a8ceb..0c137dd31dd 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql @@ -445,6 +445,29 @@ $$; CALL transaction_test11(); +-- transaction chain + +TRUNCATE test1; + +DO LANGUAGE plpgsql $$ +BEGIN + ROLLBACK; + SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; + FOR i IN 0..3 LOOP + RAISE INFO 'transaction_isolation = %', current_setting('transaction_isolation'); + INSERT INTO test1 (a) VALUES (i); + IF i % 2 = 0 THEN + COMMIT AND CHAIN; + ELSE + ROLLBACK AND CHAIN; + END IF; + END LOOP; +END +$$; + +SELECT * FROM test1; + + DROP TABLE test1; DROP TABLE test2; DROP TABLE test3; diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out index 69e176c5259..1b316cc9b8c 100644 --- a/src/test/regress/expected/transactions.out +++ b/src/test/regress/expected/transactions.out @@ -659,6 +659,197 @@ ERROR: portal "ctt" cannot be run COMMIT; DROP FUNCTION create_temp_tab(); DROP FUNCTION invert(x float8); +-- Tests for AND CHAIN +CREATE TABLE abc (a int); +-- set nondefault value so we have something to override below +SET default_transaction_read_only = on; +START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; +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) + +INSERT INTO abc VALUES (1); +INSERT INTO abc VALUES (2); +COMMIT AND CHAIN; -- TBLOCK_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) + +INSERT INTO abc VALUES ('error'); +ERROR: invalid input syntax for type integer: "error" +LINE 1: INSERT INTO abc VALUES ('error'); + ^ +INSERT INTO abc VALUES (3); -- check it's really aborted +ERROR: current transaction is aborted, commands ignored until end of transaction block +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) + +INSERT INTO abc VALUES (4); +COMMIT; +START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; +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) + +SAVEPOINT x; +INSERT INTO abc VALUES ('error'); +ERROR: invalid input syntax for type integer: "error" +LINE 1: INSERT INTO abc VALUES ('error'); + ^ +COMMIT AND CHAIN; -- TBLOCK_ABORT_PENDING +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) + +INSERT INTO abc VALUES (5); +COMMIT; +-- different mix of options just for fun +START TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ WRITE, NOT DEFERRABLE; +SHOW transaction_isolation; + transaction_isolation +----------------------- + serializable +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + off +(1 row) + +INSERT INTO abc VALUES (6); +ROLLBACK AND CHAIN; -- TBLOCK_ABORT_PENDING +SHOW transaction_isolation; + transaction_isolation +----------------------- + serializable +(1 row) + +SHOW transaction_read_only; + transaction_read_only +----------------------- + off +(1 row) + +SHOW transaction_deferrable; + transaction_deferrable +------------------------ + off +(1 row) + +INSERT INTO abc VALUES ('error'); +ERROR: invalid input syntax for type integer: "error" +LINE 1: INSERT INTO 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 +------------------------ + off +(1 row) + +ROLLBACK; +SELECT * FROM abc ORDER BY 1; + a +--- + 1 + 2 + 4 + 5 +(4 rows) + +RESET default_transaction_read_only; +DROP TABLE abc; -- Test assorted behaviors around the implicit transaction block created -- when multiple SQL commands are sent in a single Query message. These -- tests rely on the fact that psql will not break SQL commands apart at a diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql index 2e3739fd6c4..812e40a1a3a 100644 --- a/src/test/regress/sql/transactions.sql +++ b/src/test/regress/sql/transactions.sql @@ -419,6 +419,69 @@ DROP FUNCTION create_temp_tab(); DROP FUNCTION invert(x float8); +-- Tests for AND CHAIN + +CREATE TABLE abc (a int); + +-- set nondefault value so we have something to override below +SET default_transaction_read_only = on; + +START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +INSERT INTO abc VALUES (1); +INSERT INTO abc VALUES (2); +COMMIT AND CHAIN; -- TBLOCK_END +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +INSERT INTO abc VALUES ('error'); +INSERT INTO abc VALUES (3); -- check it's really aborted +COMMIT AND CHAIN; -- TBLOCK_ABORT_END +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +INSERT INTO abc VALUES (4); +COMMIT; + +START TRANSACTION ISOLATION LEVEL REPEATABLE READ, READ WRITE, DEFERRABLE; +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +SAVEPOINT x; +INSERT INTO abc VALUES ('error'); +COMMIT AND CHAIN; -- TBLOCK_ABORT_PENDING +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +INSERT INTO abc VALUES (5); +COMMIT; + +-- different mix of options just for fun +START TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ WRITE, NOT DEFERRABLE; +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +INSERT INTO abc VALUES (6); +ROLLBACK AND CHAIN; -- TBLOCK_ABORT_PENDING +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +INSERT INTO abc VALUES ('error'); +ROLLBACK AND CHAIN; -- TBLOCK_ABORT_END +SHOW transaction_isolation; +SHOW transaction_read_only; +SHOW transaction_deferrable; +ROLLBACK; + +SELECT * FROM abc ORDER BY 1; + +RESET default_transaction_read_only; + +DROP TABLE abc; + + -- Test assorted behaviors around the implicit transaction block created -- when multiple SQL commands are sent in a single Query message. These -- tests rely on the fact that psql will not break SQL commands apart at a