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)