diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 1b390a2..f80f6d0 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -6304,6 +6304,32 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
+
+ default_transaction_rollback_scope (enum)
+
+ transaction rollback scope
+ setting default
+
+
+ default_transaction_rollback_scope> configuration parameter
+
+
+
+
+ This parameter controls which range of operations
+ to roll back when an SQL statement fails.
+ transaction
rolls back the entire
+ transaction or current subtransaction.
+ statement
rolls back the failed SQL statement.
+ The default is transaction
.
+
+
+
+ Consult for more information.
+
+
+
+
default_transaction_read_only (boolean)
diff --git a/doc/src/sgml/ref/set_transaction.sgml b/doc/src/sgml/ref/set_transaction.sgml
index ca55a5b..06aee05 100644
--- a/doc/src/sgml/ref/set_transaction.sgml
+++ b/doc/src/sgml/ref/set_transaction.sgml
@@ -10,6 +10,11 @@
+ transaction rollback scope
+ setting
+
+
+
read-only transaction
setting
@@ -39,6 +44,7 @@ SET SESSION CHARACTERISTICS AS TRANSACTION transa
where transaction_mode is one of:
ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
+ ROLLBACK SCOPE { TRANSACTION | STATEMENT }
READ WRITE | READ ONLY
[ NOT ] DEFERRABLE
@@ -59,8 +65,8 @@ SET SESSION CHARACTERISTICS AS TRANSACTION transa
The available transaction characteristics are the transaction
- isolation level, the transaction access mode (read/write or
- read-only), and the deferrable mode.
+ isolation level, the rollback scope,
+ the transaction access mode (read/write or read-only), and the deferrable mode.
In addition, a snapshot can be selected, though only for the current
transaction, not as a session default.
@@ -123,6 +129,36 @@ SET SESSION CHARACTERISTICS AS TRANSACTION transa
isolation and concurrency control.
+tunatuna
+
+ The isolation level of a transaction determines what data the
+ The rollback scope of a transaction determines which range of
+ operations to roll back when an SQL statement fails:
+
+
+
+ TRANSACTION
+
+
+ A statement can only see rows committed before it began. This
+ Rolls back the entire transaction or current subtransaction.
+ This is the default.
+
+
+
+
+
+ STATEMENT
+
+
+ All statements of the current transaction can only see rows committed
+ Rolls back the failed SQL statement.
+
+
+
+
+
+
The transaction access mode determines whether the transaction is
read/write or read-only. Read/write is the default. When a
@@ -200,6 +236,7 @@ SET SESSION CHARACTERISTICS AS TRANSACTION transa
The session default transaction modes can also be set by setting the
configuration parameters ,
+ ,
, and
.
(In fact SET SESSION CHARACTERISTICS is just a
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 82f9a3c..96d63ce 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -73,6 +73,9 @@
int DefaultXactIsoLevel = XACT_READ_COMMITTED;
int XactIsoLevel;
+int DefaultXactRollbackScope = XACT_SCOPE_XACT;
+int XactRollbackScope;
+
bool DefaultXactReadOnly = false;
bool XactReadOnly;
@@ -1844,6 +1847,7 @@ StartTransaction(void)
}
XactDeferrable = DefaultXactDeferrable;
XactIsoLevel = DefaultXactIsoLevel;
+ XactRollbackScope = DefaultXactRollbackScope;
forceSyncCommit = false;
MyXactAccessedTempRel = false;
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index d75bddd..6d61c80 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -608,6 +608,58 @@ show_XactIsoLevel(void)
}
/*
+ * SET TRANSACTION ROLLBACK SCOPE
+ */
+bool
+check_XactRollbackScope(char **newval, void **extra, GucSource source)
+{
+ int newXactRollbackScope;
+
+ if (strcmp(*newval, "transaction") == 0)
+ {
+ newXactRollbackScope = XACT_SCOPE_XACT;
+ }
+ else if (strcmp(*newval, "statement") == 0)
+ {
+ newXactRollbackScope = XACT_SCOPE_STMT;
+ }
+ else if (strcmp(*newval, "default") == 0)
+ {
+ newXactRollbackScope = DefaultXactRollbackScope;
+ }
+ else
+ return false;
+
+ *extra = malloc(sizeof(int));
+ if (!*extra)
+ return false;
+ *((int *) *extra) = newXactRollbackScope;
+
+ return true;
+}
+
+void
+assign_XactRollbackScope(const char *newval, void *extra)
+{
+ XactRollbackScope = *((int *) extra);
+}
+
+const char *
+show_XactRollbackScope(void)
+{
+ /* We need this because we don't want to show "default". */
+ switch (XactRollbackScope)
+ {
+ case XACT_SCOPE_XACT:
+ return "transaction";
+ case XACT_SCOPE_STMT:
+ return "statement";
+ default:
+ return "bogus";
+ }
+}
+
+/*
* SET TRANSACTION [NOT] DEFERRABLE
*/
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index e833b2e..f0cc8d8 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -351,7 +351,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type RowSecurityOptionalWithCheck RowSecurityOptionalExpr
%type RowSecurityDefaultToRole RowSecurityOptionalToRole
-%type iso_level opt_encoding
+%type iso_level rollback_scope opt_encoding
%type grantee
%type grantee_list
%type privilege
@@ -662,7 +662,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROW ROWS RULE
- SAVEPOINT SCHEMA SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
+ SAVEPOINT SCHEMA SCOPE SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SIMILAR SIMPLE SKIP SLOT SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P
@@ -1576,6 +1576,10 @@ iso_level: READ UNCOMMITTED { $$ = "read uncommitted"; }
| SERIALIZABLE { $$ = "serializable"; }
;
+rollback_scope: TRANSACTION { $$ = "transaction"; }
+ | STATEMENT { $$ = "statement"; }
+ ;
+
opt_boolean_or_string:
TRUE_P { $$ = "true"; }
| FALSE_P { $$ = "false"; }
@@ -9459,6 +9463,9 @@ transaction_mode_item:
ISOLATION LEVEL iso_level
{ $$ = makeDefElem("transaction_isolation",
makeStringConst($3, @3), @1); }
+ | ROLLBACK SCOPE rollback_scope
+ { $$ = makeDefElem("transaction_rollback_scope",
+ makeStringConst($3, @3), @1); }
| READ ONLY
{ $$ = makeDefElem("transaction_read_only",
makeIntConst(TRUE, @1), @1); }
@@ -14490,6 +14497,7 @@ unreserved_keyword:
| RULE
| SAVEPOINT
| SCHEMA
+ | SCOPE
| SCROLL
| SEARCH
| SECOND_P
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index b07d6c6..a4e36e3 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -149,6 +149,13 @@ static bool doing_extended_query_message = false;
static bool ignore_till_sync = false;
/*
+ * Flag to keep track of whether we have started a transaction.
+ * Flag to keep track of whether we have created a savepoint
+ * for statement rollback.
+ */
+static bool stmt_savepoint_created = false;
+
+/*
* If an unnamed prepared statement exists, it's stored here.
* We keep it separate from the hashtable kept by commands/prepare.c
* in order to reduce overhead for short-lived queries.
@@ -915,6 +922,16 @@ exec_simple_query(const char *query_string)
start_xact_command();
/*
+ * Create a savepoint for statement rollback.
+ */
+ if (XactRollbackScope == XACT_SCOPE_STMT &&
+ IsTransactionBlock())
+ {
+ BeginInternalSubTransaction(NULL);
+ stmt_savepoint_created = true;
+ }
+
+ /*
* Zap any pre-existing unnamed statement. (While not strictly necessary,
* it seems best to define simple-Query mode as if it used the unnamed
* statement and portal; this ensures we recover any storage used by prior
@@ -1003,6 +1020,17 @@ exec_simple_query(const char *query_string)
/* Make sure we are in a transaction command */
start_xact_command();
+ /*
+ * Create a savepoint for statement rollback.
+ */
+ if (XactRollbackScope == XACT_SCOPE_STMT &&
+ !stmt_savepoint_created &&
+ IsTransactionBlock())
+ {
+ BeginInternalSubTransaction(NULL);
+ stmt_savepoint_created = true;
+ }
+
/* If we got a cancel signal in parsing or prior command, quit */
CHECK_FOR_INTERRUPTS();
@@ -1116,6 +1144,7 @@ exec_simple_query(const char *query_string)
* start a new xact command for the next command (if any).
*/
finish_xact_command();
+ stmt_savepoint_created = false;
}
else if (lnext(parsetree_item) == NULL)
{
@@ -1138,6 +1167,15 @@ exec_simple_query(const char *query_string)
* those that start or end a transaction block.
*/
CommandCounterIncrement();
+
+ /*
+ * Delete the savepoint for statement rollback.
+ */
+ if (stmt_savepoint_created)
+ {
+ ReleaseCurrentSubTransaction();
+ stmt_savepoint_created = false;
+ }
}
/*
@@ -1150,6 +1188,15 @@ exec_simple_query(const char *query_string)
} /* end loop over parsetrees */
/*
+ * Delete the savepoint for statement rollback.
+ */
+ if (stmt_savepoint_created)
+ {
+ ReleaseCurrentSubTransaction();
+ stmt_savepoint_created = false;
+ }
+
+ /*
* Close down transaction statement, if one is open.
*/
finish_xact_command();
@@ -1234,6 +1281,16 @@ exec_parse_message(const char *query_string, /* string to execute */
start_xact_command();
/*
+ * Create a savepoint for statement rollback.
+ */
+ if (XactRollbackScope == XACT_SCOPE_STMT &&
+ IsTransactionBlock())
+ {
+ BeginInternalSubTransaction(NULL);
+ stmt_savepoint_created = true;
+ }
+
+ /*
* Switch to appropriate context for constructing parsetrees.
*
* We have two strategies depending on whether the prepared statement is
@@ -1411,6 +1468,12 @@ exec_parse_message(const char *query_string, /* string to execute */
MemoryContextSwitchTo(oldcontext);
/*
+ * Delete the savepoint for statement rollback.
+ */
+ ReleaseCurrentSubTransaction();
+ stmt_savepoint_created = false;
+
+ /*
* We do NOT close the open transaction command here; that only happens
* when the client sends Sync. Instead, do CommandCounterIncrement just
* in case something happened during parse/plan.
@@ -1521,6 +1584,16 @@ exec_bind_message(StringInfo input_message)
*/
start_xact_command();
+ /*
+ * Create a savepoint for statement rollback.
+ */
+ if (XactRollbackScope == XACT_SCOPE_STMT &&
+ IsTransactionBlock())
+ {
+ BeginInternalSubTransaction(NULL);
+ stmt_savepoint_created = true;
+ }
+
/* Switch back to message context */
MemoryContextSwitchTo(MessageContext);
@@ -1798,6 +1871,12 @@ exec_bind_message(StringInfo input_message)
PortalSetResultFormat(portal, numRFormats, rformats);
/*
+ * Delete the savepoint for statement rollback.
+ */
+ ReleaseCurrentSubTransaction();
+ stmt_savepoint_created = false;
+
+ /*
* Send BindComplete.
*/
if (whereToSendOutput == DestRemote)
@@ -1859,6 +1938,16 @@ exec_execute_message(const char *portal_name, long max_rows)
if (dest == DestRemote)
dest = DestRemoteExecute;
+ /*
+ * Create a savepoint for statement rollback.
+ */
+ if (XactRollbackScope == XACT_SCOPE_STMT &&
+ IsTransactionBlock())
+ {
+ BeginInternalSubTransaction(NULL);
+ stmt_savepoint_created = true;
+ }
+
portal = GetPortalByName(portal_name);
if (!PortalIsValid(portal))
ereport(ERROR,
@@ -2000,6 +2089,7 @@ exec_execute_message(const char *portal_name, long max_rows)
* will start a new xact command for the next command (if any).
*/
finish_xact_command();
+ stmt_savepoint_created = false;
}
else
{
@@ -2021,6 +2111,15 @@ exec_execute_message(const char *portal_name, long max_rows)
}
/*
+ * Delete the savepoint for statement rollback.
+ */
+ if (stmt_savepoint_created)
+ {
+ ReleaseCurrentSubTransaction();
+ stmt_savepoint_created = false;
+ }
+
+ /*
* Emit duration logging if appropriate.
*/
switch (check_log_duration(msec_str, was_logged))
@@ -2299,6 +2398,16 @@ exec_describe_statement_message(const char *stmt_name)
*/
start_xact_command();
+ /*
+ * Create a savepoint for statement rollback.
+ */
+ if (XactRollbackScope == XACT_SCOPE_STMT &&
+ IsTransactionBlock())
+ {
+ BeginInternalSubTransaction(NULL);
+ stmt_savepoint_created = true;
+ }
+
/* Switch back to message context */
MemoryContextSwitchTo(MessageContext);
@@ -2340,6 +2449,12 @@ exec_describe_statement_message(const char *stmt_name)
"commands ignored until end of transaction block"),
errdetail_abort()));
+ /*
+ * Delete the savepoint for statement rollback.
+ */
+ ReleaseCurrentSubTransaction();
+ stmt_savepoint_created = false;
+
if (whereToSendOutput != DestRemote)
return; /* can't actually do anything... */
@@ -2390,6 +2505,16 @@ exec_describe_portal_message(const char *portal_name)
*/
start_xact_command();
+ /*
+ * Create a savepoint for statement rollback.
+ */
+ if (XactRollbackScope == XACT_SCOPE_STMT &&
+ IsTransactionBlock())
+ {
+ BeginInternalSubTransaction(NULL);
+ stmt_savepoint_created = true;
+ }
+
/* Switch back to message context */
MemoryContextSwitchTo(MessageContext);
@@ -2415,6 +2540,12 @@ exec_describe_portal_message(const char *portal_name)
"commands ignored until end of transaction block"),
errdetail_abort()));
+ /*
+ * Delete the savepoint for statement rollback.
+ */
+ ReleaseCurrentSubTransaction();
+ stmt_savepoint_created = false;
+
if (whereToSendOutput != DestRemote)
return; /* can't actually do anything... */
@@ -3859,6 +3990,13 @@ PostgresMain(int argc, char *argv[],
*/
AbortCurrentTransaction();
+ /* Roll back the failed statement, if requested. */
+ if (stmt_savepoint_created)
+ {
+ RollbackAndReleaseCurrentSubTransaction();
+ stmt_savepoint_created = false;
+ }
+
if (am_walsender)
WalSndErrorCleanup();
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 3bc0ae5..273c935 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -415,6 +415,10 @@ standard_ProcessUtility(PlannedStmt *pstmt,
SetPGVariable("transaction_isolation",
list_make1(item->arg),
true);
+ if (strcmp(item->defname, "transaction_rollback_scope") == 0)
+ SetPGVariable("transaction_rollback_scope",
+ list_make1(item->arg),
+ true);
else if (strcmp(item->defname, "transaction_read_only") == 0)
SetPGVariable("transaction_read_only",
list_make1(item->arg),
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0707f66..8581ca3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -277,6 +277,12 @@ static const struct config_enum_entry isolation_level_options[] = {
{NULL, 0}
};
+static const struct config_enum_entry rollback_scope_options[] = {
+ {"transaction", XACT_SCOPE_XACT, false},
+ {"statement", XACT_SCOPE_STMT, false},
+ {NULL, 0}
+};
+
static const struct config_enum_entry session_replication_role_options[] = {
{"origin", SESSION_REPLICATION_ROLE_ORIGIN, false},
{"replica", SESSION_REPLICATION_ROLE_REPLICA, false},
@@ -507,6 +513,7 @@ static char *timezone_string;
static char *log_timezone_string;
static char *timezone_abbreviations_string;
static char *XactIsoLevel_string;
+static char *XactRollbackScope_string;
static char *data_directory;
static char *session_authorization_string;
static int max_function_args;
@@ -3366,6 +3373,17 @@ static struct config_string ConfigureNamesString[] =
},
{
+ {"transaction_rollback_scope", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Sets the scope of rollback when the current transaction aborts."),
+ NULL,
+ GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+ },
+ &XactRollbackScope_string,
+ "default",
+ check_XactRollbackScope, assign_XactRollbackScope, show_XactRollbackScope
+ },
+
+ {
{"unix_socket_group", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
gettext_noop("Sets the owning group of the Unix-domain socket."),
gettext_noop("The owning user of the socket is always the user "
@@ -3658,6 +3676,16 @@ static struct config_enum ConfigureNamesEnum[] =
},
{
+ {"default_transaction_rollback_scope", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("Sets the scope of rollback when the current transaction aborts."),
+ NULL
+ },
+ &DefaultXactRollbackScope,
+ XACT_SCOPE_XACT, rollback_scope_options,
+ NULL, NULL, NULL
+ },
+
+ {
{"IntervalStyle", PGC_USERSET, CLIENT_CONN_LOCALE,
gettext_noop("Sets the display format for interval values."),
NULL,
@@ -4455,6 +4483,8 @@ InitializeGUCOptions(void)
*/
SetConfigOption("transaction_isolation", "default",
PGC_POSTMASTER, PGC_S_OVERRIDE);
+ SetConfigOption("transaction_rollback_scope", "default",
+ PGC_POSTMASTER, PGC_S_OVERRIDE);
SetConfigOption("transaction_read_only", "no",
PGC_POSTMASTER, PGC_S_OVERRIDE);
SetConfigOption("transaction_deferrable", "no",
@@ -7283,6 +7313,9 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
if (strcmp(item->defname, "transaction_isolation") == 0)
SetPGVariable("transaction_isolation",
list_make1(item->arg), stmt->is_local);
+ else if (strcmp(item->defname, "transaction_rollback_scope") == 0)
+ SetPGVariable("transaction_isolation",
+ list_make1(item->arg), stmt->is_local);
else if (strcmp(item->defname, "transaction_read_only") == 0)
SetPGVariable("transaction_read_only",
list_make1(item->arg), stmt->is_local);
@@ -7305,6 +7338,9 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
if (strcmp(item->defname, "transaction_isolation") == 0)
SetPGVariable("default_transaction_isolation",
list_make1(item->arg), stmt->is_local);
+ else if (strcmp(item->defname, "transaction_rollback_scope") == 0)
+ SetPGVariable("default_transaction_isolation",
+ list_make1(item->arg), stmt->is_local);
else if (strcmp(item->defname, "transaction_read_only") == 0)
SetPGVariable("default_transaction_read_only",
list_make1(item->arg), stmt->is_local);
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 157d775..49919d2 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -534,6 +534,7 @@
# only default tablespace
#check_function_bodies = on
#default_transaction_isolation = 'read committed'
+#default_transaction_rollback_scope = 'transaction'
#default_transaction_read_only = off
#default_transaction_deferrable = off
#session_replication_role = 'origin'
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index e7d1191..c8a4cee 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -43,6 +43,15 @@ extern PGDLLIMPORT int XactIsoLevel;
#define IsolationUsesXactSnapshot() (XactIsoLevel >= XACT_REPEATABLE_READ)
#define IsolationIsSerializable() (XactIsoLevel == XACT_SERIALIZABLE)
+/*
+ * Xact rollback scopes
+ */
+#define XACT_SCOPE_XACT 0
+#define XACT_SCOPE_STMT 1
+
+extern int DefaultXactRollbackScope;
+extern PGDLLIMPORT int XactRollbackScope;
+
/* Xact read-only state */
extern bool DefaultXactReadOnly;
extern bool XactReadOnly;
diff --git a/src/include/commands/variable.h b/src/include/commands/variable.h
index 247423c..32ff6a9 100644
--- a/src/include/commands/variable.h
+++ b/src/include/commands/variable.h
@@ -25,6 +25,9 @@ extern bool check_transaction_read_only(bool *newval, void **extra, GucSource so
extern bool check_XactIsoLevel(char **newval, void **extra, GucSource source);
extern void assign_XactIsoLevel(const char *newval, void *extra);
extern const char *show_XactIsoLevel(void);
+extern bool check_XactRollbackScope(char **newval, void **extra, GucSource source);
+extern void assign_XactRollbackScope(const char *newval, void *extra);
+extern const char *show_XactRollbackScope(void);
extern bool check_transaction_deferrable(bool *newval, void **extra, GucSource source);
extern bool check_random_seed(double *newval, void **extra, GucSource source);
extern void assign_random_seed(double newval, void *extra);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 985d650..485f5eb 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -342,6 +342,7 @@ PG_KEYWORD("rows", ROWS, UNRESERVED_KEYWORD)
PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD)
PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD)
+PG_KEYWORD("scope", SCOPE, UNRESERVED_KEYWORD)
PG_KEYWORD("scroll", SCROLL, UNRESERVED_KEYWORD)
PG_KEYWORD("search", SEARCH, UNRESERVED_KEYWORD)
PG_KEYWORD("second", SECOND_P, UNRESERVED_KEYWORD)