mirror of
https://github.com/postgres/postgres.git
synced 2025-07-27 12:41:57 +03:00
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.
This commit is contained in:
@ -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.)
|
||||
@ -1744,15 +1775,6 @@ CommitTransactionCommand(void)
|
||||
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.
|
||||
*/
|
||||
@ -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,29 +1799,80 @@ 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
|
||||
*/
|
||||
@ -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();
|
||||
@ -2406,6 +2732,28 @@ AbortOutOfAnyTransaction(void)
|
||||
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?
|
||||
*/
|
||||
@ -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:
|
||||
|
Reference in New Issue
Block a user