aboutsummaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2004-07-27 05:11:48 +0000
committerTom Lane <tgl@sss.pgh.pa.us>2004-07-27 05:11:48 +0000
commitcc813fc2b8d9293bbd4d0e0d6a6f3b9cf02fe32f (patch)
tree9f7e6635c94bb61cb9d6340c3647c429dca9504b /src/backend
parentb1ee93884d528672fbce446a38659954a86219e1 (diff)
downloadpostgresql-cc813fc2b8d9293bbd4d0e0d6a6f3b9cf02fe32f.tar.gz
postgresql-cc813fc2b8d9293bbd4d0e0d6a6f3b9cf02fe32f.zip
Replace nested-BEGIN syntax for subtransactions with spec-compliant
SAVEPOINT/RELEASE/ROLLBACK-TO syntax. (Alvaro) Cause COMMIT of a failed transaction to report ROLLBACK instead of COMMIT in its command tag. (Tom) Fix a few loose ends in the nested-transactions stuff.
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/access/transam/xact.c610
-rw-r--r--src/backend/executor/spi.c25
-rw-r--r--src/backend/parser/gram.y36
-rw-r--r--src/backend/parser/keywords.c4
-rw-r--r--src/backend/storage/lmgr/lmgr.c23
-rw-r--r--src/backend/tcop/postgres.c17
-rw-r--r--src/backend/tcop/utility.c52
7 files changed, 602 insertions, 165 deletions
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index d88f7164d34..55d5ef9b80a 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.171 2004/07/17 03:28:23 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.172 2004/07/27 05:10:49 tgl Exp $
*
* NOTES
* Transaction aborts can now occur two ways:
@@ -186,21 +186,26 @@ typedef enum TransState
*/
typedef enum TBlockState
{
+ /* not-in-transaction-block states */
TBLOCK_DEFAULT,
TBLOCK_STARTED,
+
+ /* transaction block states */
TBLOCK_BEGIN,
TBLOCK_INPROGRESS,
TBLOCK_END,
TBLOCK_ABORT,
TBLOCK_ENDABORT,
+ /* subtransaction states */
TBLOCK_SUBBEGIN,
- TBLOCK_SUBBEGINABORT,
TBLOCK_SUBINPROGRESS,
TBLOCK_SUBEND,
TBLOCK_SUBABORT,
- TBLOCK_SUBENDABORT_OK,
- TBLOCK_SUBENDABORT_ERROR
+ TBLOCK_SUBABORT_PENDING,
+ TBLOCK_SUBENDABORT_ALL,
+ TBLOCK_SUBENDABORT_RELEASE,
+ TBLOCK_SUBENDABORT
} TBlockState;
/*
@@ -209,6 +214,8 @@ typedef enum TBlockState
typedef struct TransactionStateData
{
TransactionId transactionIdData; /* my XID */
+ char *name; /* savepoint name, if any */
+ int savepointLevel; /* savepoint level */
CommandId commandId; /* current CID */
TransState state; /* low-level state */
TBlockState blockState; /* high-level state */
@@ -245,6 +252,8 @@ static void CleanupSubTransaction(void);
static void StartAbortedSubTransaction(void);
static void PushTransaction(void);
static void PopTransaction(void);
+static void CommitTransactionToLevel(int level);
+static char *CleanupAbortedSubTransactions(bool returnName);
static void AtSubAbort_Memory(void);
static void AtSubCleanup_Memory(void);
@@ -264,6 +273,8 @@ static const char *TransStateAsString(TransState state);
*/
static TransactionStateData TopTransactionStateData = {
0, /* transaction id */
+ NULL, /* savepoint name */
+ 0, /* savepoint level */
FirstCommandId, /* command id */
TRANS_DEFAULT, /* transaction state */
TBLOCK_DEFAULT, /* transaction block state from the client
@@ -1638,11 +1649,12 @@ StartTransactionCommand(void)
case TBLOCK_STARTED:
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
- case TBLOCK_SUBBEGINABORT:
case TBLOCK_END:
case TBLOCK_SUBEND:
- case TBLOCK_SUBENDABORT_OK:
- case TBLOCK_SUBENDABORT_ERROR:
+ case TBLOCK_SUBENDABORT_ALL:
+ case TBLOCK_SUBENDABORT:
+ case TBLOCK_SUBABORT_PENDING:
+ case TBLOCK_SUBENDABORT_RELEASE:
case TBLOCK_ENDABORT:
elog(FATAL, "StartTransactionCommand: unexpected state %s",
BlockStateAsString(s->blockState));
@@ -1670,10 +1682,13 @@ CommitTransactionCommand(void)
/*
* This shouldn't happen, because it means the previous
* StartTransactionCommand didn't set the STARTED state
- * appropiately.
+ * appropriately, or we didn't manage previous pending
+ * abort states.
*/
case TBLOCK_DEFAULT:
- elog(FATAL, "CommitTransactionCommand: unexpected TBLOCK_DEFAULT");
+ case TBLOCK_SUBABORT_PENDING:
+ elog(FATAL, "CommitTransactionCommand: unexpected state %s",
+ BlockStateAsString(s->blockState));
break;
/*
@@ -1710,6 +1725,12 @@ CommitTransactionCommand(void)
* default state.
*/
case TBLOCK_END:
+ /* commit all open subtransactions */
+ if (s->nestingLevel > 1)
+ CommitTransactionToLevel(2);
+ s = CurrentTransactionState;
+ Assert(s->parent == NULL);
+ /* and now the outer transaction */
CommitTransaction();
s->blockState = TBLOCK_DEFAULT;
break;
@@ -1734,7 +1755,17 @@ CommitTransactionCommand(void)
break;
/*
- * We were just issued a BEGIN inside a transaction block.
+ * Ditto, but in a subtransaction. AbortOutOfAnyTransaction
+ * will do the dirty work.
+ */
+ case TBLOCK_SUBENDABORT_ALL:
+ AbortOutOfAnyTransaction();
+ s = CurrentTransactionState; /* changed by AbortOutOfAnyTransaction */
+ /* AbortOutOfAnyTransaction sets the blockState */
+ break;
+
+ /*
+ * We were just issued a SAVEPOINT inside a transaction block.
* Start a subtransaction. (BeginTransactionBlock already
* did PushTransaction, so as to have someplace to put the
* SUBBEGIN state.)
@@ -1745,15 +1776,6 @@ CommitTransactionCommand(void)
break;
/*
- * We were issued a BEGIN inside an aborted transaction block.
- * Start a subtransaction, and put it in aborted state.
- */
- case TBLOCK_SUBBEGINABORT:
- StartAbortedSubTransaction();
- s->blockState = TBLOCK_SUBABORT;
- break;
-
- /*
* Inside a subtransaction, increment the command counter.
*/
case TBLOCK_SUBINPROGRESS:
@@ -1761,7 +1783,7 @@ CommitTransactionCommand(void)
break;
/*
- * We were issued a COMMIT command, so we end the current
+ * We were issued a RELEASE command, so we end the current
* subtransaction and return to the parent transaction.
*/
case TBLOCK_SUBEND:
@@ -1777,30 +1799,81 @@ CommitTransactionCommand(void)
break;
/*
- * We are ending an aborted subtransaction via ROLLBACK,
- * so the parent can be allowed to live.
+ * The current subtransaction is ending. Do the equivalent
+ * of a ROLLBACK TO followed by a RELEASE command.
*/
- case TBLOCK_SUBENDABORT_OK:
- CleanupSubTransaction();
- PopTransaction();
- s = CurrentTransactionState; /* changed by pop */
+ case TBLOCK_SUBENDABORT_RELEASE:
+ CleanupAbortedSubTransactions(false);
break;
/*
- * We are ending an aborted subtransaction via COMMIT.
- * End the subtransaction, and abort the parent too.
+ * The current subtransaction is ending due to a ROLLBACK
+ * TO command, so close all savepoints up to the target
+ * level. When finished, recreate the savepoint.
*/
- case TBLOCK_SUBENDABORT_ERROR:
- CleanupSubTransaction();
- PopTransaction();
- s = CurrentTransactionState; /* changed by pop */
- Assert(s->blockState != TBLOCK_SUBENDABORT_ERROR);
- AbortCurrentTransaction();
+ case TBLOCK_SUBENDABORT:
+ {
+ char *name = CleanupAbortedSubTransactions(true);
+
+ Assert(PointerIsValid(name));
+ DefineSavepoint(name);
+ s = CurrentTransactionState; /* changed by DefineSavepoint */
+ pfree(name);
+
+ /* This is the same as TBLOCK_SUBBEGIN case */
+ AssertState(s->blockState == TBLOCK_SUBBEGIN);
+ StartSubTransaction();
+ s->blockState = TBLOCK_SUBINPROGRESS;
+ }
break;
}
}
/*
+ * CleanupAbortedSubTransactions
+ *
+ * Helper function for CommitTransactionCommand. Aborts and cleans up
+ * dead subtransactions after a ROLLBACK TO command. Optionally returns
+ * the name of the last dead subtransaction so it can be reused to redefine
+ * the savepoint. (Caller is responsible for pfree'ing the result.)
+ */
+static char *
+CleanupAbortedSubTransactions(bool returnName)
+{
+ TransactionState s = CurrentTransactionState;
+ char *name = NULL;
+
+ AssertState(PointerIsValid(s->parent));
+ Assert(s->parent->blockState == TBLOCK_SUBINPROGRESS ||
+ s->parent->blockState == TBLOCK_INPROGRESS ||
+ s->parent->blockState == TBLOCK_SUBABORT_PENDING);
+
+ /*
+ * Abort everything up to the target level. The current
+ * subtransaction only needs cleanup. If we need to save the name,
+ * look for the last subtransaction in TBLOCK_SUBABORT_PENDING state.
+ */
+ if (returnName && s->parent->blockState != TBLOCK_SUBABORT_PENDING)
+ name = MemoryContextStrdup(TopMemoryContext, s->name);
+
+ CleanupSubTransaction();
+ PopTransaction();
+ s = CurrentTransactionState; /* changed by pop */
+
+ while (s->blockState == TBLOCK_SUBABORT_PENDING)
+ {
+ AbortSubTransaction();
+ if (returnName && s->parent->blockState != TBLOCK_SUBABORT_PENDING)
+ name = MemoryContextStrdup(TopMemoryContext, s->name);
+ CleanupSubTransaction();
+ PopTransaction();
+ s = CurrentTransactionState;
+ }
+
+ return name;
+}
+
+/*
* AbortCurrentTransaction
*/
void
@@ -1887,7 +1960,6 @@ AbortCurrentTransaction(void)
* in aborted state.
*/
case TBLOCK_SUBBEGIN:
- case TBLOCK_SUBBEGINABORT:
StartAbortedSubTransaction();
s->blockState = TBLOCK_SUBABORT;
break;
@@ -1902,29 +1974,36 @@ AbortCurrentTransaction(void)
* we have to abort the parent transaction too.
*/
case TBLOCK_SUBEND:
+ case TBLOCK_SUBABORT_PENDING:
AbortSubTransaction();
CleanupSubTransaction();
PopTransaction();
s = CurrentTransactionState; /* changed by pop */
Assert(s->blockState != TBLOCK_SUBEND &&
- s->blockState != TBLOCK_SUBENDABORT_OK &&
- s->blockState != TBLOCK_SUBENDABORT_ERROR);
+ s->blockState != TBLOCK_SUBENDABORT);
AbortCurrentTransaction();
break;
/*
* Same as above, except the Abort() was already done.
*/
- case TBLOCK_SUBENDABORT_OK:
- case TBLOCK_SUBENDABORT_ERROR:
+ case TBLOCK_SUBENDABORT:
+ case TBLOCK_SUBENDABORT_RELEASE:
CleanupSubTransaction();
PopTransaction();
s = CurrentTransactionState; /* changed by pop */
Assert(s->blockState != TBLOCK_SUBEND &&
- s->blockState != TBLOCK_SUBENDABORT_OK &&
- s->blockState != TBLOCK_SUBENDABORT_ERROR);
+ s->blockState != TBLOCK_SUBENDABORT);
AbortCurrentTransaction();
break;
+
+ /*
+ * We are already aborting the whole transaction tree.
+ * Do nothing, CommitTransactionCommand will call
+ * AbortOutOfAnyTransaction and set things straight.
+ */
+ case TBLOCK_SUBENDABORT_ALL:
+ break;
}
}
@@ -2135,7 +2214,8 @@ BeginTransactionBlock(void)
{
TransactionState s = CurrentTransactionState;
- switch (s->blockState) {
+ switch (s->blockState)
+ {
/*
* We are not inside a transaction block, so allow one
* to begin.
@@ -2146,35 +2226,26 @@ BeginTransactionBlock(void)
/*
* Already a transaction block in progress.
- * Start a subtransaction.
*/
case TBLOCK_INPROGRESS:
case TBLOCK_SUBINPROGRESS:
- PushTransaction();
- s = CurrentTransactionState; /* changed by push */
- s->blockState = TBLOCK_SUBBEGIN;
- break;
-
- /*
- * An aborted transaction block should be allowed to start
- * a subtransaction, but it must put it in aborted state.
- */
case TBLOCK_ABORT:
case TBLOCK_SUBABORT:
- PushTransaction();
- s = CurrentTransactionState; /* changed by push */
- s->blockState = TBLOCK_SUBBEGINABORT;
+ ereport(WARNING,
+ (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
+ errmsg("there is already a transaction in progress")));
break;
/* These cases are invalid. Reject them altogether. */
case TBLOCK_DEFAULT:
case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN:
- case TBLOCK_SUBBEGINABORT:
case TBLOCK_ENDABORT:
case TBLOCK_END:
- case TBLOCK_SUBENDABORT_OK:
- case TBLOCK_SUBENDABORT_ERROR:
+ case TBLOCK_SUBENDABORT_ALL:
+ case TBLOCK_SUBENDABORT:
+ case TBLOCK_SUBABORT_PENDING:
+ case TBLOCK_SUBENDABORT_RELEASE:
case TBLOCK_SUBEND:
elog(FATAL, "BeginTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
@@ -2185,34 +2256,32 @@ BeginTransactionBlock(void)
/*
* EndTransactionBlock
* This executes a COMMIT command.
+ *
+ * Since COMMIT may actually do a ROLLBACK, the result indicates what
+ * happened: TRUE for COMMIT, FALSE for ROLLBACK.
*/
-void
+bool
EndTransactionBlock(void)
{
TransactionState s = CurrentTransactionState;
+ bool result = false;
- switch (s->blockState) {
+ switch (s->blockState)
+ {
/*
- * here we are in a transaction block which should commit when we
+ * We are in a transaction block which should commit when we
* get to the upcoming CommitTransactionCommand() so we set the
* state to "END". CommitTransactionCommand() will recognize this
- * and commit the transaction and return us to the default state
+ * and commit the transaction and return us to the default state.
*/
case TBLOCK_INPROGRESS:
- s->blockState = TBLOCK_END;
- break;
-
- /*
- * here we are in a subtransaction block. Signal
- * CommitTransactionCommand() to end it and return to the
- * parent transaction.
- */
case TBLOCK_SUBINPROGRESS:
- s->blockState = TBLOCK_SUBEND;
+ s->blockState = TBLOCK_END;
+ result = true;
break;
/*
- * here, we are in a transaction block which aborted. Since the
+ * We are in a transaction block which aborted. Since the
* AbortTransaction() was already done, we need only
* change to the special "END ABORT" state. The upcoming
* CommitTransactionCommand() will recognise this and then put us
@@ -2223,13 +2292,12 @@ EndTransactionBlock(void)
break;
/*
- * here we are in an aborted subtransaction. Signal
- * CommitTransactionCommand() to clean up and return to the
- * parent transaction. Since the user said COMMIT, we must
- * fail the parent transaction.
+ * Here we are inside an aborted subtransaction. Go to the "abort
+ * the whole tree" state so that CommitTransactionCommand() calls
+ * AbortOutOfAnyTransaction.
*/
case TBLOCK_SUBABORT:
- s->blockState = TBLOCK_SUBENDABORT_ERROR;
+ s->blockState = TBLOCK_SUBENDABORT_ALL;
break;
case TBLOCK_STARTED:
@@ -2252,14 +2320,17 @@ EndTransactionBlock(void)
case TBLOCK_ENDABORT:
case TBLOCK_END:
case TBLOCK_SUBBEGIN:
- case TBLOCK_SUBBEGINABORT:
case TBLOCK_SUBEND:
- case TBLOCK_SUBENDABORT_OK:
- case TBLOCK_SUBENDABORT_ERROR:
+ case TBLOCK_SUBENDABORT_ALL:
+ case TBLOCK_SUBENDABORT:
+ case TBLOCK_SUBABORT_PENDING:
+ case TBLOCK_SUBENDABORT_RELEASE:
elog(FATAL, "EndTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
break;
}
+
+ return result;
}
/*
@@ -2271,27 +2342,32 @@ UserAbortTransactionBlock(void)
{
TransactionState s = CurrentTransactionState;
- switch (s->blockState) {
- /*
- * here we are inside a failed transaction block and we got an abort
- * command from the user. Abort processing is already done, we just
- * need to move to the ENDABORT state so we will end up in the default
- * state after the upcoming CommitTransactionCommand().
- */
+ switch (s->blockState)
+ {
+ /*
+ * We are inside a failed transaction block and we got an
+ * abort command from the user. Abort processing is already
+ * done, we just need to move to the ENDABORT state so we will
+ * end up in the default state after the upcoming
+ * CommitTransactionCommand().
+ */
case TBLOCK_ABORT:
s->blockState = TBLOCK_ENDABORT;
break;
/*
- * Ditto, for a subtransaction. Here it is okay to allow the
- * parent transaction to continue.
+ * We are inside a failed subtransaction and we got an
+ * abort command from the user. Abort processing is already
+ * done, so go to the "abort all" state and
+ * CommitTransactionCommand will call AbortOutOfAnyTransaction
+ * to set things straight.
*/
case TBLOCK_SUBABORT:
- s->blockState = TBLOCK_SUBENDABORT_OK;
+ s->blockState = TBLOCK_SUBENDABORT_ALL;
break;
/*
- * here we are inside a transaction block and we got an abort
+ * We are inside a transaction block and we got an abort
* command from the user, so we move to the ENDABORT state and
* do abort processing so we will end up in the default state
* after the upcoming CommitTransactionCommand().
@@ -2301,17 +2377,22 @@ UserAbortTransactionBlock(void)
s->blockState = TBLOCK_ENDABORT;
break;
- /* Ditto, for a subtransaction. */
+ /*
+ * We are inside a subtransaction. Abort the current
+ * subtransaction and go to the "abort all" state, so
+ * CommitTransactionCommand will call AbortOutOfAnyTransaction
+ * to set things straight.
+ */
case TBLOCK_SUBINPROGRESS:
AbortSubTransaction();
- s->blockState = TBLOCK_SUBENDABORT_OK;
+ s->blockState = TBLOCK_SUBENDABORT_ALL;
break;
/*
- * here, the user issued ABORT when not inside a
- * transaction. Issue a WARNING and go to abort state. The
- * upcoming call to CommitTransactionCommand() will then put us
- * back into the default state.
+ * The user issued ABORT when not inside a transaction. Issue
+ * a WARNING and go to abort state. The upcoming call to
+ * CommitTransactionCommand() will then put us back into the
+ * default state.
*/
case TBLOCK_STARTED:
ereport(WARNING,
@@ -2321,21 +2402,265 @@ UserAbortTransactionBlock(void)
s->blockState = TBLOCK_ENDABORT;
break;
- /* these cases are invalid. */
+ /* These cases are invalid. */
case TBLOCK_DEFAULT:
case TBLOCK_BEGIN:
case TBLOCK_END:
case TBLOCK_ENDABORT:
case TBLOCK_SUBEND:
- case TBLOCK_SUBENDABORT_OK:
- case TBLOCK_SUBENDABORT_ERROR:
+ case TBLOCK_SUBENDABORT_ALL:
+ case TBLOCK_SUBENDABORT:
+ case TBLOCK_SUBABORT_PENDING:
+ case TBLOCK_SUBENDABORT_RELEASE:
case TBLOCK_SUBBEGIN:
- case TBLOCK_SUBBEGINABORT:
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState));
break;
}
+}
+
+/*
+ * DefineSavepoint
+ * This executes a SAVEPOINT command.
+ */
+void
+DefineSavepoint(char *name)
+{
+ TransactionState s = CurrentTransactionState;
+
+ switch (s->blockState)
+ {
+ case TBLOCK_INPROGRESS:
+ case TBLOCK_SUBINPROGRESS:
+ /* Normal subtransaction start */
+ PushTransaction();
+ s = CurrentTransactionState; /* changed by push */
+ /*
+ * Note that we are allocating the savepoint name in the
+ * parent transaction's CurTransactionContext, since we
+ * don't yet have a transaction context for the new guy.
+ */
+ s->name = MemoryContextStrdup(CurTransactionContext, name);
+ s->blockState = TBLOCK_SUBBEGIN;
+ break;
+
+ /* These cases are invalid. Reject them altogether. */
+ case TBLOCK_DEFAULT:
+ case TBLOCK_STARTED:
+ case TBLOCK_BEGIN:
+ case TBLOCK_SUBBEGIN:
+ case TBLOCK_ABORT:
+ case TBLOCK_SUBABORT:
+ case TBLOCK_ENDABORT:
+ case TBLOCK_END:
+ case TBLOCK_SUBENDABORT_ALL:
+ case TBLOCK_SUBENDABORT:
+ case TBLOCK_SUBABORT_PENDING:
+ case TBLOCK_SUBENDABORT_RELEASE:
+ case TBLOCK_SUBEND:
+ elog(FATAL, "BeginTransactionBlock: unexpected state %s",
+ BlockStateAsString(s->blockState));
+ break;
+ }
+}
+
+/*
+ * ReleaseSavepoint
+ * This executes a RELEASE command.
+ */
+void
+ReleaseSavepoint(List *options)
+{
+ TransactionState s = CurrentTransactionState;
+ TransactionState target = s;
+ char *name = NULL;
+ ListCell *cell;
+
+ /*
+ * Check valid block state transaction status.
+ */
+ switch (s->blockState)
+ {
+ case TBLOCK_INPROGRESS:
+ case TBLOCK_ABORT:
+ ereport(ERROR,
+ (errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+ errmsg("no such savepoint")));
+ break;
+
+ /*
+ * We are in a non-aborted subtransaction. This is
+ * the only valid case.
+ */
+ case TBLOCK_SUBINPROGRESS:
+ break;
+
+ /* these cases are invalid. */
+ case TBLOCK_DEFAULT:
+ case TBLOCK_STARTED:
+ case TBLOCK_BEGIN:
+ case TBLOCK_ENDABORT:
+ case TBLOCK_END:
+ case TBLOCK_SUBABORT:
+ case TBLOCK_SUBBEGIN:
+ case TBLOCK_SUBEND:
+ case TBLOCK_SUBENDABORT_ALL:
+ case TBLOCK_SUBENDABORT:
+ case TBLOCK_SUBABORT_PENDING:
+ case TBLOCK_SUBENDABORT_RELEASE:
+ elog(FATAL, "ReleaseSavepoint: unexpected state %s",
+ BlockStateAsString(s->blockState));
+ break;
+ }
+ foreach (cell, options)
+ {
+ DefElem *elem = lfirst(cell);
+
+ if (strcmp(elem->defname, "savepoint_name") == 0)
+ name = strVal(elem->arg);
+ }
+
+ Assert(PointerIsValid(name));
+
+ while (target != NULL)
+ {
+ if (PointerIsValid(target->name) && strcmp(target->name, name) == 0)
+ break;
+ target = target->parent;
+ }
+
+ if (!PointerIsValid(target))
+ ereport(ERROR,
+ (errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+ errmsg("no such savepoint")));
+
+ CommitTransactionToLevel(target->nestingLevel);
+}
+
+/*
+ * RollbackToSavepoint
+ * This executes a ROLLBACK TO <savepoint> command.
+ */
+void
+RollbackToSavepoint(List *options)
+{
+ TransactionState s = CurrentTransactionState;
+ TransactionState target,
+ xact;
+ ListCell *cell;
+ char *name = NULL;
+
+ switch (s->blockState)
+ {
+ /*
+ * We can't rollback to a savepoint if there is no saveopint
+ * defined.
+ */
+ case TBLOCK_ABORT:
+ case TBLOCK_INPROGRESS:
+ ereport(ERROR,
+ (errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+ errmsg("no such savepoint")));
+ break;
+
+ /*
+ * There is at least one savepoint, so proceed.
+ */
+ case TBLOCK_SUBABORT:
+ case TBLOCK_SUBINPROGRESS:
+ /*
+ * Have to do AbortSubTransaction, but first check
+ * if this is the right subtransaction
+ */
+ break;
+
+ /* these cases are invalid. */
+ case TBLOCK_DEFAULT:
+ case TBLOCK_STARTED:
+ case TBLOCK_BEGIN:
+ case TBLOCK_END:
+ case TBLOCK_ENDABORT:
+ case TBLOCK_SUBEND:
+ case TBLOCK_SUBENDABORT_ALL:
+ case TBLOCK_SUBENDABORT:
+ case TBLOCK_SUBABORT_PENDING:
+ case TBLOCK_SUBENDABORT_RELEASE:
+ case TBLOCK_SUBBEGIN:
+ elog(FATAL, "RollbackToSavepoint: unexpected state %s",
+ BlockStateAsString(s->blockState));
+ break;
+ }
+
+ foreach (cell, options)
+ {
+ DefElem *elem = lfirst(cell);
+
+ if (strcmp(elem->defname, "savepoint_name") == 0)
+ name = strVal(elem->arg);
+ }
+
+ Assert(PointerIsValid(name));
+
+ target = CurrentTransactionState;
+
+ while (target != NULL)
+ {
+ if (PointerIsValid(target->name) && strcmp(target->name, name) == 0)
+ break;
+ target = target->parent;
+
+ /* we don't cross savepoint level boundaries */
+ if (target->savepointLevel != s->savepointLevel)
+ ereport(ERROR,
+ (errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+ errmsg("no such savepoint")));
+ }
+
+ if (!PointerIsValid(target))
+ ereport(ERROR,
+ (errcode(ERRCODE_S_E_INVALID_SPECIFICATION),
+ errmsg("no such savepoint")));
+
+ /*
+ * Abort the current subtransaction, if needed. We can't Cleanup the
+ * savepoint yet, so signal CommitTransactionCommand to do it and
+ * close all savepoints up to the target level.
+ */
+ if (s->blockState == TBLOCK_SUBINPROGRESS)
+ AbortSubTransaction();
+ s->blockState = TBLOCK_SUBENDABORT;
+
+ /*
+ * Mark "abort pending" all subtransactions up to the target
+ * subtransaction. (Except the current subtransaction!)
+ */
+ xact = CurrentTransactionState;
+
+ while (xact != target)
+ {
+ xact = xact->parent;
+ Assert(PointerIsValid(xact));
+ Assert(xact->blockState == TBLOCK_SUBINPROGRESS);
+ xact->blockState = TBLOCK_SUBABORT_PENDING;
+ }
+}
+
+/*
+ * RollbackAndReleaseSavepoint
+ *
+ * Executes a ROLLBACK TO command, immediately followed by a RELEASE
+ * of the same savepoint.
+ */
+void
+RollbackAndReleaseSavepoint(List *options)
+{
+ TransactionState s;
+
+ RollbackToSavepoint(options);
+ s = CurrentTransactionState;
+ Assert(s->blockState == TBLOCK_SUBENDABORT);
+ s->blockState = TBLOCK_SUBENDABORT_RELEASE;
}
/*
@@ -2375,7 +2700,6 @@ AbortOutOfAnyTransaction(void)
s->blockState = TBLOCK_DEFAULT;
break;
case TBLOCK_SUBBEGIN:
- case TBLOCK_SUBBEGINABORT:
/*
* We didn't get as far as starting the subxact, so there's
* nothing to abort. Just pop back to parent.
@@ -2385,6 +2709,7 @@ AbortOutOfAnyTransaction(void)
break;
case TBLOCK_SUBINPROGRESS:
case TBLOCK_SUBEND:
+ case TBLOCK_SUBABORT_PENDING:
/* In a subtransaction, so clean it up and abort parent too */
AbortSubTransaction();
CleanupSubTransaction();
@@ -2392,8 +2717,9 @@ AbortOutOfAnyTransaction(void)
s = CurrentTransactionState; /* changed by pop */
break;
case TBLOCK_SUBABORT:
- case TBLOCK_SUBENDABORT_OK:
- case TBLOCK_SUBENDABORT_ERROR:
+ case TBLOCK_SUBENDABORT_ALL:
+ case TBLOCK_SUBENDABORT:
+ case TBLOCK_SUBENDABORT_RELEASE:
/* As above, but AbortSubTransaction already done */
CleanupSubTransaction();
PopTransaction();
@@ -2407,6 +2733,28 @@ AbortOutOfAnyTransaction(void)
}
/*
+ * CommitTransactionToLevel
+ *
+ * Commit everything from the current transaction level
+ * up to the specified level (inclusive).
+ */
+void
+CommitTransactionToLevel(int level)
+{
+ TransactionState s = CurrentTransactionState;
+
+ Assert(s->state == TRANS_INPROGRESS);
+
+ while (s->nestingLevel >= level)
+ {
+ CommitSubTransaction();
+ PopTransaction();
+ s = CurrentTransactionState; /* changed by pop */
+ Assert(s->state == TRANS_INPROGRESS);
+ }
+}
+
+/*
* IsTransactionBlock --- are we within a transaction block?
*/
bool
@@ -2461,9 +2809,10 @@ TransactionBlockStatusCode(void)
case TBLOCK_ABORT:
case TBLOCK_ENDABORT:
case TBLOCK_SUBABORT:
- case TBLOCK_SUBENDABORT_OK:
- case TBLOCK_SUBENDABORT_ERROR:
- case TBLOCK_SUBBEGINABORT:
+ case TBLOCK_SUBENDABORT_ALL:
+ case TBLOCK_SUBENDABORT:
+ case TBLOCK_SUBABORT_PENDING:
+ case TBLOCK_SUBENDABORT_RELEASE:
return 'E'; /* in failed transaction */
}
@@ -2481,7 +2830,8 @@ IsSubTransaction(void)
{
TransactionState s = CurrentTransactionState;
- switch (s->blockState) {
+ switch (s->blockState)
+ {
case TBLOCK_DEFAULT:
case TBLOCK_STARTED:
case TBLOCK_BEGIN:
@@ -2491,12 +2841,13 @@ IsSubTransaction(void)
case TBLOCK_ENDABORT:
return false;
case TBLOCK_SUBBEGIN:
- case TBLOCK_SUBBEGINABORT:
case TBLOCK_SUBINPROGRESS:
case TBLOCK_SUBABORT:
case TBLOCK_SUBEND:
- case TBLOCK_SUBENDABORT_OK:
- case TBLOCK_SUBENDABORT_ERROR:
+ case TBLOCK_SUBENDABORT_ALL:
+ case TBLOCK_SUBENDABORT:
+ case TBLOCK_SUBABORT_PENDING:
+ case TBLOCK_SUBENDABORT_RELEASE:
return true;
}
@@ -2532,6 +2883,8 @@ StartSubTransaction(void)
SubTransSetParent(s->transactionIdData, s->parent->transactionIdData);
+ XactLockTableInsert(s->transactionIdData);
+
/*
* Finish setup of other transaction state fields.
*/
@@ -2619,6 +2972,9 @@ AbortSubTransaction(void)
ShowTransactionState("AbortSubTransaction");
+ if (s->state != TRANS_INPROGRESS)
+ elog(WARNING, "AbortSubTransaction and not in in-progress state");
+
HOLD_INTERRUPTS();
s->state = TRANS_ABORT;
@@ -2762,6 +3118,9 @@ StartAbortedSubTransaction(void)
/*
* PushTransaction
* Set up transaction state for a subtransaction
+ *
+ * The caller has to make sure to always reassign CurrentTransactionState
+ * if it has a local pointer to it after calling this function.
*/
static void
PushTransaction(void)
@@ -2777,6 +3136,7 @@ PushTransaction(void)
sizeof(TransactionStateData));
s->parent = p;
s->nestingLevel = p->nestingLevel + 1;
+ s->savepointLevel = p->savepointLevel;
s->state = TRANS_DEFAULT;
s->blockState = TBLOCK_SUBBEGIN;
@@ -2798,6 +3158,9 @@ PushTransaction(void)
/*
* PopTransaction
* Pop back to parent transaction state
+ *
+ * The caller has to make sure to always reassign CurrentTransactionState
+ * if it has a local pointer to it after calling this function.
*/
static void
PopTransaction(void)
@@ -2824,6 +3187,8 @@ PopTransaction(void)
CurrentResourceOwner = s->parent->curTransactionOwner;
/* Free the old child structure */
+ if (s->name)
+ pfree(s->name);
pfree(s);
}
@@ -2854,7 +3219,8 @@ ShowTransactionStateRec(TransactionState s)
/* use ereport to suppress computation if msg will not be printed */
ereport(DEBUG2,
- (errmsg_internal("blockState: %13s; state: %7s, xid/cid: %u/%02u, nestlvl: %d, children: %s",
+ (errmsg_internal("name: %s; blockState: %13s; state: %7s, xid/cid: %u/%02u, nestlvl: %d, children: %s",
+ PointerIsValid(s->name) ? s->name : "unnamed",
BlockStateAsString(s->blockState),
TransStateAsString(s->state),
(unsigned int) s->transactionIdData,
@@ -2870,7 +3236,8 @@ ShowTransactionStateRec(TransactionState s)
static const char *
BlockStateAsString(TBlockState blockState)
{
- switch (blockState) {
+ switch (blockState)
+ {
case TBLOCK_DEFAULT:
return "DEFAULT";
case TBLOCK_STARTED:
@@ -2887,18 +3254,20 @@ BlockStateAsString(TBlockState blockState)
return "ENDABORT";
case TBLOCK_SUBBEGIN:
return "SUB BEGIN";
- case TBLOCK_SUBBEGINABORT:
- return "SUB BEGIN AB";
case TBLOCK_SUBINPROGRESS:
return "SUB INPROGRS";
case TBLOCK_SUBEND:
return "SUB END";
case TBLOCK_SUBABORT:
return "SUB ABORT";
- case TBLOCK_SUBENDABORT_OK:
- return "SUB ENDAB OK";
- case TBLOCK_SUBENDABORT_ERROR:
- return "SUB ENDAB ERR";
+ case TBLOCK_SUBENDABORT_ALL:
+ return "SUB ENDAB ALL";
+ case TBLOCK_SUBENDABORT:
+ return "SUB ENDAB";
+ case TBLOCK_SUBABORT_PENDING:
+ return "SUB ABRT PEND";
+ case TBLOCK_SUBENDABORT_RELEASE:
+ return "SUB ENDAB REL";
}
return "UNRECOGNIZED";
}
@@ -2910,7 +3279,8 @@ BlockStateAsString(TBlockState blockState)
static const char *
TransStateAsString(TransState state)
{
- switch (state) {
+ switch (state)
+ {
case TRANS_DEFAULT:
return "DEFAULT";
case TRANS_START:
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 19dbfc13d0f..f2fa0a43163 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.120 2004/07/01 21:17:13 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.121 2004/07/27 05:10:51 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -1181,18 +1181,16 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
res = SPI_ERROR_CURSOR;
goto fail;
}
+ else if (IsA(queryTree->utilityStmt, TransactionStmt))
+ {
+ res = SPI_ERROR_TRANSACTION;
+ goto fail;
+ }
res = SPI_OK_UTILITY;
if (plan == NULL)
{
ProcessUtility(queryTree->utilityStmt, dest, NULL);
-
- if (IsA(queryTree->utilityStmt, TransactionStmt))
- {
- CommitTransactionCommand();
- StartTransactionCommand();
- }
- else
- CommandCounterIncrement();
+ CommandCounterIncrement();
}
}
else if (plan == NULL)
@@ -1308,14 +1306,7 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
{
ProcessUtility(queryTree->utilityStmt, dest, NULL);
res = SPI_OK_UTILITY;
-
- if (IsA(queryTree->utilityStmt, TransactionStmt))
- {
- CommitTransactionCommand();
- StartTransactionCommand();
- }
- else
- CommandCounterIncrement();
+ CommandCounterIncrement();
}
else
{
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 519bcce7184..1c7faa2c99d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.467 2004/07/12 05:37:44 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.468 2004/07/27 05:10:55 tgl Exp $
*
* HISTORY
* AUTHOR DATE MAJOR EVENT
@@ -386,11 +386,11 @@ static void doNegateFloat(Value *v);
QUOTE
- READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RENAME REPEATABLE REPLACE
- RESET RESTART RESTRICT RETURNS REVOKE RIGHT ROLLBACK ROW ROWS
- RULE
+ READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RELEASE RENAME
+ REPEATABLE REPLACE RESET RESTART RESTRICT RETURNS REVOKE RIGHT
+ ROLLBACK ROW ROWS RULE
- SCHEMA SCROLL SECOND_P SECURITY SELECT SEQUENCE
+ SAVEPOINT SCHEMA SCROLL SECOND_P SECURITY SELECT SEQUENCE
SERIALIZABLE SESSION SESSION_USER SET SETOF SHARE
SHOW SIMILAR SIMPLE SMALLINT SOME STABLE START STATEMENT
STATISTICS STDIN STDOUT STORAGE STRICT_P SUBSTRING SYSID
@@ -3961,6 +3961,30 @@ TransactionStmt:
n->options = NIL;
$$ = (Node *)n;
}
+ | SAVEPOINT ColId
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_SAVEPOINT;
+ n->options = list_make1(makeDefElem("savepoint_name",
+ (Node *)makeString($2)));
+ $$ = (Node *)n;
+ }
+ | RELEASE ColId
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_RELEASE;
+ n->options = list_make1(makeDefElem("savepoint_name",
+ (Node *)makeString($2)));
+ $$ = (Node *)n;
+ }
+ | ROLLBACK TO ColId
+ {
+ TransactionStmt *n = makeNode(TransactionStmt);
+ n->kind = TRANS_STMT_ROLLBACK_TO;
+ n->options = list_make1(makeDefElem("savepoint_name",
+ (Node *)makeString($3)));
+ $$ = (Node *)n;
+ }
;
opt_transaction: WORK {}
@@ -7688,6 +7712,7 @@ unreserved_keyword:
| RECHECK
| REINDEX
| RELATIVE_P
+ | RELEASE
| RENAME
| REPEATABLE
| REPLACE
@@ -7699,6 +7724,7 @@ unreserved_keyword:
| ROLLBACK
| ROWS
| RULE
+ | SAVEPOINT
| SCHEMA
| SCROLL
| SECOND_P
diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c
index cae1ed159b0..80ae597feb5 100644
--- a/src/backend/parser/keywords.c
+++ b/src/backend/parser/keywords.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.151 2004/07/12 05:37:44 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/parser/keywords.c,v 1.152 2004/07/27 05:10:55 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -254,6 +254,7 @@ static const ScanKeyword ScanKeywords[] = {
{"references", REFERENCES},
{"reindex", REINDEX},
{"relative", RELATIVE_P},
+ {"release", RELEASE},
{"rename", RENAME},
{"repeatable", REPEATABLE},
{"replace", REPLACE},
@@ -267,6 +268,7 @@ static const ScanKeyword ScanKeywords[] = {
{"row", ROW},
{"rows", ROWS},
{"rule", RULE},
+ {"savepoint", SAVEPOINT},
{"schema", SCHEMA},
{"scroll", SCROLL},
{"second", SECOND_P},
diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c
index 45305b4dea2..176767507c2 100644
--- a/src/backend/storage/lmgr/lmgr.c
+++ b/src/backend/storage/lmgr/lmgr.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/storage/lmgr/lmgr.c,v 1.64 2004/07/01 00:50:59 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/storage/lmgr/lmgr.c,v 1.65 2004/07/27 05:10:58 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -334,21 +334,23 @@ XactLockTableInsert(TransactionId xid)
* XactLockTableWait
*
* Wait for the specified transaction to commit or abort.
- * We actually wait on the topmost transaction of the transaction tree.
+ *
+ * Note that this does the right thing for subtransactions: if we
+ * wait on a subtransaction, we will be awakened as soon as it aborts
+ * or its parent commits.
*/
void
XactLockTableWait(TransactionId xid)
{
LOCKTAG tag;
TransactionId myxid = GetCurrentTransactionId();
- TransactionId waitXid = SubTransGetTopmostTransaction(xid);
- Assert(!SubTransXidsHaveCommonAncestor(waitXid, myxid));
+ Assert(!SubTransXidsHaveCommonAncestor(xid, myxid));
MemSet(&tag, 0, sizeof(tag));
tag.relId = XactLockTableId;
tag.dbId = InvalidOid;
- tag.objId.xid = waitXid;
+ tag.objId.xid = xid;
if (!LockAcquire(LockTableId, &tag, myxid,
ShareLock, false))
@@ -358,13 +360,8 @@ XactLockTableWait(TransactionId xid)
/*
* Transaction was committed/aborted/crashed - we have to update
- * pg_clog if transaction is still marked as running. If it's a
- * subtransaction, we can update the parent status too.
+ * pg_clog if transaction is still marked as running.
*/
- if (!TransactionIdDidCommit(waitXid) && !TransactionIdDidAbort(waitXid))
- {
- TransactionIdAbort(waitXid);
- if (waitXid != xid)
- TransactionIdAbort(xid);
- }
+ if (!TransactionIdDidCommit(xid) && !TransactionIdDidAbort(xid))
+ TransactionIdAbort(xid);
}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 36fb347de3e..a353122fc26 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.424 2004/07/17 03:29:00 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/postgres.c,v 1.425 2004/07/27 05:11:03 tgl Exp $
*
* NOTES
* this is the "main" module of the postgres backend and
@@ -841,8 +841,8 @@ exec_simple_query(const char *query_string)
TransactionStmt *stmt = (TransactionStmt *) parsetree;
if (stmt->kind == TRANS_STMT_COMMIT ||
- stmt->kind == TRANS_STMT_BEGIN ||
- stmt->kind == TRANS_STMT_ROLLBACK)
+ stmt->kind == TRANS_STMT_ROLLBACK ||
+ stmt->kind == TRANS_STMT_ROLLBACK_TO)
allowit = true;
}
@@ -1162,8 +1162,8 @@ exec_parse_message(const char *query_string, /* string to execute */
TransactionStmt *stmt = (TransactionStmt *) parsetree;
if (stmt->kind == TRANS_STMT_COMMIT ||
- stmt->kind == TRANS_STMT_BEGIN ||
- stmt->kind == TRANS_STMT_ROLLBACK)
+ stmt->kind == TRANS_STMT_ROLLBACK ||
+ stmt->kind == TRANS_STMT_ROLLBACK_TO)
allowit = true;
}
@@ -1625,8 +1625,8 @@ exec_execute_message(const char *portal_name, long max_rows)
is_trans_stmt = true;
if (stmt->kind == TRANS_STMT_COMMIT ||
- stmt->kind == TRANS_STMT_BEGIN ||
- stmt->kind == TRANS_STMT_ROLLBACK)
+ stmt->kind == TRANS_STMT_ROLLBACK ||
+ stmt->kind == TRANS_STMT_ROLLBACK_TO)
is_trans_exit = true;
}
}
@@ -2810,6 +2810,9 @@ PostgresMain(int argc, char *argv[], const char *username)
*/
MemoryContextSwitchTo(ErrorContext);
+ /* Make sure we are using a sane ResourceOwner, too */
+ CurrentResourceOwner = CurTransactionResourceOwner;
+
/* Do the recovery */
ereport(DEBUG2,
(errmsg_internal("AbortCurrentTransaction")));
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index a3e727472ab..6c32c6c3d78 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.220 2004/06/25 21:55:57 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/tcop/utility.c,v 1.221 2004/07/27 05:11:03 tgl Exp $
*
*-------------------------------------------------------------------------
*/
@@ -354,12 +354,51 @@ ProcessUtility(Node *parsetree,
break;
case TRANS_STMT_COMMIT:
- EndTransactionBlock();
+ if (!EndTransactionBlock())
+ {
+ /* report unsuccessful commit in completionTag */
+ if (completionTag)
+ strcpy(completionTag, "ROLLBACK");
+ }
break;
case TRANS_STMT_ROLLBACK:
UserAbortTransactionBlock();
break;
+
+ case TRANS_STMT_SAVEPOINT:
+ {
+ ListCell *cell;
+ char *name = NULL;
+
+ RequireTransactionChain((void *)stmt, "SAVEPOINT");
+
+ foreach (cell, stmt->options)
+ {
+ DefElem *elem = lfirst(cell);
+ if (strcmp(elem->defname, "savepoint_name") == 0)
+ name = strVal(elem->arg);
+ }
+
+ Assert(PointerIsValid(name));
+
+ DefineSavepoint(name);
+ }
+ break;
+
+ case TRANS_STMT_RELEASE:
+ RequireTransactionChain((void *)stmt, "RELEASE");
+ ReleaseSavepoint(stmt->options);
+ break;
+
+ case TRANS_STMT_ROLLBACK_TO:
+ RequireTransactionChain((void *)stmt, "ROLLBACK TO");
+ RollbackToSavepoint(stmt->options);
+ /*
+ * CommitTransactionCommand is in charge
+ * of re-defining the savepoint again
+ */
+ break;
}
}
break;
@@ -1114,9 +1153,18 @@ CreateCommandTag(Node *parsetree)
break;
case TRANS_STMT_ROLLBACK:
+ case TRANS_STMT_ROLLBACK_TO:
tag = "ROLLBACK";
break;
+ case TRANS_STMT_SAVEPOINT:
+ tag = "SAVEPOINT";
+ break;
+
+ case TRANS_STMT_RELEASE:
+ tag = "RELEASE";
+ break;
+
default:
tag = "???";
break;