New parameter RollbackError to control rollback behavior on error

Started by Michael Paquieralmost 12 years ago13 messages
#1Michael Paquier
michael.paquier@gmail.com
1 attachment(s)

Hi all,

The behavior of rollback when an error occurs on an handle is
controlled by extending Protocol with "$PROTNUM-[0|1|2]" where:
- 0 = let the application handle rollbacks
- 1 = rollback whole transaction when an error occurs
- 2 = rollback only statement that failed
Using such an extension is somewhat awkward as a single string is used
for two settings... The proposed attached patch adds a new parameter
called RollbackError that allows to control the behavior of rollback
on error with a different parameter.

For backward-compatibility purposes, this patch does not break the old
grammar of Protocol: it just gives the priority to RollbackError if
both Protocol and RollbackError are set for a connection. Regression
tests to test RollbackError and combinations of RollbackError/Protocol
are added in the patch in the existing test error-rollback (which has
needed some refactoring, older tests are not impacted). Docs are
included as well.

I thought first about including that in my cleanup work for 9.4, but
as this actually does not break the driver it may be worth adding it
directly to master, explaining the patch attached here. Comments
welcome. Note that if there are objections I do not mind adding that
for the work that would be merged later to 9.4 builds.

Regards,
--
Michael

Attachments:

20140326_error_rollback.patchtext/plain; charset=US-ASCII; name=20140326_error_rollback.patchDownload
diff --git a/connection.c b/connection.c
index 8601cb7..9407619 100644
--- a/connection.c
+++ b/connection.c
@@ -288,7 +288,8 @@ CC_conninfo_init(ConnInfo *conninfo, UInt4 option)
 	conninfo->bytea_as_longvarbinary = -1;
 	conninfo->use_server_side_prepare = -1;
 	conninfo->lower_case_identifier = -1;
-	conninfo->rollback_on_error = -1;
+	conninfo->rollback_error_value = -1;
+	conninfo->is_rollback_error = -1;
 	conninfo->force_abbrev_connstr = -1;
 	conninfo->bde_environment = -1;
 	conninfo->fake_mss = -1;
@@ -341,7 +342,8 @@ CC_copy_conninfo(ConnInfo *ci, const ConnInfo *sci)
 	CORR_VALCPY(bytea_as_longvarbinary);
 	CORR_VALCPY(use_server_side_prepare);
 	CORR_VALCPY(lower_case_identifier);
-	CORR_VALCPY(rollback_on_error);
+	CORR_VALCPY(rollback_error_value);
+	CORR_VALCPY(is_rollback_error);
 	CORR_VALCPY(force_abbrev_connstr);
 	CORR_VALCPY(bde_environment);
 	CORR_VALCPY(fake_mss);
@@ -2733,7 +2735,7 @@ CC_send_query_append(ConnectionClass *self, const char *query, QueryInfo *qi, UD
 	BOOL	ignore_abort_on_conn = ((flag & IGNORE_ABORT_ON_CONN) != 0),
 		create_keyset = ((flag & CREATE_KEYSET) != 0),
 		issue_begin = ((flag & GO_INTO_TRANSACTION) != 0 && !CC_is_in_trans(self)),
-		rollback_on_error, query_rollback, end_with_commit;
+		rollback_error_value, query_rollback, end_with_commit;
 
 	const char	*wq;
 	char		swallow, *ptr;
@@ -2844,13 +2846,13 @@ CC_send_query_append(ConnectionClass *self, const char *query, QueryInfo *qi, UD
 		return res;
 	}
 
-	rollback_on_error = (flag & ROLLBACK_ON_ERROR) != 0;
+	rollback_error_value = (flag & ROLLBACK_ON_ERROR) != 0;
 	end_with_commit = (flag & END_WITH_COMMIT) != 0;
 #define	return DONT_CALL_RETURN_FROM_HERE???
 	consider_rollback = (issue_begin || (CC_is_in_trans(self) && !CC_is_in_error_trans(self)) || strnicmp(query, "begin", 5) == 0);
-	if (rollback_on_error)
-		rollback_on_error = consider_rollback;
-	query_rollback = (rollback_on_error && !end_with_commit && PG_VERSION_GE(self, 8.0));
+	if (rollback_error_value)
+		rollback_error_value = consider_rollback;
+	query_rollback = (rollback_error_value && !end_with_commit && PG_VERSION_GE(self, 8.0));
 	if (!query_rollback && consider_rollback && !end_with_commit)
 	{
 		if (stmt)
@@ -3341,7 +3343,7 @@ cleanup:
 		CC_on_abort(self, CONN_DEAD);
 		retres = NULL;
 	}
-	if (rollback_on_error && CC_is_in_trans(self) && !discard_next_savepoint)
+	if (rollback_error_value && CC_is_in_trans(self) && !discard_next_savepoint)
 	{
 		char	cmd[64];
 
diff --git a/connection.h b/connection.h
index e92ff61..453ed5b 100644
--- a/connection.h
+++ b/connection.h
@@ -287,7 +287,16 @@ typedef struct
 	char		database[MEDIUM_REGISTRY_LEN];
 	char		username[MEDIUM_REGISTRY_LEN];
 	pgNAME		password;
+
+	/*
+	 * Protocol number, this can be used as well to define the behavior
+	 * of rollback in case of error.
+	 */
 	char		protocol[SMALL_REGISTRY_LEN];
+
+	/* Value of RollbackError */
+	char		rollback_error[SMALL_REGISTRY_LEN];
+
 	char		port[SMALL_REGISTRY_LEN];
 	char		sslmode[16];
 	char		onlyread[SMALL_REGISTRY_LEN];
@@ -308,7 +317,19 @@ typedef struct
 	signed char	bytea_as_longvarbinary;
 	signed char	use_server_side_prepare;
 	signed char	lower_case_identifier;
-	signed char	rollback_on_error;
+
+	/*
+	 * Control behavior of rollback on error.
+	 *
+	 * rollback_error_value is used to store the value given by either
+	 * Protovol (as an extension of the protocol number) or RollbackError.
+	 * is_rollback_error is set to true if the value set is obtained with
+	 * RollbackError and not as an extension of Protocol. Priority is given
+	 * to RollbackError if both Protocol and RollbackError are set.
+	 */
+	signed char	rollback_error_value;
+	signed char	is_rollback_error;
+
 	signed char	force_abbrev_connstr;
 	signed char	bde_environment;
 	signed char	fake_mss;
diff --git a/dlg_specific.c b/dlg_specific.c
index 8e051af..ff6066b 100644
--- a/dlg_specific.c
+++ b/dlg_specific.c
@@ -229,16 +229,11 @@ inolog("force_abbrev=%d abbrev=%d\n", ci->force_abbrev_connstr, abbrev);
 inolog("hlen=%d", hlen);
 	if (!abbrev)
 	{
-		char	protocol_and[16];
-
-		if (ci->rollback_on_error >= 0)
-			snprintf(protocol_and, sizeof(protocol_and), "%s-%d", ci->protocol, ci->rollback_on_error);
-		else
-			strncpy_null(protocol_and, ci->protocol, sizeof(protocol_and));
 		olen = snprintf(&connect_string[hlen], nlen, ";"
 			INI_SSLMODE "=%s;"
 			INI_READONLY "=%s;"
 			INI_PROTOCOL "=%s;"
+			INI_ROLLBACK_ON_ERROR "=%d;"
 			INI_FAKEOIDINDEX "=%s;"
 			INI_SHOWOIDCOLUMN "=%s;"
 			INI_ROWVERSIONING "=%s;"
@@ -276,7 +271,8 @@ inolog("hlen=%d", hlen);
 #endif /* _HANDLE_ENLIST_IN_DTC_ */
 			,ci->sslmode
 			,ci->onlyread
-			,protocol_and
+			,ci->protocol
+			,ci->rollback_error_value
 			,ci->fake_oid_index
 			,ci->show_oid_column
 			,ci->row_versioning
@@ -408,22 +404,22 @@ inolog("hlen=%d", hlen);
 				ci->int8_as,
 				ci->drivers.extra_systable_prefixes,
 				EFFECTIVE_BIT_COUNT, flag);
-		if (olen < nlen && (PROTOCOL_74(ci) || ci->rollback_on_error >= 0))
+		if (olen < nlen && (PROTOCOL_74(ci) || ci->rollback_error_value >= 0))
 		{
 			hlen = strlen(connect_string);
 			nlen = MAX_CONNECT_STRING - hlen;
 			/*
 			 * The PROTOCOL setting must be placed after CX flag
 			 * so that this option can override the CX setting.
+			 * Complete it with RollbackError.
 			 */
-			if (ci->rollback_on_error >= 0)
+			if (ci->rollback_error_value >= 0)
 				olen = snprintf(&connect_string[hlen], nlen, ";"
-				ABBR_PROTOCOL "=%s-%d",
-				ci->protocol, ci->rollback_on_error);
+					ABBR_PROTOCOL "=%s;" ABBR_ROLLBACK_ON_ERROR "=%d",
+					ci->protocol, ci->rollback_error_value);
 			else
 				olen = snprintf(&connect_string[hlen], nlen, ";"
-				ABBR_PROTOCOL "=%s",
-				ci->protocol);
+					ABBR_PROTOCOL "=%s", ci->protocol);
 		}
 	}
 	if (olen < nlen)
@@ -541,22 +537,33 @@ copyAttributes(ConnInfo *ci, const char *attribute, const char *value)
 	else if (stricmp(attribute, INI_PROTOCOL) == 0 || stricmp(attribute, ABBR_PROTOCOL) == 0)
 	{
 		char	*ptr;
-
 		ptr = strchr(value, '-');
-		if (ptr)
+
+		/*
+		 * Copy value controlling rollback on error if an extension of
+		 * Protocol is found and if it is not set already by RollbackError.
+		 */
+		if (ptr && ci->is_rollback_error < 0)
 		{
 			if ('-' != *value)
 			{
 				*ptr = '\0';
 				strcpy(ci->protocol, value);
 			}
-			ci->rollback_on_error = atoi(ptr + 1);
-			mylog("rollback_on_error=%d\n", ci->rollback_on_error);
+			ci->rollback_error_value = atoi(ptr + 1);
+			mylog("rollback_error_value=%d\n", ci->rollback_error_value);
 		}
 		else
 			strcpy(ci->protocol, value);
 	}
 
+	else if (stricmp(attribute, INI_ROLLBACK_ON_ERROR) == 0 ||
+			 stricmp(attribute, ABBR_ROLLBACK_ON_ERROR) == 0)
+	{
+		ci->rollback_error_value = atoi(value);
+		ci->is_rollback_error = 0;
+	}
+
 	else if (stricmp(attribute, INI_SHOWOIDCOLUMN) == 0 || stricmp(attribute, ABBR_SHOWOIDCOLUMN) == 0)
 		strcpy(ci->show_oid_column, value);
 
@@ -663,7 +670,7 @@ copyAttributes(ConnInfo *ci, const char *attribute, const char *value)
 	else
 		found = FALSE;
 
-	mylog("%s: DSN='%s',server='%s',dbase='%s',user='%s',passwd='%s',port='%s',onlyread='%s',protocol='%s',conn_settings='%s',disallow_premature=%d)\n", func, ci->dsn, ci->server, ci->database, ci->username, NAME_IS_VALID(ci->password) ? "xxxxx" : "", ci->port, ci->onlyread, ci->protocol, ci->conn_settings, ci->disallow_premature);
+	mylog("%s: DSN='%s',server='%s',dbase='%s',user='%s',passwd='%s',port='%s',onlyread='%s',protocol='%s',rollback_error='%s',conn_settings='%s',disallow_premature=%d)\n", func, ci->dsn, ci->server, ci->database, ci->username, NAME_IS_VALID(ci->password) ? "xxxxx" : "", ci->port, ci->onlyread, ci->protocol, ci->rollback_error, ci->conn_settings, ci->disallow_premature);
 
 	return found;
 }
@@ -893,21 +900,45 @@ getDSNinfo(ConnInfo *ci, char overwrite)
 	if (ci->show_system_tables[0] == '\0' || overwrite)
 		SQLGetPrivateProfileString(DSN, INI_SHOWSYSTEMTABLES, "", ci->show_system_tables, sizeof(ci->show_system_tables), ODBC_INI);
 
+	/*
+	 * Set up value for Protocol. Error on rollback
+	 */
 	if (ci->protocol[0] == '\0' || overwrite)
 	{
 		char	*ptr;
 		SQLGetPrivateProfileString(DSN, INI_PROTOCOL, "", ci->protocol, sizeof(ci->protocol), ODBC_INI);
-		if (ptr = strchr(ci->protocol, '-'), NULL != ptr)
+
+		/*
+		 * Check if behavior of rollback on error is set as an extension
+		 * of Protocol. Note that if this parameter has already been set
+		 * by RollbackError, we simply ignore the value set here.
+		 */
+		if (ptr = strchr(ci->protocol, '-'), NULL != ptr &&
+			ci->is_rollback_error < 0)
 		{
 			*ptr = '\0';
-			if (overwrite || ci->rollback_on_error < 0)
+			if (overwrite || ci->rollback_error_value < 0)
 			{
-				ci->rollback_on_error = atoi(ptr + 1);
-				mylog("rollback_on_error=%d\n", ci->rollback_on_error);
+				ci->rollback_error_value = atoi(ptr + 1);
+				mylog("rollback_error_value=%d\n", ci->rollback_error_value);
 			}
 		}
 	}
 
+	/*
+	 * Set up value for RollbackError. This gets priority over Protocol.
+	 */
+	if (ci->rollback_error[0] == '\0' || overwrite)
+	{
+		if ((ci->rollback_error_value < 0 && ci->is_rollback_error < 0)
+			|| overwrite)
+		{
+			ci->rollback_error_value = atoi(ci->rollback_error);
+			ci->is_rollback_error = 0;
+			mylog("rollback_error_value=%d\n", ci->rollback_error_value);
+		}
+	}
+
 	if (NAME_IS_NULL(ci->conn_settings) || overwrite)
 	{
 		SQLGetPrivateProfileString(DSN, INI_CONNSETTINGS, "", encoded_item, sizeof(encoded_item), ODBC_INI);
@@ -1205,12 +1236,14 @@ writeDSNinfo(const ConnInfo *ci)
 								 ci->show_system_tables,
 								 ODBC_INI);
 
-	if (ci->rollback_on_error >= 0)
-		sprintf(temp, "%s-%d", ci->protocol, ci->rollback_on_error);
-	else
-		strncpy_null(temp, ci->protocol, sizeof(temp));
 	SQLWritePrivateProfileString(DSN,
 								 INI_PROTOCOL,
+								 ci->protocol,
+								 ODBC_INI);
+
+	sprintf(temp, "%d", ci->rollback_error_value);
+	SQLWritePrivateProfileString(DSN,
+								 INI_ROLLBACK_ON_ERROR,
 								 temp,
 								 ODBC_INI);
 
