From a6542a4b6870a019cd952d055d2e7af2da2fe102 Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Mon, 25 Nov 2013 19:19:40 -0500
Subject: [PATCH] Change SET LOCAL/CONSTRAINTS/TRANSACTION and ABORT behavior

Change SET LOCAL/CONSTRAINTS/TRANSACTION behavior outside of a
transaction block from error (post-9.3) to warning.  (Was nothing in <=
9.3.)  Also change ABORT outside of a transaction block from notice to
warning.
---
 doc/src/sgml/ref/abort.sgml           |  3 +--
 doc/src/sgml/ref/rollback.sgml        |  4 +--
 doc/src/sgml/ref/set.sgml             |  5 ++--
 doc/src/sgml/ref/set_constraints.sgml |  5 +---
 doc/src/sgml/ref/set_transaction.sgml |  2 +-
 src/backend/access/transam/xact.c     | 37 ++++++++++++++++++++++-----
 src/backend/tcop/utility.c            |  2 +-
 src/backend/utils/misc/guc.c          | 10 ++++----
 src/include/access/xact.h             |  1 +
 src/test/regress/expected/errors.out  |  2 +-
 src/test/regress/expected/guc.out     |  4 +--
 11 files changed, 47 insertions(+), 28 deletions(-)

diff --git a/doc/src/sgml/ref/abort.sgml b/doc/src/sgml/ref/abort.sgml
index 246e8f81268..f3a2fa88ff1 100644
--- a/doc/src/sgml/ref/abort.sgml
+++ b/doc/src/sgml/ref/abort.sgml
@@ -63,8 +63,7 @@ ABORT [ WORK | TRANSACTION ]
   </para>
 
   <para>
-   Issuing <command>ABORT</> when not inside a transaction does
-   no harm, but it will provoke a warning message.
+   Issuing <command>ABORT</> outside of a transaction block has no effect.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/rollback.sgml b/doc/src/sgml/ref/rollback.sgml
index b26554567db..4f7962117c8 100644
--- a/doc/src/sgml/ref/rollback.sgml
+++ b/doc/src/sgml/ref/rollback.sgml
@@ -59,8 +59,8 @@ ROLLBACK [ WORK | TRANSACTION ]
   </para>
 
   <para>
