*
*
* 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:
*/
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;
/*
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 */
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);
*/
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
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));
/*
* 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;
/*
* 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;
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.)
s->blockState = TBLOCK_SUBINPROGRESS;
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.
*/
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:
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
*/
* in aborted state.
*/
case TBLOCK_SUBBEGIN:
- case TBLOCK_SUBBEGINABORT:
StartAbortedSubTransaction();
s->blockState = TBLOCK_SUBABORT;
break;
* 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;
}
}
{
TransactionState s = CurrentTransactionState;
- switch (s->blockState) {
+ switch (s->blockState)
+ {
/*
* We are not inside a transaction block, so allow one
* to begin.
/*
* 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));
/*
* 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
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:
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;
}
/*
{
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().
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,
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;
}
/*
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.
break;
case TBLOCK_SUBINPROGRESS:
case TBLOCK_SUBEND:
+ case TBLOCK_SUBABORT_PENDING:
/* In a subtransaction, so clean it up and abort parent too */
AbortSubTransaction();
CleanupSubTransaction();
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();
Assert(s->parent == NULL);
}
+/*
+ * 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?
*/
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 */
}
{
TransactionState s = CurrentTransactionState;
- switch (s->blockState) {
+ switch (s->blockState)
+ {
case TBLOCK_DEFAULT:
case TBLOCK_STARTED:
case TBLOCK_BEGIN:
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;
}
SubTransSetParent(s->transactionIdData, s->parent->transactionIdData);
+ XactLockTableInsert(s->transactionIdData);
+
/*
* Finish setup of other transaction state fields.
*/
ShowTransactionState("AbortSubTransaction");
+ if (s->state != TRANS_INPROGRESS)
+ elog(WARNING, "AbortSubTransaction and not in in-progress state");
+
HOLD_INTERRUPTS();
s->state = TRANS_ABORT;
/*
* 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)
sizeof(TransactionStateData));
s->parent = p;
s->nestingLevel = p->nestingLevel + 1;
+ s->savepointLevel = p->savepointLevel;
s->state = TRANS_DEFAULT;
s->blockState = TBLOCK_SUBBEGIN;
/*
* 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)
CurrentResourceOwner = s->parent->curTransactionOwner;
/* Free the old child structure */
+ if (s->name)
+ pfree(s->name);
pfree(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,
static const char *
BlockStateAsString(TBlockState blockState)
{
- switch (blockState) {
+ switch (blockState)
+ {
case TBLOCK_DEFAULT:
return "DEFAULT";
case TBLOCK_STARTED:
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";
}
static const char *
TransStateAsString(TransState state)
{
- switch (state) {
+ switch (state)
+ {
case TRANS_DEFAULT:
return "DEFAULT";
case TRANS_START:
CREATE TABLE foobar (a int);
BEGIN;
CREATE TABLE foo (a int);
- BEGIN;
+ SAVEPOINT one;
DROP TABLE foo;
CREATE TABLE bar (a int);
- ROLLBACK;
- BEGIN;
+ ROLLBACK TO one;
+ RELEASE one;
+ SAVEPOINT two;
CREATE TABLE baz (a int);
- COMMIT;
+ RELEASE two;
drop TABLE foobar;
CREATE TABLE barbaz (a int);
COMMIT;
-- inserts
BEGIN;
INSERT INTO foo VALUES (1);
- BEGIN;
+ SAVEPOINT one;
INSERT into bar VALUES (1);
ERROR: relation "bar" does not exist
- ROLLBACK;
- BEGIN;
+ ROLLBACK TO one;
+ RELEASE one;
+ SAVEPOINT two;
INSERT into barbaz VALUES (1);
- COMMIT;
- BEGIN;
- BEGIN;
+ RELEASE two;
+ SAVEPOINT three;
+ SAVEPOINT four;
INSERT INTO foo VALUES (2);
- COMMIT;
- ROLLBACK;
+ RELEASE four;
+ ROLLBACK TO three;
+ RELEASE three;
INSERT INTO foo VALUES (3);
COMMIT;
SELECT * FROM foo; -- should have 1 and 3
1
(1 row)
--- check that starting a subxact in a failed xact or subxact works
+-- test whole-tree commit
BEGIN;
- SELECT 0/0; -- fail the outer xact
-ERROR: division by zero
- BEGIN;
- SELECT 1; -- this should NOT work
-ERROR: current transaction is aborted, commands ignored until end of transaction block
- COMMIT;
- SELECT 1; -- this should NOT work
-ERROR: current transaction is aborted, commands ignored until end of transaction block
- BEGIN;
- SELECT 1; -- this should NOT work
-ERROR: current transaction is aborted, commands ignored until end of transaction block
- ROLLBACK;
- SELECT 1; -- this should NOT work
-ERROR: current transaction is aborted, commands ignored until end of transaction block
+ SAVEPOINT one;
+ SELECT foo;
+ERROR: column "foo" does not exist
+ ROLLBACK TO one;
+ RELEASE one;
+ SAVEPOINT two;
+ CREATE TABLE savepoints (a int);
+ SAVEPOINT three;
+ INSERT INTO savepoints VALUES (1);
+ SAVEPOINT four;
+ INSERT INTO savepoints VALUES (2);
+ SAVEPOINT five;
+ INSERT INTO savepoints VALUES (3);
+ ROLLBACK TO five;
COMMIT;
-SELECT 1; -- this should work
+COMMIT; -- should not be in a transaction block
+WARNING: there is no transaction in progress
+SELECT * FROM savepoints;
+ a
+---
+ 1
+ 2
+(2 rows)
+
+-- test whole-tree rollback
+BEGIN;
+ SAVEPOINT one;
+ DELETE FROM savepoints WHERE a=1;
+ RELEASE one;
+ SAVEPOINT two;
+ DELETE FROM savepoints WHERE a=1;
+ SAVEPOINT three;
+ DELETE FROM savepoints WHERE a=2;
+ROLLBACK;
+COMMIT; -- should not be in a transaction block
+WARNING: there is no transaction in progress
+
+SELECT * FROM savepoints;
+ a
+---
+ 1
+ 2
+(2 rows)
+
+-- test whole-tree commit on an aborted subtransaction
+BEGIN;
+ INSERT INTO savepoints VALUES (4);
+ SAVEPOINT one;
+ INSERT INTO savepoints VALUES (5);
+ SELECT foo;
+ERROR: column "foo" does not exist
+COMMIT;
+SELECT * FROM savepoints;
+ a
+---
+ 1
+ 2
+(2 rows)
+
+BEGIN;
+ INSERT INTO savepoints VALUES (6);
+ SAVEPOINT one;
+ INSERT INTO savepoints VALUES (7);
+ RELEASE one;
+ INSERT INTO savepoints VALUES (8);
+COMMIT;
+-- rows 6 and 8 should have been created by the same xact
+SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=8;
?column?
----------
- 1
+ t
+(1 row)
+
+-- rows 6 and 7 should have been created by different xacts
+SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=7;
+ ?column?
+----------
+ f
(1 row)
BEGIN;
- BEGIN;
- SELECT 1; -- this should work
+ INSERT INTO savepoints VALUES (9);
+ SAVEPOINT one;
+ INSERT INTO savepoints VALUES (10);
+ ROLLBACK TO one;
+ INSERT INTO savepoints VALUES (11);
+COMMIT;
+SELECT a FROM savepoints WHERE a in (9, 10, 11);
+ a
+----
+ 9
+ 11
+(2 rows)
+
+-- rows 9 and 11 should have been created by different xacts
+SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=9 AND b.a=11;
?column?
----------
- 1
+ f
(1 row)
- SELECT 0/0; -- fail the subxact
+BEGIN;
+ INSERT INTO savepoints VALUES (12);
+ SAVEPOINT one;
+ INSERT INTO savepoints VALUES (13);
+ SAVEPOINT two;
+ INSERT INTO savepoints VALUES (14);
+ ROLLBACK TO one;
+ INSERT INTO savepoints VALUES (15);
+ SAVEPOINT two;
+ INSERT INTO savepoints VALUES (16);
+ SAVEPOINT three;
+ INSERT INTO savepoints VALUES (17);
+COMMIT;
+SELECT a FROM savepoints WHERE a BETWEEN 12 AND 17;
+ a
+----
+ 12
+ 15
+ 16
+ 17
+(4 rows)
+
+BEGIN;
+ INSERT INTO savepoints VALUES (18);
+ SAVEPOINT one;
+ INSERT INTO savepoints VALUES (19);
+ SAVEPOINT two;
+ INSERT INTO savepoints VALUES (20);
+ ROLLBACK TO one;
+ INSERT INTO savepoints VALUES (21);
+ ROLLBACK TO one;
+ INSERT INTO savepoints VALUES (22);
+COMMIT;
+SELECT a FROM savepoints WHERE a BETWEEN 18 AND 22;
+ a
+----
+ 18
+ 22
+(2 rows)
+
+DROP TABLE savepoints;
+-- only in a transaction block:
+SAVEPOINT one;
+ERROR: SAVEPOINT may only be used in transaction blocks
+ROLLBACK TO one;
+ERROR: ROLLBACK TO may only be used in transaction blocks
+RELEASE one;
+ERROR: RELEASE may only be used in transaction blocks
+-- Only "rollback to" allowed in aborted state
+BEGIN;
+ SAVEPOINT one;
+ SELECT 0/0;
ERROR: division by zero
- SELECT 1; -- this should NOT work
-ERROR: current transaction is aborted, commands ignored until end of transaction block
- BEGIN;
- SELECT 1; -- this should NOT work
-ERROR: current transaction is aborted, commands ignored until end of transaction block
- ROLLBACK;
- BEGIN;
- SELECT 1; -- this should NOT work
+ SAVEPOINT two; -- ignored till the end of ...
ERROR: current transaction is aborted, commands ignored until end of transaction block
- COMMIT;
- SELECT 1; -- this should NOT work
+ RELEASE one; -- ignored till the end of ...
ERROR: current transaction is aborted, commands ignored until end of transaction block
- ROLLBACK;
- SELECT 1; -- this should work
+ ROLLBACK TO one;
+ SELECT 1;
?column?
----------
1
-- check non-transactional behavior of cursors
BEGIN;
DECLARE c CURSOR FOR SELECT unique2 FROM tenk1;
- BEGIN;
+ SAVEPOINT one;
FETCH 10 FROM c;
unique2
---------
9
(10 rows)
- ROLLBACK;
- BEGIN;
+ ROLLBACK TO one;
FETCH 10 FROM c;
unique2
---------
19
(10 rows)
- COMMIT;
+ RELEASE one;
FETCH 10 FROM c;
unique2
---------
CLOSE c;
DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1;
- BEGIN;
+ SAVEPOINT two;
FETCH 10 FROM c;
ERROR: division by zero
- ROLLBACK;
+ ROLLBACK TO two;
-- c is now dead to the world ...
- BEGIN;
FETCH 10 FROM c;
ERROR: portal "c" cannot be run
- ROLLBACK;
+ ROLLBACK TO two;
+ RELEASE two;
FETCH 10 FROM c;
ERROR: portal "c" cannot be run
COMMIT;