diff --git a/dlg_specific.h b/dlg_specific.h
index c73bb64..d2d1575 100644
--- a/dlg_specific.h
+++ b/dlg_specific.h
@@ -75,6 +75,8 @@ extern "C" {
 #define ABBR_COMMLOG			"B3"
 #define INI_PROTOCOL			"Protocol"	/* What protocol (6.2) */
 #define ABBR_PROTOCOL			"A1"
+#define INI_ROLLBACK_ON_ERROR	"RollbackError" /* Rollback on error */
+#define ABBR_ROLLBACK_ON_ERROR	"AA"
 #define INI_OPTIMIZER			"Optimizer"	/* Use backend genetic
 							 * optimizer */
 #define ABBR_OPTIMIZER			"B4"
diff --git a/docs/config-opt.html b/docs/config-opt.html
index 6673bc2..bba3266 100644
--- a/docs/config-opt.html
+++ b/docs/config-opt.html
@@ -146,6 +146,17 @@
 	</TR>
 	<TR>
 		<TD WIDTH=38%>
+			Rollback on error
+		</TD>
+		<TD WIDTH=31%>
+			RollbackError
+		</TD>
+		<TD WIDTH=31%>
+			AA
+		</TD>
+	</TR>
+	<TR>
+		<TD WIDTH=38%>
 			Backend enetic optimizer
 		</TD>
 		<TD WIDTH=31%>
diff --git a/docs/config.html b/docs/config.html
index cb1c326..b0b727a 100644
--- a/docs/config.html
+++ b/docs/config.html
@@ -238,23 +238,31 @@ with 6.4 and higher backends.<br />&nbsp;</li>
 with 7.4 and higher backends.<br />&nbsp;</li>
 </ul></li>
 
-<li><b>Level of rollback on errors:</b> Specifies what to rollback should an
-error occur.<br />&nbsp;
+<li><b>RollbackError</b>: Level of rollback on errors, specifies what to
+rollback should an error occur.<br />&nbsp;
 
 <ul>
 <li><i>Nop(0):</i> Don't rollback anything and let the application handle the
-error.<br />&nbsp;</li>
-
-<li><i>Transaction(1):</i> Rollback the entire transaction.<br />&nbsp;</li>
-
-<li><i>Statement(2):</i> Rollback the statement.<br />&nbsp;</li>
-<br>
-<b>Notes in a setup: This specification is set up with a PROTOCOL option parameter.</b><br><br>
-PROTOCOL=[6.2|6.3|6.4|7.4][-(0|1|2)]<br>
-default value is a sentence unit (it is a transaction unit before 8.0).<br>
-<br>
-
-</ul></li>
+error.<br /></li>
+
+<li><i>Transaction(1):</i> Rollback the entire transaction.<br /></li>
+
+<li><i>Statement(2):</i> Rollback the statement.<br /></li>
+<b>Notes:</b>
+  <ul>
+    <li>
+      This parameter can be set as an extension of PROTOCOL option
+      parameter.<br />
+      PROTOCOL=[6.2|6.3|6.4|7.4][-(0|1|2)]<br />
+      Default value is a sentence unit (it is a transaction unit
+      before 8.0).<br />
+    </li>
+    <li>
+      This parameter can be set with ROLLBACKERROR as well. This takes
+      precedence on PROTOCOL.<bt />
+    </li>
+  </ul>
+</ul>.<br />&nbsp;</li>
 
 
 <li><b>OID Options:</b><br />&nbsp;
diff --git a/execute.c b/execute.c
index 1cc4f04..d502df2 100644
--- a/execute.c
+++ b/execute.c
@@ -652,7 +652,7 @@ inolog("%s:%p->internal=%d\n", func, stmt, stmt->internal);
 	if (conn)
 		ci = &conn->connInfo;
 	ret = 0;
-	if (!ci || ci->rollback_on_error < 0) /* default */
+	if (!ci || ci->rollback_error_value < 0) /* default */
 	{
 		if (conn && PG_VERSION_GE(conn, 8.0))
 			ret = 2; /* statement rollback */
@@ -661,7 +661,7 @@ inolog("%s:%p->internal=%d\n", func, stmt, stmt->internal);
 	}
 	else
 	{
-		ret = ci->rollback_on_error;
+		ret = ci->rollback_error_value;
 		if (2 == ret && PG_VERSION_LT(conn, 8.0))
 			ret = 1;
 	}
diff --git a/test/expected/error-rollback.out b/test/expected/error-rollback.out
index 436a328..da9cdf3 100644
--- a/test/expected/error-rollback.out
+++ b/test/expected/error-rollback.out
@@ -1,5 +1,5 @@
 \! ./src/error-rollback-test
-Test for rollback protocol 0
+Test for Protocol=7.4-0
 connected
 Executing query that will succeed
 Executing query that will fail
@@ -10,7 +10,7 @@ Executing query that will succeed
 Result set:
 1
 disconnecting
-Test for rollback protocol 1
+Test for Protocol=7.4-1
 connected
 Executing query that will succeed
 Executing query that will fail
@@ -20,7 +20,71 @@ Executing query that will succeed
 Result set:
 1
 disconnecting
-Test for rollback protocol 2
+Test for Protocol=7.4-2
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Executing query that will succeed
+Result set:
+1
+1
+disconnecting
+Test for RollbackError=0
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Rolling back with SQLEndTran
+Executing query that will succeed
+Result set:
+1
+disconnecting
+Test for RollbackError=1
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Executing query that will succeed
+Result set:
+1
+disconnecting
+Test for RollbackError=2
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Executing query that will succeed
+Result set:
+1
+1
+disconnecting
+Test for RollbackError=0;Protocol=7.4-2
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Rolling back with SQLEndTran
+Executing query that will succeed
+Result set:
+1
+disconnecting
+Test for RollbackError=1;Protocol=7.4-0
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Executing query that will succeed
+Result set:
+1
+disconnecting
+Test for RollbackError=2;Protocol=7.4-1
 connected
 Executing query that will succeed
 Executing query that will fail
diff --git a/test/src/error-rollback-test.c b/test/src/error-rollback-test.c
index 01c0f0c..bbff007 100644
--- a/test/src/error-rollback-test.c
+++ b/test/src/error-rollback-test.c
@@ -142,18 +142,19 @@ error_rollback_print(void)
 	print_result(hstmt);
 }
 
-int
-main(int argc, char **argv)
+/*
+ * Test for rollback protocol 0.
+ *
+ * Additional options can be specified to play with combinations of
+ * Protocol and RollbackError. With protocol 0, it is the responsability
+ * of application to issue rollbacks.
+ */
+void
+error_rollback_test_0(char *options)
 {
 	SQLRETURN rc;
 
-	/*
-	 * Test for protocol at 0.
-	 * Do nothing when error occurs and let application do necessary
-	 * ROLLBACK on error.
-	 */
-	printf("Test for rollback protocol 0\n");
-	error_rollback_init("Protocol=7.4-0");
+	error_rollback_init(options);
 
 	/* Insert a row correctly */
 	error_rollback_exec_success();
@@ -181,17 +182,25 @@ main(int argc, char **argv)
 
 	/* Clean up */
 	error_rollback_clean();
+}
 
-	/*
-	 * Test for rollback protocol 1
-	 * In case of an error rollback the entire transaction.
-	 */
-	printf("Test for rollback protocol 1\n");
-	error_rollback_init("Protocol=7.4-1");
+/*
+ * Test for rollback protocols 1 or 2
+ *
+ * Options can be specified to manipulate the protocol used with Protocol
+ * and RollbackError. When priting results, there should be one row for
+ * protocol 1 as rollback is done for an entire transaction is case of
+ * failure. There will be two rows for protocol 2 as tollback is issued
+ * for each statement.
+ */
+void
+error_rollback_test_12(char *options)
+{
+	error_rollback_init(options);
 
 	/*
-	 * Insert a row, trigger an error, and re-insert a row. Only one
-	 * row should be visible here.
+	 * Insert a row, trigger an error, and re-insert a row. Depending
+	 * on the protocol, one or two rows should be visible.
 	 */
 	error_rollback_exec_success();
 	error_rollback_exec_failure();
@@ -200,23 +209,40 @@ main(int argc, char **argv)
 
 	/* Clean up */
 	error_rollback_clean();
+}
 
+int
+main(int argc, char **argv)
+{
 	/*
-	 * Test for rollback protocol 2
-	 * In the case of an error rollback only the latest statement.
+	 * Test for Protocol only
 	 */
-	printf("Test for rollback protocol 2\n");
-	error_rollback_init("Protocol=7.4-2");
+	printf("Test for Protocol=7.4-0\n");
+	error_rollback_test_0("Protocol=7.4-0");
+	printf("Test for Protocol=7.4-1\n");
+	error_rollback_test_12("Protocol=7.4-1");
+	printf("Test for Protocol=7.4-2\n");
+	error_rollback_test_12("Protocol=7.4-2");
 
 	/*
-	 * Similarly to previous case, do insert, error and insert. This
-	 * time two rows should be visible.
+	 * Now do the same tests as previously, but this time for
+	 * RollbackError.
 	 */
-	error_rollback_exec_success();
-	error_rollback_exec_failure();
-	error_rollback_exec_success();
-	error_rollback_print();
+	printf("Test for RollbackError=0\n");
+	error_rollback_test_0("RollbackError=0");
+	printf("Test for RollbackError=1\n");
+	error_rollback_test_12("RollbackError=1");
+	printf("Test for RollbackError=2\n");
+	error_rollback_test_12("RollbackError=2");
 
-	/* Clean up */
-	error_rollback_clean();
+	/*
+	 * Combinations of RollbackError and Protocol, the former
+	 * should have the priority.
+	 */
+	printf("Test for RollbackError=0;Protocol=7.4-2\n");
+	error_rollback_test_0("RollbackError=0;Protocol=7.4-2");
+	printf("Test for RollbackError=1;Protocol=7.4-0\n");
+	error_rollback_test_12("RollbackError=1;Protocol=7.4-0\n"); /* 1 row */
+	printf("Test for RollbackError=2;Protocol=7.4-1\n");
+	error_rollback_test_12("RollbackError=2;Protocol=7.4-1\n"); /* 2 rows */
 }