-   Issuing <command>ROLLBACK</> when not inside a transaction does
-   no harm, but it will provoke a warning message.
+   Issuing <command>ROLLBACK</> outside of a transaction
+   block has no effect.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/set.sgml b/doc/src/sgml/ref/set.sgml
index 6290c9de708..5a84f697e67 100644
--- a/doc/src/sgml/ref/set.sgml
+++ b/doc/src/sgml/ref/set.sgml
@@ -110,9 +110,8 @@ SET [ SESSION | LOCAL ] TIME ZONE { <replaceable class="PARAMETER">timezone</rep
      <para>
       Specifies that the command takes effect for only the current
       transaction.  After <command>COMMIT</> or <command>ROLLBACK</>,
-      the session-level setting takes effect again.
-      <productname>PostgreSQL</productname> reports an error if
-      <command>SET LOCAL</> is used outside a transaction block.
+      the session-level setting takes effect again.  This has no effect
+      outside of a transaction block.
      </para>
     </listitem>
    </varlistentry>
diff --git a/doc/src/sgml/ref/set_constraints.sgml b/doc/src/sgml/ref/set_constraints.sgml
index 895a5fdbc0e..a33190cca81 100644
--- a/doc/src/sgml/ref/set_constraints.sgml
+++ b/doc/src/sgml/ref/set_constraints.sgml
@@ -99,10 +99,7 @@ SET CONSTRAINTS { ALL | <replaceable class="parameter">name</replaceable> [, ...
 
   <para>
    This command only alters the behavior of constraints within the
-   current transaction. Thus, if you execute this command outside of a
-   transaction block
-   (<command>BEGIN</command>/<command>COMMIT</command> pair), it will
-   generate an error.
+   current transaction.  This has no effect outside of a transaction block.
   </para>
  </refsect1>
 
diff --git a/doc/src/sgml/ref/set_transaction.sgml b/doc/src/sgml/ref/set_transaction.sgml
index 391464ade83..e90ff4af725 100644
--- a/doc/src/sgml/ref/set_transaction.sgml
+++ b/doc/src/sgml/ref/set_transaction.sgml
@@ -185,7 +185,7 @@ SET SESSION CHARACTERISTICS AS TRANSACTION <replaceable class="parameter">transa
   <para>
    If <command>SET TRANSACTION</command> is executed without a prior
    <command>START TRANSACTION</command> or <command>BEGIN</command>,
-   it will generate an error.
+   it will have no effect.
   </para>
 
   <para>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 0591f3fd562..bab048d38a1 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -265,6 +265,8 @@ static void CallSubXactCallbacks(SubXactEvent event,
 					 SubTransactionId mySubid,
 					 SubTransactionId parentSubid);
 static void CleanupTransaction(void);
+static void CheckTransactionChain(bool isTopLevel, bool throwError,
+					 const char *stmtType);
 static void CommitTransaction(void);
 static TransactionId RecordTransactionAbort(bool isSubXact);
 static void StartTransaction(void);
@@ -2948,6 +2950,26 @@ PreventTransactionChain(bool isTopLevel, const char *stmtType)
 	/* all okay */
 }
 
+/*
+ *	These two functions allow for warnings or errors if a command is
+ *	executed outside of a transaction block.
+ *
+ *	While top-level transaction control commands (BEGIN/COMMIT/ABORT) and
+ *	SET that have no effect issue warnings, all other no-effect commands
+ *	generate errors.
+ */
+void
+WarnNoTransactionChain(bool isTopLevel, const char *stmtType)
+{
+	CheckTransactionChain(isTopLevel, false, stmtType);
+}
+
+void
+RequireTransactionChain(bool isTopLevel, const char *stmtType)
+{
+	CheckTransactionChain(isTopLevel, true, stmtType);
+}
+
 /*
  *	RequireTransactionChain
  *
@@ -2957,16 +2979,16 @@ PreventTransactionChain(bool isTopLevel, const char *stmtType)
  *	is presumably an error).  DECLARE CURSOR is an example.
  *
  *	If we appear to be running inside a user-defined function, we do not
- *	issue an error, since the function could issue more commands that make
+ *	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 error messages.
+ *	stmtType: statement type name, for warning or error messages.
  */
-void
-RequireTransactionChain(bool isTopLevel, const char *stmtType)
+static void
+CheckTransactionChain(bool isTopLevel, bool throwError, const char *stmtType)
 {
 	/*
 	 * xact block already started?
@@ -2986,11 +3008,12 @@ RequireTransactionChain(bool isTopLevel, const char *stmtType)
 	if (!isTopLevel)
 		return;
 
-	ereport(ERROR,
+	ereport(throwError ? ERROR : WARNING,
 			(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
 	/* translator: %s represents an SQL statement name */
 			 errmsg("%s can only be used in transaction blocks",
 					stmtType)));
+	return;
 }
 
 /*
@@ -3425,12 +3448,12 @@ UserAbortTransactionBlock(void)
 
 			/*
 			 * The user issued ABORT when not inside a transaction. Issue a
-			 * NOTICE and go to abort state.  The upcoming call to
+			 * WARNING and go to abort state.  The upcoming call to
 			 * CommitTransactionCommand() will then put us back into the
 			 * default state.
 			 */
 		case TBLOCK_STARTED:
