Statement-level rollback
I would like to bring up again the topic of statement-level rollback.
This was discussed in some depth at [1]/messages/by-id/0A3221C70F24FB45833433255569204D1F6A9286@G01JPEXMBYT05. This patch is not based on
Tsunakawa-san's patch submitted in that thread; although I started from
it, I eventually removed almost everything and replaced with a
completely different implementation.
Patch 0001 here attached introduces the new setting and behavior with a
few applicability restrictions I outlined in [2]/messages/by-id/20180615202328.7m46qo46v5a5wkd2@alvherre.pgsql, on safety grounds.
However, that wasn't exactly met with the standing ovation that I was
expecting[3]/messages/by-id/CA+TgmoavJybY0C8LXHZNcw4p=Eh48yoZwbjq8HVa10qhuP=gpg@mail.gmail.com, so patch 0002 removes those and changes the behavior to
that of any other GUC -- so much so, in fact, that after that patch you
can change the rollback scope in the middle of a transaction, and it
affects from that point onwards.
There is a definite user demand for this feature; almost anyone porting
nontrivial apps from other DBMS systems will want this to be an option.
Of course, the default behavior doesn't change.
Some commercial DBMSs offer only the behavior that this patch
introduces. While they aren't universally loved, they have lots of
users, and many of those users have migrated or are migrating or will
migrate to Postgres in some form or another. Adding this feature lowers
the barrier to such migrations, which cannot be but a good thing. I
think we should add this, and if that means some tools will need to add
a SET line to ensure they get the behavior they need in case careless
DBAs enable this behavior server-wide, that seems not a terribly onerous
cost anyway.
[1]: /messages/by-id/0A3221C70F24FB45833433255569204D1F6A9286@G01JPEXMBYT05
[2]: /messages/by-id/20180615202328.7m46qo46v5a5wkd2@alvherre.pgsql
[3]: /messages/by-id/CA+TgmoavJybY0C8LXHZNcw4p=Eh48yoZwbjq8HVa10qhuP=gpg@mail.gmail.com
--
�lvaro Herrera PostgreSQL Expert, https://www.2ndQuadrant.com/
Attachments:
0001-Support-statement-level-rollback.patchtext/x-diff; charset=us-asciiDownload
From 5201b4686c47ac37f53debe4ed3851d4ebc224b6 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 7 Dec 2018 15:50:45 -0300
Subject: [PATCH 1/2] Support statement-level rollback
---
doc/src/sgml/ref/begin.sgml | 5 +-
doc/src/sgml/ref/set_transaction.sgml | 22 +++++-
src/backend/access/transam/xact.c | 120 ++++++++++++++++++++++++++---
src/backend/commands/variable.c | 31 ++++++++
src/backend/parser/gram.y | 12 ++-
src/backend/tcop/utility.c | 4 +
src/backend/utils/misc/guc.c | 20 +++++
src/include/access/xact.h | 11 +++
src/include/commands/variable.h | 1 +
src/include/parser/kwlist.h | 1 +
src/test/regress/expected/transactions.out | 70 +++++++++++++++++
src/test/regress/sql/transactions.sql | 46 +++++++++++
12 files changed, 328 insertions(+), 15 deletions(-)
diff --git a/doc/src/sgml/ref/begin.sgml b/doc/src/sgml/ref/begin.sgml
index c23bbfb4e7..a60a3abae3 100644
--- a/doc/src/sgml/ref/begin.sgml
+++ b/doc/src/sgml/ref/begin.sgml
@@ -26,6 +26,7 @@ BEGIN [ WORK | TRANSACTION ] [ <replaceable class="parameter">transaction_mode</
<phrase>where <replaceable class="parameter">transaction_mode</replaceable> is one of:</phrase>
ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
+ ROLLBACK SCOPE TRANSACTION
READ WRITE | READ ONLY
[ NOT ] DEFERRABLE
</synopsis>
@@ -58,8 +59,8 @@ BEGIN [ WORK | TRANSACTION ] [ <replaceable class="parameter">transaction_mode</
</para>
<para>
- If the isolation level, read/write mode, or deferrable mode is specified, the new
- transaction has those characteristics, as if
+ If the isolation level, read/write mode, rollback scope, or deferrable mode
+ is specified, the new transaction has those characteristics, as if
<xref linkend="sql-set-transaction"/>
was executed.
</para>
diff --git a/doc/src/sgml/ref/set_transaction.sgml b/doc/src/sgml/ref/set_transaction.sgml
index 43b1c6c892..e6aa4f46f6 100644
--- a/doc/src/sgml/ref/set_transaction.sgml
+++ b/doc/src/sgml/ref/set_transaction.sgml
@@ -10,6 +10,11 @@
</indexterm>
<indexterm>
+ <primary>transaction rollback scope</primary>
+ <secondary>setting</secondary>
+ </indexterm>
+
+ <indexterm>
<primary>read-only transaction</primary>
<secondary>setting</secondary>
</indexterm>
@@ -39,6 +44,7 @@ SET SESSION CHARACTERISTICS AS TRANSACTION <replaceable class="parameter">transa
<phrase>where <replaceable class="parameter">transaction_mode</replaceable> is one of:</phrase>
ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ COMMITTED | READ UNCOMMITTED }
+ ROLLBACK SCOPE TRANSACTION
READ WRITE | READ ONLY
[ NOT ] DEFERRABLE
</synopsis>
@@ -59,7 +65,8 @@ SET SESSION CHARACTERISTICS AS TRANSACTION <replaceable class="parameter">transa
<para>
The available transaction characteristics are the transaction
- isolation level, the transaction access mode (read/write or
+ 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.
@@ -124,6 +131,19 @@ SET SESSION CHARACTERISTICS AS TRANSACTION <replaceable class="parameter">transa
</para>
<para>
+ The rollback scope of a transaction determines the range of operations
+ which roll back when an SQL statement fails. The default value is
+ <literal>TRANSACTION</literal>, which causes the entire transaction or
+ current subtransaction to be rolled back. This is the only mode that can
+ be selected with <command>SET TRANSACTION</command>. The other possible
+ mode, <literal>STATEMENT</literal>, can only be specified during connection
+ establishment, <literal>ALTER USER</literal> or <literal>ALTER DATABASE</literal>;
+ in that mode, only the failed <acronym>SQL</acronym> statement is
+ rolled back, and the transaction is automatically put back in normal
+ mode.
+ </para>
+
+ <para>
The transaction access mode determines whether the transaction is
read/write or read-only. Read/write is the default. When a
transaction is read-only, the following SQL commands are
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index d967400384..c6fd975b7b 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -73,6 +73,8 @@
int DefaultXactIsoLevel = XACT_READ_COMMITTED;
int XactIsoLevel;
+int XactRollbackScope = XACT_ROLLBACK_SCOPE_XACT;
+
bool DefaultXactReadOnly = false;
bool XactReadOnly;
@@ -176,6 +178,7 @@ typedef struct TransactionStateData
int savepointLevel; /* savepoint level */
TransState state; /* low-level state */
TBlockState blockState; /* high-level state */
+ XactRollbackScopeValues rollbackScope; /* automatic abort behavior */
int nestingLevel; /* transaction nesting depth */
int gucNestLevel; /* GUC context nesting depth */
MemoryContext curTransactionContext; /* my xact-lifetime context */
@@ -202,6 +205,7 @@ typedef TransactionStateData *TransactionState;
static TransactionStateData TopTransactionStateData = {
.state = TRANS_DEFAULT,
.blockState = TBLOCK_DEFAULT,
+ .rollbackScope = XACT_ROLLBACK_SCOPE_XACT,
};
/*
@@ -1820,6 +1824,8 @@ StartTransaction(void)
*/
s->state = TRANS_START;
s->transactionId = InvalidTransactionId; /* until assigned */
+ /* fixed later in CommitTransactionCommand: */
+ s->rollbackScope = XACT_ROLLBACK_SCOPE_XACT;
/*
* initialize current transaction state fields
@@ -2705,6 +2711,7 @@ StartTransactionCommand(void)
*/
case TBLOCK_DEFAULT:
StartTransaction();
+ s = CurrentTransactionState;
s->blockState = TBLOCK_STARTED;
break;
@@ -2717,7 +2724,23 @@ StartTransactionCommand(void)
*/
case TBLOCK_INPROGRESS:
case TBLOCK_IMPLICIT_INPROGRESS:
+ break;
+
+ /*
+ * Same as above in transaction rollback scope; but in statement
+ * rollback scope, release the previous subtransaction and create
+ * a new one.
+ */
case TBLOCK_SUBINPROGRESS:
+ if (s->rollbackScope == XACT_ROLLBACK_SCOPE_STMT)
+ {
+ CommitSubTransaction(); /* what if there's an intervening subxact? */
+ PushTransaction();
+ s = CurrentTransactionState;
+ s->name = MemoryContextStrdup(TopTransactionContext, "pg internal");
+ StartSubTransaction();
+ s->blockState = TBLOCK_SUBINPROGRESS;
+ }
break;
/*
@@ -2792,13 +2815,25 @@ CommitTransactionCommand(void)
break;
/*
- * We are completing a "BEGIN TRANSACTION" command, so we change
- * to the "transaction block in progress" state and return. (We
- * assume the BEGIN did nothing to the database, so we need no
- * CommandCounterIncrement.)
+ * While completing a BEGIN transaction command, we mind the
+ * prevailing transaction rollback scope. In the normal case of
+ * rolling back the entire transaction, just change to the
+ * "transaction block in progress" state and return. (We assume
+ * the BEGIN did nothing to the database, so we need no
+ * CommandCounterIncrement.) If the rollback scope is statement,
+ * however, here we start a subtransaction to serve it.
*/
case TBLOCK_BEGIN:
+ s->rollbackScope = XactRollbackScope;
s->blockState = TBLOCK_INPROGRESS;
+ if (s->rollbackScope == XACT_ROLLBACK_SCOPE_STMT)
+ {
+ PushTransaction();
+ s = CurrentTransactionState; /* changed by push */
+ s->name = MemoryContextStrdup(TopTransactionContext, "pg internal");
+ StartSubTransaction();
+ s->blockState = TBLOCK_SUBINPROGRESS;
+ }
break;
/*
@@ -2985,6 +3020,7 @@ CommitTransactionCommand(void)
savepointLevel = s->savepointLevel;
CleanupSubTransaction();
+ s = CurrentTransactionState;
DefineSavepoint(NULL);
s = CurrentTransactionState; /* changed by push */
@@ -3063,6 +3099,7 @@ AbortCurrentTransaction(void)
*/
case TBLOCK_INPROGRESS:
case TBLOCK_PARALLEL_INPROGRESS:
+ Assert(s->rollbackScope != XACT_ROLLBACK_SCOPE_STMT);
AbortTransaction();
s->blockState = TBLOCK_ABORT;
/* CleanupTransaction happens when we exit TBLOCK_ABORT_END */
@@ -3120,13 +3157,32 @@ AbortCurrentTransaction(void)
break;
/*
- * We got an error inside a subtransaction. Abort just the
- * subtransaction, and go to the persistent SUBABORT state until
- * we get ROLLBACK.
+ * We got an error inside a subtransaction. In transaction-
+ * rollback-scope mode, abort just the subtransaction, and go to
+ * the persistent SUBABORT state until we get ROLLBACK; in
+ * statement rollback-scope mode, abort and cleanup the current
+ * subtransaction and automatically start a new one.
*/
case TBLOCK_SUBINPROGRESS:
AbortSubTransaction();
- s->blockState = TBLOCK_SUBABORT;
+
+ if (s->rollbackScope == XACT_ROLLBACK_SCOPE_STMT)
+ {
+ char *name = s->name;
+
+ s->name = NULL;
+ CleanupSubTransaction();
+
+ PushTransaction();
+ s = CurrentTransactionState; /* changed by push */
+ s->name = name;
+
+ AssertState(s->blockState == TBLOCK_SUBBEGIN);
+ StartSubTransaction();
+ s->blockState = TBLOCK_SUBINPROGRESS;
+ }
+ else
+ s->blockState = TBLOCK_SUBABORT;
break;
/*
@@ -3863,9 +3919,8 @@ DefineSavepoint(const char *name)
switch (s->blockState)
{
- case TBLOCK_INPROGRESS:
- case TBLOCK_SUBINPROGRESS:
/* Normal subtransaction start */
+ case TBLOCK_INPROGRESS:
PushTransaction();
s = CurrentTransactionState; /* changed by push */
@@ -3878,6 +3933,50 @@ DefineSavepoint(const char *name)
break;
/*
+ * As above, with a subtransaction already in progress.
+ *
+ * In transaction-scope rollback, we want the user-defined
+ * savepoint to appear *before* the automatic subtransaction.
+ * Close the one we opened above, then open and start the user
+ * invoked one, then start a new automatic savepoint, which is
+ * the one that CommitTransactionCommand will act upon.
+ *
+ * In normal mode, act just like the INPROGRESS case.
+ */
+ case TBLOCK_SUBINPROGRESS:
+ if (s->rollbackScope == XACT_ROLLBACK_SCOPE_STMT)
+ {
+ CommitSubTransaction();
+ PushTransaction();
+ s = CurrentTransactionState; /* changed by push */
+ if (name)
+ s->name = MemoryContextStrdup(TopTransactionContext, name);
+ StartSubTransaction();
+ s->blockState = TBLOCK_SUBINPROGRESS;
+
+ /* same as StartTransactionCommand for SUBINPROGRESS */
+ PushTransaction();
+ s = CurrentTransactionState;
+ s->name = MemoryContextStrdup(TopTransactionContext, "pg internal");
+ StartSubTransaction();
+ s->blockState = TBLOCK_SUBINPROGRESS;
+
+ }
+ else
+ {
+ PushTransaction();
+ s = CurrentTransactionState; /* changed by push */
+
+ /*
+ * Savepoint names, like the TransactionState block itself, live
+ * in TopTransactionContext.
+ */
+ if (name)
+ s->name = MemoryContextStrdup(TopTransactionContext, name);
+ }
+ break;
+
+ /*
* We disallow savepoint commands in implicit transaction blocks.
* There would be no great difficulty in allowing them so far as
* this module is concerned, but a savepoint seems inconsistent
@@ -4547,6 +4646,7 @@ StartSubTransaction(void)
TransStateAsString(s->state));
s->state = TRANS_START;
+ s->rollbackScope = s->parent->rollbackScope;
/*
* Initialize subsystems for new subtransaction
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index c7e5a9ca9f..c2af9b9795 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -555,6 +555,37 @@ check_XactIsoLevel(int *newval, void **extra, GucSource source)
}
/*
+ * SET TRANSACTION ROLLBACK SCOPE
+ */
+bool
+check_XactRollbackScope(int *newval, void **extra, GucSource source)
+{
+ /* idempotent changes are okay */
+ if (*newval == XactRollbackScope)
+ return true;
+
+ /*
+ * if in statement mode, allow downgrade to transaction mode (no going
+ * back from this one!)
+ */
+ if ((*newval == XACT_ROLLBACK_SCOPE_XACT) &&
+ (XactRollbackScope == XACT_ROLLBACK_SCOPE_STMT))
+ return true;
+
+ /*
+ * We want to reject the variable unless it comes from ALTER
+ * USER/DATABASE or connection establishment. We don't want to
+ * accept it in postgresql.conf or in manual SET.
+ */
+ if (source < PGC_S_SESSION && source > PGC_S_GLOBAL)
+ return true;
+
+ GUC_check_errmsg("the transaction rollback scope cannot be changed at this level");
+ GUC_check_errhint("You may set it in PGOPTIONS or via ALTER USER/DATABASE SET.");
+ return false;
+}
+
+/*
* SET TRANSACTION [NOT] DEFERRABLE
*/
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 2c2208ffb7..1f924101fb 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -353,7 +353,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%type <node> RowSecurityOptionalWithCheck RowSecurityOptionalExpr
%type <list> RowSecurityDefaultToRole RowSecurityOptionalToRole
-%type <str> iso_level opt_encoding
+%type <str> iso_level rollback_scope opt_encoding
%type <rolespec> grantee
%type <list> grantee_list
%type <accesspriv> privilege
@@ -673,7 +673,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
- SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
+ SAVEPOINT SCHEMA SCHEMAS SCOPE SCROLL SEARCH SECOND_P SECURITY SELECT SEQUENCE SEQUENCES
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P
@@ -1578,6 +1578,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"; }
@@ -9916,6 +9920,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); }
@@ -15184,6 +15191,7 @@ unreserved_keyword:
| SAVEPOINT
| SCHEMA
| SCHEMAS
+ | SCOPE
| SCROLL
| SEARCH
| SECOND_P
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 970c94ee80..3077a9adb5 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -427,6 +427,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 6fe1939881..de614d5e05 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -295,6 +295,12 @@ static const struct config_enum_entry isolation_level_options[] = {
{NULL, 0}
};
+static const struct config_enum_entry rollback_scope_options[] = {
+ {"transaction", XACT_ROLLBACK_SCOPE_XACT, false},
+ {"statement", XACT_ROLLBACK_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},
@@ -4151,6 +4157,17 @@ static struct config_enum ConfigureNamesEnum[] =
},
{
+ {"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,
+ XACT_ROLLBACK_SCOPE_XACT, rollback_scope_options,
+ check_XactRollbackScope, NULL, NULL
+ },
+
+ {
{"IntervalStyle", PGC_USERSET, CLIENT_CONN_LOCALE,
gettext_noop("Sets the display format for interval values."),
NULL,
@@ -7853,6 +7870,9 @@ ExecSetVariableStmt(VariableSetStmt *stmt, bool isTopLevel)
else if (strcmp(item->defname, "transaction_read_only") == 0)
SetPGVariable("transaction_read_only",
list_make1(item->arg), stmt->is_local);
+ else if (strcmp(item->defname, "transaction_rollback_scope") == 0)
+ SetPGVariable("transaction_rollback_scope",
+ list_make1(item->arg), stmt->is_local);
else if (strcmp(item->defname, "transaction_deferrable") == 0)
SetPGVariable("transaction_deferrable",
list_make1(item->arg), stmt->is_local);
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 689c57c592..dad8bb172d 100644
--- a/src/include/access/xact.h
+++ b/src/include/access/xact.h
@@ -50,6 +50,17 @@ extern PGDLLIMPORT int XactIsoLevel;
#define IsolationUsesXactSnapshot() (XactIsoLevel >= XACT_REPEATABLE_READ)
#define IsolationIsSerializable() (XactIsoLevel == XACT_SERIALIZABLE)
+/*
+ * Transaction rollback scope
+ */
+typedef enum
+{
+ XACT_ROLLBACK_SCOPE_XACT,
+ XACT_ROLLBACK_SCOPE_STMT
+} XactRollbackScopeValues;
+
+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 7373a3f99f..6239ee1788 100644
--- a/src/include/commands/variable.h
+++ b/src/include/commands/variable.h
@@ -23,6 +23,7 @@ extern void assign_log_timezone(const char *newval, void *extra);
extern const char *show_log_timezone(void);
extern bool check_transaction_read_only(bool *newval, void **extra, GucSource source);
extern bool check_XactIsoLevel(int *newval, void **extra, GucSource source);
+extern bool check_XactRollbackScope(int *newval, void **extra, GucSource source);
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 23db40147b..cd739e1149 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -353,6 +353,7 @@ PG_KEYWORD("rule", RULE, UNRESERVED_KEYWORD)
PG_KEYWORD("savepoint", SAVEPOINT, UNRESERVED_KEYWORD)
PG_KEYWORD("schema", SCHEMA, UNRESERVED_KEYWORD)
PG_KEYWORD("schemas", SCHEMAS, 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)
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 69e176c525..1a1a28be67 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -659,6 +659,76 @@ ERROR: portal "ctt" cannot be run
COMMIT;
DROP FUNCTION create_temp_tab();
DROP FUNCTION invert(x float8);
+-- tests for statement-scope rollback
+select current_user \gset
+CREATE USER regress_transactions_user;
+ALTER USER regress_transactions_user SET transaction_rollback_scope TO statement;
+\c - regress_transactions_user
+SHOW transaction_rollback_scope;
+ transaction_rollback_scope
+----------------------------
+ statement
+(1 row)
+
+CREATE TABLE xact_rscope (a int);
+-- elementary test: three separate insertions in a transaction, an abort in the
+-- middle one. We expect the other two to succeed.
+BEGIN;
+INSERT INTO xact_rscope VALUES (1);
+INSERT INTO xact_rscope VALUES (0/0);
+ERROR: division by zero
+INSERT INTO xact_rscope VALUES (2);
+COMMIT;
+SELECT * FROM xact_rscope;
+ a
+---
+ 1
+ 2
+(2 rows)
+
+-- DO blocks don't have this behavior
+DO $$
+BEGIN
+ INSERT INTO xact_rscope VALUES (3);
+ INSERT INTO xact_rscope VALUES (4/0);
+ INSERT INTO xact_rscope VALUES (5);
+END; $$;
+ERROR: division by zero
+CONTEXT: SQL statement "INSERT INTO xact_rscope VALUES (4/0)"
+PL/pgSQL function inline_code_block line 4 at SQL statement
+SELECT * FROM xact_rscope;
+ a
+---
+ 1
+ 2
+(2 rows)
+
+-- implicit transaction blocks don't have this behavior
+INSERT INTO xact_rscope VALUES (6)\; INSERT INTO xact_rscope VALUES (7/0)\; INSERT INTO xact_rscope VALUES (8);
+ERROR: division by zero
+-- test savepoints
+BEGIN;
+INSERT INTO xact_rscope VALUES (9);
+SAVEPOINT xact_svpt;
+INSERT INTO xact_rscope VALUES (10);
+INSERT INTO xact_rscope VALUES (11/0);
+ERROR: division by zero
+ROLLBACK TO xact_svpt;
+INSERT INTO xact_rscope VALUES (12);
+COMMIT;
+SELECT * FROM xact_rscope;
+ a
+----
+ 1
+ 2
+ 9
+ 12
+(4 rows)
+
+-- clean up
+\c - :current_user
+DROP OWNED BY regress_transactions_user;
+DROP USER regress_transactions_user;
-- Test assorted behaviors around the implicit transaction block created
-- when multiple SQL commands are sent in a single Query message. These
-- tests rely on the fact that psql will not break SQL commands apart at a
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 2e3739fd6c..3ac3450545 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -418,6 +418,52 @@ COMMIT;
DROP FUNCTION create_temp_tab();
DROP FUNCTION invert(x float8);
+-- tests for statement-scope rollback
+select current_user \gset
+CREATE USER regress_transactions_user;
+ALTER USER regress_transactions_user SET transaction_rollback_scope TO statement;
+\c - regress_transactions_user
+SHOW transaction_rollback_scope;
+
+CREATE TABLE xact_rscope (a int);
+
+-- elementary test: three separate insertions in a transaction, an abort in the
+-- middle one. We expect the other two to succeed.
+BEGIN;
+INSERT INTO xact_rscope VALUES (1);
+INSERT INTO xact_rscope VALUES (0/0);
+INSERT INTO xact_rscope VALUES (2);
+COMMIT;
+SELECT * FROM xact_rscope;
+
+-- DO blocks don't have this behavior
+DO $$
+BEGIN
+ INSERT INTO xact_rscope VALUES (3);
+ INSERT INTO xact_rscope VALUES (4/0);
+ INSERT INTO xact_rscope VALUES (5);
+END; $$;
+SELECT * FROM xact_rscope;
+
+-- implicit transaction blocks don't have this behavior
+INSERT INTO xact_rscope VALUES (6)\; INSERT INTO xact_rscope VALUES (7/0)\; INSERT INTO xact_rscope VALUES (8);
+
+-- test savepoints
+BEGIN;
+INSERT INTO xact_rscope VALUES (9);
+SAVEPOINT xact_svpt;
+INSERT INTO xact_rscope VALUES (10);
+INSERT INTO xact_rscope VALUES (11/0);
+ROLLBACK TO xact_svpt;
+INSERT INTO xact_rscope VALUES (12);
+COMMIT;
+SELECT * FROM xact_rscope;
+
+-- clean up
+\c - :current_user
+DROP OWNED BY regress_transactions_user;
+DROP USER regress_transactions_user;
+
-- Test assorted behaviors around the implicit transaction block created
-- when multiple SQL commands are sent in a single Query message. These
--
2.11.0
0002-Remove-restrictions-on-setting-rollback-scope.patchtext/x-diff; charset=us-asciiDownload
From 671de0c55ba20133b858933e02346327c65791a4 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Fri, 7 Dec 2018 15:57:28 -0300
Subject: [PATCH 2/2] Remove restrictions on setting rollback scope
---
doc/src/sgml/ref/set_transaction.sgml | 11 +-
src/backend/access/transam/xact.c | 39 ++++++-
src/backend/commands/variable.c | 31 ------
src/backend/utils/misc/guc.c | 2 +-
src/include/commands/variable.h | 4 +-
src/test/regress/expected/transactions.out | 170 +++++++++++++++++++++++++++++
src/test/regress/sql/transactions.sql | 95 ++++++++++++++++
7 files changed, 311 insertions(+), 41 deletions(-)
diff --git a/doc/src/sgml/ref/set_transaction.sgml b/doc/src/sgml/ref/set_transaction.sgml
index e6aa4f46f6..ef94e57ef6 100644
--- a/doc/src/sgml/ref/set_transaction.sgml
+++ b/doc/src/sgml/ref/set_transaction.sgml
@@ -134,13 +134,10 @@ SET SESSION CHARACTERISTICS AS TRANSACTION <replaceable class="parameter">transa
The rollback scope of a transaction determines the range of operations
which roll back when an SQL statement fails. The default value is
<literal>TRANSACTION</literal>, which causes the entire transaction or
- current subtransaction to be rolled back. This is the only mode that can
- be selected with <command>SET TRANSACTION</command>. The other possible
- mode, <literal>STATEMENT</literal>, can only be specified during connection
- establishment, <literal>ALTER USER</literal> or <literal>ALTER DATABASE</literal>;
- in that mode, only the failed <acronym>SQL</acronym> statement is
- rolled back, and the transaction is automatically put back in normal
- mode.
+ current subtransaction to be rolled back. The other possible mode is
+ <literal>STATEMENT</literal>, in which only the failed
+ <acronym>SQL</acronym> statement is rolled back, and the transaction
+ is automatically put back in normal mode.
</para>
<para>
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index c6fd975b7b..b30683eb9f 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -36,6 +36,7 @@
#include "commands/async.h"
#include "commands/tablecmds.h"
#include "commands/trigger.h"
+#include "commands/variable.h"
#include "executor/spi.h"
#include "libpq/be-fsstubs.h"
#include "libpq/pqsignal.h"
@@ -2722,10 +2723,26 @@ StartTransactionCommand(void)
* that any needed CommandCounterIncrement was done by the
* previous CommitTransactionCommand.)
*/
- case TBLOCK_INPROGRESS:
case TBLOCK_IMPLICIT_INPROGRESS:
break;
+ /* Normally there's nothing to do for the in-progress case. But in
+ * statement-scoped rollback mode, we start a new subtransaction
+ * here to which we can roll back. This case is pretty rare: it
+ * only occurs when the GUC is changed partway through a
+ * transaction.
+ */
+ case TBLOCK_INPROGRESS:
+ if (s->rollbackScope == XACT_ROLLBACK_SCOPE_STMT)
+ {
+ PushTransaction();
+ s = CurrentTransactionState;
+ s->name = MemoryContextStrdup(TopTransactionContext, "pg internal");
+ StartSubTransaction();
+ s->blockState = TBLOCK_SUBINPROGRESS;
+ }
+ break;
+
/*
* Same as above in transaction rollback scope; but in statement
* rollback scope, release the previous subtransaction and create
@@ -5243,6 +5260,26 @@ ShowTransactionStateRec(const char *str, TransactionState s)
}
/*
+ * GUC assign hook for SET TRANSACTION ROLLBACK SCOPE
+ */
+void
+assign_XactRollbackScope(int newval, void *extra)
+{
+ TransactionState s = CurrentTransactionState;
+
+ /*
+ * Alter the GUC setting for future transactions ...
+ */
+ XactRollbackScope = newval;
+
+ /* ... as well as update the current transaction's status */
+ if ((s->state == TRANS_INPROGRESS) && (newval != s->rollbackScope))
+ s->rollbackScope = newval;
+
+ /* Should we prepare transaction state here? */
+}
+
+/*
* BlockStateAsString
* Debug support
*/
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index c2af9b9795..c7e5a9ca9f 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -555,37 +555,6 @@ check_XactIsoLevel(int *newval, void **extra, GucSource source)
}
/*
- * SET TRANSACTION ROLLBACK SCOPE
- */
-bool
-check_XactRollbackScope(int *newval, void **extra, GucSource source)
-{
- /* idempotent changes are okay */
- if (*newval == XactRollbackScope)
- return true;
-
- /*
- * if in statement mode, allow downgrade to transaction mode (no going
- * back from this one!)
- */
- if ((*newval == XACT_ROLLBACK_SCOPE_XACT) &&
- (XactRollbackScope == XACT_ROLLBACK_SCOPE_STMT))
- return true;
-
- /*
- * We want to reject the variable unless it comes from ALTER
- * USER/DATABASE or connection establishment. We don't want to
- * accept it in postgresql.conf or in manual SET.
- */
- if (source < PGC_S_SESSION && source > PGC_S_GLOBAL)
- return true;
-
- GUC_check_errmsg("the transaction rollback scope cannot be changed at this level");
- GUC_check_errhint("You may set it in PGOPTIONS or via ALTER USER/DATABASE SET.");
- return false;
-}
-
-/*
* SET TRANSACTION [NOT] DEFERRABLE
*/
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index de614d5e05..a8da9decf9 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -4164,7 +4164,7 @@ static struct config_enum ConfigureNamesEnum[] =
},
&XactRollbackScope,
XACT_ROLLBACK_SCOPE_XACT, rollback_scope_options,
- check_XactRollbackScope, NULL, NULL
+ NULL, assign_XactRollbackScope, NULL
},
{
diff --git a/src/include/commands/variable.h b/src/include/commands/variable.h
index 6239ee1788..9cd8131e5b 100644
--- a/src/include/commands/variable.h
+++ b/src/include/commands/variable.h
@@ -23,7 +23,6 @@ extern void assign_log_timezone(const char *newval, void *extra);
extern const char *show_log_timezone(void);
extern bool check_transaction_read_only(bool *newval, void **extra, GucSource source);
extern bool check_XactIsoLevel(int *newval, void **extra, GucSource source);
-extern bool check_XactRollbackScope(int *newval, void **extra, GucSource source);
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);
@@ -36,4 +35,7 @@ extern bool check_role(char **newval, void **extra, GucSource source);
extern void assign_role(const char *newval, void *extra);
extern const char *show_role(void);
+/* in xact.c */
+extern void assign_XactRollbackScope(int newval, void *extra);
+
#endif /* VARIABLE_H */
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 1a1a28be67..a259cd6121 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -725,10 +725,180 @@ SELECT * FROM xact_rscope;
12
(4 rows)
+-- test changing rollback scope to 'transaction' partway through
+TRUNCATE TABLE xact_rscope;
+BEGIN;
+SHOW transaction_rollback_scope;
+ transaction_rollback_scope
+----------------------------
+ statement
+(1 row)
+
+INSERT INTO xact_rscope VALUES (1);
+INSERT INTO xact_rscope VALUES (2/0);
+ERROR: division by zero
+SET LOCAL transaction_rollback_scope TO 'transaction';
+INSERT INTO xact_rscope VALUES (3);
+COMMIT;
+SELECT * FROM xact_rscope;
+ a
+---
+ 1
+ 3
+(2 rows)
+
+TRUNCATE TABLE xact_rscope;
+BEGIN;
+INSERT INTO xact_rscope VALUES (1);
+INSERT INTO xact_rscope VALUES (2/0);
+ERROR: division by zero
+SET LOCAL transaction_rollback_scope TO 'transaction';
+INSERT INTO xact_rscope VALUES (3);
+INSERT INTO xact_rscope VALUES (4/0);
+ERROR: division by zero
+COMMIT;
+SELECT * FROM xact_rscope;
+ a
+---
+(0 rows)
+
+TRUNCATE TABLE xact_rscope;
+BEGIN;
+INSERT INTO xact_rscope VALUES (1);
+SAVEPOINT foo;
+INSERT INTO xact_rscope VALUES (2/0);
+ERROR: division by zero
+SET LOCAL transaction_rollback_scope TO 'transaction';
+INSERT INTO xact_rscope VALUES (4/0);
+ERROR: division by zero
+ROLLBACK TO foo;
+INSERT INTO xact_rscope VALUES (5);
+COMMIT;
+SELECT * FROM xact_rscope;
+ a
+---
+ 1
+ 5
+(2 rows)
+
+TRUNCATE TABLE xact_rscope;
+BEGIN;
+INSERT INTO xact_rscope VALUES (1);
+SAVEPOINT foo;
+INSERT INTO xact_rscope VALUES (2/0);
+ERROR: division by zero
+SET LOCAL transaction_rollback_scope TO 'transaction';
+INSERT INTO xact_rscope VALUES (4);
+RELEASE SAVEPOINT foo;
+INSERT INTO xact_rscope VALUES (5);
+COMMIT;
+SELECT * FROM xact_rscope;
+ a
+---
+ 1
+ 4
+ 5
+(3 rows)
+
-- clean up
\c - :current_user
DROP OWNED BY regress_transactions_user;
DROP USER regress_transactions_user;
+-- We allow the GUC to be changed mid-session too
+CREATE TABLE xact_rscope (a int);
+SHOW transaction_rollback_scope;
+ transaction_rollback_scope
+----------------------------
+ transaction
+(1 row)
+
+BEGIN TRANSACTION ROLLBACK SCOPE STATEMENT;
+SHOW transaction_rollback_scope;
+ transaction_rollback_scope
+----------------------------
+ statement
+(1 row)
+
+INSERT INTO xact_rscope VALUES (1);
+INSERT INTO xact_rscope VALUES (2/0);
+ERROR: division by zero
+INSERT INTO xact_rscope VALUES (3), (4/0);
+ERROR: division by zero
+INSERT INTO xact_rscope VALUES (5);
+COMMIT;
+SELECT * FROM xact_rscope;
+ a
+---
+ 1
+ 5
+(2 rows)
+
+TRUNCATE TABLE xact_rscope;
+SET transaction_rollback_scope TO 'statement';
+BEGIN;
+INSERT INTO xact_rscope VALUES (5);
+INSERT INTO xact_rscope VALUES (6/0);
+ERROR: division by zero
+COMMIT;
+SELECT * FROM xact_rscope;
+ a
+---
+ 5
+(1 row)
+
+RESET transaction_rollback_scope;
+TRUNCATE TABLE xact_rscope;
+BEGIN;
+SHOW transaction_rollback_scope;
+ transaction_rollback_scope
+----------------------------
+ transaction
+(1 row)
+
+INSERT INTO xact_rscope VALUES (1);
+SET LOCAL transaction_rollback_scope TO 'statement';
+INSERT INTO xact_rscope VALUES (2/0);
+ERROR: division by zero
+INSERT INTO xact_rscope VALUES (3);
+COMMIT;
+SELECT * FROM xact_rscope;
+ a
+---
+ 1
+ 3
+(2 rows)
+
+SHOW transaction_rollback_scope;
+ transaction_rollback_scope
+----------------------------
+ transaction
+(1 row)
+
+TRUNCATE TABLE xact_rscope;
+BEGIN ROLLBACK SCOPE STATEMENT;
+INSERT INTO xact_rscope VALUES (1);
+SAVEPOINT foo;
+INSERT INTO xact_rscope VALUES (2/0);
+ERROR: division by zero
+INSERT INTO xact_rscope VALUES (3);
+RELEASE SAVEPOINT foo;
+INSERT INTO xact_rscope VALUES (4);
+SAVEPOINT bar;
+INSERT INTO xact_rscope VALUES (5);
+INSERT INTO xact_rscope VALUES (6/0);
+ERROR: division by zero
+ROLLBACK TO bar;
+INSERT INTO xact_rscope VALUES (7);
+COMMIT;
+SELECT * FROM xact_rscope;
+ a
+---
+ 1
+ 3
+ 4
+ 7
+(4 rows)
+
-- Test assorted behaviors around the implicit transaction block created
-- when multiple SQL commands are sent in a single Query message. These
-- tests rely on the fact that psql will not break SQL commands apart at a
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 3ac3450545..4fa8246de7 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -459,11 +459,106 @@ INSERT INTO xact_rscope VALUES (12);
COMMIT;
SELECT * FROM xact_rscope;
+-- test changing rollback scope to 'transaction' partway through
+TRUNCATE TABLE xact_rscope;
+BEGIN;
+SHOW transaction_rollback_scope;
+INSERT INTO xact_rscope VALUES (1);
+INSERT INTO xact_rscope VALUES (2/0);
+SET LOCAL transaction_rollback_scope TO 'transaction';
+INSERT INTO xact_rscope VALUES (3);
+COMMIT;
+SELECT * FROM xact_rscope;
+
+TRUNCATE TABLE xact_rscope;
+BEGIN;
+INSERT INTO xact_rscope VALUES (1);
+INSERT INTO xact_rscope VALUES (2/0);
+SET LOCAL transaction_rollback_scope TO 'transaction';
+INSERT INTO xact_rscope VALUES (3);
+INSERT INTO xact_rscope VALUES (4/0);
+COMMIT;
+SELECT * FROM xact_rscope;
+
+TRUNCATE TABLE xact_rscope;
+BEGIN;
+INSERT INTO xact_rscope VALUES (1);
+SAVEPOINT foo;
+INSERT INTO xact_rscope VALUES (2/0);
+SET LOCAL transaction_rollback_scope TO 'transaction';
+INSERT INTO xact_rscope VALUES (4/0);
+ROLLBACK TO foo;
+INSERT INTO xact_rscope VALUES (5);
+COMMIT;
+SELECT * FROM xact_rscope;
+
+
+TRUNCATE TABLE xact_rscope;
+BEGIN;
+INSERT INTO xact_rscope VALUES (1);
+SAVEPOINT foo;
+INSERT INTO xact_rscope VALUES (2/0);
+SET LOCAL transaction_rollback_scope TO 'transaction';
+INSERT INTO xact_rscope VALUES (4);
+RELEASE SAVEPOINT foo;
+INSERT INTO xact_rscope VALUES (5);
+COMMIT;
+SELECT * FROM xact_rscope;
+
-- clean up
\c - :current_user
DROP OWNED BY regress_transactions_user;
DROP USER regress_transactions_user;
+-- We allow the GUC to be changed mid-session too
+CREATE TABLE xact_rscope (a int);
+SHOW transaction_rollback_scope;
+BEGIN TRANSACTION ROLLBACK SCOPE STATEMENT;
+SHOW transaction_rollback_scope;
+INSERT INTO xact_rscope VALUES (1);
+INSERT INTO xact_rscope VALUES (2/0);
+INSERT INTO xact_rscope VALUES (3), (4/0);
+INSERT INTO xact_rscope VALUES (5);
+COMMIT;
+SELECT * FROM xact_rscope;
+
+TRUNCATE TABLE xact_rscope;
+SET transaction_rollback_scope TO 'statement';
+BEGIN;
+INSERT INTO xact_rscope VALUES (5);
+INSERT INTO xact_rscope VALUES (6/0);
+COMMIT;
+SELECT * FROM xact_rscope;
+RESET transaction_rollback_scope;
+
+TRUNCATE TABLE xact_rscope;
+BEGIN;
+SHOW transaction_rollback_scope;
+INSERT INTO xact_rscope VALUES (1);
+SET LOCAL transaction_rollback_scope TO 'statement';
+INSERT INTO xact_rscope VALUES (2/0);
+INSERT INTO xact_rscope VALUES (3);
+COMMIT;
+SELECT * FROM xact_rscope;
+SHOW transaction_rollback_scope;
+
+TRUNCATE TABLE xact_rscope;
+BEGIN ROLLBACK SCOPE STATEMENT;
+INSERT INTO xact_rscope VALUES (1);
+SAVEPOINT foo;
+INSERT INTO xact_rscope VALUES (2/0);
+INSERT INTO xact_rscope VALUES (3);
+RELEASE SAVEPOINT foo;
+INSERT INTO xact_rscope VALUES (4);
+SAVEPOINT bar;
+INSERT INTO xact_rscope VALUES (5);
+INSERT INTO xact_rscope VALUES (6/0);
+ROLLBACK TO bar;
+INSERT INTO xact_rscope VALUES (7);
+COMMIT;
+SELECT * FROM xact_rscope;
+
+
-- Test assorted behaviors around the implicit transaction block created
-- when multiple SQL commands are sent in a single Query message. These
--
2.11.0
Hi,
On 2018-12-07 16:20:06 -0300, Alvaro Herrera wrote:
case TBLOCK_BEGIN: + s->rollbackScope = XactRollbackScope; s->blockState = TBLOCK_INPROGRESS; + if (s->rollbackScope == XACT_ROLLBACK_SCOPE_STMT) + { + PushTransaction(); + s = CurrentTransactionState; /* changed by push */ + s->name = MemoryContextStrdup(TopTransactionContext, "pg internal"); + StartSubTransaction(); + s->blockState = TBLOCK_SUBINPROGRESS; + }
Isn't this going to be performing ridiculously bad, to the point of
being not much but a trap for users?
I can see the feature being useful, but I don't think we should accept a
feature that'll turn bulkloading with insert into a server shutdown
scenario.
Greetings,
Andres Freund
On 2018-Dec-07, Andres Freund wrote:
Hi,
On 2018-12-07 16:20:06 -0300, Alvaro Herrera wrote:
case TBLOCK_BEGIN: + s->rollbackScope = XactRollbackScope; s->blockState = TBLOCK_INPROGRESS; + if (s->rollbackScope == XACT_ROLLBACK_SCOPE_STMT) + { + PushTransaction(); + s = CurrentTransactionState; /* changed by push */ + s->name = MemoryContextStrdup(TopTransactionContext, "pg internal"); + StartSubTransaction(); + s->blockState = TBLOCK_SUBINPROGRESS; + }Isn't this going to be performing ridiculously bad, to the point of
being not much but a trap for users?
If you bulk-load with INSERT under this behavior, yeah it'll create lots
of subtransactions, with the obvious downsides -- eating lots of xids
for one. I think the answer to that is "don't do that". However, if
you want to do 100k inserts and have the 10 bogus of those fail cleanly
(and not abort the other 99990), that's clearly this patch's intention.
I can see the feature being useful, but I don't think we should accept a
feature that'll turn bulkloading with insert into a server shutdown
scenario.
Hm.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Hi,
On December 7, 2018 11:44:17 AM PST, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
On 2018-Dec-07, Andres Freund wrote:
Hi,
On 2018-12-07 16:20:06 -0300, Alvaro Herrera wrote:
case TBLOCK_BEGIN: + s->rollbackScope = XactRollbackScope; s->blockState = TBLOCK_INPROGRESS; + if (s->rollbackScope == XACT_ROLLBACK_SCOPE_STMT) + { + PushTransaction(); + s = CurrentTransactionState; /* changed by push */ + s->name = MemoryContextStrdup(TopTransactionContext, "pginternal");
+ StartSubTransaction(); + s->blockState = TBLOCK_SUBINPROGRESS; + }Isn't this going to be performing ridiculously bad, to the point of
being not much but a trap for users?If you bulk-load with INSERT under this behavior, yeah it'll create
lots
of subtransactions, with the obvious downsides -- eating lots of xids
for one. I think the answer to that is "don't do that". However, if
you want to do 100k inserts and have the 10 bogus of those fail cleanly
(and not abort the other 99990), that's clearly this patch's intentionI can see the feature being useful, but I don't think we should
accept a
feature that'll turn bulkloading with insert into a server shutdown
scenario.Hm.
Isn't the realistic scenario for migrations that people will turn this feature on on a more global basis? If they need to do per transaction choices, that makes this much less useful for easy migrations.
Andres
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.
Hi
On 2018-Dec-07, Andres Freund wrote:
Isn't the realistic scenario for migrations that people will turn this
feature on on a more global basis? If they need to do per transaction
choices, that makes this much less useful for easy migrations.
The way I envision this to be used in practice is that it's enabled
globally when the migration effort starts, then gradually disabled as
parts of applications affected by these downsides are taught how to deal
with the other behavior.
BTW, a couple of months ago I measured the performance implications for
a single update under pgbench and it represented a decrease of about
3%-5%. Side-effects such as xid consumption have worse implications,
but as far as performance is concerned, it's not as bad as all that.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On December 7, 2018 11:56:55 AM PST, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
BTW, a couple of months ago I measured the performance implications for
a single update under pgbench and it represented a decrease of about
3%-5%. Side-effects such as xid consumption have worse implications,
but as far as performance is concerned, it's not as bad as all that.
I don't think that's a fair test for the performance downsides. For pgbench with modifications the full commit is such a large portion of the time that you'd need to make things a lot slower to show a large slowdown. And for ro pgbench the subxacts don't matter that much. It'd probably be more meaningful to have a mixed workload of 15 ro statements per xact in one type of session, and 5rw /10ro in another.
Andres
--
Sent from my Android device with K-9 Mail. Please excuse my brevity.
On 2018-Dec-07, Andres Freund wrote:
On December 7, 2018 11:56:55 AM PST, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
BTW, a couple of months ago I measured the performance implications for
a single update under pgbench and it represented a decrease of about
3%-5%. Side-effects such as xid consumption have worse implications,
but as far as performance is concerned, it's not as bad as all that.I don't think that's a fair test for the performance downsides. For
pgbench with modifications the full commit is such a large portion of
the time that you'd need to make things a lot slower to show a large
slowdown. And for ro pgbench the subxacts don't matter that much. It'd
probably be more meaningful to have a mixed workload of 15 ro
statements per xact in one type of session, and 5rw /10ro in another.
I just reviewed the old emails where I reported some performance
figures. Turns out I tried pgbench with varying number of inserts per
transaction:
nr_inserts │ transaction │ statement │ decrease
────────────┼─────────────┼───────────┼────────────────────────
10 │ 20674.12 │ 19983.33 │ 3.34132722456868780900
63 │ 3704.95 │ 3613.39 │ 2.47128841144954722700
64 │ 3646.99 │ 3553.79 │ 2.55553209633149528800
65 │ 3582.17 │ 3503.47 │ 2.19699232588068126300
66 │ 3513.27 │ 3423.34 │ 2.55972356237920797400
80 │ 2906.50 │ 2833.80 │ 2.50129021159470153100
100 │ 2337.58 │ 2272.01 │ 2.80503768854969669500
200 │ 1171.06 │ 1143.22 │ 2.37733335610472563300
500 │ 464.55 │ 451.04 │ 2.90819072220428371500
1000 │ 229.79 │ 223.66 │ 2.66765307454632490500
10000 │ 22.02 │ 21.43 │ 2.67938237965485921900
(The numbers around 64 were tested to make sure we weren't particularly
in trouble when we hit PGPROC subxid cache size.)
In a different test, I used HOT updates, and I said "This works out to
be between 4.3% and 5.6% in the four cases, which is not growing with
the number of ops per transaction."
nr_updates │ transaction │ statement
────────────┼──────────────────┼─────────────────
10 │ 6714.67 +-114.37 │ 6362.89 +-99.96
100 │ 699.19 +-7.20 │ 671.64 +-7.75
200 │ 359.92 +-0.86 │ 342.07 +-8.34
500 │ 143.11 +-2.47 │ 135.14 +-3.21
1000 │ 70.07 +-2.61 │ 67.04 +-1.52
I didn't measure read-only statements. I definitely expect that case to
have a larger performance decrease; however, that's the kind of code
site that, if it becomes a hot path, you'd change from the slow mode to
the fast mode.
--
Álvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Fri, Dec 7, 2018 at 2:57 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
The way I envision this to be used in practice is that it's enabled
globally when the migration effort starts, then gradually disabled as
parts of applications affected by these downsides are taught how to deal
with the other behavior.
I'm not sure how realistic that is, honestly. I think if you add this
feature, people are going to turn it on and leave it on. But even if
you're right and people only use it temporarily, you'd really only
need to do one large bulk load to break the whole system. A
five-minute bulk load could burn through tens of millions of XIDs, and
some EDB customers, at least, do loads that last many hours.
Full disclosure: EDB has a feature like this and has for years, but it
uses a subtransaction per statement, not a subtransaction per row. It
is indeed useful to customers, but it also does cause its share of
problems. It is *very* easy to burn through a *lot* of XIDs this way,
even with a subtransaction per statement. For example, PL code in
function A can call PL code in function B which can call PL code in
function C, and you throw in a loop here and an EXCEPTION block there
and all kinds of fun ensues.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 2018-Dec-07, Robert Haas wrote:
Full disclosure: EDB has a feature like this and has for years, but it
uses a subtransaction per statement, not a subtransaction per row.
Well, this implementation only uses one subtransaction per statement;
Andres says per-row referring to the case of one INSERT per row, so it's
still one statement.
It is indeed useful to customers, but it also does cause its share of
problems. It is *very* easy to burn through a *lot* of XIDs this way,
even with a subtransaction per statement. For example, PL code in
function A can call PL code in function B which can call PL code in
function C, and you throw in a loop here and an EXCEPTION block there
and all kinds of fun ensues.
Yeah, I agree that this downside is real. I think our only protection
against that is to say "don't do that". Like any other tool, it has
upsides and downsides; we shouldn't keep it away from users only because
they might misuse it.
I would be interested to know if the EDB implementation does something
in a better way than this one; then we can improve ours.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Fri, Dec 7, 2018 at 3:34 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
Yeah, I agree that this downside is real. I think our only protection
against that is to say "don't do that". Like any other tool, it has
upsides and downsides; we shouldn't keep it away from users only because
they might misuse it.
I have a hard time arguing against that given that EDB has this thing
in our bag of tricks, but if it weren't for that I'd be fighting
against this tooth and nail. Behavior-changing GUCs suuuuck.
More generally, whether or not we should "keep something away from our
users" really depends on how likely the upsides are to occur relative
to the downsides. We don't try to keep users from running DELETE
because they might delete data they want; that would be nanny-ism.
But we do try to keep them from reading dirty data from an uncommitted
transaction because we can't implement that without a risk of server
crashes, and that's too big a downside to justify the upside. If we
could do it safely, we might.
From that point of view, this is doubtless not the worst feature
PostgreSQL will ever have, but it sure ain't the best.
I would be interested to know if the EDB implementation does something
in a better way than this one; then we can improve ours.
I haven't studied yours (and don't have time for that right now) so
can't comment, at least not now.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 2018-Dec-07, Robert Haas wrote:
More generally, whether or not we should "keep something away from our
users" really depends on how likely the upsides are to occur relative
to the downsides. We don't try to keep users from running DELETE
because they might delete data they want; that would be nanny-ism.
But we do try to keep them from reading dirty data from an uncommitted
transaction because we can't implement that without a risk of server
crashes, and that's too big a downside to justify the upside. If we
could do it safely, we might.From that point of view, this is doubtless not the worst feature
PostgreSQL will ever have, but it sure ain't the best.
Well, look at this from this point of view: EnterpriseDB implemented
this because of customer demand (presumably). Fujitsu also implemented
this for customers. The pgjdbc driver implemented this for its users.
Now 2ndQuadrant also implemented this, and not out of the goodness of
our hearts. Is there any room to say that there is no customer demand
for this feature?
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On 12/7/18 12:50 PM, Alvaro Herrera wrote:
On 2018-Dec-07, Robert Haas wrote:
More generally, whether or not we should "keep something away from our
users" really depends on how likely the upsides are to occur relative
to the downsides. We don't try to keep users from running DELETE
because they might delete data they want; that would be nanny-ism.
But we do try to keep them from reading dirty data from an uncommitted
transaction because we can't implement that without a risk of server
crashes, and that's too big a downside to justify the upside. If we
could do it safely, we might.From that point of view, this is doubtless not the worst feature
PostgreSQL will ever have, but it sure ain't the best.Well, look at this from this point of view: EnterpriseDB implemented
this because of customer demand (presumably). Fujitsu also implemented
this for customers. The pgjdbc driver implemented this for its users.
Now 2ndQuadrant also implemented this, and not out of the goodness of
our hearts. Is there any room to say that there is no customer demand
for this feature?
Amazon also implemented something similar for the database migration
tool. I am unsure if they do it similarly with the DMS. With the DMT it
definitely had the XID issue that some are concerned with but I would
argue that is the cost of doing business.
JD
--
Command Prompt, Inc. || http://the.postgres.company/ || @cmdpromptinc
*** A fault and talent of mine is to tell it exactly how it is. ***
PostgreSQL centered full stack support, consulting and development.
Advocate: @amplifypostgres || Learn: https://postgresconf.org
***** Unless otherwise stated, opinions are my own. *****
Robert Haas <robertmhaas@gmail.com> writes:
I have a hard time arguing against that given that EDB has this thing
in our bag of tricks, but if it weren't for that I'd be fighting
against this tooth and nail. Behavior-changing GUCs suuuuck.
Uh, we're not seriously considering a GUC that changes transactional
behavior are we? I thought we learned our lesson about that from the
autocommit fiasco. I'm not quite going to say "that'll go in over my
dead body", but I *urgently* recommend finding a less fragile way
to do it.
In a quick look at the patch, it seems that it has a BEGIN/START
TRANSACTION option, which perhaps would do for the "less fragile"
way; the problem is that it's also adding a GUC. Maybe we could
make the GUC read-only, so that it's only a reporting mechanism
not a twiddlable knob? (BTW, if it's not GUC_REPORT, you are
missing a large bet; that would at least make it *possible* for
clients to not be broken by this, even if it would still be an
unreasonable amount of work for them to cope with it.)
regards, tom lane
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Well, look at this from this point of view: EnterpriseDB implemented
this because of customer demand (presumably). Fujitsu also implemented
this for customers. The pgjdbc driver implemented this for its users.
Now 2ndQuadrant also implemented this, and not out of the goodness of
our hearts. Is there any room to say that there is no customer demand
for this feature?
Yeah, but there is also lots of demand for stability in basic
transactional semantics. I refer you again to the AUTOCOMMIT business;
there were a lot of claims that that wouldn't break too much, and they
were all wrong.
regards, tom lane
Hi,
On 2018-12-07 16:02:53 -0500, Tom Lane wrote:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Well, look at this from this point of view: EnterpriseDB implemented
this because of customer demand (presumably). Fujitsu also implemented
this for customers. The pgjdbc driver implemented this for its users.
Now 2ndQuadrant also implemented this, and not out of the goodness of
our hearts. Is there any room to say that there is no customer demand
for this feature?Yeah, but there is also lots of demand for stability in basic
transactional semantics. I refer you again to the AUTOCOMMIT business;
there were a lot of claims that that wouldn't break too much, and they
were all wrong.
I think it could partially be addressed by not allowing to set it on the
commandline, server config, etc. So the user would have to set it on a
per-connection basis, potentially via the connection string.
I'm quite concerned how this'll make it much harder to write UDFs that
work correctly. If suddenly the error handling expected doesn't work
anymore - because they an error just skips over a statements - a lot of
things will break in hard to understand ways.
Greetings,
Andres Freund
On Fri, Dec 7, 2018 at 11:34 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
On 2018-Dec-07, Robert Haas wrote:
Full disclosure: EDB has a feature like this and has for years, but it
uses a subtransaction per statement, not a subtransaction per row.Well, this implementation only uses one subtransaction per statement;
Andres says per-row referring to the case of one INSERT per row, so it's
still one statement.
I'd like to add my 2 cents.
I think usage of subtransaction per statement for statement level
rollback is fair. It's unlikely we're going to invent something
better in this part.
The issue here is that our subtransaction mechanism is not effective
enough to use it for every statement. Especially when there are many
statements and each of them touches the only row. But we can improve
that.
The first thing, which comes to the mind is undo log. When you have
undo log, then on new subtransaction you basically memorize the
current undo log position. If subtransaction rollbacks, you have to
just play back undo log until reach memorized position. This might be
an option for zheap. But actually, I didn't inspect zheap code to
understand how far we're from this.
But nevetheless, we also have to do something with our heap, which is
not undo-based storage. I think it is possible to implement a
compromise solution, which allows to accelerate small subtransactions
on heap. Instead of allocating xid for subtransaction immediately on
write, we can start with using top transaction xid. But also allocate
small memory buffer to memorize locations of heap tuples added or
modified by this subtransaction. If we wouldn't overflow this small
buffer, we can use it to rollback state of heap on subtransaction
abort. Otherwise, we can decide to nevertheless allocate xid for this
subtransaction. But then xid allocation would happen only for
large-enough subtransactions. If there is an interest in this, I can
write a POC.
------
Alexander Korotkov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
On Fri, Dec 7, 2018 at 7:25 PM Alexander Korotkov
<a.korotkov@postgrespro.ru> wrote:
The first thing, which comes to the mind is undo log. When you have
undo log, then on new subtransaction you basically memorize the
current undo log position. If subtransaction rollbacks, you have to
just play back undo log until reach memorized position. This might be
an option for zheap. But actually, I didn't inspect zheap code to
understand how far we're from this.
Yeah, zheap works this way.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Fri, Dec 7, 2018 at 3:50 PM Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
On 2018-Dec-07, Robert Haas wrote:
More generally, whether or not we should "keep something away from our
users" really depends on how likely the upsides are to occur relative
to the downsides. We don't try to keep users from running DELETE
because they might delete data they want; that would be nanny-ism.
But we do try to keep them from reading dirty data from an uncommitted
transaction because we can't implement that without a risk of server
crashes, and that's too big a downside to justify the upside. If we
could do it safely, we might.From that point of view, this is doubtless not the worst feature
PostgreSQL will ever have, but it sure ain't the best.Well, look at this from this point of view: EnterpriseDB implemented
this because of customer demand (presumably). Fujitsu also implemented
this for customers. The pgjdbc driver implemented this for its users.
Now 2ndQuadrant also implemented this, and not out of the goodness of
our hearts. Is there any room to say that there is no customer demand
for this feature?
There is no room at all for doubt on that point. The issue, at least
IMV, is collateral damage -- customers surely want it. Indeed, if
we'd been smarter, maybe we would've invented a way to make it work
like that from the beginning. But changing it now, even as an optional
behavior, will (as Tom and Andres are rightly saying) mean that every
tool and extension author potentially has a problem with things not
working as expected. One can make an argument that such pain is worth
enduring, or that it isn't.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 2018-Dec-07, Andres Freund wrote:
I think it could partially be addressed by not allowing to set it on the
commandline, server config, etc. So the user would have to set it on a
per-connection basis, potentially via the connection string.
This is what patch 0001 does -- it's only allowed in the connection
string, or on ALTER USER / ALTER DATABASE. Setting it in
postgresql.conf is forbidden, as well as changing from transaction to
statement in SET (the opposite is allowed, though.)
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Hi,
On 2018-12-08 17:10:27 -0300, Alvaro Herrera wrote:
On 2018-Dec-07, Andres Freund wrote:
I think it could partially be addressed by not allowing to set it on the
commandline, server config, etc. So the user would have to set it on a
per-connection basis, potentially via the connection string.This is what patch 0001 does -- it's only allowed in the connection
string, or on ALTER USER / ALTER DATABASE. Setting it in
postgresql.conf is forbidden, as well as changing from transaction to
statement in SET (the opposite is allowed, though.)
I don't think allowing to set it on a per-user basis is acceptable
either, it still leaves the client in a state where they'll potentially
be confused about it.
Do you have a proposal to address the issue that this makes it just
about impossible to write UDFs in a safe way?
Greetings,
Andres Freund
On 2018-Dec-08, Andres Freund wrote:
On 2018-12-08 17:10:27 -0300, Alvaro Herrera wrote:
This is what patch 0001 does -- it's only allowed in the connection
string, or on ALTER USER / ALTER DATABASE. Setting it in
postgresql.conf is forbidden, as well as changing from transaction to
statement in SET (the opposite is allowed, though.)I don't think allowing to set it on a per-user basis is acceptable
either, it still leaves the client in a state where they'll potentially
be confused about it.
Hmm, true.
Do you have a proposal to address the issue that this makes it just
about impossible to write UDFs in a safe way?
Not yet, but I'll give it a think next week.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
From: Andres Freund [mailto:andres@anarazel.de]
Isn't the realistic scenario for migrations that people will turn this
feature on on a more global basis? If they need to do per transaction choices,
that makes this much less useful for easy migrations.
Agreed. My approach of per transaction choice may be overkill. Actually, I didn't think per-transaction change was really necessary in practice. But I didn't think of any reason to prohibit per transaction change, so I just wanted to allow flexibility.
I think per database/user change (ALTER DATABASE/USER) can be useful, in cases where applications are migrated from other DBMSs to a PostgreSQL instance. That is, database consolidation for easier data analytics and database management. One database/schema holds data for a PostgreSQL application, and another one stores data for a migrated application.
Or, should we recommend a separate PostgreSQL instance on a different VM or container, and just introduce the parameter only in postgresql.conf?
Regards
Takayuki Tsunakawa
On 2018-12-08 17:55:03 -0300, Alvaro Herrera wrote:
On 2018-Dec-08, Andres Freund wrote:
On 2018-12-08 17:10:27 -0300, Alvaro Herrera wrote:
This is what patch 0001 does -- it's only allowed in the connection
string, or on ALTER USER / ALTER DATABASE. Setting it in
postgresql.conf is forbidden, as well as changing from transaction to
statement in SET (the opposite is allowed, though.)I don't think allowing to set it on a per-user basis is acceptable
either, it still leaves the client in a state where they'll potentially
be confused about it.Hmm, true.
Do you have a proposal to address the issue that this makes it just
about impossible to write UDFs in a safe way?Not yet, but I'll give it a think next week.
Is this still in development? Or should we mark this as returned with
feedback?
Greetings,
Andres Freund
On Fri 7 Dec 2018, 21:43 Robert Haas <robertmhaas@gmail.com wrote:
On Fri, Dec 7, 2018 at 3:34 PM Alvaro Herrera <alvherre@2ndquadrant.com>
wrote:Yeah, I agree that this downside is real. I think our only protection
against that is to say "don't do that". Like any other tool, it has
upsides and downsides; we shouldn't keep it away from users only because
they might misuse it.I have a hard time arguing against that given that EDB has this thing
in our bag of tricks, but if it weren't for that I'd be fighting
against this tooth and nail. Behavior-changing GUCs suuuuck.
This looks like repeating the autocommit mistakes of the past.
And based on that experience wouldn't the replacement approach be to do
this client side? If libpq had a library option to wrap every statement in
a subtransaction by adding begin/end then this problem would be completely
sidestepped.
Show quoted text
On 2019-Jan-31, Greg Stark wrote:
This looks like repeating the autocommit mistakes of the past.
Yeah, this argument has been made before. Peter E argued against that:
/messages/by-id/ea395aa8-5ac4-6bcd-366d-aab2ff2b05ef@2ndquadrant.com
And based on that experience wouldn't the replacement approach be to do
this client side?
JDBC has a feature for this already (see "autosave" in [1]https://jdbc.postgresql.org/documentation/head/connect.html#connection-parameters), but
apparently you get a good performance improvement by doing it
server-side, which is why this was written in the first place. psql
also has ON_ERROR_ROLLBACK [2]https://www.postgresql.org/docs/current/app-psql.html.
If libpq had a library option to wrap every statement in a
subtransaction by adding begin/end then this problem would be
completely sidestepped.
I don't think anyone's talked about doing it in libpq before
specifically, and if you did that, it would obviously have to be
implemented separately on JDBC.
[1]: https://jdbc.postgresql.org/documentation/head/connect.html#connection-parameters
[2]: https://www.postgresql.org/docs/current/app-psql.html
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Thu, Jan 31, 2019 at 04:38:27AM -0800, Andres Freund wrote:
Is this still in development? Or should we mark this as returned with
feedback?
Marked as returned with feedback, as it has already been two months.
If you have an updated patch set, please feel free to resubmit.
--
Michael
On 2019-Feb-04, Michael Paquier wrote:
On Thu, Jan 31, 2019 at 04:38:27AM -0800, Andres Freund wrote:
Is this still in development? Or should we mark this as returned with
feedback?Marked as returned with feedback, as it has already been two months.
If you have an updated patch set, please feel free to resubmit.
It was reasonable to close this patch as returned-with-feedback in
January commitfest, but what you did here was first move it to the March
commitfest and then close it as returned-with-feedback in the March
commitfest, which has not even started. That makes no sense.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Tue, Feb 12, 2019 at 07:13:45PM -0300, Alvaro Herrera wrote:
It was reasonable to close this patch as returned-with-feedback in
January commitfest, but what you did here was first move it to the March
commitfest and then close it as returned-with-feedback in the March
commitfest, which has not even started. That makes no sense.
Except if the CF app does not leave any place for error, particularly
as it is not possible to move back a patch to a previous commit fest
once it has been moved to the next one. In this case, it looks that I
did a mistake in moving the patch first, my apologies for that. There
were many patches to classify last week, so it may be possible that I
did similar mistakes for some other patches.
--
Michael