chained transactions

Started by Peter Eisentrautalmost 8 years ago28 messages
#1Peter Eisentraut
peter.eisentraut@2ndquadrant.com
8 attachment(s)

This feature is meant to help manage transaction isolation in
procedures. I proposed elsewhere a patch that allows running SET
TRANSACTION in PL/pgSQL. But if you have complex procedures that commit
many times in different branches perhaps, you'd have to do this in every
new transaction, which would create code that is difficult to manage.

The SQL standard offers the "chained transactions" feature to address
this. The new command variants COMMIT AND CHAIN and ROLLBACK AND CHAIN
immediately start a new transaction with the characteristics (isolation
level, read/write, deferrable) of the previous one. So code that has
particular requirements regard transaction isolation and such can use
this to simplify code management.

In the patch series, 0001 through 0006 is some preparatory code cleanup
that is useful independently. 0007 is the implementation of the feature
for the main SQL environment, 0008 is for PL/pgSQL. Support in other
PLs could be added easily.

The patch series also requires the "SET TRANSACTION in PL/pgSQL" patch
for use in the test suite.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v1-0001-Update-function-comments.patchtext/plain; charset=UTF-8; name=v1-0001-Update-function-comments.patch; x-mac-creator=0; x-mac-type=0Download
From ce07129f4d7ba376c59fa5d8244953a4eec05027 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Fri, 16 Feb 2018 20:29:20 -0500
Subject: [PATCH v1 1/8] Update function comments

After a6542a4b6870a019cd952d055d2e7af2da2fe102, some function comments
were misplaced.  Fix that.
---
 src/backend/access/transam/xact.c | 40 ++++++++++++++++++---------------------
 1 file changed, 18 insertions(+), 22 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index dbaaf8e005..d7688879a3 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -3190,12 +3190,25 @@ PreventTransactionChain(bool isTopLevel, const char *stmtType)
 }
 
 /*
- *	These two functions allow for warnings or errors if a command is
- *	executed outside of a transaction block.
+ *	WarnNoTranactionChain
+ *	RequireTransactionChain
+ *
+ *	These two functions allow for warnings or errors if a command is executed
+ *	outside of a transaction block.  This is useful for commands that have no
+ *	effects that persist past transaction end (and so calling them outside a
+ *	transaction block is presumably an error).  DECLARE CURSOR is an example.
+ *	While top-level transaction control commands (BEGIN/COMMIT/ABORT) and SET
+ *	that have no effect issue warnings, all other no-effect commands generate
+ *	errors.
+ *
+ *	If we appear to be running inside a user-defined function, we do not
+ *	issue anything, since the function could issue more commands that make
+ *	use of the current statement's results.  Likewise subtransactions.
+ *	Thus this is an inverse for PreventTransactionChain.
  *
- *	While top-level transaction control commands (BEGIN/COMMIT/ABORT) and
- *	SET that have no effect issue warnings, all other no-effect commands
- *	generate errors.
+ *	isTopLevel: passed down from ProcessUtility to determine whether we are
+ *	inside a function.
+ *	stmtType: statement type name, for warning or error messages.
  */
 void
 WarnNoTransactionChain(bool isTopLevel, const char *stmtType)
@@ -3209,23 +3222,6 @@ RequireTransactionChain(bool isTopLevel, const char *stmtType)
 	CheckTransactionChain(isTopLevel, true, stmtType);
 }
 