#2Heikki Linnakangas
hlinnakangas@vmware.com
In reply to: Michael Paquier (#1)
Re: New parameter RollbackError to control rollback behavior on error

On 03/26/2014 08:39 AM, Michael Paquier wrote:

Hi all,

The behavior of rollback when an error occurs on an handle is
controlled by extending Protocol with "$PROTNUM-[0|1|2]" where:
- 0 = let the application handle rollbacks
- 1 = rollback whole transaction when an error occurs
- 2 = rollback only statement that failed
Using such an extension is somewhat awkward as a single string is used
for two settings... The proposed attached patch adds a new parameter
called RollbackError that allows to control the behavior of rollback
on error with a different parameter.

Great!

Since we're designing a new user interface for this, let's try to make
it as user-friendly as possible:

* I'm not too fond of the RollbackError name. It sounds like "an error
while rolling back". I googled around and found out that DataDirect's
proprietary driver has the same option, and they call it
TransactionErrorBehavior. See
http://blogs.datadirect.com/2013/07/solution-unexpected-postgres-current-transaction-aborted-error.html.

* Instead of using 0-2 as the values, let's give them descriptive names.
Something like "none", "RollbackTransaction", "RollbackStatement".
(actually, we'll probably want to also allow the integers, to keep the
connection string short, as there is a size limit on that)

I thought first about including that in my cleanup work for 9.4, but
as this actually does not break the driver it may be worth adding it
directly to master, explaining the patch attached here. Comments
welcome. Note that if there are objections I do not mind adding that
for the work that would be merged later to 9.4 builds.

Yeah, let's get this into the master branch before your big 9.4 cleanup
work.

- Heikki

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#1)
Re: New parameter RollbackError to control rollback behavior on error

On Wed, Mar 26, 2014 at 3:39 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

Hi all,

The behavior of rollback when an error occurs on an handle is
controlled by extending Protocol with "$PROTNUM-[0|1|2]"...

My apologies. This message was sent to the wrong mailing list and was
dedicated to odbc.
Once again sorry for that.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Hiroshi Inoue
inoue@tpf.co.jp
In reply to: Michael Paquier (#1)
Re: New parameter RollbackError to control rollback behavior on error

Hi Michael,

Isn't it an ODBC issue?

regards,
Hiroshi Inoue

(2014/03/26 15:39), Michael Paquier wrote:

Hi all,

The behavior of rollback when an error occurs on an handle is
controlled by extending Protocol with "$PROTNUM-[0|1|2]" where:
- 0 = let the application handle rollbacks
- 1 = rollback whole transaction when an error occurs
- 2 = rollback only statement that failed
Using such an extension is somewhat awkward as a single string is used
for two settings... The proposed attached patch adds a new parameter
called RollbackError that allows to control the behavior of rollback
on error with a different parameter.

For backward-compatibility purposes, this patch does not break the old
grammar of Protocol: it just gives the priority to RollbackError if
both Protocol and RollbackError are set for a connection. Regression
tests to test RollbackError and combinations of RollbackError/Protocol
are added in the patch in the existing test error-rollback (which has
needed some refactoring, older tests are not impacted). Docs are
included as well.

I thought first about including that in my cleanup work for 9.4, but
as this actually does not break the driver it may be worth adding it
directly to master, explaining the patch attached here. Comments
welcome. Note that if there are objections I do not mind adding that
for the work that would be merged later to 9.4 builds.

Regards,

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Michael Paquier
michael.paquier@gmail.com
In reply to: Heikki Linnakangas (#2)
Re: [HACKERS] New parameter RollbackError to control rollback behavior on error

(Moving back this thread to pgsql-odbc, so keeping a maximum of message history)

On Wed, Mar 26, 2014 at 5:53 PM, Heikki Linnakangas
<hlinnakangas@vmware.com> wrote:

On 03/26/2014 08:39 AM, Michael Paquier wrote:

The behavior of rollback when an error occurs on an handle is
controlled by extending Protocol with "$PROTNUM-[0|1|2]" where:
- 0 = let the application handle rollbacks
- 1 = rollback whole transaction when an error occurs
- 2 = rollback only statement that failed
Using such an extension is somewhat awkward as a single string is used
for two settings... The proposed attached patch adds a new parameter
called RollbackError that allows to control the behavior of rollback
on error with a different parameter.

Since we're designing a new user interface for this, let's try to make it as
user-friendly as possible:

* I'm not too fond of the RollbackError name. It sounds like "an error while
rolling back". I googled around and found out that DataDirect's proprietary
driver has the same option, and they call it TransactionErrorBehavior. See
http://blogs.datadirect.com/2013/07/solution-unexpected-postgres-current-transaction-aborted-error.html.

Yeah, sounds fine... I am not coming with a better name now.

* Instead of using 0-2 as the values, let's give them descriptive names.
Something like "none", "RollbackTransaction", "RollbackStatement".
(actually, we'll probably want to also allow the integers, to keep the
connection string short, as there is a size limit on that)

Good idea. I'd vote for using only the strings in the new parameter
for simplicity, and to mention in the docs to which integer value maps
each string. We could as well use MEDIUM_REGISTRY_LEN (256 characters)
to control the size limit of the new parameter string.

I thought first about including that in my cleanup work for 9.4, but
as this actually does not break the driver it may be worth adding it
directly to master, explaining the patch attached here. Comments
welcome. Note that if there are objections I do not mind adding that
for the work that would be merged later to 9.4 builds.

Yeah, let's get this into the master branch before your big 9.4 cleanup
work.

OK, so I'll hack a patch following those lines. I shall be able to get
that soon with regression tests.
Regards,
--
Michael

--
Sent via pgsql-odbc mailing list (pgsql-odbc@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-odbc

#6Inoue, Hiroshi
inoue@tpf.co.jp
In reply to: Michael Paquier (#5)
Re: Re: [HACKERS] New parameter RollbackError to control rollback behavior on error

(2014/03/27 20:55), Michael Paquier wrote:

(Moving back this thread to pgsql-odbc, so keeping a maximum of message history)

As for the patch in the original post,

Is there any difference between rollback_on_error and
rollback_error_value except the naming?
The length of connection strings seem to be increased at least 3 bytes.
Is it right?

regards,
Hiroshi Inoue

--
I am using the free version of SPAMfighter.
SPAMfighter has removed 7015 of my spam emails to date.
Get the free SPAMfighter here: http://www.spamfighter.com/len

Do you have a slow PC? Try a Free scan
http://www.spamfighter.com/SLOW-PCfighter?cid=sigen

--
Sent via pgsql-odbc mailing list (pgsql-odbc@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-odbc

#7Michael Paquier
michael.paquier@gmail.com
In reply to: Inoue, Hiroshi (#6)
Re: Re: [HACKERS] New parameter RollbackError to control rollback behavior on error

On Fri, Mar 28, 2014 at 12:53 PM, Inoue, Hiroshi <inoue@tpf.co.jp> wrote:

(2014/03/27 20:55), Michael Paquier wrote:

(Moving back this thread to pgsql-odbc, so keeping a maximum of message
history)

As for the patch in the original post,

Is there any difference between rollback_on_error and
rollback_error_value except the naming?

No differences. I just changed the name of this variable to be a
maximum consistent with the parameter introduced.

The length of connection strings seem to be increased at least 3 bytes.
Is it right?

Do you mean that with the introduction of the new parameter AA? If
yes, well I guess it is increased by 5 bytes ";AA=[0|1|2]".
--
Michael

--
Sent via pgsql-odbc mailing list (pgsql-odbc@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-odbc

#8Hiroshi Inoue
inoue@tpf.co.jp
In reply to: Michael Paquier (#7)
Re: Re: [HACKERS] New parameter RollbackError to control rollback behavior on error

(2014/03/28 14:37), Michael Paquier wrote:

On Fri, Mar 28, 2014 at 12:53 PM, Inoue, Hiroshi <inoue@tpf.co.jp> wrote:

(2014/03/27 20:55), Michael Paquier wrote:

(Moving back this thread to pgsql-odbc, so keeping a maximum of message
history)

As for the patch in the original post,

Is there any difference between rollback_on_error and
rollback_error_value except the naming?

No differences. I just changed the name of this variable to be a
maximum consistent with the parameter introduced.

There are some rollback_on_error in dlg_wingui.c.

The length of connection strings seem to be increased at least 3 bytes.
Is it right?

Do you mean that with the introduction of the new parameter AA? If
yes, well I guess it is increased by 5 bytes ";AA=[0|1|2]".

Isn't the difference between
A1=7.4;AA=[0|1|2] and A1=7.4-[0|1|2]
?
I introduced the latter only to skimp on 3 bytes.

regards,
Hiroshi Inoue

--
Sent via pgsql-odbc mailing list (pgsql-odbc@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-odbc

#9Michael Paquier
michael.paquier@gmail.com
In reply to: Hiroshi Inoue (#8)
Re: Re: [HACKERS] New parameter RollbackError to control rollback behavior on error

On Sat, Mar 29, 2014 at 7:16 AM, Hiroshi Inoue <inoue@tpf.co.jp> wrote:

(2014/03/28 14:37), Michael Paquier wrote:

On Fri, Mar 28, 2014 at 12:53 PM, Inoue, Hiroshi <inoue@tpf.co.jp> wrote:
No differences. I just changed the name of this variable to be a
maximum consistent with the parameter introduced.

There are some rollback_on_error in dlg_wingui.c.

Ouch, thanks.

The length of connection strings seem to be increased at least 3 bytes.
Is it right?

Do you mean that with the introduction of the new parameter AA? If
yes, well I guess it is increased by 5 bytes ";AA=[0|1|2]".

Isn't the difference between
A1=7.4;AA=[0|1|2] and A1=7.4-[0|1|2]
?
I introduced the latter only to skimp on 3 bytes.

Oh yes, sorry. Is it a problem though? Because if we switch to
string-only values for AA this could get longer by 10~20 bytes
depending on the parameter values.
--
Michael

--
Sent via pgsql-odbc mailing list (pgsql-odbc@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-odbc

#10Hiroshi Inoue
inoue@tpf.co.jp
In reply to: Michael Paquier (#9)
Re: Re: [HACKERS] New parameter RollbackError to control rollback behavior on error

(2014/03/29 7:23), Michael Paquier wrote:

On Sat, Mar 29, 2014 at 7:16 AM, Hiroshi Inoue <inoue@tpf.co.jp> wrote:

(2014/03/28 14:37), Michael Paquier wrote:

On Fri, Mar 28, 2014 at 12:53 PM, Inoue, Hiroshi <inoue@tpf.co.jp> wrote:
No differences. I just changed the name of this variable to be a
maximum consistent with the parameter introduced.

There are some rollback_on_error in dlg_wingui.c.

Ouch, thanks.

The length of connection strings seem to be increased at least 3 bytes.
Is it right?

Do you mean that with the introduction of the new parameter AA? If
yes, well I guess it is increased by 5 bytes ";AA=[0|1|2]".

Isn't the difference between
A1=7.4;AA=[0|1|2] and A1=7.4-[0|1|2]
?
I introduced the latter only to skimp on 3 bytes.

Oh yes, sorry. Is it a problem though? Because if we switch to
string-only values for AA this could get longer by 10~20 bytes
depending on the parameter values.

SQLDriverConnect() returns the complete connection strings when
requested. makeConnectString() in dlg_specific.c makes connection
strings which are acceptable for specified buffers.

regards,
Hiroshi Inoue

--
Sent via pgsql-odbc mailing list (pgsql-odbc@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-odbc

#11Michael Paquier
michael.paquier@gmail.com
In reply to: Heikki Linnakangas (#2)
1 attachment(s)
Re: New parameter RollbackError to control rollback behavior on error

On Wed, Mar 26, 2014 at 5:53 PM, Heikki Linnakangas
<hlinnakangas@vmware.com> wrote:

* I'm not too fond of the RollbackError name. It sounds like "an error while
rolling back". I googled around and found out that DataDirect's proprietary
driver has the same option, and they call it TransactionErrorBehavior. See
http://blogs.datadirect.com/2013/07/solution-unexpected-postgres-current-transaction-aborted-error.html.

* Instead of using 0-2 as the values, let's give them descriptive names.
Something like "none", "RollbackTransaction", "RollbackStatement".
(actually, we'll probably want to also allow the integers, to keep the
connection string short, as there is a size limit on that)

OK, I have been working more on that, giving the attached patch. The
parameter name is changed to TransactionErrorBehavior, able to use the
values "Statement", "Nop", "Transaction" and "Default". "Default"
corresponds to the default behavior, that is used to set the rollback
behavior depending on the Postgres version driver is connected with.
As of now, TransactionErrorBehavior does not accept integer values as
it makes the patch a bit more simple, this could be added with some
atoi calls in dlg_specific.c.

I have updated dlg_wingui.c as well. Patch has always its set of docs
and regression tests.

Regards,
--
Michael

Attachments:

20140331_error_rollback_v2.patchtext/plain; charset=US-ASCII; name=20140331_error_rollback_v2.patchDownload
diff --git a/connection.c b/connection.c
index 8601cb7..8361691 100644
--- a/connection.c
+++ b/connection.c
@@ -288,7 +288,7 @@ CC_conninfo_init(ConnInfo *conninfo, UInt4 option)
 	conninfo->bytea_as_longvarbinary = -1;
 	conninfo->use_server_side_prepare = -1;
 	conninfo->lower_case_identifier = -1;
-	conninfo->rollback_on_error = -1;
+	conninfo->transaction_error_value = TRANSACTION_ERROR_DEFAULT;
 	conninfo->force_abbrev_connstr = -1;
 	conninfo->bde_environment = -1;
 	conninfo->fake_mss = -1;
@@ -321,6 +321,7 @@ CC_copy_conninfo(ConnInfo *ci, const ConnInfo *sci)
 	CORR_STRCPY(username);
 	NAME_TO_NAME(ci->password, sci->password);
 	CORR_STRCPY(protocol);
+	CORR_STRCPY(transaction_error);
 	CORR_STRCPY(port);
 	CORR_STRCPY(sslmode);
 	CORR_STRCPY(onlyread);
@@ -341,7 +342,7 @@ CC_copy_conninfo(ConnInfo *ci, const ConnInfo *sci)
 	CORR_VALCPY(bytea_as_longvarbinary);
 	CORR_VALCPY(use_server_side_prepare);
 	CORR_VALCPY(lower_case_identifier);
-	CORR_VALCPY(rollback_on_error);
+	CORR_VALCPY(transaction_error_value);
 	CORR_VALCPY(force_abbrev_connstr);
 	CORR_VALCPY(bde_environment);
 	CORR_VALCPY(fake_mss);
@@ -2370,7 +2371,7 @@ int	CC_get_max_idlen(ConnectionClass *self)
 	{
 		QResultClass	*res;
 
-		res = CC_send_query(self, "show max_identifier_length", NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL);
+		res = CC_send_query(self, "show max_identifier_length", NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL);
 		if (QR_command_maybe_successful(res))
 			len = self->max_identifier_length = atoi(res->command);
 		QR_Destructor(res);
@@ -2539,7 +2540,7 @@ static void CC_clear_cursors(ConnectionClass *self, BOOL on_abort)
 				{
 					snprintf(cmd, sizeof(cmd), "MOVE 0 in \"%s\"", QR_get_cursor(res));
 					CONNLOCK_RELEASE(self);
-					wres = CC_send_query(self, cmd, NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL);
+					wres = CC_send_query(self, cmd, NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL);
 					QR_set_no_survival_check(res);
 					if (QR_command_maybe_successful(wres))
 						QR_set_permanent(res);
@@ -2733,7 +2734,7 @@ CC_send_query_append(ConnectionClass *self, const char *query, QueryInfo *qi, UD
 	BOOL	ignore_abort_on_conn = ((flag & IGNORE_ABORT_ON_CONN) != 0),
 		create_keyset = ((flag & CREATE_KEYSET) != 0),
 		issue_begin = ((flag & GO_INTO_TRANSACTION) != 0 && !CC_is_in_trans(self)),
-		rollback_on_error, query_rollback, end_with_commit;
+		rollback_error_value, query_rollback, end_with_commit;
 
 	const char	*wq;
 	char		swallow, *ptr;
@@ -2844,13 +2845,13 @@ CC_send_query_append(ConnectionClass *self, const char *query, QueryInfo *qi, UD
 		return res;
 	}
 
-	rollback_on_error = (flag & ROLLBACK_ON_ERROR) != 0;
+	rollback_error_value = (flag & TRANSACTION_ERROR) != 0;
 	end_with_commit = (flag & END_WITH_COMMIT) != 0;
 #define	return DONT_CALL_RETURN_FROM_HERE???
 	consider_rollback = (issue_begin || (CC_is_in_trans(self) && !CC_is_in_error_trans(self)) || strnicmp(query, "begin", 5) == 0);
-	if (rollback_on_error)
-		rollback_on_error = consider_rollback;
-	query_rollback = (rollback_on_error && !end_with_commit && PG_VERSION_GE(self, 8.0));
+	if (rollback_error_value)
+		rollback_error_value = consider_rollback;
+	query_rollback = (rollback_error_value && !end_with_commit && PG_VERSION_GE(self, 8.0));
 	if (!query_rollback && consider_rollback && !end_with_commit)
 	{
 		if (stmt)
@@ -3341,7 +3342,7 @@ cleanup:
 		CC_on_abort(self, CONN_DEAD);
 		retres = NULL;
 	}
-	if (rollback_on_error && CC_is_in_trans(self) && !discard_next_savepoint)
+	if (rollback_error_value && CC_is_in_trans(self) && !discard_next_savepoint)
 	{
 		char	cmd[64];
 
@@ -3819,10 +3820,10 @@ CC_lookup_lo(ConnectionClass *self)
 
 	if (PG_VERSION_GE(self, 7.4))
 		res = CC_send_query(self, "select oid, typbasetype from pg_type where typname = '"  PG_TYPE_LO_NAME "'",
-			NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL);
+			NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL);
 	else
 		res = CC_send_query(self, "select oid, 0 from pg_type where typname='" PG_TYPE_LO_NAME "'",
-			NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL);
+			NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL);
 	if (QR_command_maybe_successful(res) && QR_get_num_cached_tuples(res) > 0)
 	{
 		OID	basetype;
@@ -4009,7 +4010,7 @@ CC_get_current_schema(ConnectionClass *conn)
 	{
 		QResultClass	*res;
 
-		if (res = CC_send_query(conn, "select current_schema()", NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL), QR_command_maybe_successful(res))
+		if (res = CC_send_query(conn, "select current_schema()", NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL), QR_command_maybe_successful(res))
 		{
 			if (QR_get_num_total_tuples(res) == 1)
 				conn->current_schema = strdup(QR_get_value_backend_text(res, 0, 0));
@@ -4124,7 +4125,7 @@ int	CC_discard_marked_objects(ConnectionClass *conn)
 			snprintf(cmd, sizeof(cmd), "DEALLOCATE \"%s\"", pname + 1);
 		else
 			snprintf(cmd, sizeof(cmd), "CLOSE \"%s\"", pname + 1);
-		res = CC_send_query(conn, cmd, NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL);
+		res = CC_send_query(conn, cmd, NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL);
 		QR_Destructor(res);
 		free(conn->discardp[i]);
 		conn->num_discardp--;
@@ -4133,6 +4134,47 @@ int	CC_discard_marked_objects(ConnectionClass *conn)
 	return 1;
 }
 
+/*
+ * Get text value of TransactionErrorBehavior.
+ */
+const char *
+CC_get_transaction_error_txt(int value)
+{
+	switch (value)
+	{
+		case TRANSACTION_ERROR_NOP:
+			return TRANSACTION_ERROR_TXT_NOP;
+		case TRANSACTION_ERROR_TRANSACTION:
+			return TRANSACTION_ERROR_TXT_TRANSACTION;
+		case TRANSACTION_ERROR_STATEMENT:
+			return TRANSACTION_ERROR_TXT_STATEMENT;
+		case TRANSACTION_ERROR_DEFAULT:
+		default:
+			return TRANSACTION_ERROR_TXT_DEFAULT;
+	}
+}
+
+/*
+ * Get integer value of TransactionErrorBehavior.
+ */
+int
+CC_get_transaction_error_int(const char *value)
+{
+	/* Leave if there is nothing */
+	if (value == NULL || value[0] == '\0')
+		return TRANSACTION_ERROR_DEFAULT;
+
+	if (strcmp(TRANSACTION_ERROR_TXT_NOP, value) == 0)
+		return TRANSACTION_ERROR_NOP;
+	else if (strcmp(TRANSACTION_ERROR_TXT_TRANSACTION, value) == 0)
+		return TRANSACTION_ERROR_TRANSACTION;
+	else if (strcmp(TRANSACTION_ERROR_TXT_STATEMENT, value) == 0)
+		return TRANSACTION_ERROR_STATEMENT;
+
+	/* Default case */
+	return TRANSACTION_ERROR_DEFAULT;
+}
+
 #ifdef USE_LIBPQ
 static int
 LIBPQ_connect(ConnectionClass *self)
diff --git a/connection.h b/connection.h
index e92ff61..7e56b79 100644
--- a/connection.h
+++ b/connection.h
@@ -287,7 +287,16 @@ typedef struct
 	char		database[MEDIUM_REGISTRY_LEN];
 	char		username[MEDIUM_REGISTRY_LEN];
 	pgNAME		password;
+
+	/*
+	 * Protocol number, this can be used as well to define the behavior
+	 * of rollback in case of error.
+	 */
 	char		protocol[SMALL_REGISTRY_LEN];
+
+	/* Value of TransactionErrorBehavior */
+	char		transaction_error[MEDIUM_REGISTRY_LEN];
+
 	char		port[SMALL_REGISTRY_LEN];
 	char		sslmode[16];
 	char		onlyread[SMALL_REGISTRY_LEN];
@@ -308,7 +317,16 @@ typedef struct
 	signed char	bytea_as_longvarbinary;
 	signed char	use_server_side_prepare;
 	signed char	lower_case_identifier;
-	signed char	rollback_on_error;
+
+	/*
+	 * Control behavior of transaction rollback on error.
+	 *
+	 * transaction_error_value is used to store the value given by either
+	 * Protocol (as an extension of the protocol number) or
+	 * TransactionErrorBehavior as a connection parameter.
+	 */
+	signed char	transaction_error_value;
+
 	signed char	force_abbrev_connstr;
 	signed char	bde_environment;
 	signed char	fake_mss;
@@ -377,6 +395,25 @@ typedef struct
 #define PG_VERSION_LE(conn, ver) (! PG_VERSION_GT(conn, ver))
 #define PG_VERSION_LT(conn, ver) (! PG_VERSION_GE(conn, ver))
 
+/*
+ * Variables used to control the behavior of rollback on error
+ * Default defines a behavior that defers depending on the backend
+ * version. Those numerical values can be used as an extension of
+ * Protocol, and map text values below.
+ */
+#define TRANSACTION_ERROR_DEFAULT		-1
+#define TRANSACTION_ERROR_NOP			0
+#define TRANSACTION_ERROR_TRANSACTION	1
+#define TRANSACTION_ERROR_STATEMENT		2
+/*
+ * Text versions of precedent parameters. They can be used by
+ * TransactionErrorBehavior.
+ */
+#define TRANSACTION_ERROR_TXT_DEFAULT		"Default"
+#define TRANSACTION_ERROR_TXT_NOP			"Nop"
+#define TRANSACTION_ERROR_TXT_TRANSACTION	"Transaction"
+#define TRANSACTION_ERROR_TXT_STATEMENT		"Statement"
+
 /*	This is used to store cached table information in the connection */
 struct col_info
 {
@@ -585,6 +622,12 @@ void		ProcessRollback(ConnectionClass *conn, BOOL undo, BOOL partial);
 const char	*CC_get_current_schema(ConnectionClass *conn);
 int             CC_mark_a_object_to_discard(ConnectionClass *conn, int type, const char *plan);
 int             CC_discard_marked_objects(ConnectionClass *conn);
+/*
+ * Procedures to get txt and numerical values of transaction rollback
+ * behavior.
+ */
+const char	*CC_get_transaction_error_txt(int value);
+int			CC_get_transaction_error_int(const char *value);
 
 int	handle_error_message(ConnectionClass *self, char *msgbuf, size_t buflen,
 		 char *sqlstate, const char *comment, QResultClass *res);
@@ -606,7 +649,7 @@ enum {
 	IGNORE_ABORT_ON_CONN	= 1L /* not set the error result even when  */
 	,CREATE_KEYSET		= (1L << 1) /* create keyset for updatable curosrs */
 	,GO_INTO_TRANSACTION	= (1L << 2) /* issue begin in advance */
-	,ROLLBACK_ON_ERROR	= (1L << 3) /* rollback the query when an error occurs */
+	,TRANSACTION_ERROR	= (1L << 3) /* rollback the query when an error occurs */
 	,END_WITH_COMMIT	= (1L << 4) /* the query ends with COMMMIT command */
 	,IGNORE_ROUND_TRIP	= (1L << 5) /* the commincation round trip time is considered ignorable */
 };
diff --git a/dlg_specific.c b/dlg_specific.c
index 8e051af..06be993 100644
--- a/dlg_specific.c
+++ b/dlg_specific.c
@@ -229,16 +229,11 @@ inolog("force_abbrev=%d abbrev=%d\n", ci->force_abbrev_connstr, abbrev);
 inolog("hlen=%d", hlen);
 	if (!abbrev)
 	{
-		char	protocol_and[16];
-
-		if (ci->rollback_on_error >= 0)
-			snprintf(protocol_and, sizeof(protocol_and), "%s-%d", ci->protocol, ci->rollback_on_error);
-		else
-			strncpy_null(protocol_and, ci->protocol, sizeof(protocol_and));
 		olen = snprintf(&connect_string[hlen], nlen, ";"
 			INI_SSLMODE "=%s;"
 			INI_READONLY "=%s;"
 			INI_PROTOCOL "=%s;"
+			INI_TRANSACTION_ERROR "=%s;"
 			INI_FAKEOIDINDEX "=%s;"
 			INI_SHOWOIDCOLUMN "=%s;"
 			INI_ROWVERSIONING "=%s;"
@@ -276,7 +271,8 @@ inolog("hlen=%d", hlen);
 #endif /* _HANDLE_ENLIST_IN_DTC_ */
 			,ci->sslmode
 			,ci->onlyread
-			,protocol_and
+			,ci->protocol
+			,CC_get_transaction_error_txt(ci->transaction_error_value)
 			,ci->fake_oid_index
 			,ci->show_oid_column
 			,ci->row_versioning
@@ -408,22 +404,25 @@ inolog("hlen=%d", hlen);
 				ci->int8_as,
 				ci->drivers.extra_systable_prefixes,
 				EFFECTIVE_BIT_COUNT, flag);
-		if (olen < nlen && (PROTOCOL_74(ci) || ci->rollback_on_error >= 0))
+		if (olen < nlen &&
+			(PROTOCOL_74(ci) ||
+			 ci->transaction_error_value != TRANSACTION_ERROR_DEFAULT))
 		{
 			hlen = strlen(connect_string);
 			nlen = MAX_CONNECT_STRING - hlen;
 			/*
 			 * The PROTOCOL setting must be placed after CX flag
 			 * so that this option can override the CX setting.
+			 * Complete it with TransactionErrorBehavior.
 			 */
-			if (ci->rollback_on_error >= 0)
+			if (ci->transaction_error_value != TRANSACTION_ERROR_DEFAULT)
 				olen = snprintf(&connect_string[hlen], nlen, ";"
-				ABBR_PROTOCOL "=%s-%d",
-				ci->protocol, ci->rollback_on_error);
+					ABBR_PROTOCOL "=%s;" ABBR_TRANSACTION_ERROR "=%s",
+					ci->protocol,
+					CC_get_transaction_error_txt(ci->transaction_error_value));
 			else
 				olen = snprintf(&connect_string[hlen], nlen, ";"
-				ABBR_PROTOCOL "=%s",
-				ci->protocol);
+					ABBR_PROTOCOL "=%s", ci->protocol);
 		}
 	}
 	if (olen < nlen)
@@ -541,22 +540,33 @@ copyAttributes(ConnInfo *ci, const char *attribute, const char *value)
 	else if (stricmp(attribute, INI_PROTOCOL) == 0 || stricmp(attribute, ABBR_PROTOCOL) == 0)
 	{
 		char	*ptr;
-
 		ptr = strchr(value, '-');
-		if (ptr)
+
+		/*
+		 * Copy value controlling rollback on error if an extension of
+		 * Protocol is found and if it is not set already by TransactionErrorBehavior.
+		 */
+		if (ptr && ci->transaction_error[0] == '\0')
 		{
 			if ('-' != *value)
 			{
 				*ptr = '\0';
 				strcpy(ci->protocol, value);
 			}
-			ci->rollback_on_error = atoi(ptr + 1);
-			mylog("rollback_on_error=%d\n", ci->rollback_on_error);
+			ci->transaction_error_value = atoi(ptr + 1);
+			mylog("transaction_error_value=%d\n", ci->transaction_error_value);
 		}
 		else
 			strcpy(ci->protocol, value);
 	}
 
+	else if (stricmp(attribute, INI_TRANSACTION_ERROR) == 0 ||
+			 stricmp(attribute, ABBR_TRANSACTION_ERROR) == 0)
+	{
+		strcpy(ci->transaction_error, value);
+		ci->transaction_error_value = CC_get_transaction_error_int(value);
+	}
+
 	else if (stricmp(attribute, INI_SHOWOIDCOLUMN) == 0 || stricmp(attribute, ABBR_SHOWOIDCOLUMN) == 0)
 		strcpy(ci->show_oid_column, value);
 
@@ -663,7 +673,7 @@ copyAttributes(ConnInfo *ci, const char *attribute, const char *value)
 	else
 		found = FALSE;
 
-	mylog("%s: DSN='%s',server='%s',dbase='%s',user='%s',passwd='%s',port='%s',onlyread='%s',protocol='%s',conn_settings='%s',disallow_premature=%d)\n", func, ci->dsn, ci->server, ci->database, ci->username, NAME_IS_VALID(ci->password) ? "xxxxx" : "", ci->port, ci->onlyread, ci->protocol, ci->conn_settings, ci->disallow_premature);
+	mylog("%s: DSN='%s',server='%s',dbase='%s',user='%s',passwd='%s',port='%s',onlyread='%s',protocol='%s',rollback_error='%s',conn_settings='%s',disallow_premature=%d)\n", func, ci->dsn, ci->server, ci->database, ci->username, NAME_IS_VALID(ci->password) ? "xxxxx" : "", ci->port, ci->onlyread, ci->protocol, CC_get_transaction_error_txt(ci->transaction_error_value), ci->conn_settings, ci->disallow_premature);
 
 	return found;
 }
@@ -893,21 +903,45 @@ getDSNinfo(ConnInfo *ci, char overwrite)
 	if (ci->show_system_tables[0] == '\0' || overwrite)
 		SQLGetPrivateProfileString(DSN, INI_SHOWSYSTEMTABLES, "", ci->show_system_tables, sizeof(ci->show_system_tables), ODBC_INI);
 
+	/*
+	 * Set up value for Protocol. Error on rollback
+	 */
 	if (ci->protocol[0] == '\0' || overwrite)
 	{
 		char	*ptr;
 		SQLGetPrivateProfileString(DSN, INI_PROTOCOL, "", ci->protocol, sizeof(ci->protocol), ODBC_INI);
-		if (ptr = strchr(ci->protocol, '-'), NULL != ptr)
+
+		/*
+		 * Check if behavior of rollback on error is set as an extension
+		 * of Protocol. Note that if this parameter has already been set
+		 * by TransactionErrorBehavior, we simply ignore the value set here.
+		 */
+		if (ptr = strchr(ci->protocol, '-'), NULL != ptr &&
+			ci->transaction_error[0] == '\0')
 		{
 			*ptr = '\0';
-			if (overwrite || ci->rollback_on_error < 0)
+			if (overwrite ||
+				ci->transaction_error_value == TRANSACTION_ERROR_DEFAULT)
 			{
-				ci->rollback_on_error = atoi(ptr + 1);
-				mylog("rollback_on_error=%d\n", ci->rollback_on_error);
+				ci->transaction_error_value = atoi(ptr + 1);
+				mylog("transaction_error_value=%d\n", ci->transaction_error_value);
 			}
 		}
 	}
 
+	/*
+	 * Set up value for TransactionErrorBehavior. This gets priority over Protocol.
+	 */
+	if (ci->transaction_error[0] == '\0' || overwrite)
+	{
+		if (ci->transaction_error_value == TRANSACTION_ERROR_DEFAULT
+			|| overwrite)
+		{
+			ci->transaction_error_value = CC_get_transaction_error_int(ci->transaction_error);
+			mylog("transaction_error_value=%d\n", ci->transaction_error_value);
+		}
+	}
+
 	if (NAME_IS_NULL(ci->conn_settings) || overwrite)
 	{
 		SQLGetPrivateProfileString(DSN, INI_CONNSETTINGS, "", encoded_item, sizeof(encoded_item), ODBC_INI);
@@ -1205,12 +1239,14 @@ writeDSNinfo(const ConnInfo *ci)
 								 ci->show_system_tables,
 								 ODBC_INI);
 
-	if (ci->rollback_on_error >= 0)
-		sprintf(temp, "%s-%d", ci->protocol, ci->rollback_on_error);
-	else
-		strncpy_null(temp, ci->protocol, sizeof(temp));
 	SQLWritePrivateProfileString(DSN,
 								 INI_PROTOCOL,
+								 ci->protocol,
+								 ODBC_INI);
+
+	sprintf(temp, "%s", CC_get_transaction_error_txt(ci->transaction_error_value));
+	SQLWritePrivateProfileString(DSN,
+								 INI_TRANSACTION_ERROR,
 								 temp,
 								 ODBC_INI);
 
diff --git a/dlg_specific.h b/dlg_specific.h
index c73bb64..d994423 100644
--- a/dlg_specific.h
+++ b/dlg_specific.h
@@ -75,6 +75,8 @@ extern "C" {
 #define ABBR_COMMLOG			"B3"
 #define INI_PROTOCOL			"Protocol"	/* What protocol (6.2) */
 #define ABBR_PROTOCOL			"A1"
+#define INI_TRANSACTION_ERROR	"TransactionErrorBehavior" /* Rollback on error */
+#define ABBR_TRANSACTION_ERROR	"AA"
 #define INI_OPTIMIZER			"Optimizer"	/* Use backend genetic
 							 * optimizer */
 #define ABBR_OPTIMIZER			"B4"
diff --git a/dlg_wingui.c b/dlg_wingui.c
index 072f542..394f7df 100644
--- a/dlg_wingui.c
+++ b/dlg_wingui.c
@@ -550,7 +550,7 @@ ds_options2Proc(HWND hdlg,
 				CheckDlgButton(hdlg, DS_PG74, 1);
 
 			/* How to issue Rollback */
-			switch (ci->rollback_on_error)
+			switch (ci->transaction_error_value)
 			{
 				case 0:
 					CheckDlgButton(hdlg, DS_NO_ROLLBACK, 1);
@@ -639,14 +639,14 @@ ds_options2Proc(HWND hdlg,
 
 					/* Issue rollback command on error */
 					if (IsDlgButtonChecked(hdlg, DS_NO_ROLLBACK))
-						ci->rollback_on_error = 0;
+						ci->transaction_error_value = TRANSACTION_ERROR_NONE;
 					else if (IsDlgButtonChecked(hdlg, DS_TRANSACTION_ROLLBACK))
-						ci->rollback_on_error = 1;
+						ci->transaction_error_value = TRANSACTION_ERROR_TRANSACTION;
 					else if (IsDlgButtonChecked(hdlg, DS_STATEMENT_ROLLBACK))
-						ci->rollback_on_error = 2;
+						ci->transaction_error_value = TRANSACTION_ERROR_STATEMENT;
 					else
 						/* legacy */
-						ci->rollback_on_error = 1;
+						ci->transaction_error_value = TRANSACTION_ERROR_DEFAULT;
 
 					/* Int8 As */
 					if (IsDlgButtonChecked(hdlg, DS_INT8_AS_DEFAULT))
diff --git a/docs/config-opt.html b/docs/config-opt.html
index 6673bc2..8e18086 100644
--- a/docs/config-opt.html
+++ b/docs/config-opt.html
@@ -146,6 +146,17 @@
 	</TR>
 	<TR>
 		<TD WIDTH=38%>
+			Rollback on error
+		</TD>
+		<TD WIDTH=31%>
+			TransactionErrorBehavior
+		</TD>
+		<TD WIDTH=31%>
+			AA
+		</TD>
+	</TR>
+	<TR>
+		<TD WIDTH=38%>
 			Backend enetic optimizer
 		</TD>
 		<TD WIDTH=31%>
diff --git a/docs/config.html b/docs/config.html
index cb1c326..8b29ba6 100644
--- a/docs/config.html
+++ b/docs/config.html
@@ -238,23 +238,35 @@ with 6.4 and higher backends.<br />&nbsp;</li>
 with 7.4 and higher backends.<br />&nbsp;</li>
 </ul></li>
 
-<li><b>Level of rollback on errors:</b> Specifies what to rollback should an
-error occur.<br />&nbsp;
+<li><b>TransactionErrorBehavior</b>: Level of rollback on errors, specifies what to
+rollback should an error occur.<br />&nbsp;
 
 <ul>
-<li><i>Nop(0):</i> Don't rollback anything and let the application handle the
-error.<br />&nbsp;</li>
-
-<li><i>Transaction(1):</i> Rollback the entire transaction.<br />&nbsp;</li>
-
-<li><i>Statement(2):</i> Rollback the statement.<br />&nbsp;</li>
-<br>
-<b>Notes in a setup: This specification is set up with a PROTOCOL option parameter.</b><br><br>
-PROTOCOL=[6.2|6.3|6.4|7.4][-(0|1|2)]<br>
-default value is a sentence unit (it is a transaction unit before 8.0).<br>
-<br>
-
-</ul></li>
+<li><i>Default:</i> Default value. Transaction rollback behavior is chosen
+depending on the database backend version: 'Transaction' for Postgres servers
+older than 8.0 and 'Statement' for servers newer than 8.1.<br /></li>
+
+<li><i>Nop:</i> Don't rollback anything and let the application handle the
+error.<br /></li>
+
+<li><i>Transaction:</i> Rollback the entire transaction.<br /></li>
+
+<li><i>Statement:</i> Rollback the statement.<br /></li>
+<b>Notes:</b>
+  <ul>
+    <li>
+      This parameter can be set as an extension of PROTOCOL option
+      parameter.<br />
+      PROTOCOL=[6.2|6.3|6.4|7.4][-(0|1|2)]<br />
+      Default value is a sentence unit (it is a transaction unit
+      before 8.0).<br />
+    </li>
+    <li>
+      This parameter can be set with TRANSACTIONROLLBACKBEHAVIOR as well.
+      This takes precedence on PROTOCOL.<bt />
+    </li>
+  </ul>
+</ul>.<br />&nbsp;</li>
 
 
 <li><b>OID Options:</b><br />&nbsp;
diff --git a/execute.c b/execute.c
index 1cc4f04..a732eb9 100644
--- a/execute.c
+++ b/execute.c
@@ -651,28 +651,31 @@ inolog("%s:%p->internal=%d\n", func, stmt, stmt->internal);
 	conn = SC_get_conn(stmt);
 	if (conn)
 		ci = &conn->connInfo;
-	ret = 0;
-	if (!ci || ci->rollback_on_error < 0) /* default */
+	ret = TRANSACTION_ERROR_NOP;
+	if (!ci || ci->transaction_error_value == TRANSACTION_ERROR_DEFAULT)
 	{
 		if (conn && PG_VERSION_GE(conn, 8.0))
-			ret = 2; /* statement rollback */
+			ret = TRANSACTION_ERROR_STATEMENT;
 		else
-			ret = 1; /* transaction rollback */
+			ret = TRANSACTION_ERROR_TRANSACTION;
 	}
 	else
 	{
-		ret = ci->rollback_on_error;
-		if (2 == ret && PG_VERSION_LT(conn, 8.0))
-			ret = 1;
+		ret = ci->transaction_error_value;
+		if (ret == TRANSACTION_ERROR_STATEMENT &&
+			PG_VERSION_LT(conn, 8.0))
+			ret = TRANSACTION_ERROR_TRANSACTION;
 	}
 	switch (ret)
 	{
-		case 1:
+		case TRANSACTION_ERROR_TRANSACTION:
 			SC_start_tc_stmt(stmt);
 			break;
-		case 2:
+		case TRANSACTION_ERROR_STATEMENT:
 			SC_start_rb_stmt(stmt);
 			break;
+		default:
+			break;
 	}
 	return	ret;
 }
diff --git a/info.c b/info.c
index 81bfe6d..a6c7274 100644
--- a/info.c
+++ b/info.c
@@ -4012,7 +4012,7 @@ getClientColumnName(ConnectionClass *conn, UInt4 relid, char *serverColumnName,
 	BOOL		continueExec = TRUE,
 				bError = FALSE;
 	QResultClass *res = NULL;
-	UWORD	flag = IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR;
+	UWORD	flag = IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR;
 
 	*nameAlloced = FALSE;
 	if (!conn->original_client_encoding || !isMultibyte(serverColumnName))
diff --git a/multibyte.c b/multibyte.c
index 2f7b594..512b9cc 100644
--- a/multibyte.c
+++ b/multibyte.c
@@ -444,7 +444,7 @@ CC_lookup_cs_new(ConnectionClass *self)
 	char		*encstr = NULL;
 	QResultClass	*res;
 
-	res = CC_send_query(self, "select pg_client_encoding()", NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL);
+	res = CC_send_query(self, "select pg_client_encoding()", NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL);
 	if (QR_command_maybe_successful(res))
 	{
 		const char *enc = QR_get_value_backend_text(res, 0, 0);
@@ -606,7 +606,7 @@ CC_lookup_characterset(ConnectionClass *self)
 			BOOL		cmd_success;
 
 			sprintf(query, "set client_encoding to '%s'", wenc);
-			res = CC_send_query(self, query, NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL);
+			res = CC_send_query(self, query, NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL);
 			cmd_success = QR_command_maybe_successful(res);
 			QR_Destructor(res);
 			CC_set_errornumber(self, errnum);
diff --git a/parse.c b/parse.c
index 5aa7723..a45ddf4 100644
--- a/parse.c
+++ b/parse.c
@@ -382,7 +382,7 @@ static BOOL CheckHasOids(StatementClass * stmt)
 	snprintf(query, sizeof(query),
 			 "select relhasoids, c.oid from pg_class c, pg_namespace n where relname = '%s' and nspname = '%s' and c.relnamespace = n.oid",
 			 SAFE_NAME(ti->table_name), SAFE_NAME(ti->schema_name));
-	res = CC_send_query(conn, query, NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL);
+	res = CC_send_query(conn, query, NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL);
 	if (QR_command_maybe_successful(res))
 	{
 		stmt->num_key_fields = PG_NUM_NORMAL_KEYS;
@@ -410,7 +410,7 @@ static BOOL CheckHasOids(StatementClass * stmt)
 		if (!hasoids)
 		{
 			sprintf(query, "select a.attname, a.atttypid from pg_index i, pg_attribute a where indrelid=%u and indnatts=1 and indisunique and indexprs is null and indpred is null and i.indrelid = a.attrelid and a.attnum=i.indkey[0] and attnotnull and atttypid in (%d, %d)", ti->table_oid, PG_TYPE_INT4, PG_TYPE_OID);
-			res = CC_send_query(conn, query, NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL);
+			res = CC_send_query(conn, query, NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL);
 			if (QR_command_maybe_successful(res) && QR_get_num_total_tuples(res) > 0)
 			{
 				foundKey = TRUE;
@@ -744,7 +744,7 @@ COL_INFO **coli)
 						 "select nspname from pg_namespace n, pg_class c"
 						 " where c.relnamespace=n.oid and c.oid='\"%s\"'::regclass",
 						 SAFE_NAME(table_name));
-				res = CC_send_query(conn, token, NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL);
+				res = CC_send_query(conn, token, NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL);
 				if (QR_command_maybe_successful(res))
 				{
 					if (QR_get_num_total_tuples(res) == 1)
diff --git a/qresult.c b/qresult.c
index 361be56..11558bb 100644
--- a/qresult.c
+++ b/qresult.c
@@ -487,7 +487,7 @@ QR_free_memory(QResultClass *self)
 				char		cmd[64];
 
 				snprintf(cmd, sizeof(cmd), "DEALLOCATE \"%s\"", plannm);
-				res = CC_send_query(conn, cmd, NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL);
+				res = CC_send_query(conn, cmd, NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL);
 				QR_Destructor(res);
 			}
 		}
@@ -710,7 +710,7 @@ QR_close(QResultClass *self)
 			char		buf[64];
 
 			if (QR_needs_survival_check(self))
-				flag = ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN;
+				flag = TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN;
 
 			snprintf(buf, sizeof(buf), "close \"%s\"", QR_get_cursor(self));
 			/* End the transaction if there are no cursors left on this conn */
@@ -719,7 +719,7 @@ QR_close(QResultClass *self)
 			    CC_cursor_count(conn) <= 1)
 			{
 				mylog("QResult: END transaction on conn=%p\n", conn);
-				if ((ROLLBACK_ON_ERROR & flag) == 0)
+				if ((TRANSACTION_ERROR & flag) == 0)
 				{
 					strlcat(buf, ";commit", sizeof(buf));
 					flag |= END_WITH_COMMIT;
diff --git a/statement.c b/statement.c
index 19510bf..86f65ac 100644
--- a/statement.c
+++ b/statement.c
@@ -698,7 +698,7 @@ SC_set_prepared(StatementClass *stmt, int prepared)
 					char dealloc_stmt[128];
 
 					sprintf(dealloc_stmt, "DEALLOCATE \"%s\"", stmt->plan_name);
-					res = CC_send_query(conn, dealloc_stmt, NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL);
+					res = CC_send_query(conn, dealloc_stmt, NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL);
 					QR_Destructor(res);
 				}
 			}
diff --git a/test/expected/error-rollback.out b/test/expected/error-rollback.out
index 436a328..51276c8 100644
--- a/test/expected/error-rollback.out
+++ b/test/expected/error-rollback.out
@@ -1,5 +1,5 @@
 \! ./src/error-rollback-test
-Test for rollback protocol 0
+Test for Protocol=7.4-0
 connected
 Executing query that will succeed
 Executing query that will fail
@@ -10,7 +10,7 @@ Executing query that will succeed
 Result set:
 1
 disconnecting
-Test for rollback protocol 1
+Test for Protocol=7.4-1
 connected
 Executing query that will succeed
 Executing query that will fail
@@ -20,7 +20,71 @@ Executing query that will succeed
 Result set:
 1
 disconnecting
-Test for rollback protocol 2
+Test for Protocol=7.4-2
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Executing query that will succeed
+Result set:
+1
+1
+disconnecting
+Test for TransactionErrorBehavior=Nop
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Rolling back with SQLEndTran
+Executing query that will succeed
+Result set:
+1
+disconnecting
+Test for TransactionErrorBehavior=Transaction
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Executing query that will succeed
+Result set:
+1
+disconnecting
+Test for TransactionErrorBehavior=Statement
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Executing query that will succeed
+Result set:
+1
+1
+disconnecting
+Test for TransactionErrorBehavior=Nop;Protocol=7.4-2
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Rolling back with SQLEndTran
+Executing query that will succeed
+Result set:
+1
+disconnecting
+Test for TransactionErrorBehavior=Transaction;Protocol=7.4-0
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Executing query that will succeed
+Result set:
+1
+disconnecting
+Test for TransactionErrorBehavior=Statement;Protocol=7.4-1
 connected
 Executing query that will succeed
 Executing query that will fail
diff --git a/test/src/error-rollback-test.c b/test/src/error-rollback-test.c
index 01c0f0c..b21affd 100644
--- a/test/src/error-rollback-test.c
+++ b/test/src/error-rollback-test.c
@@ -142,18 +142,19 @@ error_rollback_print(void)
 	print_result(hstmt);
 }
 
-int
-main(int argc, char **argv)
+/*
+ * Test for rollback protocol 0 (Nop).
+ *
+ * Additional options can be specified to play with combinations of
+ * Protocol and TransactionErrorBehavior. With protocol 0, it is the
+ * responsability of application to issue rollbacks.
+ */
+void
+error_rollback_test_0(char *options)
 {
 	SQLRETURN rc;
 
-	/*
-	 * Test for protocol at 0.
-	 * Do nothing when error occurs and let application do necessary
-	 * ROLLBACK on error.
-	 */
-	printf("Test for rollback protocol 0\n");
-	error_rollback_init("Protocol=7.4-0");
+	error_rollback_init(options);
 
 	/* Insert a row correctly */
 	error_rollback_exec_success();
@@ -181,17 +182,25 @@ main(int argc, char **argv)
 
 	/* Clean up */
 	error_rollback_clean();
+}
 
-	/*
-	 * Test for rollback protocol 1
-	 * In case of an error rollback the entire transaction.
-	 */
-	printf("Test for rollback protocol 1\n");
-	error_rollback_init("Protocol=7.4-1");
+/*
+ * Test for rollback protocols 1 (Statement) or 2 (Transaction)
+ *
+ * Options can be specified to manipulate the protocol used with Protocol
+ * and TransactionErrorBehavior. When priting results, there should be one
+ * row for protocol 1 as rollback is done for an entire transaction is case
+ * of a failure. There will be two rows for protocol 2 as tollback is issued
+ * for each statement.
+ */
+void
+error_rollback_test_12(char *options)
+{
+	error_rollback_init(options);
 
 	/*
-	 * Insert a row, trigger an error, and re-insert a row. Only one
-	 * row should be visible here.
+	 * Insert a row, trigger an error, and re-insert a row. Depending
+	 * on the protocol, one or two rows should be visible.
 	 */
 	error_rollback_exec_success();
 	error_rollback_exec_failure();
@@ -200,23 +209,40 @@ main(int argc, char **argv)
 
 	/* Clean up */
 	error_rollback_clean();
+}
 
+int
+main(int argc, char **argv)
+{
 	/*
-	 * Test for rollback protocol 2
-	 * In the case of an error rollback only the latest statement.
+	 * Test for Protocol only
 	 */
-	printf("Test for rollback protocol 2\n");
-	error_rollback_init("Protocol=7.4-2");
+	printf("Test for Protocol=7.4-0\n");
+	error_rollback_test_0("Protocol=7.4-0");
+	printf("Test for Protocol=7.4-1\n");
+	error_rollback_test_12("Protocol=7.4-1");
+	printf("Test for Protocol=7.4-2\n");
+	error_rollback_test_12("Protocol=7.4-2");
 
 	/*
-	 * Similarly to previous case, do insert, error and insert. This
-	 * time two rows should be visible.
+	 * Now do the same tests as previously, but this time for
+	 * TransactionErrorBehavior.
 	 */
-	error_rollback_exec_success();
-	error_rollback_exec_failure();
-	error_rollback_exec_success();
-	error_rollback_print();
+	printf("Test for TransactionErrorBehavior=Nop\n");
+	error_rollback_test_0("TransactionErrorBehavior=0");
+	printf("Test for TransactionErrorBehavior=Transaction\n");
+	error_rollback_test_12("TransactionErrorBehavior=Transaction");
+	printf("Test for TransactionErrorBehavior=Statement\n");
+	error_rollback_test_12("TransactionErrorBehavior=Statement");
 
-	/* Clean up */
-	error_rollback_clean();
+	/*
+	 * Combinations of TransactionErrorBehavior and Protocol, the former
+	 * should have the priority.
+	 */
+	printf("Test for TransactionErrorBehavior=Nop;Protocol=7.4-2\n");
+	error_rollback_test_0("TransactionErrorBehavior=Nop;Protocol=7.4-2");
+	printf("Test for TransactionErrorBehavior=Transaction;Protocol=7.4-0\n");
+	error_rollback_test_12("TransactionErrorBehavior=Transaction;Protocol=7.4-0\n"); /* 1 row */
+	printf("Test for TransactionErrorBehavior=Statement;Protocol=7.4-1\n");
+	error_rollback_test_12("TransactionErrorBehavior=Statement;Protocol=7.4-1\n"); /* 2 rows */
 }
#12Michael Paquier
michael.paquier@gmail.com
In reply to: Michael Paquier (#11)
Re: New parameter RollbackError to control rollback behavior on error

On Mon, Mar 31, 2014 at 9:40 PM, Michael Paquier
<michael.paquier@gmail.com> wrote:

OK, I have been working more on that, giving the attached patch...

Sorry, wrong mailing list...
My apologies.
--
Michael

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13Michael Paquier
michael.paquier@gmail.com
In reply to: Hiroshi Inoue (#8)
1 attachment(s)
Re: Re: [HACKERS] New parameter RollbackError to control rollback behavior on error

Unfortunately I sent this patch to hackers once again... For ODBC
users please use the patch on this email.

* I'm not too fond of the RollbackError name. It sounds like "an error while
rolling back". I googled around and found out that DataDirect's proprietary
driver has the same option, and they call it TransactionErrorBehavior. See
http://blogs.datadirect.com/2013/07/solution-unexpected-postgres-current-transaction-aborted-error.html.

* Instead of using 0-2 as the values, let's give them descriptive names.
Something like "none", "RollbackTransaction", "RollbackStatement".
(actually, we'll probably want to also allow the integers, to keep the
connection string short, as there is a size limit on that)

I have been working more on that, resulting in the patch attached. The
parameter name is changed to TransactionErrorBehavior, able to use the
values "Statement", "Nop", "Transaction" and "Default". "Default"
corresponds to the default behavior, that is used to set the rollback
behavior depending on the Postgres version driver is connected with.
As of now, TransactionErrorBehavior does not accept integer values as
it makes the patch a bit more simple, this could be added with some
atoi calls in dlg_specific.c.

I have updated dlg_wingui.c as well. Patch has always its set of docs
and regression tests.

On Sat, Mar 29, 2014 at 7:16 AM, Hiroshi Inoue <inoue@tpf.co.jp> wrote:

There are some rollback_on_error in dlg_wingui.c.

Corrected in the patch attached.

Regards,
--
Michael

Attachments:

20140331_error_rollback_v2.patchtext/plain; charset=US-ASCII; name=20140331_error_rollback_v2.patchDownload
diff --git a/connection.c b/connection.c
index 8601cb7..8361691 100644
--- a/connection.c
+++ b/connection.c
@@ -288,7 +288,7 @@ CC_conninfo_init(ConnInfo *conninfo, UInt4 option)
 	conninfo->bytea_as_longvarbinary = -1;
 	conninfo->use_server_side_prepare = -1;
 	conninfo->lower_case_identifier = -1;
-	conninfo->rollback_on_error = -1;
+	conninfo->transaction_error_value = TRANSACTION_ERROR_DEFAULT;
 	conninfo->force_abbrev_connstr = -1;
 	conninfo->bde_environment = -1;
 	conninfo->fake_mss = -1;
@@ -321,6 +321,7 @@ CC_copy_conninfo(ConnInfo *ci, const ConnInfo *sci)
 	CORR_STRCPY(username);
 	NAME_TO_NAME(ci->password, sci->password);
 	CORR_STRCPY(protocol);
+	CORR_STRCPY(transaction_error);
 	CORR_STRCPY(port);
 	CORR_STRCPY(sslmode);
 	CORR_STRCPY(onlyread);
@@ -341,7 +342,7 @@ CC_copy_conninfo(ConnInfo *ci, const ConnInfo *sci)
 	CORR_VALCPY(bytea_as_longvarbinary);
 	CORR_VALCPY(use_server_side_prepare);
 	CORR_VALCPY(lower_case_identifier);
-	CORR_VALCPY(rollback_on_error);
+	CORR_VALCPY(transaction_error_value);
 	CORR_VALCPY(force_abbrev_connstr);
 	CORR_VALCPY(bde_environment);
 	CORR_VALCPY(fake_mss);
@@ -2370,7 +2371,7 @@ int	CC_get_max_idlen(ConnectionClass *self)
 	{
 		QResultClass	*res;
 
-		res = CC_send_query(self, "show max_identifier_length", NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL);
+		res = CC_send_query(self, "show max_identifier_length", NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL);
 		if (QR_command_maybe_successful(res))
 			len = self->max_identifier_length = atoi(res->command);
 		QR_Destructor(res);
@@ -2539,7 +2540,7 @@ static void CC_clear_cursors(ConnectionClass *self, BOOL on_abort)
 				{
 					snprintf(cmd, sizeof(cmd), "MOVE 0 in \"%s\"", QR_get_cursor(res));
 					CONNLOCK_RELEASE(self);
-					wres = CC_send_query(self, cmd, NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL);
+					wres = CC_send_query(self, cmd, NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL);
 					QR_set_no_survival_check(res);
 					if (QR_command_maybe_successful(wres))
 						QR_set_permanent(res);
@@ -2733,7 +2734,7 @@ CC_send_query_append(ConnectionClass *self, const char *query, QueryInfo *qi, UD
 	BOOL	ignore_abort_on_conn = ((flag & IGNORE_ABORT_ON_CONN) != 0),
 		create_keyset = ((flag & CREATE_KEYSET) != 0),
 		issue_begin = ((flag & GO_INTO_TRANSACTION) != 0 && !CC_is_in_trans(self)),
-		rollback_on_error, query_rollback, end_with_commit;
+		rollback_error_value, query_rollback, end_with_commit;
 
 	const char	*wq;
 	char		swallow, *ptr;
@@ -2844,13 +2845,13 @@ CC_send_query_append(ConnectionClass *self, const char *query, QueryInfo *qi, UD
 		return res;
 	}
 
-	rollback_on_error = (flag & ROLLBACK_ON_ERROR) != 0;
+	rollback_error_value = (flag & TRANSACTION_ERROR) != 0;
 	end_with_commit = (flag & END_WITH_COMMIT) != 0;
 #define	return DONT_CALL_RETURN_FROM_HERE???
 	consider_rollback = (issue_begin || (CC_is_in_trans(self) && !CC_is_in_error_trans(self)) || strnicmp(query, "begin", 5) == 0);
-	if (rollback_on_error)
-		rollback_on_error = consider_rollback;
-	query_rollback = (rollback_on_error && !end_with_commit && PG_VERSION_GE(self, 8.0));
+	if (rollback_error_value)
+		rollback_error_value = consider_rollback;
+	query_rollback = (rollback_error_value && !end_with_commit && PG_VERSION_GE(self, 8.0));
 	if (!query_rollback && consider_rollback && !end_with_commit)
 	{
 		if (stmt)
@@ -3341,7 +3342,7 @@ cleanup:
 		CC_on_abort(self, CONN_DEAD);
 		retres = NULL;
 	}
-	if (rollback_on_error && CC_is_in_trans(self) && !discard_next_savepoint)
+	if (rollback_error_value && CC_is_in_trans(self) && !discard_next_savepoint)
 	{
 		char	cmd[64];
 
@@ -3819,10 +3820,10 @@ CC_lookup_lo(ConnectionClass *self)
 
 	if (PG_VERSION_GE(self, 7.4))
 		res = CC_send_query(self, "select oid, typbasetype from pg_type where typname = '"  PG_TYPE_LO_NAME "'",
-			NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL);
+			NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL);
 	else
 		res = CC_send_query(self, "select oid, 0 from pg_type where typname='" PG_TYPE_LO_NAME "'",
-			NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL);
+			NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL);
 	if (QR_command_maybe_successful(res) && QR_get_num_cached_tuples(res) > 0)
 	{
 		OID	basetype;
@@ -4009,7 +4010,7 @@ CC_get_current_schema(ConnectionClass *conn)
 	{
 		QResultClass	*res;
 
-		if (res = CC_send_query(conn, "select current_schema()", NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL), QR_command_maybe_successful(res))
+		if (res = CC_send_query(conn, "select current_schema()", NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL), QR_command_maybe_successful(res))
 		{
 			if (QR_get_num_total_tuples(res) == 1)
 				conn->current_schema = strdup(QR_get_value_backend_text(res, 0, 0));
@@ -4124,7 +4125,7 @@ int	CC_discard_marked_objects(ConnectionClass *conn)
 			snprintf(cmd, sizeof(cmd), "DEALLOCATE \"%s\"", pname + 1);
 		else
 			snprintf(cmd, sizeof(cmd), "CLOSE \"%s\"", pname + 1);
-		res = CC_send_query(conn, cmd, NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL);
+		res = CC_send_query(conn, cmd, NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL);
 		QR_Destructor(res);
 		free(conn->discardp[i]);
 		conn->num_discardp--;
@@ -4133,6 +4134,47 @@ int	CC_discard_marked_objects(ConnectionClass *conn)
 	return 1;
 }
 
+/*
+ * Get text value of TransactionErrorBehavior.
+ */
+const char *
+CC_get_transaction_error_txt(int value)
+{
+	switch (value)
+	{
+		case TRANSACTION_ERROR_NOP:
+			return TRANSACTION_ERROR_TXT_NOP;
+		case TRANSACTION_ERROR_TRANSACTION:
+			return TRANSACTION_ERROR_TXT_TRANSACTION;
+		case TRANSACTION_ERROR_STATEMENT:
+			return TRANSACTION_ERROR_TXT_STATEMENT;
+		case TRANSACTION_ERROR_DEFAULT:
+		default:
+			return TRANSACTION_ERROR_TXT_DEFAULT;
+	}
+}
+
+/*
+ * Get integer value of TransactionErrorBehavior.
+ */
+int
+CC_get_transaction_error_int(const char *value)
+{
+	/* Leave if there is nothing */
+	if (value == NULL || value[0] == '\0')
+		return TRANSACTION_ERROR_DEFAULT;
+
+	if (strcmp(TRANSACTION_ERROR_TXT_NOP, value) == 0)
+		return TRANSACTION_ERROR_NOP;
+	else if (strcmp(TRANSACTION_ERROR_TXT_TRANSACTION, value) == 0)
+		return TRANSACTION_ERROR_TRANSACTION;
+	else if (strcmp(TRANSACTION_ERROR_TXT_STATEMENT, value) == 0)
+		return TRANSACTION_ERROR_STATEMENT;
+
+	/* Default case */
+	return TRANSACTION_ERROR_DEFAULT;
+}
+
 #ifdef USE_LIBPQ
 static int
 LIBPQ_connect(ConnectionClass *self)
diff --git a/connection.h b/connection.h
index e92ff61..7e56b79 100644
--- a/connection.h
+++ b/connection.h
@@ -287,7 +287,16 @@ typedef struct
 	char		database[MEDIUM_REGISTRY_LEN];
 	char		username[MEDIUM_REGISTRY_LEN];
 	pgNAME		password;
+
+	/*
+	 * Protocol number, this can be used as well to define the behavior
+	 * of rollback in case of error.
+	 */
 	char		protocol[SMALL_REGISTRY_LEN];
+
+	/* Value of TransactionErrorBehavior */
+	char		transaction_error[MEDIUM_REGISTRY_LEN];
+
 	char		port[SMALL_REGISTRY_LEN];
 	char		sslmode[16];
 	char		onlyread[SMALL_REGISTRY_LEN];
@@ -308,7 +317,16 @@ typedef struct
 	signed char	bytea_as_longvarbinary;
 	signed char	use_server_side_prepare;
 	signed char	lower_case_identifier;
-	signed char	rollback_on_error;
+
+	/*
+	 * Control behavior of transaction rollback on error.
+	 *
+	 * transaction_error_value is used to store the value given by either
+	 * Protocol (as an extension of the protocol number) or
+	 * TransactionErrorBehavior as a connection parameter.
+	 */
+	signed char	transaction_error_value;
+
 	signed char	force_abbrev_connstr;
 	signed char	bde_environment;
 	signed char	fake_mss;
@@ -377,6 +395,25 @@ typedef struct
 #define PG_VERSION_LE(conn, ver) (! PG_VERSION_GT(conn, ver))
 #define PG_VERSION_LT(conn, ver) (! PG_VERSION_GE(conn, ver))
 
+/*
+ * Variables used to control the behavior of rollback on error
+ * Default defines a behavior that defers depending on the backend
+ * version. Those numerical values can be used as an extension of
+ * Protocol, and map text values below.
+ */
+#define TRANSACTION_ERROR_DEFAULT		-1
+#define TRANSACTION_ERROR_NOP			0
+#define TRANSACTION_ERROR_TRANSACTION	1
+#define TRANSACTION_ERROR_STATEMENT		2
+/*
+ * Text versions of precedent parameters. They can be used by
+ * TransactionErrorBehavior.
+ */
+#define TRANSACTION_ERROR_TXT_DEFAULT		"Default"
+#define TRANSACTION_ERROR_TXT_NOP			"Nop"
+#define TRANSACTION_ERROR_TXT_TRANSACTION	"Transaction"
+#define TRANSACTION_ERROR_TXT_STATEMENT		"Statement"
+
 /*	This is used to store cached table information in the connection */
 struct col_info
 {
@@ -585,6 +622,12 @@ void		ProcessRollback(ConnectionClass *conn, BOOL undo, BOOL partial);
 const char	*CC_get_current_schema(ConnectionClass *conn);
 int             CC_mark_a_object_to_discard(ConnectionClass *conn, int type, const char *plan);
 int             CC_discard_marked_objects(ConnectionClass *conn);
+/*
+ * Procedures to get txt and numerical values of transaction rollback
+ * behavior.
+ */
+const char	*CC_get_transaction_error_txt(int value);
+int			CC_get_transaction_error_int(const char *value);
 
 int	handle_error_message(ConnectionClass *self, char *msgbuf, size_t buflen,
 		 char *sqlstate, const char *comment, QResultClass *res);
@@ -606,7 +649,7 @@ enum {
 	IGNORE_ABORT_ON_CONN	= 1L /* not set the error result even when  */
 	,CREATE_KEYSET		= (1L << 1) /* create keyset for updatable curosrs */
 	,GO_INTO_TRANSACTION	= (1L << 2) /* issue begin in advance */
-	,ROLLBACK_ON_ERROR	= (1L << 3) /* rollback the query when an error occurs */
+	,TRANSACTION_ERROR	= (1L << 3) /* rollback the query when an error occurs */
 	,END_WITH_COMMIT	= (1L << 4) /* the query ends with COMMMIT command */
 	,IGNORE_ROUND_TRIP	= (1L << 5) /* the commincation round trip time is considered ignorable */
 };
diff --git a/dlg_specific.c b/dlg_specific.c
index 8e051af..06be993 100644
--- a/dlg_specific.c
+++ b/dlg_specific.c
@@ -229,16 +229,11 @@ inolog("force_abbrev=%d abbrev=%d\n", ci->force_abbrev_connstr, abbrev);
 inolog("hlen=%d", hlen);
 	if (!abbrev)
 	{
-		char	protocol_and[16];
-
-		if (ci->rollback_on_error >= 0)
-			snprintf(protocol_and, sizeof(protocol_and), "%s-%d", ci->protocol, ci->rollback_on_error);
-		else
-			strncpy_null(protocol_and, ci->protocol, sizeof(protocol_and));
 		olen = snprintf(&connect_string[hlen], nlen, ";"
 			INI_SSLMODE "=%s;"
 			INI_READONLY "=%s;"
 			INI_PROTOCOL "=%s;"
+			INI_TRANSACTION_ERROR "=%s;"
 			INI_FAKEOIDINDEX "=%s;"
 			INI_SHOWOIDCOLUMN "=%s;"
 			INI_ROWVERSIONING "=%s;"
@@ -276,7 +271,8 @@ inolog("hlen=%d", hlen);
 #endif /* _HANDLE_ENLIST_IN_DTC_ */
 			,ci->sslmode
 			,ci->onlyread
-			,protocol_and
+			,ci->protocol
+			,CC_get_transaction_error_txt(ci->transaction_error_value)
 			,ci->fake_oid_index
 			,ci->show_oid_column
 			,ci->row_versioning
@@ -408,22 +404,25 @@ inolog("hlen=%d", hlen);
 				ci->int8_as,
 				ci->drivers.extra_systable_prefixes,
 				EFFECTIVE_BIT_COUNT, flag);
-		if (olen < nlen && (PROTOCOL_74(ci) || ci->rollback_on_error >= 0))
+		if (olen < nlen &&
+			(PROTOCOL_74(ci) ||
+			 ci->transaction_error_value != TRANSACTION_ERROR_DEFAULT))
 		{
 			hlen = strlen(connect_string);
 			nlen = MAX_CONNECT_STRING - hlen;
 			/*
 			 * The PROTOCOL setting must be placed after CX flag
 			 * so that this option can override the CX setting.
+			 * Complete it with TransactionErrorBehavior.
 			 */
-			if (ci->rollback_on_error >= 0)
+			if (ci->transaction_error_value != TRANSACTION_ERROR_DEFAULT)
 				olen = snprintf(&connect_string[hlen], nlen, ";"
-				ABBR_PROTOCOL "=%s-%d",
-				ci->protocol, ci->rollback_on_error);
+					ABBR_PROTOCOL "=%s;" ABBR_TRANSACTION_ERROR "=%s",
+					ci->protocol,
+					CC_get_transaction_error_txt(ci->transaction_error_value));
 			else
 				olen = snprintf(&connect_string[hlen], nlen, ";"
-				ABBR_PROTOCOL "=%s",
-				ci->protocol);
+					ABBR_PROTOCOL "=%s", ci->protocol);
 		}
 	}
 	if (olen < nlen)
@@ -541,22 +540,33 @@ copyAttributes(ConnInfo *ci, const char *attribute, const char *value)
 	else if (stricmp(attribute, INI_PROTOCOL) == 0 || stricmp(attribute, ABBR_PROTOCOL) == 0)
 	{
 		char	*ptr;
-
 		ptr = strchr(value, '-');
-		if (ptr)
+
+		/*
+		 * Copy value controlling rollback on error if an extension of
+		 * Protocol is found and if it is not set already by TransactionErrorBehavior.
+		 */
+		if (ptr && ci->transaction_error[0] == '\0')
 		{
 			if ('-' != *value)
 			{
 				*ptr = '\0';
 				strcpy(ci->protocol, value);
 			}
-			ci->rollback_on_error = atoi(ptr + 1);
-			mylog("rollback_on_error=%d\n", ci->rollback_on_error);
+			ci->transaction_error_value = atoi(ptr + 1);
+			mylog("transaction_error_value=%d\n", ci->transaction_error_value);
 		}
 		else
 			strcpy(ci->protocol, value);
 	}
 
+	else if (stricmp(attribute, INI_TRANSACTION_ERROR) == 0 ||
+			 stricmp(attribute, ABBR_TRANSACTION_ERROR) == 0)
+	{
+		strcpy(ci->transaction_error, value);
+		ci->transaction_error_value = CC_get_transaction_error_int(value);
+	}
+
 	else if (stricmp(attribute, INI_SHOWOIDCOLUMN) == 0 || stricmp(attribute, ABBR_SHOWOIDCOLUMN) == 0)
 		strcpy(ci->show_oid_column, value);
 
@@ -663,7 +673,7 @@ copyAttributes(ConnInfo *ci, const char *attribute, const char *value)
 	else
 		found = FALSE;
 
-	mylog("%s: DSN='%s',server='%s',dbase='%s',user='%s',passwd='%s',port='%s',onlyread='%s',protocol='%s',conn_settings='%s',disallow_premature=%d)\n", func, ci->dsn, ci->server, ci->database, ci->username, NAME_IS_VALID(ci->password) ? "xxxxx" : "", ci->port, ci->onlyread, ci->protocol, ci->conn_settings, ci->disallow_premature);
+	mylog("%s: DSN='%s',server='%s',dbase='%s',user='%s',passwd='%s',port='%s',onlyread='%s',protocol='%s',rollback_error='%s',conn_settings='%s',disallow_premature=%d)\n", func, ci->dsn, ci->server, ci->database, ci->username, NAME_IS_VALID(ci->password) ? "xxxxx" : "", ci->port, ci->onlyread, ci->protocol, CC_get_transaction_error_txt(ci->transaction_error_value), ci->conn_settings, ci->disallow_premature);
 
 	return found;
 }
@@ -893,21 +903,45 @@ getDSNinfo(ConnInfo *ci, char overwrite)
 	if (ci->show_system_tables[0] == '\0' || overwrite)
 		SQLGetPrivateProfileString(DSN, INI_SHOWSYSTEMTABLES, "", ci->show_system_tables, sizeof(ci->show_system_tables), ODBC_INI);
 
+	/*
+	 * Set up value for Protocol. Error on rollback
+	 */
 	if (ci->protocol[0] == '\0' || overwrite)
 	{
 		char	*ptr;
 		SQLGetPrivateProfileString(DSN, INI_PROTOCOL, "", ci->protocol, sizeof(ci->protocol), ODBC_INI);
-		if (ptr = strchr(ci->protocol, '-'), NULL != ptr)
+
+		/*
+		 * Check if behavior of rollback on error is set as an extension
+		 * of Protocol. Note that if this parameter has already been set
+		 * by TransactionErrorBehavior, we simply ignore the value set here.
+		 */
+		if (ptr = strchr(ci->protocol, '-'), NULL != ptr &&
+			ci->transaction_error[0] == '\0')
 		{
 			*ptr = '\0';
-			if (overwrite || ci->rollback_on_error < 0)
+			if (overwrite ||
+				ci->transaction_error_value == TRANSACTION_ERROR_DEFAULT)
 			{
-				ci->rollback_on_error = atoi(ptr + 1);
-				mylog("rollback_on_error=%d\n", ci->rollback_on_error);
+				ci->transaction_error_value = atoi(ptr + 1);
+				mylog("transaction_error_value=%d\n", ci->transaction_error_value);
 			}
 		}
 	}
 
+	/*
+	 * Set up value for TransactionErrorBehavior. This gets priority over Protocol.
+	 */
+	if (ci->transaction_error[0] == '\0' || overwrite)
+	{
+		if (ci->transaction_error_value == TRANSACTION_ERROR_DEFAULT
+			|| overwrite)
+		{
+			ci->transaction_error_value = CC_get_transaction_error_int(ci->transaction_error);
+			mylog("transaction_error_value=%d\n", ci->transaction_error_value);
+		}
+	}
+
 	if (NAME_IS_NULL(ci->conn_settings) || overwrite)
 	{
 		SQLGetPrivateProfileString(DSN, INI_CONNSETTINGS, "", encoded_item, sizeof(encoded_item), ODBC_INI);
@@ -1205,12 +1239,14 @@ writeDSNinfo(const ConnInfo *ci)
 								 ci->show_system_tables,
 								 ODBC_INI);
 
-	if (ci->rollback_on_error >= 0)
-		sprintf(temp, "%s-%d", ci->protocol, ci->rollback_on_error);
-	else
-		strncpy_null(temp, ci->protocol, sizeof(temp));
 	SQLWritePrivateProfileString(DSN,
 								 INI_PROTOCOL,
+								 ci->protocol,
+								 ODBC_INI);
+
+	sprintf(temp, "%s", CC_get_transaction_error_txt(ci->transaction_error_value));
+	SQLWritePrivateProfileString(DSN,
+								 INI_TRANSACTION_ERROR,
 								 temp,
 								 ODBC_INI);
 
diff --git a/dlg_specific.h b/dlg_specific.h
index c73bb64..d994423 100644
--- a/dlg_specific.h
+++ b/dlg_specific.h
@@ -75,6 +75,8 @@ extern "C" {
 #define ABBR_COMMLOG			"B3"
 #define INI_PROTOCOL			"Protocol"	/* What protocol (6.2) */
 #define ABBR_PROTOCOL			"A1"
+#define INI_TRANSACTION_ERROR	"TransactionErrorBehavior" /* Rollback on error */
+#define ABBR_TRANSACTION_ERROR	"AA"
 #define INI_OPTIMIZER			"Optimizer"	/* Use backend genetic
 							 * optimizer */
 #define ABBR_OPTIMIZER			"B4"
diff --git a/dlg_wingui.c b/dlg_wingui.c
index 072f542..394f7df 100644
--- a/dlg_wingui.c
+++ b/dlg_wingui.c
@@ -550,7 +550,7 @@ ds_options2Proc(HWND hdlg,
 				CheckDlgButton(hdlg, DS_PG74, 1);
 
 			/* How to issue Rollback */
-			switch (ci->rollback_on_error)
+			switch (ci->transaction_error_value)
 			{
 				case 0:
 					CheckDlgButton(hdlg, DS_NO_ROLLBACK, 1);
@@ -639,14 +639,14 @@ ds_options2Proc(HWND hdlg,
 
 					/* Issue rollback command on error */
 					if (IsDlgButtonChecked(hdlg, DS_NO_ROLLBACK))
-						ci->rollback_on_error = 0;
+						ci->transaction_error_value = TRANSACTION_ERROR_NONE;
 					else if (IsDlgButtonChecked(hdlg, DS_TRANSACTION_ROLLBACK))
-						ci->rollback_on_error = 1;
+						ci->transaction_error_value = TRANSACTION_ERROR_TRANSACTION;
 					else if (IsDlgButtonChecked(hdlg, DS_STATEMENT_ROLLBACK))
-						ci->rollback_on_error = 2;
+						ci->transaction_error_value = TRANSACTION_ERROR_STATEMENT;
 					else
 						/* legacy */
-						ci->rollback_on_error = 1;
+						ci->transaction_error_value = TRANSACTION_ERROR_DEFAULT;
 
 					/* Int8 As */
 					if (IsDlgButtonChecked(hdlg, DS_INT8_AS_DEFAULT))
diff --git a/docs/config-opt.html b/docs/config-opt.html
index 6673bc2..8e18086 100644
--- a/docs/config-opt.html
+++ b/docs/config-opt.html
@@ -146,6 +146,17 @@
 	</TR>
 	<TR>
 		<TD WIDTH=38%>
+			Rollback on error
+		</TD>
+		<TD WIDTH=31%>
+			TransactionErrorBehavior
+		</TD>
+		<TD WIDTH=31%>
+			AA
+		</TD>
+	</TR>
+	<TR>
+		<TD WIDTH=38%>
 			Backend enetic optimizer
 		</TD>
 		<TD WIDTH=31%>
diff --git a/docs/config.html b/docs/config.html
index cb1c326..8b29ba6 100644
--- a/docs/config.html
+++ b/docs/config.html
@@ -238,23 +238,35 @@ with 6.4 and higher backends.<br />&nbsp;</li>
 with 7.4 and higher backends.<br />&nbsp;</li>
 </ul></li>
 
-<li><b>Level of rollback on errors:</b> Specifies what to rollback should an
-error occur.<br />&nbsp;
+<li><b>TransactionErrorBehavior</b>: Level of rollback on errors, specifies what to
+rollback should an error occur.<br />&nbsp;
 
 <ul>
-<li><i>Nop(0):</i> Don't rollback anything and let the application handle the
-error.<br />&nbsp;</li>
-
-<li><i>Transaction(1):</i> Rollback the entire transaction.<br />&nbsp;</li>
-
-<li><i>Statement(2):</i> Rollback the statement.<br />&nbsp;</li>
-<br>
-<b>Notes in a setup: This specification is set up with a PROTOCOL option parameter.</b><br><br>
-PROTOCOL=[6.2|6.3|6.4|7.4][-(0|1|2)]<br>
-default value is a sentence unit (it is a transaction unit before 8.0).<br>
-<br>
-
-</ul></li>
+<li><i>Default:</i> Default value. Transaction rollback behavior is chosen
+depending on the database backend version: 'Transaction' for Postgres servers
+older than 8.0 and 'Statement' for servers newer than 8.1.<br /></li>
+
+<li><i>Nop:</i> Don't rollback anything and let the application handle the
+error.<br /></li>
+
+<li><i>Transaction:</i> Rollback the entire transaction.<br /></li>
+
+<li><i>Statement:</i> Rollback the statement.<br /></li>
+<b>Notes:</b>
+  <ul>
+    <li>
+      This parameter can be set as an extension of PROTOCOL option
+      parameter.<br />
+      PROTOCOL=[6.2|6.3|6.4|7.4][-(0|1|2)]<br />
+      Default value is a sentence unit (it is a transaction unit
+      before 8.0).<br />
+    </li>
+    <li>
+      This parameter can be set with TRANSACTIONROLLBACKBEHAVIOR as well.
+      This takes precedence on PROTOCOL.<bt />
+    </li>
+  </ul>
+</ul>.<br />&nbsp;</li>
 
 
 <li><b>OID Options:</b><br />&nbsp;
diff --git a/execute.c b/execute.c
index 1cc4f04..a732eb9 100644
--- a/execute.c
+++ b/execute.c
@@ -651,28 +651,31 @@ inolog("%s:%p->internal=%d\n", func, stmt, stmt->internal);
 	conn = SC_get_conn(stmt);
 	if (conn)
 		ci = &conn->connInfo;
-	ret = 0;
-	if (!ci || ci->rollback_on_error < 0) /* default */
+	ret = TRANSACTION_ERROR_NOP;
+	if (!ci || ci->transaction_error_value == TRANSACTION_ERROR_DEFAULT)
 	{
 		if (conn && PG_VERSION_GE(conn, 8.0))
-			ret = 2; /* statement rollback */
+			ret = TRANSACTION_ERROR_STATEMENT;
 		else
-			ret = 1; /* transaction rollback */
+			ret = TRANSACTION_ERROR_TRANSACTION;
 	}
 	else
 	{
-		ret = ci->rollback_on_error;
-		if (2 == ret && PG_VERSION_LT(conn, 8.0))
-			ret = 1;
+		ret = ci->transaction_error_value;
+		if (ret == TRANSACTION_ERROR_STATEMENT &&
+			PG_VERSION_LT(conn, 8.0))
+			ret = TRANSACTION_ERROR_TRANSACTION;
 	}
 	switch (ret)
 	{
-		case 1:
+		case TRANSACTION_ERROR_TRANSACTION:
 			SC_start_tc_stmt(stmt);
 			break;
-		case 2:
+		case TRANSACTION_ERROR_STATEMENT:
 			SC_start_rb_stmt(stmt);
 			break;
+		default:
+			break;
 	}
 	return	ret;
 }
diff --git a/info.c b/info.c
index 81bfe6d..a6c7274 100644
--- a/info.c
+++ b/info.c
@@ -4012,7 +4012,7 @@ getClientColumnName(ConnectionClass *conn, UInt4 relid, char *serverColumnName,
 	BOOL		continueExec = TRUE,
 				bError = FALSE;
 	QResultClass *res = NULL;
-	UWORD	flag = IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR;
+	UWORD	flag = IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR;
 
 	*nameAlloced = FALSE;
 	if (!conn->original_client_encoding || !isMultibyte(serverColumnName))
diff --git a/multibyte.c b/multibyte.c
index 2f7b594..512b9cc 100644
--- a/multibyte.c
+++ b/multibyte.c
@@ -444,7 +444,7 @@ CC_lookup_cs_new(ConnectionClass *self)
 	char		*encstr = NULL;
 	QResultClass	*res;
 
-	res = CC_send_query(self, "select pg_client_encoding()", NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL);
+	res = CC_send_query(self, "select pg_client_encoding()", NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL);
 	if (QR_command_maybe_successful(res))
 	{
 		const char *enc = QR_get_value_backend_text(res, 0, 0);
@@ -606,7 +606,7 @@ CC_lookup_characterset(ConnectionClass *self)
 			BOOL		cmd_success;
 
 			sprintf(query, "set client_encoding to '%s'", wenc);
-			res = CC_send_query(self, query, NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL);
+			res = CC_send_query(self, query, NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL);
 			cmd_success = QR_command_maybe_successful(res);
 			QR_Destructor(res);
 			CC_set_errornumber(self, errnum);
diff --git a/parse.c b/parse.c
index 5aa7723..a45ddf4 100644
--- a/parse.c
+++ b/parse.c
@@ -382,7 +382,7 @@ static BOOL CheckHasOids(StatementClass * stmt)
 	snprintf(query, sizeof(query),
 			 "select relhasoids, c.oid from pg_class c, pg_namespace n where relname = '%s' and nspname = '%s' and c.relnamespace = n.oid",
 			 SAFE_NAME(ti->table_name), SAFE_NAME(ti->schema_name));
-	res = CC_send_query(conn, query, NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL);
+	res = CC_send_query(conn, query, NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL);
 	if (QR_command_maybe_successful(res))
 	{
 		stmt->num_key_fields = PG_NUM_NORMAL_KEYS;
@@ -410,7 +410,7 @@ static BOOL CheckHasOids(StatementClass * stmt)
 		if (!hasoids)
 		{
 			sprintf(query, "select a.attname, a.atttypid from pg_index i, pg_attribute a where indrelid=%u and indnatts=1 and indisunique and indexprs is null and indpred is null and i.indrelid = a.attrelid and a.attnum=i.indkey[0] and attnotnull and atttypid in (%d, %d)", ti->table_oid, PG_TYPE_INT4, PG_TYPE_OID);
-			res = CC_send_query(conn, query, NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL);
+			res = CC_send_query(conn, query, NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL);
 			if (QR_command_maybe_successful(res) && QR_get_num_total_tuples(res) > 0)
 			{
 				foundKey = TRUE;
@@ -744,7 +744,7 @@ COL_INFO **coli)
 						 "select nspname from pg_namespace n, pg_class c"
 						 " where c.relnamespace=n.oid and c.oid='\"%s\"'::regclass",
 						 SAFE_NAME(table_name));
-				res = CC_send_query(conn, token, NULL, ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN, NULL);
+				res = CC_send_query(conn, token, NULL, TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN, NULL);
 				if (QR_command_maybe_successful(res))
 				{
 					if (QR_get_num_total_tuples(res) == 1)
diff --git a/qresult.c b/qresult.c
index 361be56..11558bb 100644
--- a/qresult.c
+++ b/qresult.c
@@ -487,7 +487,7 @@ QR_free_memory(QResultClass *self)
 				char		cmd[64];
 
 				snprintf(cmd, sizeof(cmd), "DEALLOCATE \"%s\"", plannm);
-				res = CC_send_query(conn, cmd, NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL);
+				res = CC_send_query(conn, cmd, NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL);
 				QR_Destructor(res);
 			}
 		}
@@ -710,7 +710,7 @@ QR_close(QResultClass *self)
 			char		buf[64];
 
 			if (QR_needs_survival_check(self))
-				flag = ROLLBACK_ON_ERROR | IGNORE_ABORT_ON_CONN;
+				flag = TRANSACTION_ERROR | IGNORE_ABORT_ON_CONN;
 
 			snprintf(buf, sizeof(buf), "close \"%s\"", QR_get_cursor(self));
 			/* End the transaction if there are no cursors left on this conn */
@@ -719,7 +719,7 @@ QR_close(QResultClass *self)
 			    CC_cursor_count(conn) <= 1)
 			{
 				mylog("QResult: END transaction on conn=%p\n", conn);
-				if ((ROLLBACK_ON_ERROR & flag) == 0)
+				if ((TRANSACTION_ERROR & flag) == 0)
 				{
 					strlcat(buf, ";commit", sizeof(buf));
 					flag |= END_WITH_COMMIT;
diff --git a/statement.c b/statement.c
index 19510bf..86f65ac 100644
--- a/statement.c
+++ b/statement.c
@@ -698,7 +698,7 @@ SC_set_prepared(StatementClass *stmt, int prepared)
 					char dealloc_stmt[128];
 
 					sprintf(dealloc_stmt, "DEALLOCATE \"%s\"", stmt->plan_name);
-					res = CC_send_query(conn, dealloc_stmt, NULL, IGNORE_ABORT_ON_CONN | ROLLBACK_ON_ERROR, NULL);
+					res = CC_send_query(conn, dealloc_stmt, NULL, IGNORE_ABORT_ON_CONN | TRANSACTION_ERROR, NULL);
 					QR_Destructor(res);
 				}
 			}
diff --git a/test/expected/error-rollback.out b/test/expected/error-rollback.out
index 436a328..51276c8 100644
--- a/test/expected/error-rollback.out
+++ b/test/expected/error-rollback.out
@@ -1,5 +1,5 @@
 \! ./src/error-rollback-test
-Test for rollback protocol 0
+Test for Protocol=7.4-0
 connected
 Executing query that will succeed
 Executing query that will fail
@@ -10,7 +10,7 @@ Executing query that will succeed
 Result set:
 1
 disconnecting
-Test for rollback protocol 1
+Test for Protocol=7.4-1
 connected
 Executing query that will succeed
 Executing query that will fail
@@ -20,7 +20,71 @@ Executing query that will succeed
 Result set:
 1
 disconnecting
-Test for rollback protocol 2
+Test for Protocol=7.4-2
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Executing query that will succeed
+Result set:
+1
+1
+disconnecting
+Test for TransactionErrorBehavior=Nop
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Rolling back with SQLEndTran
+Executing query that will succeed
+Result set:
+1
+disconnecting
+Test for TransactionErrorBehavior=Transaction
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Executing query that will succeed
+Result set:
+1
+disconnecting
+Test for TransactionErrorBehavior=Statement
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Executing query that will succeed
+Result set:
+1
+1
+disconnecting
+Test for TransactionErrorBehavior=Nop;Protocol=7.4-2
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Rolling back with SQLEndTran
+Executing query that will succeed
+Result set:
+1
+disconnecting
+Test for TransactionErrorBehavior=Transaction;Protocol=7.4-0
+connected
+Executing query that will succeed
+Executing query that will fail
+Failed to execute statement
+22P02=ERROR: invalid input syntax for integer: "foo"
+Executing query that will succeed
+Result set:
+1
+disconnecting
+Test for TransactionErrorBehavior=Statement;Protocol=7.4-1
 connected
 Executing query that will succeed
 Executing query that will fail
diff --git a/test/src/error-rollback-test.c b/test/src/error-rollback-test.c
index 01c0f0c..b21affd 100644
--- a/test/src/error-rollback-test.c
+++ b/test/src/error-rollback-test.c
@@ -142,18 +142,19 @@ error_rollback_print(void)
 	print_result(hstmt);
 }
 
-int
-main(int argc, char **argv)
+/*
+ * Test for rollback protocol 0 (Nop).
+ *
+ * Additional options can be specified to play with combinations of
+ * Protocol and TransactionErrorBehavior. With protocol 0, it is the
+ * responsability of application to issue rollbacks.
+ */
+void
+error_rollback_test_0(char *options)
 {
 	SQLRETURN rc;
 
-	/*
-	 * Test for protocol at 0.
-	 * Do nothing when error occurs and let application do necessary
-	 * ROLLBACK on error.
-	 */
-	printf("Test for rollback protocol 0\n");
-	error_rollback_init("Protocol=7.4-0");
+	error_rollback_init(options);
 
 	/* Insert a row correctly */
 	error_rollback_exec_success();
@@ -181,17 +182,25 @@ main(int argc, char **argv)
 
 	/* Clean up */
 	error_rollback_clean();
+}
 
-	/*
-	 * Test for rollback protocol 1
-	 * In case of an error rollback the entire transaction.
-	 */
-	printf("Test for rollback protocol 1\n");
-	error_rollback_init("Protocol=7.4-1");
+/*
+ * Test for rollback protocols 1 (Statement) or 2 (Transaction)
+ *
+ * Options can be specified to manipulate the protocol used with Protocol
+ * and TransactionErrorBehavior. When priting results, there should be one
+ * row for protocol 1 as rollback is done for an entire transaction is case
+ * of a failure. There will be two rows for protocol 2 as tollback is issued
+ * for each statement.
+ */
+void
+error_rollback_test_12(char *options)
+{
+	error_rollback_init(options);
 
 	/*
-	 * Insert a row, trigger an error, and re-insert a row. Only one
-	 * row should be visible here.
+	 * Insert a row, trigger an error, and re-insert a row. Depending
+	 * on the protocol, one or two rows should be visible.
 	 */
 	error_rollback_exec_success();
 	error_rollback_exec_failure();
@@ -200,23 +209,40 @@ main(int argc, char **argv)
 
 	/* Clean up */
 	error_rollback_clean();
+}
 
+int
+main(int argc, char **argv)
+{
 	/*
-	 * Test for rollback protocol 2
-	 * In the case of an error rollback only the latest statement.
+	 * Test for Protocol only
 	 */
-	printf("Test for rollback protocol 2\n");
-	error_rollback_init("Protocol=7.4-2");
+	printf("Test for Protocol=7.4-0\n");
+	error_rollback_test_0("Protocol=7.4-0");
+	printf("Test for Protocol=7.4-1\n");
+	error_rollback_test_12("Protocol=7.4-1");
+	printf("Test for Protocol=7.4-2\n");
+	error_rollback_test_12("Protocol=7.4-2");
 
 	/*
-	 * Similarly to previous case, do insert, error and insert. This
-	 * time two rows should be visible.
+	 * Now do the same tests as previously, but this time for
+	 * TransactionErrorBehavior.
 	 */
-	error_rollback_exec_success();
-	error_rollback_exec_failure();
-	error_rollback_exec_success();
-	error_rollback_print();
+	printf("Test for TransactionErrorBehavior=Nop\n");
+	error_rollback_test_0("TransactionErrorBehavior=0");
+	printf("Test for TransactionErrorBehavior=Transaction\n");
+	error_rollback_test_12("TransactionErrorBehavior=Transaction");
+	printf("Test for TransactionErrorBehavior=Statement\n");
+	error_rollback_test_12("TransactionErrorBehavior=Statement");
 
-	/* Clean up */
-	error_rollback_clean();
+	/*
+	 * Combinations of TransactionErrorBehavior and Protocol, the former
+	 * should have the priority.
+	 */
+	printf("Test for TransactionErrorBehavior=Nop;Protocol=7.4-2\n");
+	error_rollback_test_0("TransactionErrorBehavior=Nop;Protocol=7.4-2");
+	printf("Test for TransactionErrorBehavior=Transaction;Protocol=7.4-0\n");
+	error_rollback_test_12("TransactionErrorBehavior=Transaction;Protocol=7.4-0\n"); /* 1 row */
+	printf("Test for TransactionErrorBehavior=Statement;Protocol=7.4-1\n");
+	error_rollback_test_12("TransactionErrorBehavior=Statement;Protocol=7.4-1\n"); /* 2 rows */
 }