-			ereport(NOTICE,
+			ereport(WARNING,
 					(errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION),
 					 errmsg("there is no transaction in progress")));
 			s->blockState = TBLOCK_ABORT_PENDING;
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6a7bf0de7d7..5895102b518 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -754,7 +754,7 @@ standard_ProcessUtility(Node *parsetree,
 			break;
 
 		case T_ConstraintsSetStmt:
-			RequireTransactionChain(isTopLevel, "SET CONSTRAINTS");
+			WarnNoTransactionChain(isTopLevel, "SET CONSTRAINTS");
 			AfterTriggerSetState((ConstraintsSetStmt *) parsetree);
 			break;
 
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 54d8078fe7a..cbf3186789c 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -6274,7 +6274,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 		case VAR_SET_VALUE:
 		case VAR_SET_CURRENT:
 			if (stmt->is_local)
-				RequireTransactionChain(isTopLevel, "SET LOCAL");
+				WarnNoTransactionChain(isTopLevel, "SET LOCAL");
 			(void) set_config_option(stmt->name,
 									 ExtractSetVariableArgs(stmt),
 									 (superuser() ? PGC_SUSET : PGC_USERSET),
@@ -6295,7 +6295,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 			{
 				ListCell   *head;
 
-				RequireTransactionChain(isTopLevel, "SET TRANSACTION");
+				WarnNoTransactionChain(isTopLevel, "SET TRANSACTION");
 
 				foreach(head, stmt->args)
 				{
@@ -6346,7 +6346,7 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 							 errmsg("SET LOCAL TRANSACTION SNAPSHOT is not implemented")));
 
-				RequireTransactionChain(isTopLevel, "SET TRANSACTION");
+				WarnNoTransactionChain(isTopLevel, "SET TRANSACTION");
 				Assert(IsA(con, A_Const));
 				Assert(nodeTag(&con->val) == T_String);
 				ImportSnapshot(strVal(&con->val));
@@ -6357,11 +6357,11 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
 			break;
 		case VAR_SET_DEFAULT:
 			if (stmt->is_local)
-				RequireTransactionChain(isTopLevel, "SET LOCAL");
+				WarnNoTransactionChain(isTopLevel, "SET LOCAL");
 			/* fall through */
 		case VAR_RESET:
 			if (strcmp(stmt->name, "transaction_isolation") == 0)
-				RequireTransactionChain(isTopLevel, "RESET TRANSACTION");
+				WarnNoTransactionChain(isTopLevel, "RESET TRANSACTION");
 
 			(void) set_config_option(stmt->name,
 									 NULL,
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 835f6acbee0..1d3e7d8938a 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -245,6 +245,7 @@ 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 RegisterXactCallback(XactCallback callback, void *arg);
 extern void UnregisterXactCallback(XactCallback callback, void *arg);
diff --git a/src/test/regress/expected/errors.out b/src/test/regress/expected/errors.out
index fa0bd82819a..40615129778 100644
--- a/src/test/regress/expected/errors.out
+++ b/src/test/regress/expected/errors.out
@@ -114,7 +114,7 @@ ERROR:  column name "oid" conflicts with a system column name
 -- TRANSACTION STUFF
 -- not in a xact
 abort;
-NOTICE:  there is no transaction in progress
+WARNING:  there is no transaction in progress
 -- not in a xact
 end;
 WARNING:  there is no transaction in progress
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 203fa6ef8ea..4f0065cb7ef 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -29,7 +29,7 @@ SELECT '2006-08-13 12:34:56'::timestamptz;
 
 -- SET LOCAL has no effect outside of a transaction
 SET LOCAL vacuum_cost_delay TO 50;
-ERROR:  SET LOCAL can only be used in transaction blocks
+WARNING:  SET LOCAL can only be used in transaction blocks
 SHOW vacuum_cost_delay;
  vacuum_cost_delay 
 -------------------
@@ -37,7 +37,7 @@ SHOW vacuum_cost_delay;
 (1 row)
 
 SET LOCAL datestyle = 'SQL';
-ERROR:  SET LOCAL can only be used in transaction blocks
+WARNING:  SET LOCAL can only be used in transaction blocks
 SHOW datestyle;
  DateStyle 
 -----------
-- 
GitLab