-/*
- *	RequireTransactionChain
- *
- *	This routine is to be called by statements that must run inside
- *	a transaction block, because they have no effects that persist past
- *	transaction end (and so calling them outside a transaction block
- *	is presumably an error).  DECLARE CURSOR is an example.
- *
- *	If we appear to be running inside a user-defined function, we do not
- *	issue anything, since the function could issue more commands that make
- *	use of the current statement's results.  Likewise subtransactions.
- *	Thus this is an inverse for PreventTransactionChain.
- *
- *	isTopLevel: passed down from ProcessUtility to determine whether we are
- *	inside a function.
- *	stmtType: statement type name, for warning or error messages.
- */
 static void
 CheckTransactionChain(bool isTopLevel, bool throwError, const char *stmtType)
 {

base-commit: 51057feaa6bd24b51e6a4715c2090491ef037534
prerequisite-patch-id: 767f2e4d21b2ba347d4d08c3257abb91921fdb7b
-- 
2.16.2

v1-0002-Rename-TransactionChain-functions.patchtext/plain; charset=UTF-8; name=v1-0002-Rename-TransactionChain-functions.patch; x-mac-creator=0; x-mac-type=0Download
From c1d5189f52537d4632d0a4069a732e8666c123e1 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Fri, 16 Feb 2018 20:44:15 -0500
Subject: [PATCH v1 2/8] Rename TransactionChain functions

We call this thing a "transaction block" everywhere except in a few
functions, where it is mysteriously called a "transaction chain".  In
the SQL standard, a transaction chain is something different.  So rename
these functions to match the common terminology.
---
 src/backend/access/transam/xact.c       | 28 ++++++++++++++--------------
 src/backend/commands/cluster.c          |  2 +-
 src/backend/commands/dbcommands.c       |  2 +-
 src/backend/commands/discard.c          |  2 +-
 src/backend/commands/portalcmds.c       |  2 +-
 src/backend/commands/subscriptioncmds.c |  4 ++--
 src/backend/commands/typecmds.c         |  2 +-
 src/backend/commands/vacuum.c           |  4 ++--
 src/backend/replication/walsender.c     |  6 +++---
 src/backend/tcop/utility.c              | 30 +++++++++++++++---------------
 src/backend/utils/misc/guc.c            | 10 +++++-----
 src/backend/utils/time/snapmgr.c        |  2 +-
 src/bin/psql/common.c                   |  2 +-
 src/include/access/xact.h               |  8 ++++----
 14 files changed, 52 insertions(+), 52 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index d7688879a3..e8a4412671 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -310,7 +310,7 @@ static void CallSubXactCallbacks(SubXactEvent event,
 					 SubTransactionId mySubid,
 					 SubTransactionId parentSubid);
 static void CleanupTransaction(void);
-static void CheckTransactionChain(bool isTopLevel, bool throwError,
+static void CheckTransactionBlock(bool isTopLevel, bool throwError,
 					  const char *stmtType);
 static void CommitTransaction(void);
 static TransactionId RecordTransactionAbort(bool isSubXact);
@@ -3134,7 +3134,7 @@ AbortCurrentTransaction(void)
 }
 
 /*
- *	PreventTransactionChain
+ *	PreventInTransactionBlock
  *
  *	This routine is to be called by statements that must not run inside
  *	a transaction block, typically because they have non-rollback-able
@@ -3151,7 +3151,7 @@ AbortCurrentTransaction(void)
  *	stmtType: statement type name, for error messages.
  */
 void
-PreventTransactionChain(bool isTopLevel, const char *stmtType)
+PreventInTransactionBlock(bool isTopLevel, const char *stmtType)
 {
 	/*
 	 * xact block already started?
@@ -3190,8 +3190,8 @@ PreventTransactionChain(bool isTopLevel, const char *stmtType)
 }
 
 /*
- *	WarnNoTranactionChain
- *	RequireTransactionChain
+ *	WarnNoTranactionBlock
+ *	RequireTransactionBlock
  *
  *	These two functions allow for warnings or errors if a command is executed
  *	outside of a transaction block.  This is useful for commands that have no
@@ -3204,26 +3204,26 @@ PreventTransactionChain(bool isTopLevel, const char *stmtType)
  *	If we appear to be running inside a user-defined function, we do not
  *	issue anything, since the function could issue more commands that make
  *	use of the current statement's results.  Likewise subtransactions.
- *	Thus this is an inverse for PreventTransactionChain.
+ *	Thus this is an inverse for PreventInTransactionBlock.
  *
  *	isTopLevel: passed down from ProcessUtility to determine whether we are
  *	inside a function.
  *	stmtType: statement type name, for warning or error messages.
  */
 void
-WarnNoTransactionChain(bool isTopLevel, const char *stmtType)
+WarnNoTransactionBlock(bool isTopLevel, const char *stmtType)
 {
-	CheckTransactionChain(isTopLevel, false, stmtType);
+	CheckTransactionBlock(isTopLevel, false, stmtType);
 }
 
 void
-RequireTransactionChain(bool isTopLevel, const char *stmtType)
+RequireTransactionBlock(bool isTopLevel, const char *stmtType)
 {
-	CheckTransactionChain(isTopLevel, true, stmtType);
+	CheckTransactionBlock(isTopLevel, true, stmtType);
 }
 
 static void
-CheckTransactionChain(bool isTopLevel, bool throwError, const char *stmtType)
+CheckTransactionBlock(bool isTopLevel, bool throwError, const char *stmtType)
 {
 	/*
 	 * xact block already started?
@@ -3252,7 +3252,7 @@ CheckTransactionChain(bool isTopLevel, bool throwError, const char *stmtType)
 }
 
 /*
- *	IsInTransactionChain
+ *	IsInTransactionBlock
  *
  *	This routine is for statements that need to behave differently inside
  *	a transaction block than when running as single commands.  ANALYZE is
@@ -3262,10 +3262,10 @@ CheckTransactionChain(bool isTopLevel, bool throwError, const char *stmtType)
  *	inside a function.
  */
 bool
-IsInTransactionChain(bool isTopLevel)
+IsInTransactionBlock(bool isTopLevel)
 {
 	/*
-	 * Return true on same conditions that would make PreventTransactionChain
+	 * Return true on same conditions that would make PreventInTransactionBlock
 	 * error out
 	 */
 	if (IsTransactionBlock())
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 5d481dd50d..96a51bb760 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -202,7 +202,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
 		 * We cannot run this form of CLUSTER inside a user transaction block;
 		 * we'd be holding locks way too long.
 		 */
-		PreventTransactionChain(isTopLevel, "CLUSTER");
+		PreventInTransactionBlock(isTopLevel, "CLUSTER");
 
 		/*
 		 * Create special memory context for cross-transaction storage.
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index d1718f04ee..5342f217c0 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -1476,7 +1476,7 @@ AlterDatabase(ParseState *pstate, AlterDatabaseStmt *stmt, bool isTopLevel)
 							dtablespace->defname),
 					 parser_errposition(pstate, dtablespace->location)));
 		/* this case isn't allowed within a transaction block */
-		PreventTransactionChain(isTopLevel, "ALTER DATABASE SET TABLESPACE");
+		PreventInTransactionBlock(isTopLevel, "ALTER DATABASE SET TABLESPACE");
 		movedb(stmt->dbname, defGetString(dtablespace));
 		return InvalidOid;
 	}
diff --git a/src/backend/commands/discard.c b/src/backend/commands/discard.c
index 353ec990af..01a999c2ac 100644
--- a/src/backend/commands/discard.c
+++ b/src/backend/commands/discard.c
@@ -63,7 +63,7 @@ DiscardAll(bool isTopLevel)
 	 * DISCARD ALL inside a transaction block would leave the transaction
 	 * still uncommitted.
 	 */
-	PreventTransactionChain(isTopLevel, "DISCARD ALL");
+	PreventInTransactionBlock(isTopLevel, "DISCARD ALL");
 
 	/* Closing portals might run user-defined code, so do that first. */
 	PortalHashTableDeleteAll();
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 6ecaea1443..73821502ba 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -63,7 +63,7 @@ PerformCursorOpen(DeclareCursorStmt *cstmt, ParamListInfo params,
 	 * user-visible effect).
 	 */
 	if (!(cstmt->options & CURSOR_OPT_HOLD))
-		RequireTransactionChain(isTopLevel, "DECLARE CURSOR");
+		RequireTransactionBlock(isTopLevel, "DECLARE CURSOR");
 
 	/*
 	 * Parse analysis was done already, but we still have to run the rule
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 9de5969302..2694e1b2d7 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -339,7 +339,7 @@ CreateSubscription(CreateSubscriptionStmt *stmt, bool isTopLevel)
 	 * replication slot.
 	 */
 	if (create_slot)
-		PreventTransactionChain(isTopLevel, "CREATE SUBSCRIPTION ... WITH (create_slot = true)");
+		PreventInTransactionBlock(isTopLevel, "CREATE SUBSCRIPTION ... WITH (create_slot = true)");
 
 	if (!superuser())
 		ereport(ERROR,
@@ -897,7 +897,7 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
 	 * don't have the proper facilities for that.
 	 */
 	if (slotname)
-		PreventTransactionChain(isTopLevel, "DROP SUBSCRIPTION");
+		PreventInTransactionBlock(isTopLevel, "DROP SUBSCRIPTION");
 
 
 	ObjectAddressSet(myself, SubscriptionRelationId, subid);
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 899a5c4cd4..d1409bf9f0 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1321,7 +1321,7 @@ AlterEnum(AlterEnumStmt *stmt, bool isTopLevel)
 			!(tup->t_data->t_infomask & HEAP_UPDATED))
 			 /* safe to do inside transaction block */ ;
 		else
-			PreventTransactionChain(isTopLevel, "ALTER TYPE ... ADD");
+			PreventInTransactionBlock(isTopLevel, "ALTER TYPE ... ADD");
 
 		AddEnumLabel(enum_type_oid, stmt->newVal,
 					 stmt->newValNeighbor, stmt->newValIsAfter,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 7aca69a0ba..8e7b69a400 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -186,11 +186,11 @@ vacuum(int options, List *relations, VacuumParams *params,
 	 */
 	if (options & VACOPT_VACUUM)
 	{
-		PreventTransactionChain(isTopLevel, stmttype);
+		PreventInTransactionBlock(isTopLevel, stmttype);
 		in_outer_xact = false;
 	}
 	else
-		in_outer_xact = IsInTransactionChain(isTopLevel);
+		in_outer_xact = IsInTransactionBlock(isTopLevel);
 
 	/*
 	 * Due to static variables vac_context, anl_context and vac_strategy,
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index d46374ddce..5f52e33207 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1516,7 +1516,7 @@ exec_replication_command(const char *cmd_string)
 			break;
 
 		case T_BaseBackupCmd:
-			PreventTransactionChain(true, "BASE_BACKUP");
+			PreventInTransactionBlock(true, "BASE_BACKUP");
 			SendBaseBackup((BaseBackupCmd *) cmd_node);
 			break;
 
@@ -1532,7 +1532,7 @@ exec_replication_command(const char *cmd_string)
 			{
 				StartReplicationCmd *cmd = (StartReplicationCmd *) cmd_node;
 
-				PreventTransactionChain(true, "START_REPLICATION");
+				PreventInTransactionBlock(true, "START_REPLICATION");
 
 				if (cmd->kind == REPLICATION_KIND_PHYSICAL)
 					StartReplication(cmd);
@@ -1542,7 +1542,7 @@ exec_replication_command(const char *cmd_string)
 			}
 
 		case T_TimeLineHistoryCmd:
-			PreventTransactionChain(true, "TIMELINE_HISTORY");
+			PreventInTransactionBlock(true, "TIMELINE_HISTORY");
 			SendTimeLineHistory((TimeLineHistoryCmd *) cmd_node);
 			break;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index f78efdf359..65fc947fc6 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -453,13 +453,13 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 						break;
 
 					case TRANS_STMT_COMMIT_PREPARED:
-						PreventTransactionChain(isTopLevel, "COMMIT PREPARED");
+						PreventInTransactionBlock(isTopLevel, "COMMIT PREPARED");
 						PreventCommandDuringRecovery("COMMIT PREPARED");
 						FinishPreparedTransaction(stmt->gid, true);
 						break;
 
 					case TRANS_STMT_ROLLBACK_PREPARED:
-						PreventTransactionChain(isTopLevel, "ROLLBACK PREPARED");
+						PreventInTransactionBlock(isTopLevel, "ROLLBACK PREPARED");
 						PreventCommandDuringRecovery("ROLLBACK PREPARED");
 						FinishPreparedTransaction(stmt->gid, false);
 						break;
@@ -473,7 +473,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 							ListCell   *cell;
 							char	   *name = NULL;
 
-							RequireTransactionChain(isTopLevel, "SAVEPOINT");
+							RequireTransactionBlock(isTopLevel, "SAVEPOINT");
 
 							foreach(cell, stmt->options)
 							{
@@ -490,12 +490,12 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 						break;
 
 					case TRANS_STMT_RELEASE:
-						RequireTransactionChain(isTopLevel, "RELEASE SAVEPOINT");
+						RequireTransactionBlock(isTopLevel, "RELEASE SAVEPOINT");
 						ReleaseSavepoint(stmt->options);
 						break;
 
 					case TRANS_STMT_ROLLBACK_TO:
-						RequireTransactionChain(isTopLevel, "ROLLBACK TO SAVEPOINT");
+						RequireTransactionBlock(isTopLevel, "ROLLBACK TO SAVEPOINT");
 						RollbackToSavepoint(stmt->options);
 
 						/*
@@ -536,13 +536,13 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 
 		case T_CreateTableSpaceStmt:
 			/* no event triggers for global objects */
-			PreventTransactionChain(isTopLevel, "CREATE TABLESPACE");
+			PreventInTransactionBlock(isTopLevel, "CREATE TABLESPACE");
 			CreateTableSpace((CreateTableSpaceStmt *) parsetree);
 			break;
 
 		case T_DropTableSpaceStmt:
 			/* no event triggers for global objects */
-			PreventTransactionChain(isTopLevel, "DROP TABLESPACE");
+			PreventInTransactionBlock(isTopLevel, "DROP TABLESPACE");
 			DropTableSpace((DropTableSpaceStmt *) parsetree);
 			break;
 
@@ -592,7 +592,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 
 		case T_CreatedbStmt:
 			/* no event triggers for global objects */
-			PreventTransactionChain(isTopLevel, "CREATE DATABASE");
+			PreventInTransactionBlock(isTopLevel, "CREATE DATABASE");
 			createdb(pstate, (CreatedbStmt *) parsetree);
 			break;
 
@@ -611,7 +611,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 				DropdbStmt *stmt = (DropdbStmt *) parsetree;
 
 				/* no event triggers for global objects */
-				PreventTransactionChain(isTopLevel, "DROP DATABASE");
+				PreventInTransactionBlock(isTopLevel, "DROP DATABASE");
 				dropdb(stmt->dbname, stmt->missing_ok);
 			}
 			break;
@@ -689,7 +689,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			break;
 
 		case T_AlterSystemStmt:
-			PreventTransactionChain(isTopLevel, "ALTER SYSTEM");
+			PreventInTransactionBlock(isTopLevel, "ALTER SYSTEM");
 			AlterSystemSetConfigFile((AlterSystemStmt *) parsetree);
 			break;
 
@@ -755,13 +755,13 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			 * Since the lock would just get dropped immediately, LOCK TABLE
 			 * outside a transaction block is presumed to be user error.
 			 */
-			RequireTransactionChain(isTopLevel, "LOCK TABLE");
+			RequireTransactionBlock(isTopLevel, "LOCK TABLE");
 			/* forbidden in parallel mode due to CommandIsReadOnly */
 			LockTableCommand((LockStmt *) parsetree);
 			break;
 
 		case T_ConstraintsSetStmt:
-			WarnNoTransactionChain(isTopLevel, "SET CONSTRAINTS");
+			WarnNoTransactionBlock(isTopLevel, "SET CONSTRAINTS");
 			AfterTriggerSetState((ConstraintsSetStmt *) parsetree);
 			break;
 
@@ -807,7 +807,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 						 * start-transaction-command calls would not have the
 						 * intended effect!
 						 */
-						PreventTransactionChain(isTopLevel,
+						PreventInTransactionBlock(isTopLevel,
 												(stmt->kind == REINDEX_OBJECT_SCHEMA) ? "REINDEX SCHEMA" :
 												(stmt->kind == REINDEX_OBJECT_SYSTEM) ? "REINDEX SYSTEM" :
 												"REINDEX DATABASE");
@@ -1306,7 +1306,7 @@ ProcessUtilitySlow(ParseState *pstate,
 					List	   *inheritors = NIL;
 
 					if (stmt->concurrent)
-						PreventTransactionChain(isTopLevel,
+						PreventInTransactionBlock(isTopLevel,
 												"CREATE INDEX CONCURRENTLY");
 
 					/*
@@ -1714,7 +1714,7 @@ ExecDropStmt(DropStmt *stmt, bool isTopLevel)
 	{
 		case OBJECT_INDEX:
 			if (stmt->concurrent)
-				PreventTransactionChain(isTopLevel,
+				PreventInTransactionBlock(isTopLevel,
 										"DROP INDEX CONCURRENTLY");
 			/* fall through */
 
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 1db7845d5a..14dadf514e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -7347,7 +7347,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 		case VAR_SET_VALUE:
 		case VAR_SET_CURRENT:
 			if (stmt->is_local)
-				WarnNoTransactionChain(isTopLevel, "SET LOCAL");
+				WarnNoTransactionBlock(isTopLevel, "SET LOCAL");
 			(void) set_config_option(stmt->name,
 									 ExtractSetVariableArgs(stmt),
 									 (superuser() ? PGC_SUSET : PGC_USERSET),
@@ -7367,7 +7367,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 			{
 				ListCell   *head;
 
-				WarnNoTransactionChain(isTopLevel, "SET TRANSACTION");
+				WarnNoTransactionBlock(isTopLevel, "SET TRANSACTION");
 
 				foreach(head, stmt->args)
 				{
@@ -7418,7 +7418,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 							 errmsg("SET LOCAL TRANSACTION SNAPSHOT is not implemented")));
 
-				WarnNoTransactionChain(isTopLevel, "SET TRANSACTION");
+				WarnNoTransactionBlock(isTopLevel, "SET TRANSACTION");
 				Assert(nodeTag(&con->val) == T_String);
 				ImportSnapshot(strVal(&con->val));
 			}
@@ -7428,11 +7428,11 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 			break;
 		case VAR_SET_DEFAULT:
 			if (stmt->is_local)
-				WarnNoTransactionChain(isTopLevel, "SET LOCAL");
+				WarnNoTransactionBlock(isTopLevel, "SET LOCAL");
 			/* fall through */
 		case VAR_RESET:
 			if (strcmp(stmt->name, "transaction_isolation") == 0)
-				WarnNoTransactionChain(isTopLevel, "RESET TRANSACTION");
+				WarnNoTransactionBlock(isTopLevel, "RESET TRANSACTION");
 
 			(void) set_config_option(stmt->name,
 									 NULL,
diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c
index e58c69dbd7..4b45d3cccd 100644
--- a/src/backend/utils/time/snapmgr.c
+++ b/src/backend/utils/time/snapmgr.c
@@ -1171,7 +1171,7 @@ ExportSnapshot(Snapshot snapshot)
 	char		pathtmp[MAXPGPATH];
 
 	/*
-	 * It's tempting to call RequireTransactionChain here, since it's not very
+	 * It's tempting to call RequireTransactionBlock here, since it's not very
 	 * useful to export a snapshot that will disappear immediately afterwards.
 	 * However, we haven't got enough information to do that, since we don't
 	 * know if we're at top level or not.  For example, we could be inside a
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 1c82180efb..2b1c4daced 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -2047,7 +2047,7 @@ command_no_begin(const char *query)
 
 	/*
 	 * Commands not allowed within transactions.  The statements checked for
-	 * here should be exactly those that call PreventTransactionChain() in the
+	 * here should be exactly those that call PreventInTransactionBlock() in the
 	 * backend.
 	 */
 	if (wordlen == 6 && pg_strncasecmp(query, "vacuum", 6) == 0)
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 6445bbc46f..4a1307a4f0 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -369,10 +369,10 @@ extern bool IsTransactionBlock(void);
 extern bool IsTransactionOrTransactionBlock(void);
 extern char TransactionBlockStatusCode(void);
 extern void AbortOutOfAnyTransaction(void);
-extern void PreventTransactionChain(bool isTopLevel, const char *stmtType);
-extern void RequireTransactionChain(bool isTopLevel, const char *stmtType);
-extern void WarnNoTransactionChain(bool isTopLevel, const char *stmtType);
-extern bool IsInTransactionChain(bool isTopLevel);
+extern void PreventInTransactionBlock(bool isTopLevel, const char *stmtType);
+extern void RequireTransactionBlock(bool isTopLevel, const char *stmtType);
+extern void WarnNoTransactionBlock(bool isTopLevel, const char *stmtType);
+extern bool IsInTransactionBlock(bool isTopLevel);
 extern void RegisterXactCallback(XactCallback callback, void *arg);
 extern void UnregisterXactCallback(XactCallback callback, void *arg);
 extern void RegisterSubXactCallback(SubXactCallback callback, void *arg);
-- 
2.16.2

v1-0003-Simplify-parse-representation-of-savepoint-comman.patchtext/plain; charset=UTF-8; name=v1-0003-Simplify-parse-representation-of-savepoint-comman.patch; x-mac-creator=0; x-mac-type=0Download
From 2f2d4eec311f8bd7c208de33894c0db9f269dcc1 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Fri, 16 Feb 2018 20:57:06 -0500
Subject: [PATCH v1 3/8] Simplify parse representation of savepoint commands

Instead of embedding the savepoint name in a list and then requiring
complex code to unpack it, just add another struct field to store it
directly.
---
 src/backend/access/transam/xact.c | 28 ++--------------------------
 src/backend/nodes/copyfuncs.c     |  1 +
 src/backend/nodes/equalfuncs.c    |  1 +
 src/backend/parser/gram.y         | 15 +++++----------
 src/backend/tcop/utility.c        | 24 ++++--------------------
 src/include/access/xact.h         |  4 ++--
 src/include/nodes/parsenodes.h    |  3 ++-
 7 files changed, 17 insertions(+), 59 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index e8a4412671..06a7e108d7 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -3905,13 +3905,11 @@ DefineSavepoint(const char *name)
  * As above, we don't actually do anything here except change blockState.
  */
 void
-ReleaseSavepoint(List *options)
+ReleaseSavepoint(const char *name)
 {
 	TransactionState s = CurrentTransactionState;
 	TransactionState target,
 				xact;
-	ListCell   *cell;
-	char	   *name = NULL;
 
 	/*
 	 * Workers synchronize transaction state at the beginning of each parallel
@@ -3975,16 +3973,6 @@ ReleaseSavepoint(List *options)
 			break;
 	}
 
-	foreach(cell, options)
-	{
-		DefElem    *elem = lfirst(cell);
-
-		if (strcmp(elem->defname, "savepoint_name") == 0)
-			name = strVal(elem->arg);
-	}
-
-	Assert(PointerIsValid(name));
-
 	for (target = s; PointerIsValid(target); target = target->parent)
 	{
 		if (PointerIsValid(target->name) && strcmp(target->name, name) == 0)
@@ -4026,13 +4014,11 @@ ReleaseSavepoint(List *options)
  * As above, we don't actually do anything here except change blockState.
  */
 void
-RollbackToSavepoint(List *options)
+RollbackToSavepoint(const char *name)
 {
 	TransactionState s = CurrentTransactionState;
 	TransactionState target,
 				xact;
-	ListCell   *cell;
-	char	   *name = NULL;
 
 	/*
 	 * Workers synchronize transaction state at the beginning of each parallel
@@ -4096,16 +4082,6 @@ RollbackToSavepoint(List *options)
 			break;
 	}
 
-	foreach(cell, options)
-	{
-		DefElem    *elem = lfirst(cell);
-
-		if (strcmp(elem->defname, "savepoint_name") == 0)
-			name = strVal(elem->arg);
-	}
-
-	Assert(PointerIsValid(name));
-
 	for (target = s; PointerIsValid(target); target = target->parent)
 	{
 		if (PointerIsValid(target->name) && strcmp(target->name, name) == 0)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 266a3ef8ef..b013539ee9 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3601,6 +3601,7 @@ _copyTransactionStmt(const TransactionStmt *from)
 
 	COPY_SCALAR_FIELD(kind);
 	COPY_NODE_FIELD(options);
+	COPY_STRING_FIELD(savepoint_name);
 	COPY_STRING_FIELD(gid);
 
 	return newnode;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index bbffc87842..ba5ebdf408 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1512,6 +1512,7 @@ _equalTransactionStmt(const TransactionStmt *a, const TransactionStmt *b)
 {
 	COMPARE_SCALAR_FIELD(kind);
 	COMPARE_NODE_FIELD(options);
+	COMPARE_STRING_FIELD(savepoint_name);
 	COMPARE_STRING_FIELD(gid);
 
 	return true;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d99f2be2c9..ecb2494226 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -9872,40 +9872,35 @@ TransactionStmt:
 				{
 					TransactionStmt *n = makeNode(TransactionStmt);
 					n->kind = TRANS_STMT_SAVEPOINT;
-					n->options = list_make1(makeDefElem("savepoint_name",
-														(Node *)makeString($2), @1));
+					n->savepoint_name = $2;
 					$$ = (Node *)n;
 				}
 			| RELEASE SAVEPOINT ColId
 				{
 					TransactionStmt *n = makeNode(TransactionStmt);
 					n->kind = TRANS_STMT_RELEASE;
-					n->options = list_make1(makeDefElem("savepoint_name",
-														(Node *)makeString($3), @1));
+					n->savepoint_name = $3;
 					$$ = (Node *)n;
 				}
 			| RELEASE ColId
 				{
 					TransactionStmt *n = makeNode(TransactionStmt);
 					n->kind = TRANS_STMT_RELEASE;
-					n->options = list_make1(makeDefElem("savepoint_name",
-														(Node *)makeString($2), @1));
+					n->savepoint_name = $2;
 					$$ = (Node *)n;
 				}
 			| ROLLBACK opt_transaction TO SAVEPOINT ColId
 				{
 					TransactionStmt *n = makeNode(TransactionStmt);
 					n->kind = TRANS_STMT_ROLLBACK_TO;
-					n->options = list_make1(makeDefElem("savepoint_name",
-														(Node *)makeString($5), @1));
+					n->savepoint_name = $5;
 					$$ = (Node *)n;
 				}
 			| ROLLBACK opt_transaction TO ColId
 				{
 					TransactionStmt *n = makeNode(TransactionStmt);
 					n->kind = TRANS_STMT_ROLLBACK_TO;
-					n->options = list_make1(makeDefElem("savepoint_name",
-														(Node *)makeString($4), @1));
+					n->savepoint_name = $4;
 					$$ = (Node *)n;
 				}
 			| PREPARE TRANSACTION Sconst
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 65fc947fc6..2d29778787 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -469,34 +469,18 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 						break;
 
 					case TRANS_STMT_SAVEPOINT:
-						{
-							ListCell   *cell;
-							char	   *name = NULL;
-
-							RequireTransactionBlock(isTopLevel, "SAVEPOINT");
-
-							foreach(cell, stmt->options)
-							{
-								DefElem    *elem = lfirst(cell);
-
-								if (strcmp(elem->defname, "savepoint_name") == 0)
-									name = strVal(elem->arg);
-							}
-
-							Assert(PointerIsValid(name));
-
-							DefineSavepoint(name);
-						}
+						RequireTransactionBlock(isTopLevel, "SAVEPOINT");
+						DefineSavepoint(stmt->savepoint_name);
 						break;
 
 					case TRANS_STMT_RELEASE:
 						RequireTransactionBlock(isTopLevel, "RELEASE SAVEPOINT");
-						ReleaseSavepoint(stmt->options);
+						ReleaseSavepoint(stmt->savepoint_name);
 						break;
 
 					case TRANS_STMT_ROLLBACK_TO:
 						RequireTransactionBlock(isTopLevel, "ROLLBACK TO SAVEPOINT");
-						RollbackToSavepoint(stmt->options);
+						RollbackToSavepoint(stmt->savepoint_name);
 
 						/*
 						 * CommitTransactionCommand is in charge of
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 4a1307a4f0..87ae2cd4df 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -354,9 +354,9 @@ extern bool PrepareTransactionBlock(const char *gid);
 extern void UserAbortTransactionBlock(void);
 extern void BeginImplicitTransactionBlock(void);
 extern void EndImplicitTransactionBlock(void);
-extern void ReleaseSavepoint(List *options);
+extern void ReleaseSavepoint(const char *name);
 extern void DefineSavepoint(const char *name);
-extern void RollbackToSavepoint(List *options);
+extern void RollbackToSavepoint(const char *name);
 extern void BeginInternalSubTransaction(const char *name);
 extern void ReleaseCurrentSubTransaction(void);
 extern void RollbackAndReleaseCurrentSubTransaction(void);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ac292bc6e7..e3a63e9db4 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2964,7 +2964,8 @@ typedef struct TransactionStmt
 {
 	NodeTag		type;
 	TransactionStmtKind kind;	/* see above */
-	List	   *options;		/* for BEGIN/START and savepoint commands */
+	List	   *options;		/* for BEGIN/START commands */
+	char	   *savepoint_name;	/* for savepoint commands */
 	char	   *gid;			/* for two-phase-commit related commands */
 } TransactionStmt;
 
-- 
2.16.2

v1-0004-Improve-savepoint-error-messages.patchtext/plain; charset=UTF-8; name=v1-0004-Improve-savepoint-error-messages.patch; x-mac-creator=0; x-mac-type=0Download
From 09d48e8de680ee6b293897d95fb783b22a50f0f6 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Sat, 17 Feb 2018 20:29:27 -0500
Subject: [PATCH v1 4/8] Improve savepoint error messages

Include the savepoint name in the error message and rephrase it a bit to
match common style.
---
 src/backend/access/transam/xact.c          | 12 ++++++------
 src/test/regress/expected/transactions.out |  2 +-
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 06a7e108d7..4709eabf4d 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -3931,7 +3931,7 @@ ReleaseSavepoint(const char *name)
 		case TBLOCK_INPROGRESS:
 			ereport(ERROR,
 					(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
-					 errmsg("no such savepoint")));
+					 errmsg("savepoint \"%s\" does not exist", name)));
 			break;
 
 		case TBLOCK_IMPLICIT_INPROGRESS:
@@ -3982,13 +3982,13 @@ ReleaseSavepoint(const char *name)
 	if (!PointerIsValid(target))
 		ereport(ERROR,
 				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
-				 errmsg("no such savepoint")));
+				 errmsg("savepoint \"%s\" does not exist", name)));
 
 	/* disallow crossing savepoint level boundaries */
 	if (target->savepointLevel != s->savepointLevel)
 		ereport(ERROR,
 				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
-				 errmsg("no such savepoint")));
+				 errmsg("savepoint \"%s\" does not exist within current savepoint level", name)));
 
 	/*
 	 * Mark "commit pending" all subtransactions up to the target
@@ -4042,7 +4042,7 @@ RollbackToSavepoint(const char *name)
 		case TBLOCK_ABORT:
 			ereport(ERROR,
 					(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
-					 errmsg("no such savepoint")));
+					 errmsg("savepoint \"%s\" does not exist", name)));
 			break;
 
 		case TBLOCK_IMPLICIT_INPROGRESS:
@@ -4091,13 +4091,13 @@ RollbackToSavepoint(const char *name)
 	if (!PointerIsValid(target))
 		ereport(ERROR,
 				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
-				 errmsg("no such savepoint")));
+				 errmsg("savepoint \"%s\" does not exist", name)));
 
 	/* disallow crossing savepoint level boundaries */
 	if (target->savepointLevel != s->savepointLevel)
 		ereport(ERROR,
 				(errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
-				 errmsg("no such savepoint")));
+				 errmsg("savepoint \"%s\" does not exist within current savepoint level", name)));
 
 	/*
 	 * Mark "abort pending" all subtransactions up to the target
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index a7fdcf45fd..fa7fccc63b 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -749,5 +749,5 @@ begin;
 select 1/0;
 ERROR:  division by zero
 rollback to X;
-ERROR:  no such savepoint
+ERROR:  savepoint "x" does not exist
 -- DO NOT ADD ANYTHING HERE.
-- 
2.16.2

v1-0005-Change-transaction-state-debug-strings-to-match-e.patchtext/plain; charset=UTF-8; name=v1-0005-Change-transaction-state-debug-strings-to-match-e.patch; x-mac-creator=0; x-mac-type=0Download
From ab9bf507f18af7dce1447ccc60077f51e5df7acf Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Sat, 17 Feb 2018 10:19:21 -0500
Subject: [PATCH v1 5/8] Change transaction state debug strings to match enum
 symbols

In some cases, these were different for no apparent reason, making
debugging unnecessarily mysterious.
---
 src/backend/access/transam/xact.c | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 4709eabf4d..15edc93d91 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -5141,29 +5141,29 @@ BlockStateAsString(TBlockState blockState)
 		case TBLOCK_ABORT:
 			return "ABORT";
 		case TBLOCK_ABORT_END:
-			return "ABORT END";
+			return "ABORT_END";
 		case TBLOCK_ABORT_PENDING:
-			return "ABORT PEND";
+			return "ABORT_PENDING";
 		case TBLOCK_PREPARE:
 			return "PREPARE";
 		case TBLOCK_SUBBEGIN:
-			return "SUB BEGIN";
+			return "SUBBEGIN";
 		case TBLOCK_SUBINPROGRESS:
-			return "SUB INPROGRS";
+			return "SUBINPROGRESS";
 		case TBLOCK_SUBRELEASE:
-			return "SUB RELEASE";
+			return "SUBRELEASE";
 		case TBLOCK_SUBCOMMIT:
-			return "SUB COMMIT";
+			return "SUBCOMMIT";
 		case TBLOCK_SUBABORT:
-			return "SUB ABORT";
+			return "SUBABORT";
 		case TBLOCK_SUBABORT_END:
-			return "SUB ABORT END";
+			return "SUBABORT_END";
 		case TBLOCK_SUBABORT_PENDING:
-			return "SUB ABRT PEND";
+			return "SUBABORT_PENDING";
 		case TBLOCK_SUBRESTART:
-			return "SUB RESTART";
+			return "SUBRESTART";
 		case TBLOCK_SUBABORT_RESTART:
-			return "SUB AB RESTRT";
+			return "SUBABORT_RESTART";
 	}
 	return "UNRECOGNIZED";
 }
@@ -5182,7 +5182,7 @@ TransStateAsString(TransState state)
 		case TRANS_START:
 			return "START";
 		case TRANS_INPROGRESS:
-			return "INPROGR";
+			return "INPROGRESS";
 		case TRANS_COMMIT:
 			return "COMMIT";
 		case TRANS_ABORT:
-- 
2.16.2

v1-0006-Turn-transaction_isolation-into-GUC-enum.patchtext/plain; charset=UTF-8; name=v1-0006-Turn-transaction_isolation-into-GUC-enum.patch; x-mac-creator=0; x-mac-type=0Download
From 517bc6d9fefdee9135857d9562f644f2984ace32 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Sun, 18 Feb 2018 09:33:53 -0500
Subject: [PATCH v1 6/8] Turn transaction_isolation into GUC enum

XXX no idea why it was done the way it was, but this seems much simpler
and apparently doesn't change any functionality.
---
 src/backend/commands/variable.c | 57 ++---------------------------------------
 src/backend/utils/misc/guc.c    | 25 +++++++++---------
 src/include/commands/variable.h |  4 +--
 3 files changed, 15 insertions(+), 71 deletions(-)

diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 9a754dae3f..c2d7a5bebf 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -522,32 +522,9 @@ check_transaction_read_only(bool *newval, void **extra, GucSource source)
  * As in check_transaction_read_only, allow it if not inside a transaction.
  */
 bool
-check_XactIsoLevel(char **newval, void **extra, GucSource source)
+check_XactIsoLevel(int *newval, void **extra, GucSource source)
 {
-	int			newXactIsoLevel;
-
-	if (strcmp(*newval, "serializable") == 0)
-	{
-		newXactIsoLevel = XACT_SERIALIZABLE;
-	}
-	else if (strcmp(*newval, "repeatable read") == 0)
-	{
-		newXactIsoLevel = XACT_REPEATABLE_READ;
-	}
-	else if (strcmp(*newval, "read committed") == 0)
-	{
-		newXactIsoLevel = XACT_READ_COMMITTED;
-	}
-	else if (strcmp(*newval, "read uncommitted") == 0)
-	{
-		newXactIsoLevel = XACT_READ_UNCOMMITTED;
-	}
-	else if (strcmp(*newval, "default") == 0)
-	{
-		newXactIsoLevel = DefaultXactIsoLevel;
-	}
-	else
-		return false;
+	int			newXactIsoLevel = *newval;
 
 	if (newXactIsoLevel != XactIsoLevel && IsTransactionState())
 	{
@@ -574,39 +551,9 @@ check_XactIsoLevel(char **newval, void **extra, GucSource source)
 		}
 	}
 
-	*extra = malloc(sizeof(int));
-	if (!*extra)
-		return false;
-	*((int *) *extra) = newXactIsoLevel;
-
 	return true;
 }
 
-void
-assign_XactIsoLevel(const char *newval, void *extra)
-{
-	XactIsoLevel = *((int *) extra);
-}
-
-const char *
-show_XactIsoLevel(void)
-{
-	/* We need this because we don't want to show "default". */
-	switch (XactIsoLevel)
-	{
-		case XACT_READ_UNCOMMITTED:
-			return "read uncommitted";
-		case XACT_READ_COMMITTED:
-			return "read committed";
-		case XACT_REPEATABLE_READ:
-			return "repeatable read";
-		case XACT_SERIALIZABLE:
-			return "serializable";
-		default:
-			return "bogus";
-	}
-}
-
 /*
  * SET TRANSACTION [NOT] DEFERRABLE
  */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 14dadf514e..aeb9bf4ed5 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -504,7 +504,6 @@ static int	server_version_num;
 static char *timezone_string;
 static char *log_timezone_string;
 static char *timezone_abbreviations_string;
