v1-0008-Transaction-chaining-support-in-PL-pgSQL.patch

text/plain

Filename: v1-0008-Transaction-chaining-support-in-PL-pgSQL.patch
Type: text/plain
Part: 7
Message: chained transactions

Patch

Format: format-patch
Series: patch v1-0008
Subject: Transaction chaining support in PL/pgSQL
File+
doc/src/sgml/plpgsql.sgml 9 0
doc/src/sgml/spi.sgml 10 4
src/backend/access/transam/xact.c 2 2
src/backend/executor/spi.c 23 2
src/include/access/xact.h 2 0
src/include/executor/spi.h 2 2
src/pl/plperl/plperl.c 2 2
src/pl/plpgsql/src/expected/plpgsql_transaction.out 31 0
src/pl/plpgsql/src/pl_exec.c 6 4
src/pl/plpgsql/src/pl_funcs.c 8 2
src/pl/plpgsql/src/pl_gram.y 16 2
src/pl/plpgsql/src/plpgsql.h 2 0
src/pl/plpgsql/src/pl_scanner.c 2 0
src/pl/plpgsql/src/sql/plpgsql_transaction.sql 23 0
src/pl/plpython/plpy_plpymodule.c 2 2
src/pl/tcl/pltcl.c 2 2
From 06affcd2fcdc9d6feb86b1754fb86728b073eede Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Wed, 28 Feb 2018 10:04:06 -0500
Subject: [PATCH v1 8/8] Transaction chaining support in PL/pgSQL

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

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