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
From ce07129f4d7ba376c59fa5d8244953a4eec05027 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pete...@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

From c1d5189f52537d4632d0a4069a732e8666c123e1 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pete...@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

From 2f2d4eec311f8bd7c208de33894c0db9f269dcc1 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pete...@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

From 09d48e8de680ee6b293897d95fb783b22a50f0f6 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pete...@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

From ab9bf507f18af7dce1447ccc60077f51e5df7acf Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pete...@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

From 517bc6d9fefdee9135857d9562f644f2984ace32 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pete...@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

From 65bfb7be0aee872eb0405a77ec23ee2483745b37 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pete...@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

From 06affcd2fcdc9d6feb86b1754fb86728b073eede Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pete...@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

Reply via email to