-static char *XactIsoLevel_string;
 static char *data_directory;
 static char *session_authorization_string;
 static int	max_function_args;
@@ -3434,17 +3433,6 @@ static struct config_string ConfigureNamesString[] =
 		check_timezone_abbreviations, assign_timezone_abbreviations, NULL
 	},
 
-	{
-		{"transaction_isolation", PGC_USERSET, CLIENT_CONN_STATEMENT,
-			gettext_noop("Sets the current transaction's isolation level."),
-			NULL,
-			GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
-		},
-		&XactIsoLevel_string,
-		"default",
-		check_XactIsoLevel, assign_XactIsoLevel, show_XactIsoLevel
-	},
-
 	{
 		{"unix_socket_group", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
 			gettext_noop("Sets the owning group of the Unix-domain socket."),
@@ -3748,6 +3736,17 @@ static struct config_enum ConfigureNamesEnum[] =
 		NULL, NULL, NULL
 	},
 
+	{
+		{"transaction_isolation", PGC_USERSET, CLIENT_CONN_STATEMENT,
+			gettext_noop("Sets the current transaction's isolation level."),
+			NULL,
+			GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+		},
+		&XactIsoLevel,
+		XACT_READ_COMMITTED, isolation_level_options,
+		check_XactIsoLevel, NULL, NULL
+	},
+
 	{
 		{"IntervalStyle", PGC_USERSET, CLIENT_CONN_LOCALE,
 			gettext_noop("Sets the display format for interval values."),
@@ -4544,7 +4543,7 @@ InitializeGUCOptions(void)
 	 * Prevent any attempt to override the transaction modes from
 	 * non-interactive sources.
 	 */
-	SetConfigOption("transaction_isolation", "default",
+	SetConfigOption("transaction_isolation", "read committed",
 					PGC_POSTMASTER, PGC_S_OVERRIDE);
 	SetConfigOption("transaction_read_only", "no",
 					PGC_POSTMASTER, PGC_S_OVERRIDE);
diff --git a/src/include/commands/variable.h b/src/include/commands/variable.h
index 4ea3b0209b..7373a3f99f 100644
--- a/src/include/commands/variable.h
+++ b/src/include/commands/variable.h
@@ -22,9 +22,7 @@ extern bool check_log_timezone(char **newval, void **extra, GucSource source);
 extern void assign_log_timezone(const char *newval, void *extra);
 extern const char *show_log_timezone(void);
 extern bool check_transaction_read_only(bool *newval, void **extra, GucSource source);
-extern bool check_XactIsoLevel(char **newval, void **extra, GucSource source);
-extern void assign_XactIsoLevel(const char *newval, void *extra);
-extern const char *show_XactIsoLevel(void);
+extern bool check_XactIsoLevel(int *newval, void **extra, GucSource source);
 extern bool check_transaction_deferrable(bool *newval, void **extra, GucSource source);
 extern bool check_random_seed(double *newval, void **extra, GucSource source);
 extern void assign_random_seed(double newval, void *extra);
-- 
2.16.2

v1-0007-Transaction-chaining.patchtext/plain; charset=UTF-8; name=v1-0007-Transaction-chaining.patch; x-mac-creator=0; x-mac-type=0Download
From 65bfb7be0aee872eb0405a77ec23ee2483745b37 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Fri, 16 Feb 2018 21:37:55 -0500
Subject: [PATCH v1 7/8] Transaction chaining

Add command variants COMMIT AND CHAIN and ROLLBACK AND CHAIN, which
start new transactions with the same transaction characteristics as the
just finished one, per SQL standard.
---
 doc/src/sgml/ref/abort.sgml                |  14 ++-
 doc/src/sgml/ref/commit.sgml               |  14 ++-
 doc/src/sgml/ref/end.sgml                  |  14 ++-
 doc/src/sgml/ref/rollback.sgml             |  14 ++-
 src/backend/access/transam/xact.c          |  74 ++++++++++++++-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/parser/gram.y                  |  19 +++-
 src/backend/tcop/utility.c                 |   4 +-
 src/include/access/xact.h                  |   4 +-
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/transactions.out | 139 +++++++++++++++++++++++++++++
 src/test/regress/sql/transactions.sql      |  49 ++++++++++
 13 files changed, 333 insertions(+), 15 deletions(-)

diff --git a/doc/src/sgml/ref/abort.sgml b/doc/src/sgml/ref/abort.sgml
index 21799d2a83..0372913365 100644
--- a/doc/src/sgml/ref/abort.sgml
+++ b/doc/src/sgml/ref/abort.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-ABORT [ WORK | TRANSACTION ]
+ABORT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -51,6 +51,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/commit.sgml b/doc/src/sgml/ref/commit.sgml
index b2e8d5d180..37c706a66f 100644
--- a/doc/src/sgml/ref/commit.sgml
+++ b/doc/src/sgml/ref/commit.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-COMMIT [ WORK | TRANSACTION ]
+COMMIT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -48,6 +48,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/end.sgml b/doc/src/sgml/ref/end.sgml
index 7523315f34..8b8f4f0dbb 100644
--- a/doc/src/sgml/ref/end.sgml
+++ b/doc/src/sgml/ref/end.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-END [ WORK | TRANSACTION ]
+END [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -50,6 +50,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/rollback.sgml b/doc/src/sgml/ref/rollback.sgml
index 3cafb848a9..3019273a47 100644
--- a/doc/src/sgml/ref/rollback.sgml
+++ b/doc/src/sgml/ref/rollback.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-ROLLBACK [ WORK | TRANSACTION ]
+ROLLBACK [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -47,6 +47,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 15edc93d91..3e468f4d38 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -189,6 +189,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;
 
@@ -220,6 +221,7 @@ static TransactionStateData TopTransactionStateData = {
 	false,						/* startedInRecovery */
 	false,						/* didLogXid */
 	0,							/* parallelModeLevel */
+	false,						/* chain */
 	NULL						/* link to parent state block */
 };
 
@@ -2738,6 +2740,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;
+
+static void
+SaveTransactionCharacteristics(void)
+{
+	save_XactIsoLevel = XactIsoLevel;
+	save_XactReadOnly = XactReadOnly;
+	save_XactDeferrable = XactDeferrable;
+}
+
+static void
+RestoreTransactionCharacteristics(void)
+{
+	XactIsoLevel = save_XactIsoLevel;
+	XactReadOnly = save_XactReadOnly;
+	XactDeferrable = save_XactDeferrable;
+}
+
+
 /*
  *	CommitTransactionCommand
  */
@@ -2746,6 +2778,9 @@ CommitTransactionCommand(void)
 {
 	TransactionState s = CurrentTransactionState;
 
+	if (s->chain)
+		SaveTransactionCharacteristics();
+
 	switch (s->blockState)
 	{
 			/*
@@ -2797,6 +2832,13 @@ CommitTransactionCommand(void)
 		case TBLOCK_END:
 			CommitTransaction();
 			s->blockState = TBLOCK_DEFAULT;
+			if (s->chain)
+			{
+				StartTransaction();
+				s->blockState = TBLOCK_INPROGRESS;
+				s->chain = false;
+				RestoreTransactionCharacteristics();
+			}
 			break;
 
 			/*
@@ -2816,6 +2858,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;
 
 			/*
@@ -2827,6 +2876,13 @@ CommitTransactionCommand(void)
 			AbortTransaction();
 			CleanupTransaction();
 			s->blockState = TBLOCK_DEFAULT;
+			if (s->chain)
+			{
+				StartTransaction();
+				s->blockState = TBLOCK_INPROGRESS;
+				s->chain = false;
+				RestoreTransactionCharacteristics();
+			}
 			break;
 
 			/*
@@ -3481,7 +3537,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)
@@ -3527,7 +3583,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;
@@ -3653,6 +3709,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;
 }
 
@@ -3663,7 +3726,7 @@ EndTransactionBlock(void)
  * As above, we don't actually do anything here except change blockState.
  */
 void
-UserAbortTransactionBlock(void)
+UserAbortTransactionBlock(bool chain)
 {
 	TransactionState s = CurrentTransactionState;
 
@@ -3761,6 +3824,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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b013539ee9..7e0a0a90d5 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3603,6 +3603,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 ba5ebdf408..8e22a97818 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1514,6 +1514,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 ecb2494226..a5e75c9cbf 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -309,6 +309,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>	opt_or_replace
 				opt_grant_grant_option opt_grant_admin_option
 				opt_nowait opt_if_exists opt_with_data
+				opt_transaction_chain
 %type <ival>	opt_nowait_or_skip
 
 %type <list>	OptRoleList AlterOptRoleList
@@ -9826,11 +9827,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
@@ -9847,25 +9849,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
@@ -9965,6 +9970,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 2d29778787..35d21c23d5 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -434,7 +434,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 						break;
 
 					case TRANS_STMT_COMMIT:
-						if (!EndTransactionBlock())
+						if (!EndTransactionBlock(stmt->chain))
 						{
 							/* report unsuccessful commit in completionTag */
 							if (completionTag)
@@ -465,7 +465,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 						break;
 
 					case TRANS_STMT_ROLLBACK:
-						UserAbortTransactionBlock();
+						UserAbortTransactionBlock(stmt->chain);
 						break;
 
 					case TRANS_STMT_SAVEPOINT:
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 87ae2cd4df..12db48c6b8 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -349,9 +349,9 @@ extern void StartTransactionCommand(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/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e3a63e9db4..391b1033d6 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2967,6 +2967,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/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index fa7fccc63b..4b4272b4e9 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -659,6 +659,145 @@ 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);
+START TRANSACTION ISOLATION LEVEL REPEATABLE READ, DEFERRABLE;
+SHOW transaction_isolation;
+ transaction_isolation 
+-----------------------
+ repeatable read
+(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_deferrable;
+ transaction_deferrable 
+------------------------
+ on
+(1 row)
+
+INSERT INTO abc VALUES ('error');
+ERROR:  invalid input syntax for 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_deferrable;
+ transaction_deferrable 
+------------------------
+ on
+(1 row)
+
+INSERT INTO abc VALUES (3);
+COMMIT;
+START TRANSACTION ISOLATION LEVEL REPEATABLE READ, DEFERRABLE;
+SHOW transaction_isolation;
+ transaction_isolation 
+-----------------------
+ repeatable read
+(1 row)
+
+SHOW transaction_deferrable;
+ transaction_deferrable 
+------------------------
+ on
+(1 row)
+
+SAVEPOINT x;
+INSERT INTO abc VALUES ('error');
+ERROR:  invalid input syntax for 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_deferrable;
+ transaction_deferrable 
+------------------------
+ on
+(1 row)
+
+INSERT INTO abc VALUES (4);
+COMMIT;
+START TRANSACTION ISOLATION LEVEL REPEATABLE READ, DEFERRABLE;
+SHOW transaction_isolation;
+ transaction_isolation 
+-----------------------
+ repeatable read
+(1 row)
+
+SHOW transaction_deferrable;
+ transaction_deferrable 
+------------------------
+ on
+(1 row)
+
+INSERT INTO abc VALUES (5);
+ROLLBACK AND CHAIN;  -- TBLOCK_ABORT_PENDING
+SHOW transaction_isolation;
+ transaction_isolation 
+-----------------------
+ repeatable read
+(1 row)
+
+SHOW transaction_deferrable;
+ transaction_deferrable 
+------------------------
+ on
+(1 row)
+
+INSERT INTO abc VALUES ('error');
+ERROR:  invalid input syntax for integer: "error"
+LINE 1: INSERT INTO abc VALUES ('error');
+                                ^
+ROLLBACK AND CHAIN;  -- TBLOCK_ABORT_END
+SHOW transaction_isolation;
+ transaction_isolation 
+-----------------------
+ repeatable read
+(1 row)
+
+SHOW transaction_deferrable;
+ transaction_deferrable 
+------------------------
+ on
+(1 row)
+
+ROLLBACK;
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+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 82661ab610..4d56fab464 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -419,6 +419,55 @@ CREATE FUNCTION create_temp_tab() RETURNS text
 DROP FUNCTION invert(x float8);
 
 
+-- Tests for AND CHAIN
+
+CREATE TABLE abc (a int);
+
+START TRANSACTION ISOLATION LEVEL REPEATABLE READ, DEFERRABLE;
+SHOW transaction_isolation;
+SHOW transaction_deferrable;
+INSERT INTO abc VALUES (1);
+INSERT INTO abc VALUES (2);
+COMMIT AND CHAIN;  -- TBLOCK_END
+SHOW transaction_isolation;
+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_deferrable;
+INSERT INTO abc VALUES (3);
+COMMIT;
+
+START TRANSACTION ISOLATION LEVEL REPEATABLE READ, DEFERRABLE;
+SHOW transaction_isolation;
+SHOW transaction_deferrable;
+SAVEPOINT x;
+INSERT INTO abc VALUES ('error');
+COMMIT AND CHAIN;  -- TBLOCK_ABORT_PENDING
+SHOW transaction_isolation;
+SHOW transaction_deferrable;
+INSERT INTO abc VALUES (4);
+COMMIT;
+
+START TRANSACTION ISOLATION LEVEL REPEATABLE READ, DEFERRABLE;
+SHOW transaction_isolation;
+SHOW transaction_deferrable;
+INSERT INTO abc VALUES (5);
+ROLLBACK AND CHAIN;  -- TBLOCK_ABORT_PENDING
+SHOW transaction_isolation;
+SHOW transaction_deferrable;
+INSERT INTO abc VALUES ('error');
+ROLLBACK AND CHAIN;  -- TBLOCK_ABORT_END
+SHOW transaction_isolation;
+SHOW transaction_deferrable;
+ROLLBACK;
+
+SELECT * FROM abc ORDER BY 1;
+
+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
-- 
2.16.2

v1-0008-Transaction-chaining-support-in-PL-pgSQL.patchtext/plain; charset=UTF-8; name=v1-0008-Transaction-chaining-support-in-PL-pgSQL.patch; x-mac-creator=0; x-mac-type=0Download
From 06affcd2fcdc9d6feb86b1754fb86728b073eede Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 28 Feb 2018 10:04:06 -0500
Subject: [PATCH v1 8/8] Transaction chaining support in PL/pgSQL

---
 doc/src/sgml/plpgsql.sgml                          |  9 +++++++
 doc/src/sgml/spi.sgml                              | 14 +++++++---
 src/backend/access/transam/xact.c                  |  4 +--
 src/backend/executor/spi.c                         | 25 +++++++++++++++--
 src/include/access/xact.h                          |  2 ++
 src/include/executor/spi.h                         |  4 +--
 src/pl/plperl/plperl.c                             |  4 +--
 .../plpgsql/src/expected/plpgsql_transaction.out   | 31 ++++++++++++++++++++++
 src/pl/plpgsql/src/pl_exec.c                       | 10 ++++---
 src/pl/plpgsql/src/pl_funcs.c                      | 10 +++++--
 src/pl/plpgsql/src/pl_gram.y                       | 18 +++++++++++--
 src/pl/plpgsql/src/pl_scanner.c                    |  2 ++
 src/pl/plpgsql/src/plpgsql.h                       |  2 ++
 src/pl/plpgsql/src/sql/plpgsql_transaction.sql     | 23 ++++++++++++++++
 src/pl/plpython/plpy_plpymodule.c                  |  4 +--
 src/pl/tcl/pltcl.c                                 |  4 +--
 16 files changed, 142 insertions(+), 24 deletions(-)

diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index c1e3c6a19d..539f5bc4b2 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -3479,6 +3479,15 @@ <title>Transaction Management</title>
 </programlisting>
    </para>
 
+   <para>
+    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
+    <command>COMMIT AND CHAIN</command> and <command>ROLLBACK AND
+    CHAIN</command> accomplish this.
+   </para>
+
    <para>
     A transaction cannot be ended inside a loop over a query result, nor
     inside a block with exception handlers.
diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml
index 0bac342322..f99a34181c 100644
--- a/doc/src/sgml/spi.sgml
+++ b/doc/src/sgml/spi.sgml
@@ -4396,7 +4396,7 @@ <title>Transaction Management</title>
 
  <refsynopsisdiv>
 <synopsis>
-void SPI_commit(void)
+void SPI_commit(bool <parameter>chain</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -4409,7 +4409,10 @@ <title>Description</title>
    command <command>COMMIT</command>.  After a transaction is committed, a new
    transaction has to be started
    using <function>SPI_start_transaction</function> before further database
-   actions can be executed.
+   actions can be executed.  If <parameter>chain</parameter> is true, then a
+   new transaction is immediately started with the same transaction
+   characteristics as the just finished one, like with the SQL command
+   <command>COMMIT AND CHAIN</command>.
   </para>
 
   <para>
@@ -4436,7 +4439,7 @@ <title>Description</title>
 
  <refsynopsisdiv>
 <synopsis>
-void SPI_rollback(void)
+void SPI_rollback(bool <parameter>chain</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -4449,7 +4452,10 @@ <title>Description</title>
    command <command>ROLLBACK</command>.  After a transaction is rolled back, a
    new transaction has to be started
    using <function>SPI_start_transaction</function> before further database
-   actions can be executed.
+   actions can be executed.  If <parameter>chain</parameter> is true, then a
+   new transaction is immediately started with the same transaction
+   characteristics as the just finished one, like with the SQL command
+   <command>COMMIT AND CHAIN</command>.
   </para>
 
   <para>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 3e468f4d38..66db22c8f9 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2753,7 +2753,7 @@ static int	save_XactIsoLevel;
 static bool	save_XactReadOnly;
 static bool	save_XactDeferrable;
 
-static void
+void
 SaveTransactionCharacteristics(void)
 {
 	save_XactIsoLevel = XactIsoLevel;
@@ -2761,7 +2761,7 @@ SaveTransactionCharacteristics(void)
 	save_XactDeferrable = XactDeferrable;
 }
 
-static void
+void
 RestoreTransactionCharacteristics(void)
 {
 	XactIsoLevel = save_XactIsoLevel;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 9fc4431b80..8bedd98b68 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -203,7 +203,7 @@ SPI_start_transaction(void)
 }
 
 void
-SPI_commit(void)
+SPI_commit(bool chain)
 {
 	MemoryContext oldcontext = CurrentMemoryContext;
 
@@ -230,14 +230,25 @@ SPI_commit(void)
 
 	if (ActiveSnapshotSet())
 		PopActiveSnapshot();
+
+	if (chain)
+		SaveTransactionCharacteristics();
+
 	CommitTransactionCommand();
+
+	if (chain)
+	{
+		StartTransactionCommand();
+		RestoreTransactionCharacteristics();
+	}
+
 	MemoryContextSwitchTo(oldcontext);
 
 	_SPI_current->internal_xact = false;
 }
 
 void
-SPI_rollback(void)
+SPI_rollback(bool chain)
 {
 	MemoryContext oldcontext = CurrentMemoryContext;
 
@@ -254,7 +265,17 @@ SPI_rollback(void)
 
 	_SPI_current->internal_xact = true;
 
+	if (chain)
+		SaveTransactionCharacteristics();
+
 	AbortCurrentTransaction();
+
+	if (chain)
+	{
+		StartTransactionCommand();
+		RestoreTransactionCharacteristics();
+	}
+
 	MemoryContextSwitchTo(oldcontext);
 
 	_SPI_current->internal_xact = false;
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 12db48c6b8..176314cae6 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -346,6 +346,8 @@ 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);
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index e5bdaecc4e..e68310e0cc 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -160,8 +160,8 @@ extern int	SPI_unregister_relation(const char *name);
 extern int	SPI_register_trigger_data(TriggerData *tdata);
 
 extern void SPI_start_transaction(void);
-extern void SPI_commit(void);
-extern void SPI_rollback(void);
+extern void SPI_commit(bool chain);
+extern void SPI_rollback(bool chain);
 
 extern void AtEOXact_SPI(bool isCommit);
 extern void AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid);
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 77c41b2821..3471800090 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -3970,7 +3970,7 @@ plperl_spi_commit(void)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("cannot commit transaction while a cursor is open")));
 
-		SPI_commit();
+		SPI_commit(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
@@ -4000,7 +4000,7 @@ plperl_spi_rollback(void)
 					(errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
 					 errmsg("cannot abort transaction while a cursor is open")));
 
-		SPI_rollback();
+		SPI_rollback(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
diff --git a/src/pl/plpgsql/src/expected/plpgsql_transaction.out b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
index 5f569dc64a..b006b58cf3 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_transaction.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
@@ -256,6 +256,37 @@ $$;
 INFO:  read committed
 INFO:  repeatable read
 INFO:  read committed
+-- transaction chain
+TRUNCATE test1;
+DO LANGUAGE plpgsql $$
+BEGIN
+    ROLLBACK;
+    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+    FOR i IN 0..5 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
+INFO:  transaction_isolation = repeatable read
+INFO:  transaction_isolation = repeatable read
+SELECT * FROM test1;
+ a | b 
+---+---
+ 0 | 
+ 2 | 
+ 4 | 
+(3 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 9a25ee9ad9..73540c9f7f 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -4538,8 +4538,9 @@ exec_stmt_commit(PLpgSQL_execstate *estate, PLpgSQL_stmt_commit *stmt)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("committing inside a cursor loop is not supported")));
 
-	SPI_commit();
-	SPI_start_transaction();
+	SPI_commit(stmt->chain);
+	if (!stmt->chain)
+		SPI_start_transaction();
 
 	estate->simple_eval_estate = NULL;
 	plpgsql_create_econtext(estate);
@@ -4564,8 +4565,9 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt)
 				(errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
 				 errmsg("cannot abort transaction inside a cursor loop")));
 
-	SPI_rollback();
-	SPI_start_transaction();
+	SPI_rollback(stmt->chain);
+	if (!stmt->chain)
+		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 9acee818cc..37859c08c0 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -1295,14 +1295,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 4734c80766..ad69f2391e 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -218,6 +218,8 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %type <ival>	opt_scrollable
 %type <fetch>	opt_fetch_direction
 
+%type <ival>	opt_transaction_chain
+
 %type <keyword>	unreserved_keyword
 
 
@@ -251,12 +253,14 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_ABSOLUTE
 %token <keyword>	K_ALIAS
 %token <keyword>	K_ALL
+%token <keyword>	K_AND
 %token <keyword>	K_ARRAY
 %token <keyword>	K_ASSERT
 %token <keyword>	K_BACKWARD
 %token <keyword>	K_BEGIN
 %token <keyword>	K_BY
 %token <keyword>	K_CASE
+%token <keyword>	K_CHAIN
 %token <keyword>	K_CLOSE
 %token <keyword>	K_COLLATE
 %token <keyword>	K_COLUMN
@@ -2147,30 +2151,38 @@ stmt_null		: K_NULL ';'
 					}
 				;
 
-stmt_commit		: K_COMMIT ';'
+stmt_commit		: K_COMMIT opt_transaction_chain ';'
 					{
 						PLpgSQL_stmt_commit *new;
 
 						new = palloc(sizeof(PLpgSQL_stmt_commit));
 						new->cmd_type = PLPGSQL_STMT_COMMIT;
 						new->lineno = plpgsql_location_to_lineno(@1);
+						new->chain = $2;
 
 						$$ = (PLpgSQL_stmt *)new;
 					}
 				;
 
-stmt_rollback	: K_ROLLBACK ';'
+stmt_rollback	: K_ROLLBACK opt_transaction_chain ';'
 					{
 						PLpgSQL_stmt_rollback *new;
 
 						new = palloc(sizeof(PLpgSQL_stmt_rollback));
 						new->cmd_type = PLPGSQL_STMT_ROLLBACK;
 						new->lineno = plpgsql_location_to_lineno(@1);
+						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;
@@ -2425,9 +2437,11 @@ any_identifier	: T_WORD
 unreserved_keyword	:
 				K_ABSOLUTE
 				| K_ALIAS
+				| K_AND
 				| K_ARRAY
 				| K_ASSERT
 				| K_BACKWARD
+				| K_CHAIN
 				| K_CLOSE
 				| K_COLLATE
 				| K_COLUMN
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index ed8933f69a..a2cf56bed0 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -99,9 +99,11 @@ static const int num_reserved_keywords = lengthof(reserved_keywords);
 static const ScanKeyword unreserved_keywords[] = {
 	PG_KEYWORD("absolute", K_ABSOLUTE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("alias", K_ALIAS, UNRESERVED_KEYWORD)
+	PG_KEYWORD("and", K_AND, UNRESERVED_KEYWORD)
 	PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
 	PG_KEYWORD("assert", K_ASSERT, UNRESERVED_KEYWORD)
 	PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
+	PG_KEYWORD("chain", K_CHAIN, UNRESERVED_KEYWORD)
 	PG_KEYWORD("close", K_CLOSE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("collate", K_COLLATE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("column", K_COLUMN, UNRESERVED_KEYWORD)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index bece089529..6f43e1d4f0 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -516,6 +516,7 @@ typedef struct PLpgSQL_stmt_commit
 {
 	PLpgSQL_stmt_type cmd_type;
 	int			lineno;
+	bool		chain;
 } PLpgSQL_stmt_commit;
 
 /*
@@ -525,6 +526,7 @@ typedef struct PLpgSQL_stmt_rollback
 {
 	PLpgSQL_stmt_type cmd_type;
 	int			lineno;
+	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 d2a2efd661..3a8f4629da 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
@@ -229,6 +229,29 @@ CREATE TABLE test2 (x int);
 $$;
 
 
+-- transaction chain
+
+TRUNCATE test1;
+
+DO LANGUAGE plpgsql $$
+BEGIN
+    ROLLBACK;
+    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+    FOR i IN 0..5 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/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c
index 3d7dd13f0c..88f103275c 100644
--- a/src/pl/plpython/plpy_plpymodule.c
+++ b/src/pl/plpython/plpy_plpymodule.c
@@ -599,7 +599,7 @@ PLy_commit(PyObject *self, PyObject *args)
 			   (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				errmsg("cannot commit transaction while a cursor is open")));
 
-	SPI_commit();
+	SPI_commit(false);
 	SPI_start_transaction();
 
 	/* was cleared at transaction end, reset pointer */
@@ -618,7 +618,7 @@ PLy_rollback(PyObject *self, PyObject *args)
 			   (errcode(ERRCODE_INVALID_TRANSACTION_TERMINATION),
 				errmsg("cannot abort transaction while a cursor is open")));
 
-	SPI_rollback();
+	SPI_rollback(false);
 	SPI_start_transaction();
 
 	/* was cleared at transaction end, reset pointer */
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 5df4dfdf55..e548450808 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -2962,7 +2962,7 @@ pltcl_commit(ClientData cdata, Tcl_Interp *interp,
 
 	PG_TRY();
 	{
-		SPI_commit();
+		SPI_commit(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
@@ -3002,7 +3002,7 @@ pltcl_rollback(ClientData cdata, Tcl_Interp *interp,
 
 	PG_TRY();
 	{
-		SPI_rollback();
+		SPI_rollback(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
-- 
2.16.2

#2Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#1)
Re: chained transactions

Hi,

On 2018-02-28 22:35:52 -0500, Peter Eisentraut wrote:

This feature is meant to help manage transaction isolation in
procedures.

This is a major new feature, submitted the evening before the last CF
for v11 starts. Therefore I think it should just be moved to the next
fest, it doesn't seems realistic to attempt to get this into v11.

Greetings,

Andres Freund

#3Simon Riggs
simon@2ndquadrant.com
In reply to: Andres Freund (#2)
Re: chained transactions

On 2 March 2018 at 07:18, Andres Freund <andres@anarazel.de> wrote:

Hi,

On 2018-02-28 22:35:52 -0500, Peter Eisentraut wrote:

This feature is meant to help manage transaction isolation in
procedures.

This is a major new feature, submitted the evening before the last CF
for v11 starts. Therefore I think it should just be moved to the next
fest, it doesn't seems realistic to attempt to get this into v11.

Looks like a useful patch that adds fairly minor syntax that follows
the SQL Standard.

It introduces no new internal infrastructure, so I can't call this a
major feature.

--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#4Andres Freund
andres@anarazel.de
In reply to: Simon Riggs (#3)
Re: chained transactions

On 2018-03-05 09:21:33 +0000, Simon Riggs wrote:

On 2 March 2018 at 07:18, Andres Freund <andres@anarazel.de> wrote:

Hi,

On 2018-02-28 22:35:52 -0500, Peter Eisentraut wrote:

This feature is meant to help manage transaction isolation in
procedures.

This is a major new feature, submitted the evening before the last CF
for v11 starts. Therefore I think it should just be moved to the next
fest, it doesn't seems realistic to attempt to get this into v11.

Looks like a useful patch that adds fairly minor syntax that follows
the SQL Standard.

It introduces no new internal infrastructure, so I can't call this a
major feature.

You can avoid calling it new infrastructure, but it certainly modifies
new one. And it adds quite some new user interface, which certainly make
s it important to get it right.

doc/src/sgml/plpgsql.sgml | 9
doc/src/sgml/ref/abort.sgml | 14 +
doc/src/sgml/ref/commit.sgml | 14 +
doc/src/sgml/ref/end.sgml | 14 +
doc/src/sgml/ref/rollback.sgml | 14 +
doc/src/sgml/spi.sgml | 14 -
src/backend/access/transam/xact.c | 210 +++++++++++---------
src/backend/commands/cluster.c | 2
src/backend/commands/dbcommands.c | 2
src/backend/commands/discard.c | 2
src/backend/commands/portalcmds.c | 2
src/backend/commands/subscriptioncmds.c | 4
src/backend/commands/typecmds.c | 2
src/backend/commands/vacuum.c | 4
src/backend/commands/variable.c | 57 -----
src/backend/executor/spi.c | 25 ++
src/backend/nodes/copyfuncs.c | 2
src/backend/nodes/equalfuncs.c | 2
src/backend/parser/gram.y | 34 +--
src/backend/replication/walsender.c | 6
src/backend/tcop/utility.c | 58 ++---
src/backend/utils/misc/guc.c | 35 +--
src/backend/utils/time/snapmgr.c | 2
src/bin/psql/common.c | 2
src/include/access/xact.h | 18 -
src/include/commands/variable.h | 4
src/include/executor/spi.h | 4
src/include/nodes/parsenodes.h | 4
src/pl/plperl/plperl.c | 4
src/pl/plpgsql/src/expected/plpgsql_transaction.out | 31 ++
src/pl/plpgsql/src/pl_exec.c | 10
src/pl/plpgsql/src/pl_funcs.c | 10
src/pl/plpgsql/src/pl_gram.y | 18 +
src/pl/plpgsql/src/pl_scanner.c | 2
src/pl/plpgsql/src/plpgsql.h | 2
src/pl/plpgsql/src/sql/plpgsql_transaction.sql | 23 ++
src/pl/plpython/plpy_plpymodule.c | 4
src/pl/tcl/pltcl.c | 4
src/test/regress/expected/transactions.out | 141 +++++++++++++
src/test/regress/sql/transactions.sql | 49 ++++
40 files changed, 596 insertions(+), 262 deletions(-)

Greetings,

Andres Freund

#5Alvaro Herrera
alvherre@alvh.no-ip.org
In reply to: Peter Eisentraut (#1)
Re: chained transactions

Peter Eisentraut wrote:

Subject: [PATCH v1 1/8] Update function comments

After a6542a4b6870a019cd952d055d2e7af2da2fe102, some function comments
were misplaced. Fix that.

Note typo WarnNoTranactionChain in one comment. The patch leaves
CheckTransactionChain with no comment whatsoever; maybe add four words
to indicate that it's implementation for the other two? The phrase
"Thus this is an inverse for PreventTransactionChain" seems to apply to
both functions, maybe it should be in plural? Or perhaps "thus this
behavior is the inverse of"?

Subject: [PATCH v1 2/8] Rename TransactionChain functions

We call this thing a "transaction block" everywhere except in a few
functions, where it is mysteriously called a "transaction chain". In
the SQL standard, a transaction chain is something different. So rename
these functions to match the common terminology.

Seems reasonable to me; doesn't change any functionality.

Subject: [PATCH v1 3/8] Simplify parse representation of savepoint commands

Instead of embedding the savepoint name in a list and then requiring
complex code to unpack it, just add another struct field to store it
directly.

Obvious in hindsight.

Subject: [PATCH v1 4/8] Improve savepoint error messages

Include the savepoint name in the error message and rephrase it a bit to
match common style.

A no-brainer. It's a bit disquieting that this changes so few test
results ...

Subject: [PATCH v1 5/8] Change transaction state debug strings to match enum
symbols

In some cases, these were different for no apparent reason, making
debugging unnecessarily mysterious.

I guess I was trying to save bytes (573a71a5da70) ... looks OK to me.

From 517bc6d9fefdee9135857d9562f644f2984ace32 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Sun, 18 Feb 2018 09:33:53 -0500
Subject: [PATCH v1 6/8] Turn transaction_isolation into GUC enum

XXX no idea why it was done the way it was, but this seems much simpler
and apparently doesn't change any functionality.

Enums are recent -- 52a8d4f8f7e2, only 10 years old, and the commit
didn't convert all cases, leaving some for later. Funnily enough,
default_transaction_isolation was changed afterwards by ad6bf716baa7 but
not this one ... I guess "later" is now upon us for it.

No opinions (yet?) on the rest of it.

--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#6Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Alvaro Herrera (#5)
Re: chained transactions

On 3/15/18 12:39, Alvaro Herrera wrote:

Subject: [PATCH v1 1/8] Update function comments
Subject: [PATCH v1 2/8] Rename TransactionChain functions
Subject: [PATCH v1 3/8] Simplify parse representation of savepoint commands
Subject: [PATCH v1 4/8] Improve savepoint error messages
Subject: [PATCH v1 5/8] Change transaction state debug strings to match enum
symbols

I have committed these with your suggested edits.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#7Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Alvaro Herrera (#5)
Re: chained transactions

On 15/03/18 18:39, Alvaro Herrera wrote:

From 517bc6d9fefdee9135857d9562f644f2984ace32 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut<peter_e@gmx.net>
Date: Sun, 18 Feb 2018 09:33:53 -0500
Subject: [PATCH v1 6/8] Turn transaction_isolation into GUC enum

XXX no idea why it was done the way it was, but this seems much simpler
and apparently doesn't change any functionality.

Enums are recent -- 52a8d4f8f7e2, only 10 years old, and the commit
didn't convert all cases, leaving some for later. Funnily enough,
default_transaction_isolation was changed afterwards by ad6bf716baa7 but
not this one ... I guess "later" is now upon us for it.

With this patch, this stops working:

set transaction_isolation='default';

Other than that, +1 on this patch. I haven't looked closely at the rest
of the patches yet.

- Heikki

#8Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Heikki Linnakangas (#7)
Re: chained transactions

On 4/5/18 04:35, Heikki Linnakangas wrote:

With this patch, this stops working:

set transaction_isolation='default';

But why is this supposed to work? This form is not documented anywhere.
You can use RESET or SET TO DEFAULT.

I suspect this is some artifact in the implementation that this patch is
proposing to get rid of.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#9Heikki Linnakangas
hlinnaka@iki.fi
In reply to: Peter Eisentraut (#1)
Re: chained transactions

On 01/03/18 05:35, Peter Eisentraut wrote:

The SQL standard offers the "chained transactions" feature to address
this. The new command variants COMMIT AND CHAIN and ROLLBACK AND CHAIN
immediately start a new transaction with the characteristics (isolation
level, read/write, deferrable) of the previous one. So code that has
particular requirements regard transaction isolation and such can use
this to simplify code management.

Oh, is that all it does? That's disappointing, because that's a lot less
powerful than how I understand chained transactions. And at the same
time relieving, because that's a lot simpler to implement :-).

In Gray & Reuter's classic book, Transaction Processing, they describe
chained transactions so that you also keep locks and cursors.
Unfortunately I don't have a copy at hand, but that's my recollection,
at least. I guess the SQL standard committee had a different idea.

- Heikki

#10Michael Paquier
michael@paquier.xyz
In reply to: Heikki Linnakangas (#9)
Re: chained transactions

On Tue, Jul 17, 2018 at 08:21:20PM +0300, Heikki Linnakangas wrote:

Oh, is that all it does? That's disappointing, because that's a lot less
powerful than how I understand chained transactions. And at the same time
relieving, because that's a lot simpler to implement :-).

In Gray & Reuter's classic book, Transaction Processing, they describe
chained transactions so that you also keep locks and cursors. Unfortunately
I don't have a copy at hand, but that's my recollection, at least. I guess
the SQL standard committee had a different idea.

The patch set does not apply anymore, so this patch is moved to next CF,
waiting on author.
--
Michael

#11Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Michael Paquier (#10)
2 attachment(s)
Re: chained transactions

On 02/10/2018 07:38, Michael Paquier wrote:

The patch set does not apply anymore, so this patch is moved to next CF,
waiting on author.

Attached is a rebased patch set. No functionality changes.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v2-0001-Transaction-chaining.patchtext/plain; charset=UTF-8; name=v2-0001-Transaction-chaining.patch; x-mac-creator=0; x-mac-type=0Download
From e964e279827635feb95df7d6518a6ba9fa55a4df Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Fri, 16 Feb 2018 21:37:55 -0500
Subject: [PATCH v2 1/2] Transaction chaining

Add command variants COMMIT AND CHAIN and ROLLBACK AND CHAIN, which
start new transactions with the same transaction characteristics as the
just finished one, per SQL standard.
---
 doc/src/sgml/ref/abort.sgml                |  14 ++-
 doc/src/sgml/ref/commit.sgml               |  14 ++-
 doc/src/sgml/ref/end.sgml                  |  14 ++-
 doc/src/sgml/ref/rollback.sgml             |  14 ++-
 src/backend/access/transam/xact.c          |  73 ++++++++++-
 src/backend/catalog/sql_features.txt       |   2 +-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/parser/gram.y                  |  19 ++-
 src/backend/tcop/utility.c                 |   4 +-
 src/include/access/xact.h                  |   4 +-
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/transactions.out | 139 +++++++++++++++++++++
 src/test/regress/sql/transactions.sql      |  49 ++++++++
 14 files changed, 333 insertions(+), 16 deletions(-)

diff --git a/doc/src/sgml/ref/abort.sgml b/doc/src/sgml/ref/abort.sgml
index 21799d2a83..0372913365 100644
--- a/doc/src/sgml/ref/abort.sgml
+++ b/doc/src/sgml/ref/abort.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-ABORT [ WORK | TRANSACTION ]
+ABORT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -51,6 +51,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/commit.sgml b/doc/src/sgml/ref/commit.sgml
index b2e8d5d180..37c706a66f 100644
--- a/doc/src/sgml/ref/commit.sgml
+++ b/doc/src/sgml/ref/commit.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-COMMIT [ WORK | TRANSACTION ]
+COMMIT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -48,6 +48,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/end.sgml b/doc/src/sgml/ref/end.sgml
index 7523315f34..8b8f4f0dbb 100644
--- a/doc/src/sgml/ref/end.sgml
+++ b/doc/src/sgml/ref/end.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-END [ WORK | TRANSACTION ]
+END [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -50,6 +50,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/rollback.sgml b/doc/src/sgml/ref/rollback.sgml
index 3cafb848a9..3019273a47 100644
--- a/doc/src/sgml/ref/rollback.sgml
+++ b/doc/src/sgml/ref/rollback.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-ROLLBACK [ WORK | TRANSACTION ]
+ROLLBACK [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -47,6 +47,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index e3f668bf6a..58565f1f72 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -188,6 +188,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;
 
@@ -2746,6 +2747,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;
+
+static void
+SaveTransactionCharacteristics(void)
+{
+	save_XactIsoLevel = XactIsoLevel;
+	save_XactReadOnly = XactReadOnly;
+	save_XactDeferrable = XactDeferrable;
+}
+
+static void
+RestoreTransactionCharacteristics(void)
+{
+	XactIsoLevel = save_XactIsoLevel;
+	XactReadOnly = save_XactReadOnly;
+	XactDeferrable = save_XactDeferrable;
+}
+
+
 /*
  *	CommitTransactionCommand
  */
@@ -2754,6 +2785,9 @@ CommitTransactionCommand(void)
 {
 	TransactionState s = CurrentTransactionState;
 
+	if (s->chain)
+		SaveTransactionCharacteristics();
+
 	switch (s->blockState)
 	{
 			/*
@@ -2805,6 +2839,13 @@ CommitTransactionCommand(void)
 		case TBLOCK_END:
 			CommitTransaction();
 			s->blockState = TBLOCK_DEFAULT;
+			if (s->chain)
+			{
+				StartTransaction();
+				s->blockState = TBLOCK_INPROGRESS;
+				s->chain = false;
+				RestoreTransactionCharacteristics();
+			}
 			break;
 
 			/*
@@ -2824,6 +2865,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;
 
 			/*
@@ -2835,6 +2883,13 @@ CommitTransactionCommand(void)
 			AbortTransaction();
 			CleanupTransaction();
 			s->blockState = TBLOCK_DEFAULT;
+			if (s->chain)
+			{
+				StartTransaction();
+				s->blockState = TBLOCK_INPROGRESS;
+				s->chain = false;
+				RestoreTransactionCharacteristics();
+			}
 			break;
 
 			/*
@@ -3492,7 +3547,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)
@@ -3538,7 +3593,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;
@@ -3664,6 +3719,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;
 }
 
@@ -3674,7 +3736,7 @@ EndTransactionBlock(void)
  * As above, we don't actually do anything here except change blockState.
  */
 void
-UserAbortTransactionBlock(void)
+UserAbortTransactionBlock(bool chain)
 {
 	TransactionState s = CurrentTransactionState;
 
@@ -3772,6 +3834,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 aeb262a5b0..49df7e08c0 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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index e47641d572..f78aa375fc 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3655,6 +3655,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 3bb91c9595..8c6162c67d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1506,6 +1506,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 6d23bfb0b3..bc42b3400f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>	opt_or_replace
 				opt_grant_grant_option opt_grant_admin_option
 				opt_nowait opt_if_exists opt_with_data
+				opt_transaction_chain
 %type <ival>	opt_nowait_or_skip
 
 %type <list>	OptRoleList AlterOptRoleList
@@ -9832,11 +9833,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
@@ -9853,25 +9855,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
@@ -9971,6 +9976,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 b5804f64ad..4c3ac2689b 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -437,7 +437,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 						break;
 
 					case TRANS_STMT_COMMIT:
-						if (!EndTransactionBlock())
+						if (!EndTransactionBlock(stmt->chain))
 						{
 							/* report unsuccessful commit in completionTag */
 							if (completionTag)
@@ -468,7 +468,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 						break;
 
 					case TRANS_STMT_ROLLBACK:
-						UserAbortTransactionBlock();
+						UserAbortTransactionBlock(stmt->chain);
 						break;
 
 					case TRANS_STMT_SAVEPOINT:
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 689c57c592..c757146e4d 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -372,9 +372,9 @@ extern void StartTransactionCommand(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/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index aa4a0dba2a..da34aeb27f 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2989,6 +2989,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/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 69e176c525..0167520333 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -659,6 +659,145 @@ 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);
+START TRANSACTION ISOLATION LEVEL REPEATABLE READ, DEFERRABLE;
+SHOW transaction_isolation;
+ transaction_isolation 
+-----------------------
+ repeatable read
+(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_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_deferrable;
+ transaction_deferrable 
+------------------------
+ on
+(1 row)
+
+INSERT INTO abc VALUES (3);
+COMMIT;
+START TRANSACTION ISOLATION LEVEL REPEATABLE READ, DEFERRABLE;
+SHOW transaction_isolation;
+ transaction_isolation 
+-----------------------
+ repeatable read
+(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_deferrable;
+ transaction_deferrable 
+------------------------
+ on
+(1 row)
+
+INSERT INTO abc VALUES (4);
+COMMIT;
+START TRANSACTION ISOLATION LEVEL REPEATABLE READ, DEFERRABLE;
+SHOW transaction_isolation;
+ transaction_isolation 
+-----------------------
+ repeatable read
+(1 row)
+
+SHOW transaction_deferrable;
+ transaction_deferrable 
+------------------------
+ on
+(1 row)
+
+INSERT INTO abc VALUES (5);
+ROLLBACK AND CHAIN;  -- TBLOCK_ABORT_PENDING
+SHOW transaction_isolation;
+ transaction_isolation 
+-----------------------
+ repeatable read
+(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');
+                                ^
+ROLLBACK AND CHAIN;  -- TBLOCK_ABORT_END
+SHOW transaction_isolation;
+ transaction_isolation 
+-----------------------
+ repeatable read
+(1 row)
+
+SHOW transaction_deferrable;
+ transaction_deferrable 
+------------------------
+ on
+(1 row)
+
+ROLLBACK;
+SELECT * FROM abc ORDER BY 1;
+ a 
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+
+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 2e3739fd6c..7cd89f2477 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -419,6 +419,55 @@ CREATE FUNCTION create_temp_tab() RETURNS text
 DROP FUNCTION invert(x float8);
 
 
+-- Tests for AND CHAIN
+
+CREATE TABLE abc (a int);
+
+START TRANSACTION ISOLATION LEVEL REPEATABLE READ, DEFERRABLE;
+SHOW transaction_isolation;
+SHOW transaction_deferrable;
+INSERT INTO abc VALUES (1);
+INSERT INTO abc VALUES (2);
+COMMIT AND CHAIN;  -- TBLOCK_END
+SHOW transaction_isolation;
+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_deferrable;
+INSERT INTO abc VALUES (3);
+COMMIT;
+
+START TRANSACTION ISOLATION LEVEL REPEATABLE READ, DEFERRABLE;
+SHOW transaction_isolation;
+SHOW transaction_deferrable;
+SAVEPOINT x;
+INSERT INTO abc VALUES ('error');
+COMMIT AND CHAIN;  -- TBLOCK_ABORT_PENDING
+SHOW transaction_isolation;
+SHOW transaction_deferrable;
+INSERT INTO abc VALUES (4);
+COMMIT;
+
+START TRANSACTION ISOLATION LEVEL REPEATABLE READ, DEFERRABLE;
+SHOW transaction_isolation;
+SHOW transaction_deferrable;
+INSERT INTO abc VALUES (5);
+ROLLBACK AND CHAIN;  -- TBLOCK_ABORT_PENDING
+SHOW transaction_isolation;
+SHOW transaction_deferrable;
+INSERT INTO abc VALUES ('error');
+ROLLBACK AND CHAIN;  -- TBLOCK_ABORT_END
+SHOW transaction_isolation;
+SHOW transaction_deferrable;
+ROLLBACK;
+
+SELECT * FROM abc ORDER BY 1;
+
+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

base-commit: eee01d606eb40f760799320e75d45c84022353d8
-- 
2.19.0

v2-0002-Transaction-chaining-support-in-PL-pgSQL.patchtext/plain; charset=UTF-8; name=v2-0002-Transaction-chaining-support-in-PL-pgSQL.patch; x-mac-creator=0; x-mac-type=0Download
From 647de712265239fa03cab409bb239a226204dc20 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 28 Feb 2018 10:04:06 -0500
Subject: [PATCH v2 2/2] Transaction chaining support in PL/pgSQL

---
 doc/src/sgml/plpgsql.sgml                     |  9 ++++++
 doc/src/sgml/spi.sgml                         | 14 ++++++---
 src/backend/access/transam/xact.c             |  4 +--
 src/backend/executor/spi.c                    | 24 ++++++++++++--
 src/include/access/xact.h                     |  2 ++
 src/include/executor/spi.h                    |  4 +--
 src/pl/plperl/plperl.c                        |  4 +--
 .../src/expected/plpgsql_transaction.out      | 31 +++++++++++++++++++
 src/pl/plpgsql/src/pl_exec.c                  | 10 +++---
 src/pl/plpgsql/src/pl_funcs.c                 | 10 ++++--
 src/pl/plpgsql/src/pl_gram.y                  | 18 +++++++++--
 src/pl/plpgsql/src/pl_scanner.c               |  2 ++
 src/pl/plpgsql/src/plpgsql.h                  |  2 ++
 .../plpgsql/src/sql/plpgsql_transaction.sql   | 23 ++++++++++++++
 src/pl/plpython/plpy_plpymodule.c             |  4 +--
 src/pl/tcl/pltcl.c                            |  4 +--
 16 files changed, 141 insertions(+), 24 deletions(-)

diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 4344ceadbe..321953fd46 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -3478,6 +3478,15 @@ <title>Transaction Management</title>
 </programlisting>
    </para>
 
+   <para>
+    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
+    <command>COMMIT AND CHAIN</command> and <command>ROLLBACK AND
+    CHAIN</command> accomplish this.
+   </para>
+
    <para>
     Transaction control is only possible in <command>CALL</command> or
     <command>DO</command> invocations from the top level or nested
diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml
index 9db11d22fb..9b4e63e352 100644
--- a/doc/src/sgml/spi.sgml
+++ b/doc/src/sgml/spi.sgml
@@ -4389,7 +4389,7 @@ <title>Transaction Management</title>
 
  <refsynopsisdiv>
 <synopsis>
-void SPI_commit(void)
+void SPI_commit(bool <parameter>chain</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -4402,7 +4402,10 @@ <title>Description</title>
    command <command>COMMIT</command>.  After a transaction is committed, a new
    transaction has to be started
    using <function>SPI_start_transaction</function> before further database
-   actions can be executed.
+   actions can be executed.  If <parameter>chain</parameter> is true, then a
+   new transaction is immediately started with the same transaction
+   characteristics as the just finished one, like with the SQL command
+   <command>COMMIT AND CHAIN</command>.
   </para>
 
   <para>
@@ -4429,7 +4432,7 @@ <title>Description</title>
 
  <refsynopsisdiv>
 <synopsis>
-void SPI_rollback(void)
+void SPI_rollback(bool <parameter>chain</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -4442,7 +4445,10 @@ <title>Description</title>
    command <command>ROLLBACK</command>.  After a transaction is rolled back, a
    new transaction has to be started
    using <function>SPI_start_transaction</function> before further database
-   actions can be executed.
+   actions can be executed.  If <parameter>chain</parameter> is true, then a
+   new transaction is immediately started with the same transaction
+   characteristics as the just finished one, like with the SQL command
+   <command>COMMIT AND CHAIN</command>.
   </para>
 
   <para>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 58565f1f72..b4aa8ab6e0 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2760,7 +2760,7 @@ static int	save_XactIsoLevel;
 static bool	save_XactReadOnly;
 static bool	save_XactDeferrable;
 
-static void
+void
 SaveTransactionCharacteristics(void)
 {
 	save_XactIsoLevel = XactIsoLevel;
@@ -2768,7 +2768,7 @@ SaveTransactionCharacteristics(void)
 	save_XactDeferrable = XactDeferrable;
 }
 
-static void
+void
 RestoreTransactionCharacteristics(void)
 {
 	XactIsoLevel = save_XactIsoLevel;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 11ca800e4c..444a8765f7 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -223,7 +223,7 @@ SPI_start_transaction(void)
 }
 
 void
-SPI_commit(void)
+SPI_commit(bool chain)
 {
 	MemoryContext oldcontext = CurrentMemoryContext;
 
@@ -255,14 +255,24 @@ 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_rollback(bool chain)
 {
 	MemoryContext oldcontext = CurrentMemoryContext;
 
@@ -279,7 +289,17 @@ SPI_rollback(void)
 
 	_SPI_current->internal_xact = true;
 
+	if (chain)
+		SaveTransactionCharacteristics();
+
 	AbortCurrentTransaction();
+
+	if (chain)
+	{
+		StartTransactionCommand();
+		RestoreTransactionCharacteristics();
+	}
+
 	MemoryContextSwitchTo(oldcontext);
 
 	_SPI_current->internal_xact = false;
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index c757146e4d..2abdbece6c 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -369,6 +369,8 @@ 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);
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index 143a89a16c..a391e92405 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -160,8 +160,8 @@ extern int	SPI_unregister_relation(const char *name);
 extern int	SPI_register_trigger_data(TriggerData *tdata);
 
 extern void SPI_start_transaction(void);
-extern void SPI_commit(void);
-extern void SPI_rollback(void);
+extern void SPI_commit(bool chain);
+extern void SPI_rollback(bool chain);
 
 extern void SPICleanup(void);
 extern void AtEOXact_SPI(bool isCommit);
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 4cfc506253..003fd594c6 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -3968,7 +3968,7 @@ plperl_spi_commit(void)
 	{
 		HoldPinnedPortals();
 
-		SPI_commit();
+		SPI_commit(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
@@ -3995,7 +3995,7 @@ plperl_spi_rollback(void)
 	{
 		HoldPinnedPortals();
 
-		SPI_rollback();
+		SPI_rollback(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
diff --git a/src/pl/plpgsql/src/expected/plpgsql_transaction.out b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
index 77a83adab5..5441e997a6 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_transaction.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
@@ -493,6 +493,37 @@ CALL transaction_test10b(10);
  9
 (1 row)
 
+-- transaction chain
+TRUNCATE test1;
+DO LANGUAGE plpgsql $$
+BEGIN
+    ROLLBACK;
+    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+    FOR i IN 0..5 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
+INFO:  transaction_isolation = repeatable read
+INFO:  transaction_isolation = repeatable read
+SELECT * FROM test1;
+ a | b 
+---+---
+ 0 | 
+ 2 | 
+ 4 | 
+(3 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 45526383f2..68567afb03 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -4753,8 +4753,9 @@ exec_stmt_commit(PLpgSQL_execstate *estate, PLpgSQL_stmt_commit *stmt)
 {
 	HoldPinnedPortals();
 
-	SPI_commit();
-	SPI_start_transaction();
+	SPI_commit(stmt->chain);
+	if (!stmt->chain)
+		SPI_start_transaction();
 
 	estate->simple_eval_estate = NULL;
 	plpgsql_create_econtext(estate);
@@ -4772,8 +4773,9 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt)
 {
 	HoldPinnedPortals();
 
-	SPI_rollback();
-	SPI_start_transaction();
+	SPI_rollback(stmt->chain);
+	if (!stmt->chain)
+		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 b93f866223..a2e9a8dcb8 100644
--- a/src/pl/plpgsql/src/pl_funcs.c
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -1320,14 +1320,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 68e399f9cf..498c17c0fb 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 <ival>	opt_scrollable
 %type <fetch>	opt_fetch_direction
 
+%type <ival>	opt_transaction_chain
+
 %type <keyword>	unreserved_keyword
 
 
@@ -252,6 +254,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_ABSOLUTE
 %token <keyword>	K_ALIAS
 %token <keyword>	K_ALL
+%token <keyword>	K_AND
 %token <keyword>	K_ARRAY
 %token <keyword>	K_ASSERT
 %token <keyword>	K_BACKWARD
@@ -259,6 +262,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_BY
 %token <keyword>	K_CALL
 %token <keyword>	K_CASE
+%token <keyword>	K_CHAIN
 %token <keyword>	K_CLOSE
 %token <keyword>	K_COLLATE
 %token <keyword>	K_COLUMN
@@ -2182,30 +2186,38 @@ stmt_null		: K_NULL ';'
 					}
 				;
 
-stmt_commit		: K_COMMIT ';'
+stmt_commit		: K_COMMIT opt_transaction_chain ';'
 					{
 						PLpgSQL_stmt_commit *new;
 
 						new = palloc(sizeof(PLpgSQL_stmt_commit));
 						new->cmd_type = PLPGSQL_STMT_COMMIT;
 						new->lineno = plpgsql_location_to_lineno(@1);
+						new->chain = $2;
 
 						$$ = (PLpgSQL_stmt *)new;
 					}
 				;
 
-stmt_rollback	: K_ROLLBACK ';'
+stmt_rollback	: K_ROLLBACK opt_transaction_chain ';'
 					{
 						PLpgSQL_stmt_rollback *new;
 
 						new = palloc(sizeof(PLpgSQL_stmt_rollback));
 						new->cmd_type = PLPGSQL_STMT_ROLLBACK;
 						new->lineno = plpgsql_location_to_lineno(@1);
+						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;
@@ -2460,10 +2472,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_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index fc4ba3054a..0be191f270 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -99,10 +99,12 @@ static const int num_reserved_keywords = lengthof(reserved_keywords);
 static const ScanKeyword unreserved_keywords[] = {
 	PG_KEYWORD("absolute", K_ABSOLUTE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("alias", K_ALIAS, UNRESERVED_KEYWORD)
+	PG_KEYWORD("and", K_AND, UNRESERVED_KEYWORD)
 	PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
 	PG_KEYWORD("assert", K_ASSERT, UNRESERVED_KEYWORD)
 	PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
 	PG_KEYWORD("call", K_CALL, UNRESERVED_KEYWORD)
+	PG_KEYWORD("chain", K_CHAIN, UNRESERVED_KEYWORD)
 	PG_KEYWORD("close", K_CLOSE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("collate", K_COLLATE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("column", K_COLUMN, UNRESERVED_KEYWORD)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index f6c35a5049..fad2ad0d87 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -534,6 +534,7 @@ typedef struct PLpgSQL_stmt_commit
 {
 	PLpgSQL_stmt_type cmd_type;
 	int			lineno;
+	bool		chain;
 } PLpgSQL_stmt_commit;
 
 /*
@@ -543,6 +544,7 @@ typedef struct PLpgSQL_stmt_rollback
 {
 	PLpgSQL_stmt_type cmd_type;
 	int			lineno;
+	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 0ed9ab873a..e6a2d89ec7 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
@@ -412,6 +412,29 @@ CREATE PROCEDURE transaction_test10b(INOUT x int)
 CALL transaction_test10b(10);
 
 
+-- transaction chain
+
+TRUNCATE test1;
+
+DO LANGUAGE plpgsql $$
+BEGIN
+    ROLLBACK;
+    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+    FOR i IN 0..5 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/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c
index 23e49e4b75..729f402ea1 100644
--- a/src/pl/plpython/plpy_plpymodule.c
+++ b/src/pl/plpython/plpy_plpymodule.c
@@ -590,7 +590,7 @@ PLy_commit(PyObject *self, PyObject *args)
 
 	HoldPinnedPortals();
 
-	SPI_commit();
+	SPI_commit(false);
 	SPI_start_transaction();
 
 	/* was cleared at transaction end, reset pointer */
@@ -606,7 +606,7 @@ PLy_rollback(PyObject *self, PyObject *args)
 
 	HoldPinnedPortals();
 
-	SPI_rollback();
+	SPI_rollback(false);
 	SPI_start_transaction();
 
 	/* was cleared at transaction end, reset pointer */
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index e2fa43b890..7a317e1f89 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -2957,7 +2957,7 @@ pltcl_commit(ClientData cdata, Tcl_Interp *interp,
 
 	PG_TRY();
 	{
-		SPI_commit();
+		SPI_commit(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
@@ -2997,7 +2997,7 @@ pltcl_rollback(ClientData cdata, Tcl_Interp *interp,
 
 	PG_TRY();
 	{
-		SPI_rollback();
+		SPI_rollback(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
-- 
2.19.0

#12Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Heikki Linnakangas (#7)
Re: chained transactions

On 05/04/2018 10:35, Heikki Linnakangas wrote:

On 15/03/18 18:39, Alvaro Herrera wrote:

From 517bc6d9fefdee9135857d9562f644f2984ace32 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut<peter_e@gmx.net>
Date: Sun, 18 Feb 2018 09:33:53 -0500
Subject: [PATCH v1 6/8] Turn transaction_isolation into GUC enum

XXX no idea why it was done the way it was, but this seems much simpler
and apparently doesn't change any functionality.

Enums are recent -- 52a8d4f8f7e2, only 10 years old, and the commit
didn't convert all cases, leaving some for later. Funnily enough,
default_transaction_isolation was changed afterwards by ad6bf716baa7 but
not this one ... I guess "later" is now upon us for it.

With this patch, this stops working:

set transaction_isolation='default';

Other than that, +1 on this patch. I haven't looked closely at the rest
of the patches yet.

Based on this, I have committed this part of the patch series.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#13Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Peter Eisentraut (#11)
Re: chained transactions

Hello Peter,

Attached is a rebased patch set. No functionality changes.

Patch applies cleanly, compile, global make check ok, doc gen ok.

Shouldn't psql tab completion be updated as well?

About the code:

I must admit that do not like much the three global variables &
Save/Restore functions. I'd suggest saving directly into 3 local variables
in function CommitTransactionCommand, and restoring them when needed. Code
should not be longer. I'd would not bother to skip saving when not
chaining.

Copying & comparing nodes are updated. Should making, outing and reading
nodes also be updated?

About the tests: I'd suggest to use more options on the different tests,
eg SERIALIZABLE, READ ONLY� Also ISTM that tests should show
transaction_read_only value as well.

--
Fabien.

#14Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Fabien COELHO (#13)
2 attachment(s)
Re: chained transactions

On 04/11/2018 12:20, Fabien COELHO wrote:

Shouldn't psql tab completion be updated as well?

Done. (I only did the AND CHAIN, not the AND NO CHAIN.)

I must admit that do not like much the three global variables &
Save/Restore functions. I'd suggest saving directly into 3 local variables
in function CommitTransactionCommand, and restoring them when needed. Code
should not be longer. I'd would not bother to skip saving when not
chaining.

We're also using those functions in the 0002 patch. Should we just
repeat the code three times, or use macros?

Copying & comparing nodes are updated. Should making, outing and reading
nodes also be updated?

TransactionStmt isn't covered by the node serialization functions, so I
didn't see anything to update. What did you have in mind?

About the tests: I'd suggest to use more options on the different tests,
eg SERIALIZABLE, READ ONLY� Also ISTM that tests should show
transaction_read_only value as well.

OK, updated a bit. (Using READ ONLY doesn't work because then you can't
write anything inside the transaction.)

Updated patch attached. The previous (v2) patch apparently didn't apply
anymore.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v3-0001-Transaction-chaining.patchtext/plain; charset=UTF-8; name=v3-0001-Transaction-chaining.patch; x-mac-creator=0; x-mac-type=0Download
From 11488c03bf1c52ce81a659a77aceee88aa1a3f8f Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Fri, 16 Feb 2018 21:37:55 -0500
Subject: [PATCH v3 1/2] Transaction chaining

Add command variants COMMIT AND CHAIN and ROLLBACK AND CHAIN, which
start new transactions with the same transaction characteristics as the
just finished one, per SQL standard.
---
 doc/src/sgml/ref/abort.sgml                |  14 +-
 doc/src/sgml/ref/commit.sgml               |  14 +-
 doc/src/sgml/ref/end.sgml                  |  14 +-
 doc/src/sgml/ref/rollback.sgml             |  14 +-
 src/backend/access/transam/xact.c          |  73 +++++++-
 src/backend/catalog/sql_features.txt       |   2 +-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/parser/gram.y                  |  19 +-
 src/backend/tcop/utility.c                 |   4 +-
 src/bin/psql/tab-complete.c                |   8 +-
 src/include/access/xact.h                  |   4 +-
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/transactions.out | 191 +++++++++++++++++++++
 src/test/regress/sql/transactions.sql      |  63 +++++++
 15 files changed, 404 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/ref/abort.sgml b/doc/src/sgml/ref/abort.sgml
index 21799d2a83..0372913365 100644
--- a/doc/src/sgml/ref/abort.sgml
+++ b/doc/src/sgml/ref/abort.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-ABORT [ WORK | TRANSACTION ]
+ABORT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -51,6 +51,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/commit.sgml b/doc/src/sgml/ref/commit.sgml
index b2e8d5d180..37c706a66f 100644
--- a/doc/src/sgml/ref/commit.sgml
+++ b/doc/src/sgml/ref/commit.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-COMMIT [ WORK | TRANSACTION ]
+COMMIT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -48,6 +48,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/end.sgml b/doc/src/sgml/ref/end.sgml
index 7523315f34..8b8f4f0dbb 100644
--- a/doc/src/sgml/ref/end.sgml
+++ b/doc/src/sgml/ref/end.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-END [ WORK | TRANSACTION ]
+END [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -50,6 +50,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/rollback.sgml b/doc/src/sgml/ref/rollback.sgml
index 3cafb848a9..3019273a47 100644
--- a/doc/src/sgml/ref/rollback.sgml
+++ b/doc/src/sgml/ref/rollback.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-ROLLBACK [ WORK | TRANSACTION ]
+ROLLBACK [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -47,6 +47,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index d967400384..6af149d788 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -189,6 +189,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;
 
@@ -2760,6 +2761,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;
+
+static void
+SaveTransactionCharacteristics(void)
+{
+	save_XactIsoLevel = XactIsoLevel;
+	save_XactReadOnly = XactReadOnly;
+	save_XactDeferrable = XactDeferrable;
+}
+
+static void
+RestoreTransactionCharacteristics(void)
+{
+	XactIsoLevel = save_XactIsoLevel;
+	XactReadOnly = save_XactReadOnly;
+	XactDeferrable = save_XactDeferrable;
+}
+
+
 /*
  *	CommitTransactionCommand
  */
@@ -2768,6 +2799,9 @@ CommitTransactionCommand(void)
 {
 	TransactionState s = CurrentTransactionState;
 
+	if (s->chain)
+		SaveTransactionCharacteristics();
+
 	switch (s->blockState)
 	{
 			/*
@@ -2819,6 +2853,13 @@ CommitTransactionCommand(void)
 		case TBLOCK_END:
 			CommitTransaction();
 			s->blockState = TBLOCK_DEFAULT;
+			if (s->chain)
+			{
+				StartTransaction();
+				s->blockState = TBLOCK_INPROGRESS;
+				s->chain = false;
+				RestoreTransactionCharacteristics();
+			}
 			break;
 
 			/*
@@ -2838,6 +2879,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;
 
 			/*
@@ -2849,6 +2897,13 @@ CommitTransactionCommand(void)
 			AbortTransaction();
 			CleanupTransaction();
 			s->blockState = TBLOCK_DEFAULT;
+			if (s->chain)
+			{
+				StartTransaction();
+				s->blockState = TBLOCK_INPROGRESS;
+				s->chain = false;
+				RestoreTransactionCharacteristics();
+			}
 			break;
 
 			/*
@@ -3506,7 +3561,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)
@@ -3552,7 +3607,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;
@@ -3678,6 +3733,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;
 }
 
@@ -3688,7 +3750,7 @@ EndTransactionBlock(void)
  * As above, we don't actually do anything here except change blockState.
  */
 void
-UserAbortTransactionBlock(void)
+UserAbortTransactionBlock(bool chain)
 {
 	TransactionState s = CurrentTransactionState;
 
@@ -3786,6 +3848,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 aeb262a5b0..49df7e08c0 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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index db49968409..acfb05374d 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3654,6 +3654,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 3a084b4d1f..7c8cc6596d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1506,6 +1506,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 2c2208ffb7..94a407c981 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>	opt_or_replace
 				opt_grant_grant_option opt_grant_admin_option
 				opt_nowait opt_if_exists opt_with_data
+				opt_transaction_chain
 %type <ival>	opt_nowait_or_skip
 
 %type <list>	OptRoleList AlterOptRoleList
@@ -9807,11 +9808,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
@@ -9828,25 +9830,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
@@ -9946,6 +9951,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 970c94ee80..cd5c536563 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 fa44b2820b..fe89c58a7d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2050,16 +2050,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 689c57c592..c757146e4d 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -372,9 +372,9 @@ extern void StartTransactionCommand(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/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index e5bdc1cec5..bcc2f1a06c 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2986,6 +2986,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/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 69e176c525..4093b6fdf0 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 (3);
+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 (4);
+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 (5);
+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
+ 3
+ 4
+(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 2e3739fd6c..8e89481430 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -419,6 +419,69 @@ CREATE FUNCTION create_temp_tab() RETURNS text
 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 (3);
+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 (4);
+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 (5);
+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

base-commit: f2cbffc7a6183a359b339cfed65e9d609de756f0
-- 
2.19.1

v3-0002-Transaction-chaining-support-in-PL-pgSQL.patchtext/plain; charset=UTF-8; name=v3-0002-Transaction-chaining-support-in-PL-pgSQL.patch; x-mac-creator=0; x-mac-type=0Download
From 26079769e5896766c9e322f07480dd280f51b804 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 28 Feb 2018 10:04:06 -0500
Subject: [PATCH v3 2/2] Transaction chaining support in PL/pgSQL

---
 doc/src/sgml/plpgsql.sgml                     |  9 ++++++
 doc/src/sgml/spi.sgml                         | 14 ++++++---
 src/backend/access/transam/xact.c             |  4 +--
 src/backend/executor/spi.c                    | 24 ++++++++++++--
 src/include/access/xact.h                     |  2 ++
 src/include/executor/spi.h                    |  4 +--
 src/pl/plperl/plperl.c                        |  4 +--
 .../src/expected/plpgsql_transaction.out      | 31 +++++++++++++++++++
 src/pl/plpgsql/src/pl_exec.c                  | 10 +++---
 src/pl/plpgsql/src/pl_funcs.c                 | 10 ++++--
 src/pl/plpgsql/src/pl_gram.y                  | 18 +++++++++--
 src/pl/plpgsql/src/pl_scanner.c               |  2 ++
 src/pl/plpgsql/src/plpgsql.h                  |  2 ++
 .../plpgsql/src/sql/plpgsql_transaction.sql   | 23 ++++++++++++++
 src/pl/plpython/plpy_plpymodule.c             |  4 +--
 src/pl/tcl/pltcl.c                            |  4 +--
 16 files changed, 141 insertions(+), 24 deletions(-)

diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index b9eb98cfd7..128f13742a 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -3490,6 +3490,15 @@ <title>Transaction Management</title>
 </programlisting>
    </para>
 
+   <para>
+    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
+    <command>COMMIT AND CHAIN</command> and <command>ROLLBACK AND
+    CHAIN</command> accomplish this.
+   </para>
+
    <para>
     Transaction control is only possible in <command>CALL</command> or
     <command>DO</command> invocations from the top level or nested
diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml
index 9db11d22fb..9b4e63e352 100644
--- a/doc/src/sgml/spi.sgml
+++ b/doc/src/sgml/spi.sgml
@@ -4389,7 +4389,7 @@ <title>Transaction Management</title>
 
  <refsynopsisdiv>
 <synopsis>
-void SPI_commit(void)
+void SPI_commit(bool <parameter>chain</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -4402,7 +4402,10 @@ <title>Description</title>
    command <command>COMMIT</command>.  After a transaction is committed, a new
    transaction has to be started
    using <function>SPI_start_transaction</function> before further database
-   actions can be executed.
+   actions can be executed.  If <parameter>chain</parameter> is true, then a
+   new transaction is immediately started with the same transaction
+   characteristics as the just finished one, like with the SQL command
+   <command>COMMIT AND CHAIN</command>.
   </para>
 
   <para>
@@ -4429,7 +4432,7 @@ <title>Description</title>
 
  <refsynopsisdiv>
 <synopsis>
-void SPI_rollback(void)
+void SPI_rollback(bool <parameter>chain</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -4442,7 +4445,10 @@ <title>Description</title>
    command <command>ROLLBACK</command>.  After a transaction is rolled back, a
    new transaction has to be started
    using <function>SPI_start_transaction</function> before further database
-   actions can be executed.
+   actions can be executed.  If <parameter>chain</parameter> is true, then a
+   new transaction is immediately started with the same transaction
+   characteristics as the just finished one, like with the SQL command
+   <command>COMMIT AND CHAIN</command>.
   </para>
 
   <para>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 6af149d788..e8c2db3c2d 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2774,7 +2774,7 @@ static int	save_XactIsoLevel;
 static bool	save_XactReadOnly;
 static bool	save_XactDeferrable;
 
-static void
+void
 SaveTransactionCharacteristics(void)
 {
 	save_XactIsoLevel = XactIsoLevel;
@@ -2782,7 +2782,7 @@ SaveTransactionCharacteristics(void)
 	save_XactDeferrable = XactDeferrable;
 }
 
-static void
+void
 RestoreTransactionCharacteristics(void)
 {
 	XactIsoLevel = save_XactIsoLevel;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index ad726676d8..d61a56e095 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -218,7 +218,7 @@ SPI_start_transaction(void)
 }
 
 void
-SPI_commit(void)
+SPI_commit(bool chain)
 {
 	MemoryContext oldcontext = CurrentMemoryContext;
 
@@ -250,14 +250,24 @@ 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_rollback(bool chain)
 {
 	MemoryContext oldcontext = CurrentMemoryContext;
 
@@ -274,7 +284,17 @@ SPI_rollback(void)
 
 	_SPI_current->internal_xact = true;
 
+	if (chain)
+		SaveTransactionCharacteristics();
+
 	AbortCurrentTransaction();
+
+	if (chain)
+	{
+		StartTransactionCommand();
+		RestoreTransactionCharacteristics();
+	}
+
 	MemoryContextSwitchTo(oldcontext);
 
 	_SPI_current->internal_xact = false;
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index c757146e4d..2abdbece6c 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -369,6 +369,8 @@ 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);
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index d2616968ac..6bf2acb3b5 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -159,8 +159,8 @@ extern int	SPI_unregister_relation(const char *name);
 extern int	SPI_register_trigger_data(TriggerData *tdata);
 
 extern void SPI_start_transaction(void);
-extern void SPI_commit(void);
-extern void SPI_rollback(void);
+extern void SPI_commit(bool chain);
+extern void SPI_rollback(bool chain);
 
 extern void SPICleanup(void);
 extern void AtEOXact_SPI(bool isCommit);
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index fe54b20903..93190d1099 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -3968,7 +3968,7 @@ plperl_spi_commit(void)
 	{
 		HoldPinnedPortals();
 
-		SPI_commit();
+		SPI_commit(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
@@ -3995,7 +3995,7 @@ plperl_spi_rollback(void)
 	{
 		HoldPinnedPortals();
 
-		SPI_rollback();
+		SPI_rollback(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
diff --git a/src/pl/plpgsql/src/expected/plpgsql_transaction.out b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
index 6eedb215a4..ab62437bde 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_transaction.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
@@ -523,6 +523,37 @@ BEGIN
 END;
 $$;
 CALL transaction_test11();
+-- transaction chain
+TRUNCATE test1;
+DO LANGUAGE plpgsql $$
+BEGIN
+    ROLLBACK;
+    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+    FOR i IN 0..5 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
+INFO:  transaction_isolation = repeatable read
+INFO:  transaction_isolation = repeatable read
+SELECT * FROM test1;
+ a | b 
+---+---
+ 0 | 
+ 2 | 
+ 4 | 
+(3 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 39ea925820..04f5ec603c 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -4773,8 +4773,9 @@ exec_stmt_commit(PLpgSQL_execstate *estate, PLpgSQL_stmt_commit *stmt)
 {
 	HoldPinnedPortals();
 
-	SPI_commit();
-	SPI_start_transaction();
+	SPI_commit(stmt->chain);
+	if (!stmt->chain)
+		SPI_start_transaction();
 
 	estate->simple_eval_estate = NULL;
 	plpgsql_create_econtext(estate);
@@ -4792,8 +4793,9 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt)
 {
 	HoldPinnedPortals();
 
-	SPI_rollback();
-	SPI_start_transaction();
+	SPI_rollback(stmt->chain);
+	if (!stmt->chain)
+		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 4266e6cee7..0b0f892e2d 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 a979a5109d..924fbeb263 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 <ival>	opt_scrollable
 %type <fetch>	opt_fetch_direction
 
+%type <ival>	opt_transaction_chain
+
 %type <keyword>	unreserved_keyword
 
 
@@ -252,6 +254,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_ABSOLUTE
 %token <keyword>	K_ALIAS
 %token <keyword>	K_ALL
+%token <keyword>	K_AND
 %token <keyword>	K_ARRAY
 %token <keyword>	K_ASSERT
 %token <keyword>	K_BACKWARD
@@ -259,6 +262,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_BY
 %token <keyword>	K_CALL
 %token <keyword>	K_CASE
+%token <keyword>	K_CHAIN
 %token <keyword>	K_CLOSE
 %token <keyword>	K_COLLATE
 %token <keyword>	K_COLUMN
@@ -2177,30 +2181,38 @@ stmt_null		: K_NULL ';'
 					}
 				;
 
-stmt_commit		: K_COMMIT ';'
+stmt_commit		: K_COMMIT opt_transaction_chain ';'
 					{
 						PLpgSQL_stmt_commit *new;
 
 						new = palloc(sizeof(PLpgSQL_stmt_commit));
 						new->cmd_type = PLPGSQL_STMT_COMMIT;
 						new->lineno = plpgsql_location_to_lineno(@1);
+						new->chain = $2;
 
 						$$ = (PLpgSQL_stmt *)new;
 					}
 				;
 
-stmt_rollback	: K_ROLLBACK ';'
+stmt_rollback	: K_ROLLBACK opt_transaction_chain ';'
 					{
 						PLpgSQL_stmt_rollback *new;
 
 						new = palloc(sizeof(PLpgSQL_stmt_rollback));
 						new->cmd_type = PLPGSQL_STMT_ROLLBACK;
 						new->lineno = plpgsql_location_to_lineno(@1);
+						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;
@@ -2455,10 +2467,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_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index ab18946847..d4000bcaaa 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -99,10 +99,12 @@ static const int num_reserved_keywords = lengthof(reserved_keywords);
 static const ScanKeyword unreserved_keywords[] = {
 	PG_KEYWORD("absolute", K_ABSOLUTE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("alias", K_ALIAS, UNRESERVED_KEYWORD)
+	PG_KEYWORD("and", K_AND, UNRESERVED_KEYWORD)
 	PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
 	PG_KEYWORD("assert", K_ASSERT, UNRESERVED_KEYWORD)
 	PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
 	PG_KEYWORD("call", K_CALL, UNRESERVED_KEYWORD)
+	PG_KEYWORD("chain", K_CHAIN, UNRESERVED_KEYWORD)
 	PG_KEYWORD("close", K_CLOSE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("collate", K_COLLATE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("column", K_COLUMN, UNRESERVED_KEYWORD)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 42177ccaa6..cac14e87d8 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -533,6 +533,7 @@ typedef struct PLpgSQL_stmt_commit
 {
 	PLpgSQL_stmt_type cmd_type;
 	int			lineno;
+	bool		chain;
 } PLpgSQL_stmt_commit;
 
 /*
@@ -542,6 +543,7 @@ typedef struct PLpgSQL_stmt_rollback
 {
 	PLpgSQL_stmt_type cmd_type;
 	int			lineno;
+	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 ac1361a8ce..304bb3ffa8 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
@@ -445,6 +445,29 @@ CREATE PROCEDURE transaction_test11()
 CALL transaction_test11();
 
 
+-- transaction chain
+
+TRUNCATE test1;
+
+DO LANGUAGE plpgsql $$
+BEGIN
+    ROLLBACK;
+    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+    FOR i IN 0..5 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/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c
index 23e49e4b75..729f402ea1 100644
--- a/src/pl/plpython/plpy_plpymodule.c
+++ b/src/pl/plpython/plpy_plpymodule.c
@@ -590,7 +590,7 @@ PLy_commit(PyObject *self, PyObject *args)
 
 	HoldPinnedPortals();
 
-	SPI_commit();
+	SPI_commit(false);
 	SPI_start_transaction();
 
 	/* was cleared at transaction end, reset pointer */
@@ -606,7 +606,7 @@ PLy_rollback(PyObject *self, PyObject *args)
 
 	HoldPinnedPortals();
 
-	SPI_rollback();
+	SPI_rollback(false);
 	SPI_start_transaction();
 
 	/* was cleared at transaction end, reset pointer */
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 3b1454f833..27fa0fa53a 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -2931,7 +2931,7 @@ pltcl_commit(ClientData cdata, Tcl_Interp *interp,
 
 	PG_TRY();
 	{
-		SPI_commit();
+		SPI_commit(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
@@ -2971,7 +2971,7 @@ pltcl_rollback(ClientData cdata, Tcl_Interp *interp,
 
 	PG_TRY();
 	{
-		SPI_rollback();
+		SPI_rollback(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
-- 
2.19.1

#15Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Peter Eisentraut (#14)
Re: chained transactions

Hello Peter,

Shouldn't psql tab completion be updated as well?

Done. (I only did the AND CHAIN, not the AND NO CHAIN.)

Sure, that is the useful part.

I must admit that do not like much the three global variables &
Save/Restore functions. I'd suggest saving directly into 3 local variables
in function CommitTransactionCommand, and restoring them when needed. Code
should not be longer. I'd would not bother to skip saving when not
chaining.

We're also using those functions in the 0002 patch.

Hmmm. I have not looked at the second one yet.

Should we just repeat the code three times, or use macros?

I try to avoid global variables when possible as a principle, because I
paid to learn that they are bad:-) Maybe I'd do a local struct, declare an
instance within the function, and write two functions to dump/restore the
transaction status variables into the referenced struct. Maybe this is not
worth the effort.

Copying & comparing nodes are updated. Should making, outing and reading
nodes also be updated?

TransactionStmt isn't covered by the node serialization functions, so I
didn't see anything to update. What did you have in mind?

Sigh. I had in mind that the serialization feature would work with all
possible nodes, not just some of them… which seems quite naïve. The whole
make/copy/cmp/in/out functions depress me, all this verbose code should be
automatically generated from struct declarations. I'm pretty sure there
are hidden bugs in there.

About the tests: I'd suggest to use more options on the different tests,
eg SERIALIZABLE, READ ONLY… Also ISTM that tests should show
transaction_read_only value as well.

OK, updated a bit. (Using READ ONLY doesn't work because then you can't
write anything inside the transaction.)

Sure. Within a read-only tx, it could check that transaction_read_only is
on, and still on when chained, though.

Updated patch attached.

First patch applies cleanly, compiles, make check ok.

Further remarks, distinct from the above comments and suggestions:

The documentation should probably tell in the compatibility sections that
AND CHAIN is standard conforming.

In the automatic rollback tests, maybe I'd insert 3 just once, and use
another value for the chained transaction.

Tests may eventually vary BEGIN/START, COMMIT/END, ROLLBACK/ABORT.

As you added a SAVEPOINT, maybe I'd try rollbacking to it.

--
Fabien.

#16Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Peter Eisentraut (#14)
Re: chained transactions

Updated patch attached. The previous (v2) patch apparently didn't apply
anymore.

Second patch applies cleanly, compiles, "make check" ok.

As I do not know much about the SPI stuff, some of the comments below may
be very stupid.

I'm wary of changing the SPI_commit and SPI_rollback interfaces which are
certainly being used outside the source tree and could break countless
code, and it seems quite unclean that commit and rollback would do
anything else but committing or rollbacking.

ISTM that it should be kept as is and only managed from the PL/pgsql
exec_stmt_* functions, which have to be adapted anyway. That would
minimise changes and not break existing code.

If SPI_* functions are modified, which I would advise against, I find
keeping the next assignment in the chained case doubtful:

_SPI_current->internal_xact = false;

--
Fabien.

#17Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Fabien COELHO (#16)
Re: chained transactions

Updated patch attached. The previous (v2) patch apparently didn't apply
anymore.

Second patch applies cleanly, compiles, "make check" ok.

Also about testing, I'd do less rounds, 4 quite seems enough.

--
Fabien.

#18Alvaro Herrera
alvherre@2ndquadrant.com
In reply to: Fabien COELHO (#15)
Re: chained transactions

On 2018-Dec-26, Fabien COELHO wrote:

Copying & comparing nodes are updated. Should making, outing and reading
nodes also be updated?

TransactionStmt isn't covered by the node serialization functions, so I
didn't see anything to update. What did you have in mind?

Sigh. I had in mind that the serialization feature would work with all
possible nodes, not just some of them… which seems quite naïve. The whole
make/copy/cmp/in/out functions depress me, all this verbose code should be
automatically generated from struct declarations. I'm pretty sure there are
hidden bugs in there.

There may well be, but keep in mind that the nodes that have out and
read support are used in view declarations and such (stored rules); they
are used pretty extensively. Nodes that cannot be part of stored rules
don't need to have read support.

--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#19Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Fabien COELHO (#15)
2 attachment(s)
Re: chained transactions

On 26/12/2018 09:20, Fabien COELHO wrote:

I try to avoid global variables when possible as a principle, because I
paid to learn that they are bad:-) Maybe I'd do a local struct, declare an
instance within the function, and write two functions to dump/restore the
transaction status variables into the referenced struct. Maybe this is not
worth the effort.

Those are reasonable alternatives, I think, but it's a bit overkill in
this case.

About the tests: I'd suggest to use more options on the different tests,
eg SERIALIZABLE, READ ONLY… Also ISTM that tests should show
transaction_read_only value as well.

OK, updated a bit. (Using READ ONLY doesn't work because then you can't
write anything inside the transaction.)

Sure. Within a read-only tx, it could check that transaction_read_only is
on, and still on when chained, though.

I think the tests prove the point that the values are set and unset and
reset in various scenarios. We don't need to test every single
combination, that's not the job of this patch.

The documentation should probably tell in the compatibility sections that
AND CHAIN is standard conforming.

Good point. Updated that.

In the automatic rollback tests, maybe I'd insert 3 just once, and use
another value for the chained transaction.

done

Tests may eventually vary BEGIN/START, COMMIT/END, ROLLBACK/ABORT.

Those work on the parser level, so don't really affect this patch. It
would make the tests more confusing to read, I think.

As you added a SAVEPOINT, maybe I'd try rollbacking to it.

That would error out and invalidate the subsequent tests, so it's not
easily possible. We could write a totally separate test for it, but I'm
not sure what that would be proving.

Updated patches attached.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v4-0001-Transaction-chaining.patchtext/plain; charset=UTF-8; name=v4-0001-Transaction-chaining.patch; x-mac-creator=0; x-mac-type=0Download
From ec4e153976f21d48f66bde1403d569ce63f8638e Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 2 Jan 2019 15:09:04 +0100
Subject: [PATCH v4 1/2] Transaction chaining

Add command variants COMMIT AND CHAIN and ROLLBACK AND CHAIN, which
start new transactions with the same transaction characteristics as the
just finished one, per SQL standard.
---
 doc/src/sgml/ref/abort.sgml                |  14 +-
 doc/src/sgml/ref/commit.sgml               |  19 +-
 doc/src/sgml/ref/end.sgml                  |  14 +-
 doc/src/sgml/ref/rollback.sgml             |  19 +-
 src/backend/access/transam/xact.c          |  73 +++++++-
 src/backend/catalog/sql_features.txt       |   2 +-
 src/backend/nodes/copyfuncs.c              |   1 +
 src/backend/nodes/equalfuncs.c             |   1 +
 src/backend/parser/gram.y                  |  19 +-
 src/backend/tcop/utility.c                 |   4 +-
 src/bin/psql/tab-complete.c                |   8 +-
 src/include/access/xact.h                  |   4 +-
 src/include/nodes/parsenodes.h             |   1 +
 src/test/regress/expected/transactions.out | 191 +++++++++++++++++++++
 src/test/regress/sql/transactions.sql      |  63 +++++++
 15 files changed, 408 insertions(+), 25 deletions(-)

diff --git a/doc/src/sgml/ref/abort.sgml b/doc/src/sgml/ref/abort.sgml
index 21799d2a83..0372913365 100644
--- a/doc/src/sgml/ref/abort.sgml
+++ b/doc/src/sgml/ref/abort.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-ABORT [ WORK | TRANSACTION ]
+ABORT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -51,6 +51,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/commit.sgml b/doc/src/sgml/ref/commit.sgml
index b2e8d5d180..96a018e6aa 100644
--- a/doc/src/sgml/ref/commit.sgml
+++ b/doc/src/sgml/ref/commit.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-COMMIT [ WORK | TRANSACTION ]
+COMMIT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -48,6 +48,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
@@ -79,9 +91,8 @@ <title>Examples</title>
   <title>Compatibility</title>
 
   <para>
-   The SQL standard only specifies the two forms
-   <literal>COMMIT</literal> and <literal>COMMIT
-   WORK</literal>. Otherwise, this command is fully conforming.
+   The command <command>COMMIT</command> conforms to the SQL standard.  The
+   form <literal>COMMIT TRANSACTION</literal> is a PostgreSQL extension.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/end.sgml b/doc/src/sgml/ref/end.sgml
index 7523315f34..8b8f4f0dbb 100644
--- a/doc/src/sgml/ref/end.sgml
+++ b/doc/src/sgml/ref/end.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-END [ WORK | TRANSACTION ]
+END [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -50,6 +50,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/rollback.sgml b/doc/src/sgml/ref/rollback.sgml
index 3cafb848a9..54fffefe18 100644
--- a/doc/src/sgml/ref/rollback.sgml
+++ b/doc/src/sgml/ref/rollback.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-ROLLBACK [ WORK | TRANSACTION ]
+ROLLBACK [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -47,6 +47,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
@@ -78,9 +90,8 @@ <title>Examples</title>
   <title>Compatibility</title>
 
   <para>
-   The SQL standard only specifies the two forms
-   <literal>ROLLBACK</literal> and <literal>ROLLBACK
-   WORK</literal>. Otherwise, this command is fully conforming.
+   The command <command>ROLLBACK</command> conforms to the SQL standard.  The
+   form <literal>ROLLBACK TRANSACTION</literal> is a PostgreSQL extension.
   </para>
  </refsect1>
 
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index d967400384..6af149d788 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -189,6 +189,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;
 
@@ -2760,6 +2761,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;
+
+static void
+SaveTransactionCharacteristics(void)
+{
+	save_XactIsoLevel = XactIsoLevel;
+	save_XactReadOnly = XactReadOnly;
+	save_XactDeferrable = XactDeferrable;
+}
+
+static void
+RestoreTransactionCharacteristics(void)
+{
+	XactIsoLevel = save_XactIsoLevel;
+	XactReadOnly = save_XactReadOnly;
+	XactDeferrable = save_XactDeferrable;
+}
+
+
 /*
  *	CommitTransactionCommand
  */
@@ -2768,6 +2799,9 @@ CommitTransactionCommand(void)
 {
 	TransactionState s = CurrentTransactionState;
 
+	if (s->chain)
+		SaveTransactionCharacteristics();
+
 	switch (s->blockState)
 	{
 			/*
@@ -2819,6 +2853,13 @@ CommitTransactionCommand(void)
 		case TBLOCK_END:
 			CommitTransaction();
 			s->blockState = TBLOCK_DEFAULT;
+			if (s->chain)
+			{
+				StartTransaction();
+				s->blockState = TBLOCK_INPROGRESS;
+				s->chain = false;
+				RestoreTransactionCharacteristics();
+			}
 			break;
 
 			/*
@@ -2838,6 +2879,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;
 
 			/*
@@ -2849,6 +2897,13 @@ CommitTransactionCommand(void)
 			AbortTransaction();
 			CleanupTransaction();
 			s->blockState = TBLOCK_DEFAULT;
+			if (s->chain)
+			{
+				StartTransaction();
+				s->blockState = TBLOCK_INPROGRESS;
+				s->chain = false;
+				RestoreTransactionCharacteristics();
+			}
 			break;
 
 			/*
@@ -3506,7 +3561,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)
@@ -3552,7 +3607,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;
@@ -3678,6 +3733,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;
 }
 
@@ -3688,7 +3750,7 @@ EndTransactionBlock(void)
  * As above, we don't actually do anything here except change blockState.
  */
 void
-UserAbortTransactionBlock(void)
+UserAbortTransactionBlock(bool chain)
 {
 	TransactionState s = CurrentTransactionState;
 
@@ -3786,6 +3848,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 aeb262a5b0..49df7e08c0 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/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 144cd7a047..35d28c6d7b 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3653,6 +3653,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 c4579fbcc6..44e5dcc8d7 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1506,6 +1506,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 5bcaf42205..f8bc2855b5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -310,6 +310,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>	opt_or_replace
 				opt_grant_grant_option opt_grant_admin_option
 				opt_nowait opt_if_exists opt_with_data
+				opt_transaction_chain
 %type <ival>	opt_nowait_or_skip
 
 %type <list>	OptRoleList AlterOptRoleList
@@ -9805,11 +9806,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
@@ -9826,25 +9828,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
@@ -9944,6 +9949,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 970c94ee80..cd5c536563 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 91df96e796..1736bdcc2d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2077,16 +2077,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 689c57c592..c757146e4d 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -372,9 +372,9 @@ extern void StartTransactionCommand(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/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index d3dd3d0339..4010995514 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2985,6 +2985,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/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 69e176c525..1b316cc9b8 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 2e3739fd6c..812e40a1a3 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -419,6 +419,69 @@ CREATE FUNCTION create_temp_tab() RETURNS text
 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

base-commit: 1707a0d2aa6b2bcfe78f63836c769943a1a6b9e0
-- 
2.20.1

v4-0002-Transaction-chaining-support-in-PL-pgSQL.patchtext/plain; charset=UTF-8; name=v4-0002-Transaction-chaining-support-in-PL-pgSQL.patch; x-mac-creator=0; x-mac-type=0Download
From 0ee636cc22e27370eeb380a7de737e03f5c6521c Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Wed, 2 Jan 2019 15:09:11 +0100
Subject: [PATCH v4 2/2] Transaction chaining support in PL/pgSQL

---
 doc/src/sgml/plpgsql.sgml                     |  9 ++++++
 doc/src/sgml/spi.sgml                         | 14 +++++++---
 src/backend/access/transam/xact.c             |  4 +--
 src/backend/executor/spi.c                    | 24 ++++++++++++++--
 src/include/access/xact.h                     |  2 ++
 src/include/executor/spi.h                    |  4 +--
 src/pl/plperl/plperl.c                        |  4 +--
 .../src/expected/plpgsql_transaction.out      | 28 +++++++++++++++++++
 src/pl/plpgsql/src/pl_exec.c                  | 10 ++++---
 src/pl/plpgsql/src/pl_funcs.c                 | 10 +++++--
 src/pl/plpgsql/src/pl_gram.y                  | 18 ++++++++++--
 src/pl/plpgsql/src/pl_scanner.c               |  2 ++
 src/pl/plpgsql/src/plpgsql.h                  |  2 ++
 .../plpgsql/src/sql/plpgsql_transaction.sql   | 23 +++++++++++++++
 src/pl/plpython/plpy_plpymodule.c             |  4 +--
 src/pl/tcl/pltcl.c                            |  4 +--
 16 files changed, 138 insertions(+), 24 deletions(-)

diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 1f2abbb5d1..953c1246be 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -3490,6 +3490,15 @@ <title>Transaction Management</title>
 </programlisting>
    </para>
 
+   <para>
+    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
+    <command>COMMIT AND CHAIN</command> and <command>ROLLBACK AND
+    CHAIN</command> accomplish this.
+   </para>
+
    <para>
     Transaction control is only possible in <command>CALL</command> or
     <command>DO</command> invocations from the top level or nested
diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml
index 6f4f3bae6f..108c7624a8 100644
--- a/doc/src/sgml/spi.sgml
+++ b/doc/src/sgml/spi.sgml
@@ -4389,7 +4389,7 @@ <title>Transaction Management</title>
 
  <refsynopsisdiv>
 <synopsis>
-void SPI_commit(void)
+void SPI_commit(bool <parameter>chain</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -4402,7 +4402,10 @@ <title>Description</title>
    command <command>COMMIT</command>.  After a transaction is committed, a new
    transaction has to be started
    using <function>SPI_start_transaction</function> before further database
-   actions can be executed.
+   actions can be executed.  If <parameter>chain</parameter> is true, then a
+   new transaction is immediately started with the same transaction
+   characteristics as the just finished one, like with the SQL command
+   <command>COMMIT AND CHAIN</command>.
   </para>
 
   <para>
@@ -4429,7 +4432,7 @@ <title>Description</title>
 
  <refsynopsisdiv>
 <synopsis>
-void SPI_rollback(void)
+void SPI_rollback(bool <parameter>chain</parameter>)
 </synopsis>
  </refsynopsisdiv>
 
@@ -4442,7 +4445,10 @@ <title>Description</title>
    command <command>ROLLBACK</command>.  After a transaction is rolled back, a
    new transaction has to be started
    using <function>SPI_start_transaction</function> before further database
-   actions can be executed.
+   actions can be executed.  If <parameter>chain</parameter> is true, then a
+   new transaction is immediately started with the same transaction
+   characteristics as the just finished one, like with the SQL command
+   <command>COMMIT AND CHAIN</command>.
   </para>
 
   <para>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 6af149d788..e8c2db3c2d 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -2774,7 +2774,7 @@ static int	save_XactIsoLevel;
 static bool	save_XactReadOnly;
 static bool	save_XactDeferrable;
 
-static void
+void
 SaveTransactionCharacteristics(void)
 {
 	save_XactIsoLevel = XactIsoLevel;
@@ -2782,7 +2782,7 @@ SaveTransactionCharacteristics(void)
 	save_XactDeferrable = XactDeferrable;
 }
 
-static void
+void
 RestoreTransactionCharacteristics(void)
 {
 	XactIsoLevel = save_XactIsoLevel;
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index ad726676d8..d61a56e095 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -218,7 +218,7 @@ SPI_start_transaction(void)
 }
 
 void
-SPI_commit(void)
+SPI_commit(bool chain)
 {
 	MemoryContext oldcontext = CurrentMemoryContext;
 
@@ -250,14 +250,24 @@ 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_rollback(bool chain)
 {
 	MemoryContext oldcontext = CurrentMemoryContext;
 
@@ -274,7 +284,17 @@ SPI_rollback(void)
 
 	_SPI_current->internal_xact = true;
 
+	if (chain)
+		SaveTransactionCharacteristics();
+
 	AbortCurrentTransaction();
+
+	if (chain)
+	{
+		StartTransactionCommand();
+		RestoreTransactionCharacteristics();
+	}
+
 	MemoryContextSwitchTo(oldcontext);
 
 	_SPI_current->internal_xact = false;
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index c757146e4d..2abdbece6c 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -369,6 +369,8 @@ 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);
diff --git a/src/include/executor/spi.h b/src/include/executor/spi.h
index d2616968ac..6bf2acb3b5 100644
--- a/src/include/executor/spi.h
+++ b/src/include/executor/spi.h
@@ -159,8 +159,8 @@ extern int	SPI_unregister_relation(const char *name);
 extern int	SPI_register_trigger_data(TriggerData *tdata);
 
 extern void SPI_start_transaction(void);
-extern void SPI_commit(void);
-extern void SPI_rollback(void);
+extern void SPI_commit(bool chain);
+extern void SPI_rollback(bool chain);
 
 extern void SPICleanup(void);
 extern void AtEOXact_SPI(bool isCommit);
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index fe54b20903..93190d1099 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -3968,7 +3968,7 @@ plperl_spi_commit(void)
 	{
 		HoldPinnedPortals();
 
-		SPI_commit();
+		SPI_commit(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
@@ -3995,7 +3995,7 @@ plperl_spi_rollback(void)
 	{
 		HoldPinnedPortals();
 
-		SPI_rollback();
+		SPI_rollback(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
diff --git a/src/pl/plpgsql/src/expected/plpgsql_transaction.out b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
index 6eedb215a4..ba0745326a 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 f292fcad2d..0d8d08d6d8 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -4774,8 +4774,9 @@ exec_stmt_commit(PLpgSQL_execstate *estate, PLpgSQL_stmt_commit *stmt)
 {
 	HoldPinnedPortals();
 
-	SPI_commit();
-	SPI_start_transaction();
+	SPI_commit(stmt->chain);
+	if (!stmt->chain)
+		SPI_start_transaction();
 
 	estate->simple_eval_estate = NULL;
 	plpgsql_create_econtext(estate);
@@ -4793,8 +4794,9 @@ exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt)
 {
 	HoldPinnedPortals();
 
-	SPI_rollback();
-	SPI_start_transaction();
+	SPI_rollback(stmt->chain);
+	if (!stmt->chain)
+		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 4266e6cee7..0b0f892e2d 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 a979a5109d..924fbeb263 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 <ival>	opt_scrollable
 %type <fetch>	opt_fetch_direction
 
+%type <ival>	opt_transaction_chain
+
 %type <keyword>	unreserved_keyword
 
 
@@ -252,6 +254,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_ABSOLUTE
 %token <keyword>	K_ALIAS
 %token <keyword>	K_ALL
+%token <keyword>	K_AND
 %token <keyword>	K_ARRAY
 %token <keyword>	K_ASSERT
 %token <keyword>	K_BACKWARD
@@ -259,6 +262,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_BY
 %token <keyword>	K_CALL
 %token <keyword>	K_CASE
+%token <keyword>	K_CHAIN
 %token <keyword>	K_CLOSE
 %token <keyword>	K_COLLATE
 %token <keyword>	K_COLUMN
@@ -2177,30 +2181,38 @@ stmt_null		: K_NULL ';'
 					}
 				;
 
-stmt_commit		: K_COMMIT ';'
+stmt_commit		: K_COMMIT opt_transaction_chain ';'
 					{
 						PLpgSQL_stmt_commit *new;
 
 						new = palloc(sizeof(PLpgSQL_stmt_commit));
 						new->cmd_type = PLPGSQL_STMT_COMMIT;
 						new->lineno = plpgsql_location_to_lineno(@1);
+						new->chain = $2;
 
 						$$ = (PLpgSQL_stmt *)new;
 					}
 				;
 
-stmt_rollback	: K_ROLLBACK ';'
+stmt_rollback	: K_ROLLBACK opt_transaction_chain ';'
 					{
 						PLpgSQL_stmt_rollback *new;
 
 						new = palloc(sizeof(PLpgSQL_stmt_rollback));
 						new->cmd_type = PLPGSQL_STMT_ROLLBACK;
 						new->lineno = plpgsql_location_to_lineno(@1);
+						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;
@@ -2455,10 +2467,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_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
index ab18946847..d4000bcaaa 100644
--- a/src/pl/plpgsql/src/pl_scanner.c
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -99,10 +99,12 @@ static const int num_reserved_keywords = lengthof(reserved_keywords);
 static const ScanKeyword unreserved_keywords[] = {
 	PG_KEYWORD("absolute", K_ABSOLUTE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("alias", K_ALIAS, UNRESERVED_KEYWORD)
+	PG_KEYWORD("and", K_AND, UNRESERVED_KEYWORD)
 	PG_KEYWORD("array", K_ARRAY, UNRESERVED_KEYWORD)
 	PG_KEYWORD("assert", K_ASSERT, UNRESERVED_KEYWORD)
 	PG_KEYWORD("backward", K_BACKWARD, UNRESERVED_KEYWORD)
 	PG_KEYWORD("call", K_CALL, UNRESERVED_KEYWORD)
+	PG_KEYWORD("chain", K_CHAIN, UNRESERVED_KEYWORD)
 	PG_KEYWORD("close", K_CLOSE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("collate", K_COLLATE, UNRESERVED_KEYWORD)
 	PG_KEYWORD("column", K_COLUMN, UNRESERVED_KEYWORD)
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 42177ccaa6..cac14e87d8 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -533,6 +533,7 @@ typedef struct PLpgSQL_stmt_commit
 {
 	PLpgSQL_stmt_type cmd_type;
 	int			lineno;
+	bool		chain;
 } PLpgSQL_stmt_commit;
 
 /*
@@ -542,6 +543,7 @@ typedef struct PLpgSQL_stmt_rollback
 {
 	PLpgSQL_stmt_type cmd_type;
 	int			lineno;
+	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 ac1361a8ce..0c137dd31d 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
@@ -445,6 +445,29 @@ CREATE PROCEDURE transaction_test11()
 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/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c
index 23e49e4b75..729f402ea1 100644
--- a/src/pl/plpython/plpy_plpymodule.c
+++ b/src/pl/plpython/plpy_plpymodule.c
@@ -590,7 +590,7 @@ PLy_commit(PyObject *self, PyObject *args)
 
 	HoldPinnedPortals();
 
-	SPI_commit();
+	SPI_commit(false);
 	SPI_start_transaction();
 
 	/* was cleared at transaction end, reset pointer */
@@ -606,7 +606,7 @@ PLy_rollback(PyObject *self, PyObject *args)
 
 	HoldPinnedPortals();
 
-	SPI_rollback();
+	SPI_rollback(false);
 	SPI_start_transaction();
 
 	/* was cleared at transaction end, reset pointer */
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index 3b1454f833..27fa0fa53a 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -2931,7 +2931,7 @@ pltcl_commit(ClientData cdata, Tcl_Interp *interp,
 
 	PG_TRY();
 	{
-		SPI_commit();
+		SPI_commit(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
@@ -2971,7 +2971,7 @@ pltcl_rollback(ClientData cdata, Tcl_Interp *interp,
 
 	PG_TRY();
 	{
-		SPI_rollback();
+		SPI_rollback(false);
 		SPI_start_transaction();
 	}
 	PG_CATCH();
-- 
2.20.1

#20Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Fabien COELHO (#16)
Re: chained transactions

On 26/12/2018 09:47, Fabien COELHO wrote:

I'm wary of changing the SPI_commit and SPI_rollback interfaces which are
certainly being used outside the source tree and could break countless
code, and it seems quite unclean that commit and rollback would do
anything else but committing or rollbacking.

These are new as of PG11 and are only used by PL implementations that
support transaction control in procedures, of which there are very few.
We could write separate functions for the "and chain" variants, but I
hope that eventually all PLs will support chaining (because that's
really what you ought to be using in procedures), and so then the
non-chaining interfaces would end up being unused.

ISTM that it should be kept as is and only managed from the PL/pgsql
exec_stmt_* functions, which have to be adapted anyway. That would
minimise changes and not break existing code.

But we want other PLs to be able to use this too.

If SPI_* functions are modified, which I would advise against, I find
keeping the next assignment in the chained case doubtful:

_SPI_current->internal_xact = false;

This is correct as is. The internal_xact flag prevents
CommitTransactionCommand() and AbortCurrentTransaction() from releasing
SPI memory, so it only needs to be set around those calls. Afterwards
it's unset again so that a top-level transaction end will end up freeing
SPI memory.

Maybe something like internal_xact_end would have been a clearer name.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#21Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Peter Eisentraut (#20)
Re: chained transactions

Hello Peter,

I'm wary of changing the SPI_commit and SPI_rollback interfaces which are
certainly being used outside the source tree and could break countless
code, and it seems quite unclean that commit and rollback would do
anything else but committing or rollbacking.

These are new as of PG11 and are only used by PL implementations that

Ok, but that does not make it right to break them immediately in PG12.

support transaction control in procedures, of which there are very few.
We could write separate functions for the "and chain" variants, but I
hope that eventually all PLs will support chaining (because that's
really what you ought to be using in procedures), and so then the
non-chaining interfaces would end up being unused.

One of my issue is that the function name does not really reflect its
updated behavior. I'd be okay with additional _and_chain functions,
although I'm unsure whether one is really needed because it seems that you
need to handle things differently afterwards anyway on the language side.

ISTM that it should be kept as is and only managed from the PL/pgsql
exec_stmt_* functions, which have to be adapted anyway. That would
minimise changes and not break existing code.

But we want other PLs to be able to use this too.

Sure, but I do not see that as a particular issue. PLs need to be extended
to provide a syntax for the new feature anyway, it would not be automatic.
If you really feel there is an issue, then do _and_chain functions, but if
the afterwards code needs to check whether it was and chain and adjust
other internal settings, I'm not sure it is really worth it.

--
Fabien.

#22Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Peter Eisentraut (#19)
Re: chained transactions

Hello Peter,

Sure. Within a read-only tx, it could check that transaction_read_only is
on, and still on when chained, though.

I think the tests prove the point that the values are set and unset and
reset in various scenarios. We don't need to test every single
combination, that's not the job of this patch.

Hmmm... I've been quite unhappy with the status of non regression tests
for a long time, it seems that many contributors take the same approach.

ISTM that your patch saves three states, so I'd check that they are indeed
saved and restored, esp with non default values. I've noticed that you
change the default to read-only with a SET, but then this new default is
not tested afterwards, it is switched to READ WRITE on each transaction.

As you added a SAVEPOINT, maybe I'd try rollbacking to it.

That would error out and invalidate the subsequent tests, so it's not
easily possible. We could write a totally separate test for it, but I'm
not sure what that would be proving.

Hmmm. Then why put a savepoint if it is not really used?

Updated patches attached.

First patch applies cleanly, compiles, make check ok.

I do not understand the value of the SAVEPOINT in the tests.
I wish there was a also read-only transaction test.

Otherwise I'm okay with this patch.

About the second patch, I'm still unhappy with functions named commit &
rollback doing something else, which result in somehow strange code, where
you have to guess that the transaction is restarted in all cases, either
within the commit function or explicitely.

SPI_commit/rollback(chain);
if (!chain) SPI_start_transaction();

I think that the PL codes would be clearer with something like:

if (chain)
SPI_commit/rollback_and_chain();
else
{
SPI_commit/rollback();
SPI_start_transation();
}

And there would be no need to change existing SPI_commit/rollback calls to
add a false argument, for those PLs which do not support the extension
(yet).

--
Fabien.

#23Andres Freund
andres@anarazel.de
In reply to: Peter Eisentraut (#19)
Re: chained transactions

Hi,

On 2019-01-02 16:02:21 +0100, Peter Eisentraut wrote:

+++ b/src/backend/access/transam/xact.c
@@ -189,6 +189,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;

@@ -2760,6 +2761,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;

We normally don't define variables in the middle of a file? Also, why
do these need to be global vars rather than defined where we do
chaining? I'm imagining a SavedTransactionState struct declared on the
stack that's then passed to Save/Restore?

Not crucial, but I do wonder if we can come up with a prettier approach
for this.

Greetings,

Andres Freund

#24Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Fabien COELHO (#22)
1 attachment(s)
Re: chained transactions

Updated patch. I have squashed the two previously separate patches
together in this one.

On 2019-01-06 15:14, Fabien COELHO wrote:

I do not understand the value of the SAVEPOINT in the tests.

The purpose of the SAVEPOINT in the test is because it exercises
different switch cases in CommitTransactionCommand() and
AbortCurrentTransaction(). It's not entirely comprehensible from the
outside, but code coverage analysis confirms it.

Otherwise I'm okay with this patch.

About the second patch, I'm still unhappy with functions named commit &
rollback doing something else, which result in somehow strange code, where
you have to guess that the transaction is restarted in all cases, either
within the commit function or explicitely.

I have updated the SPI interface with your suggestions. I agree it's
better that way.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

Attachments:

v5-0001-Transaction-chaining.patchtext/plain; charset=UTF-8; name=v5-0001-Transaction-chaining.patch; x-mac-creator=0; x-mac-type=0Download
From 87a1c30fedddeb83f7a84a0c8f7d012a0df43814 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter@eisentraut.org>
Date: Mon, 18 Mar 2019 10:52:12 +0100
Subject: [PATCH v5] Transaction chaining

Add command variants COMMIT AND CHAIN and ROLLBACK AND CHAIN, which
start new transactions with the same transaction characteristics as the
just finished one, per SQL standard.

Support for transaction chaining in PL/pgSQL is also added.

Discussion: https://www.postgresql.org/message-id/flat/28536681-324b-10dc-ade8-ab46f7645a5a@2ndquadrant.com
---
 doc/src/sgml/plpgsql.sgml                     |   9 +
 doc/src/sgml/ref/abort.sgml                   |  14 +-
 doc/src/sgml/ref/commit.sgml                  |  19 +-
 doc/src/sgml/ref/end.sgml                     |  14 +-
 doc/src/sgml/ref/rollback.sgml                |  19 +-
 doc/src/sgml/spi.sgml                         |  25 ++-
 src/backend/access/transam/xact.c             |  73 ++++++-
 src/backend/catalog/sql_features.txt          |   2 +-
 src/backend/executor/spi.c                    |  50 ++++-
 src/backend/nodes/copyfuncs.c                 |   1 +
 src/backend/nodes/equalfuncs.c                |   1 +
 src/backend/parser/gram.y                     |  19 +-
 src/backend/tcop/utility.c                    |   4 +-
 src/bin/psql/tab-complete.c                   |   8 +-
 src/include/access/xact.h                     |   6 +-
 src/include/executor/spi.h                    |   2 +
 src/include/nodes/parsenodes.h                |   1 +
 .../src/expected/plpgsql_transaction.out      |  28 +++
 src/pl/plpgsql/src/pl_exec.c                  |  18 +-
 src/pl/plpgsql/src/pl_funcs.c                 |  10 +-
 src/pl/plpgsql/src/pl_gram.y                  |  18 +-
 src/pl/plpgsql/src/pl_unreserved_kwlist.h     |   2 +
 src/pl/plpgsql/src/plpgsql.h                  |   2 +
 .../plpgsql/src/sql/plpgsql_transaction.sql   |  23 +++
 src/test/regress/expected/transactions.out    | 191 ++++++++++++++++++
 src/test/regress/sql/transactions.sql         |  63 ++++++
 26 files changed, 584 insertions(+), 38 deletions(-)

diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index f8c6435c50..eacd67d8b6 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -3490,6 +3490,15 @@ <title>Transaction Management</title>
 </programlisting>
    </para>
 
+   <para>
+    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
+    <command>COMMIT AND CHAIN</command> and <command>ROLLBACK AND
+    CHAIN</command> accomplish this.
+   </para>
+
    <para>
     Transaction control is only possible in <command>CALL</command> or
     <command>DO</command> invocations from the top level or nested
diff --git a/doc/src/sgml/ref/abort.sgml b/doc/src/sgml/ref/abort.sgml
index 21799d2a83..0372913365 100644
--- a/doc/src/sgml/ref/abort.sgml
+++ b/doc/src/sgml/ref/abort.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-ABORT [ WORK | TRANSACTION ]
+ABORT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -51,6 +51,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/commit.sgml b/doc/src/sgml/ref/commit.sgml
index b2e8d5d180..96a018e6aa 100644
--- a/doc/src/sgml/ref/commit.sgml
+++ b/doc/src/sgml/ref/commit.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-COMMIT [ WORK | TRANSACTION ]
+COMMIT [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -48,6 +48,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
@@ -79,9 +91,8 @@ <title>Examples</title>
   <title>Compatibility</title>
 
   <para>
-   The SQL standard only specifies the two forms
-   <literal>COMMIT</literal> and <literal>COMMIT
-   WORK</literal>. Otherwise, this command is fully conforming.
+   The command <command>COMMIT</command> conforms to the SQL standard.  The
+   form <literal>COMMIT TRANSACTION</literal> is a PostgreSQL extension.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/end.sgml b/doc/src/sgml/ref/end.sgml
index 7523315f34..8b8f4f0dbb 100644
--- a/doc/src/sgml/ref/end.sgml
+++ b/doc/src/sgml/ref/end.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-END [ WORK | TRANSACTION ]
+END [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -50,6 +50,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/rollback.sgml b/doc/src/sgml/ref/rollback.sgml
index 3cafb848a9..54fffefe18 100644
--- a/doc/src/sgml/ref/rollback.sgml
+++ b/doc/src/sgml/ref/rollback.sgml
@@ -21,7 +21,7 @@
 
  <refsynopsisdiv>
 <synopsis>
-ROLLBACK [ WORK | TRANSACTION ]
+ROLLBACK [ WORK | TRANSACTION ] [ AND [ NO ] CHAIN ]
 </synopsis>
  </refsynopsisdiv>
 
@@ -47,6 +47,18 @@ <title>Parameters</title>
      </para>
     </listitem>
    </varlistentry>
+
+   <varlistentry>
+    <term><literal>AND CHAIN</literal></term>
+    <listitem>
+     <para>
+      If <literal>AND CHAIN</literal> is specified, a new transaction is
+      immediately started with the same transaction characteristics (see <xref
+      linkend="sql-set-transaction"/>) as the just finished one.  Otherwise,
+      no new transaction is started.
+     </para>
+    </listitem>
+   </varlistentry>
   </variablelist>
  </refsect1>
 
@@ -78,9 +90,8 @@ <title>Examples</title>
   <title>Compatibility</title>
 
   <para>
-   The SQL standard only specifies the two forms
-   <literal>ROLLBACK</literal> and <literal>ROLLBACK
-   WORK</literal>. Otherwise, this command is fully conforming.
+   The command <command>ROLLBACK</command> conforms to the SQL standard.  The
+   form <literal>ROLLBACK TRANSACTION</literal> is a PostgreSQL extension.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/spi.sgml b/doc/src/sgml/spi.sgml
index 6f4f3bae6f..8ed4814d9a 100644
--- a/doc/src/sgml/spi.sgml
+++ b/doc/src/sgml/spi.sgml
@@ -4390,6 +4390,10 @@ <title>Transaction Management</title>
  <refsynopsisdiv>
 <synopsis>
 void SPI_commit(void)
+</synopsis>
+
+<synopsis>
+void SPI_commit_and_chain(void)
 </synopsis>
  </refsynopsisdiv>
 
@@ -4406,7 +4410,14 @@ <title>Description</title>
   </para>
 
   <para>
-   This function can only be executed if the SPI connection has been set as
+   <function>SPI_commit_and_chain</function> 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
+   <command>COMMIT AND CHAIN</command>.
+  </para>
+
+  <para>
+   These functions can only be executed if the SPI connection has been set as
    nonatomic in the call to <function>SPI_connect_ext</function>.
   </para>
  </refsect1>
@@ -4430,6 +4441,10 @@ <title>Description</title>
  <refsynopsisdiv>
 <synopsis>
 void SPI_rollback(void)
+</synopsis>
+
+<synopsis>
+void SPI_rollback_and_chain(void)
 </synopsis>
  </refsynopsisdiv>
 
@@ -4444,9 +4459,15 @@ <title>Description</title>
    using <function>SPI_start_transaction</function> before further database
    actions can be executed.
   </para>
+  <para>
+   <function>SPI_rollback_and_chain</function> 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
+   <command>ROLLBACK AND CHAIN</command>.
+  </para>
 
   <para>
-   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 <function>SPI_connect_ext</function>.
   </para>
  </refsect1>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 6e5891749b..c3214d4f4d 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 bade0fe9ae..6b23163929 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 d898f4ca78..6e262d1a3a 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 a8a735c247..8a3b316fed 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3658,6 +3658,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 3cab90e9f8..37f3359b94 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1509,6 +1509,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 e23e68fdb3..d6836de33f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -311,6 +311,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type <boolean>	opt_or_replace
 				opt_grant_grant_option opt_grant_admin_option
 				opt_nowait opt_if_exists opt_with_data
+				opt_transaction_chain
 %type <ival>	opt_nowait_or_skip
 
 %type <list>	OptRoleList AlterOptRoleList
@@ -9789,11 +9790,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
@@ -9810,25 +9812,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
@@ -9928,6 +9933,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 6ec795f1b4..8eded2908c 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 10ae21cc61..3ba3498496 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 426e77846f..e8579dcd47 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 eafcc7a4e4..83c28b79bf 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 fe35783359..a883ff2e35 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2997,6 +2997,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 6eedb215a4..ba0745326a 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 6dfcd1611a..527cada4fe 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 0b63da2b4a..053f83dc74 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 03f7cdce8c..dea95f4230 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 <ival>	opt_scrollable
 %type <fetch>	opt_fetch_direction
 
+%type <ival>	opt_transaction_chain
+
 %type <keyword>	unreserved_keyword
 
 
@@ -252,6 +254,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_ABSOLUTE
 %token <keyword>	K_ALIAS
 %token <keyword>	K_ALL
+%token <keyword>	K_AND
 %token <keyword>	K_ARRAY
 %token <keyword>	K_ASSERT
 %token <keyword>	K_BACKWARD
@@ -259,6 +262,7 @@ static	void			check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <keyword>	K_BY
 %token <keyword>	K_CALL
 %token <keyword>	K_CASE
+%token <keyword>	K_CHAIN
 %token <keyword>	K_CLOSE
 %token <keyword>	K_COLLATE
 %token <keyword>	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 ce4be81dd8..6d85f9396e 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 0a5fbfa9d6..4eff62e8e5 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 ac1361a8ce..0c137dd31d 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
@@ -445,6 +445,29 @@ CREATE PROCEDURE transaction_test11()
 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 69e176c525..1b316cc9b8 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 2e3739fd6c..812e40a1a3 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -419,6 +419,69 @@ CREATE FUNCTION create_temp_tab() RETURNS text
 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

base-commit: a0478b69985056965a5737184279a99bde421f69
-- 
2.21.0

#25Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Andres Freund (#23)
Re: chained transactions

On 2019-02-16 06:22, Andres Freund wrote:

+static int	save_XactIsoLevel;
+static bool	save_XactReadOnly;
+static bool	save_XactDeferrable;

We normally don't define variables in the middle of a file? Also, why
do these need to be global vars rather than defined where we do
chaining? I'm imagining a SavedTransactionState struct declared on the
stack that's then passed to Save/Restore?

Not crucial, but I do wonder if we can come up with a prettier approach
for this.

If we do it with a struct that is passed to the functions, then either
the struct contents will be available outside of xact.c, which will
expose internals of xact.c that weren't previously exposed, or we do it
with an incomplete struct, but then this would involve palloc across
transaction commit, resulting in additional complexity. Neither of
these sound like obviously better alternatives.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#26Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Peter Eisentraut (#24)
Re: chained transactions

Hallo Peter,

Updated patch. I have squashed the two previously separate patches
together in this one.

Ok.

I do not understand the value of the SAVEPOINT in the tests.

The purpose of the SAVEPOINT in the test is because it exercises
different switch cases in CommitTransactionCommand() and
AbortCurrentTransaction(). It's not entirely comprehensible from the
outside, but code coverage analysis confirms it.

Ok.

Otherwise I'm okay with this patch.

About the second patch, I'm still unhappy with functions named commit &
rollback doing something else, which result in somehow strange code, where
you have to guess that the transaction is restarted in all cases, either
within the commit function or explicitely.

I have updated the SPI interface with your suggestions. I agree it's
better that way.

Patch applies cleanly, compiles, make check ok, doc build ok.

Minor remarks:

In "xact.c", maybe I'd assign blockState in the else branch, instead of
overriding it?

About the static _SPI_{commit,rollback} functions: I'm fine with these
functions, but I'm not sure about their name. Maybe
_SPI_chainable_{commit,rollback} would be is clearer about their content?

Doc looks clear to me. ISTM "chain" should be added as an index term?

Tests look ok. Maybe I'd have series with mixed commit & rollback, instead
of only commit & only rollback?

--
Fabien.

#27Peter Eisentraut
peter.eisentraut@2ndquadrant.com
In reply to: Fabien COELHO (#26)
Re: chained transactions

Patch has been committed, thanks.

On 2019-03-18 21:20, Fabien COELHO wrote:

Minor remarks:

In "xact.c", maybe I'd assign blockState in the else branch, instead of
overriding it?

I think it was better the way it is, since logically the block state is
first set, then set again after the new transaction starts.

About the static _SPI_{commit,rollback} functions: I'm fine with these
functions, but I'm not sure about their name. Maybe
_SPI_chainable_{commit,rollback} would be is clearer about their content?

I kept it as is, to mirror the names of the SQL commands.

Doc looks clear to me. ISTM "chain" should be added as an index term?

Added, good idea.

--
Peter Eisentraut http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

#28Fabien COELHO
coelho@cri.ensmp.fr
In reply to: Peter Eisentraut (#27)
Re: chained transactions

Hallo Peter,

In "xact.c", maybe I'd assign blockState in the else branch, instead of
overriding it?

I think it was better the way it is, since logically the block state is
first set, then set again after the new transaction starts.

Ok.

About the static _SPI_{commit,rollback} functions: I'm fine with these
functions, but I'm not sure about their name. Maybe
_SPI_chainable_{commit,rollback} would be is clearer about their content?

I kept it as is, to mirror the names of the SQL commands.

Hmmm. Function _SPI_commit does not implement just COMMIT, it implements
both "COMMIT" and "COMMIT AND CHAIN"?

I'm fine with SPI_commit and SPI_commit_and_chain, and the rollback
variants.

Maybe _SPI_commit_chainable? Well, you do as you please.

--
Fabie