From a65b1b738c07c98baaf38289eccd1b5fa054453d Mon Sep 17 00:00:00 2001
From: Bruce Momjian <bruce@momjian.us>
Date: Thu, 28 Apr 2005 13:09:59 +0000
Subject: [PATCH] Add psql \set ON_ERROR_ROLLBACK to allow statements in a
 transaction to error without affecting the entire transaction.  Valid values
 are "on|interactive|off".

---
 doc/src/sgml/ref/psql-ref.sgml | 24 +++++++++-
 src/bin/psql/common.c          | 80 +++++++++++++++++++++++++++++++---
 2 files changed, 96 insertions(+), 8 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 3a80b7f6940..41de5d5e7eb 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.134 2005/03/14 06:19:01 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.135 2005/04/28 13:09:59 momjian Exp $
 PostgreSQL documentation
 -->
 
@@ -2049,6 +2049,28 @@ bar
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+      <indexterm>
+       <primary>rollback</primary>
+       <secondary>psql</secondary>
+      </indexterm>
+        <term><varname>ON_ERROR_ROLLBACK</varname></term>
+        <listitem>
+        <para>
+        When <literal>on</>, if a statement in a transaction block
+        generates an error, the error is ignored and the transaction
+        continues. When <literal>interactive</>, such errors are only
+        ignored in interactive sessions, and not when reading script
+        files. When <literal>off</> (the default), a statement in a
+        transaction block that generates an error aborts the entire
+        transaction. The on_error_rollback-on mode works by issuing an
+        implicit <command>SAVEPONT</> for you, just before each command
+        that is in a transaction block, and rolls back to the savepoint
+        on error.
+        </para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>ON_ERROR_STOP</varname></term>
         <listitem>
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 6d03a4ae5a1..0feec434e20 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2005, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.96 2005/02/22 04:40:52 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.97 2005/04/28 13:09:59 momjian Exp $
  */
 #include "postgres_fe.h"
 #include "common.h"
@@ -941,11 +941,13 @@ PrintQueryResults(PGresult *results)
 bool
 SendQuery(const char *query)
 {
-	PGresult   *results;
-	TimevalStruct before,
-				after;
-	bool		OK;
-
+	PGresult 	*results;
+	TimevalStruct before, after;
+	bool OK, on_error_rollback_savepoint = false;
+	PGTransactionStatusType transaction_status;
+	static bool		on_error_rollback_warning = false;
+	const char *rollback_str;
+	
 	if (!pset.db)
 	{
 		psql_error("You are currently not connected to a database.\n");
@@ -973,7 +975,9 @@ SendQuery(const char *query)
 
 	SetCancelConn();
 
-	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE &&
+	transaction_status = PQtransactionStatus(pset.db);
+
+	if (transaction_status == PQTRANS_IDLE &&
 		!GetVariableBool(pset.vars, "AUTOCOMMIT") &&
 		!command_no_begin(query))
 	{
@@ -987,6 +991,33 @@ SendQuery(const char *query)
 		}
 		PQclear(results);
 	}
+	else if (transaction_status == PQTRANS_INTRANS &&
+			 (rollback_str = GetVariable(pset.vars, "ON_ERROR_ROLLBACK")) != NULL &&
+			 /* !off and !interactive is 'on' */
+			 pg_strcasecmp(rollback_str, "off") != 0 &&
+			 (pset.cur_cmd_interactive ||
+			  pg_strcasecmp(rollback_str, "interactive") != 0))
+	{
+		if (on_error_rollback_warning == false && pset.sversion < 80000)
+		{
+			fprintf(stderr, _("The server version (%d) does not support savepoints for ON_ERROR_ROLLBACK.\n"),
+				pset.sversion);
+			on_error_rollback_warning = true;
+		}
+		else
+		{
+			results = PQexec(pset.db, "SAVEPOINT pg_psql_temporary_savepoint");
+			if (PQresultStatus(results) != PGRES_COMMAND_OK)
+			{
+				psql_error("%s", PQerrorMessage(pset.db));
+				PQclear(results);
+				ResetCancelConn();
+				return false;
+			}
+			PQclear(results);
+			on_error_rollback_savepoint = true;
+		}
+	}
 
 	if (pset.timing)
 		GETTIMEOFDAY(&before);
@@ -1005,6 +1036,41 @@ SendQuery(const char *query)
 
 	PQclear(results);
 
+	/* If we made a temporary savepoint, possibly release/rollback */
+	if (on_error_rollback_savepoint)
+	{
+		transaction_status = PQtransactionStatus(pset.db);
+
+		/* We always rollback on an error */
+		if (transaction_status == PQTRANS_INERROR)
+			results = PQexec(pset.db, "ROLLBACK TO pg_psql_temporary_savepoint");
+		/* If they are no longer in a transaction, then do nothing */
+		else if (transaction_status != PQTRANS_INTRANS)
+			results = NULL;
+		else
+		{
+			/* 
+			 *	Do nothing if they are messing with savepoints themselves:
+			 *	If the user did RELEASE or ROLLBACK, our savepoint is gone.
+			 *	If they issued a SAVEPOINT, releasing ours would remove theirs.
+			 */
+			if (strcmp(PQcmdStatus(results), "SAVEPOINT") == 0 ||
+				strcmp(PQcmdStatus(results), "RELEASE") == 0 ||
+				strcmp(PQcmdStatus(results), "ROLLBACK") ==0)
+				results = NULL;
+			else
+				results = PQexec(pset.db, "RELEASE pg_psql_temporary_savepoint");
+		}
+		if (PQresultStatus(results) != PGRES_COMMAND_OK)
+		{
+			psql_error("%s", PQerrorMessage(pset.db));
+			PQclear(results);
+			ResetCancelConn();
+			return false;
+		}
+		PQclear(results);
+	}
+
 	/* Possible microtiming output */
 	if (OK && pset.timing)
 		printf(_("Time: %.3f ms\n"), DIFF_MSEC(&after, &before));
-- 
GitLab