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:
parent
b1ee93884d
commit
cc813fc2b8
@ -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:
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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},
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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")));
|
||||
|
@ -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;
|
||||
|
@ -3,7 +3,7 @@
|
||||
*
|
||||
* Copyright (c) 2000-2003, PostgreSQL Global Development Group
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.107 2004/05/26 13:56:55 momjian Exp $
|
||||
* $PostgreSQL: pgsql/src/bin/psql/tab-complete.c,v 1.108 2004/07/27 05:11:11 tgl Exp $
|
||||
*/
|
||||
|
||||
/*----------------------------------------------------------------------
|
||||
@ -463,8 +463,8 @@ psql_completion(char *text, int start, int end)
|
||||
"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", "COMMENT",
|
||||
"COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", "DELETE", "DROP", "EXECUTE",
|
||||
"EXPLAIN", "FETCH", "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY",
|
||||
"PREPARE", "REINDEX", "RESET", "REVOKE", "ROLLBACK", "SELECT", "SET", "SHOW", "START",
|
||||
"TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", NULL
|
||||
"PREPARE", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK", "SAVEPOINT",
|
||||
"SELECT", "SET", "SHOW", "START", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", NULL
|
||||
};
|
||||
|
||||
static const char * const pgsql_variables[] = {
|
||||
@ -722,16 +722,23 @@ psql_completion(char *text, int start, int end)
|
||||
else if (pg_strcasecmp(prev2_wd, "ANALYZE") == 0)
|
||||
COMPLETE_WITH_CONST(";");
|
||||
|
||||
/* BEGIN, COMMIT, ROLLBACK, ABORT, */
|
||||
/* BEGIN, COMMIT, ABORT */
|
||||
else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 ||
|
||||
pg_strcasecmp(prev_wd, "END") == 0 ||
|
||||
pg_strcasecmp(prev_wd, "COMMIT") == 0 ||
|
||||
pg_strcasecmp(prev_wd, "ROLLBACK") == 0 ||
|
||||
pg_strcasecmp(prev_wd, "ABORT") == 0)
|
||||
{
|
||||
static const char * const list_TRANS[] =
|
||||
{"WORK", "TRANSACTION", NULL};
|
||||
|
||||
COMPLETE_WITH_LIST(list_TRANS);
|
||||
}
|
||||
/* ROLLBACK*/
|
||||
else if ( pg_strcasecmp(prev_wd, "ROLLBACK") == 0 )
|
||||
{
|
||||
static const char * const list_TRANS[] =
|
||||
{"WORK", "TRANSACTION", "TO", NULL};
|
||||
|
||||
COMPLETE_WITH_LIST(list_TRANS);
|
||||
}
|
||||
/* CLUSTER */
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/access/xact.h,v 1.66 2004/07/21 22:31:25 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/access/xact.h,v 1.67 2004/07/27 05:11:24 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -16,6 +16,7 @@
|
||||
|
||||
#include "access/xlog.h"
|
||||
#include "storage/relfilenode.h"
|
||||
#include "nodes/pg_list.h"
|
||||
#include "utils/nabstime.h"
|
||||
|
||||
|
||||
@ -101,12 +102,16 @@ extern void StartTransactionCommand(void);
|
||||
extern void CommitTransactionCommand(void);
|
||||
extern void AbortCurrentTransaction(void);
|
||||
extern void BeginTransactionBlock(void);
|
||||
extern void EndTransactionBlock(void);
|
||||
extern bool EndTransactionBlock(void);
|
||||
extern void UserAbortTransactionBlock(void);
|
||||
extern void ReleaseSavepoint(List *options);
|
||||
extern void DefineSavepoint(char *name);
|
||||
extern void RollbackToSavepoint(List *options);
|
||||
extern void RollbackAndReleaseSavepoint(List *options);
|
||||
extern bool IsSubTransaction(void);
|
||||
extern bool IsTransactionBlock(void);
|
||||
extern bool IsTransactionOrTransactionBlock(void);
|
||||
extern char TransactionBlockStatusCode(void);
|
||||
extern void UserAbortTransactionBlock(void);
|
||||
extern void AbortOutOfAnyTransaction(void);
|
||||
extern void PreventTransactionChain(void *stmtNode, const char *stmtType);
|
||||
extern void RequireTransactionChain(void *stmtNode, const char *stmtType);
|
||||
|
@ -7,7 +7,7 @@
|
||||
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.262 2004/07/12 05:38:11 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.263 2004/07/27 05:11:30 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -1514,14 +1514,17 @@ typedef enum TransactionStmtKind
|
||||
TRANS_STMT_BEGIN,
|
||||
TRANS_STMT_START, /* semantically identical to BEGIN */
|
||||
TRANS_STMT_COMMIT,
|
||||
TRANS_STMT_ROLLBACK
|
||||
TRANS_STMT_ROLLBACK,
|
||||
TRANS_STMT_SAVEPOINT,
|
||||
TRANS_STMT_RELEASE,
|
||||
TRANS_STMT_ROLLBACK_TO
|
||||
} TransactionStmtKind;
|
||||
|
||||
typedef struct TransactionStmt
|
||||
{
|
||||
NodeTag type;
|
||||
TransactionStmtKind kind; /* see above */
|
||||
List *options; /* for BEGIN/START only */
|
||||
List *options; /* for BEGIN/START and savepoint commands */
|
||||
} TransactionStmt;
|
||||
|
||||
/* ----------------------
|
||||
|
@ -11,7 +11,7 @@
|
||||
*
|
||||
* Copyright (c) 2003, PostgreSQL Global Development Group
|
||||
*
|
||||
* $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.12 2004/06/01 21:49:22 tgl Exp $
|
||||
* $PostgreSQL: pgsql/src/include/utils/errcodes.h,v 1.13 2004/07/27 05:11:35 tgl Exp $
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
@ -213,6 +213,10 @@
|
||||
#define ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED MAKE_SQLSTATE('3','9', 'P','0','1')
|
||||
#define ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED MAKE_SQLSTATE('3','9', 'P','0','2')
|
||||
|
||||
/* Class 3B - Savepoint Exception */
|
||||
#define ERRCODE_SAVEPOINT_EXCEPTION MAKE_SQLSTATE('3','B', '0','0','0')
|
||||
#define ERRCODE_S_E_INVALID_SPECIFICATION MAKE_SQLSTATE('3','B', '0','0','1')
|
||||
|
||||
/* Class 3D - Invalid Catalog Name */
|
||||
#define ERRCODE_INVALID_CATALOG_NAME MAKE_SQLSTATE('3','D', '0','0','0')
|
||||
|
||||
|
@ -74,13 +74,14 @@ SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE;
|
||||
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;
|
||||
@ -105,18 +106,20 @@ SELECT * FROM baz; -- should be empty
|
||||
-- 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
|
||||
@ -132,53 +135,168 @@ SELECT * FROM barbaz; -- should have 1
|
||||
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
|
||||
SAVEPOINT two; -- ignored till the end of ...
|
||||
ERROR: current transaction is aborted, commands ignored until end of transaction block
|
||||
BEGIN;
|
||||
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;
|
||||
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
|
||||
ROLLBACK;
|
||||
SELECT 1; -- this should work
|
||||
ROLLBACK TO one;
|
||||
SELECT 1;
|
||||
?column?
|
||||
----------
|
||||
1
|
||||
@ -194,7 +312,7 @@ SELECT 1; -- this should work
|
||||
-- check non-transactional behavior of cursors
|
||||
BEGIN;
|
||||
DECLARE c CURSOR FOR SELECT unique2 FROM tenk1;
|
||||
BEGIN;
|
||||
SAVEPOINT one;
|
||||
FETCH 10 FROM c;
|
||||
unique2
|
||||
---------
|
||||
@ -210,8 +328,7 @@ BEGIN;
|
||||
9
|
||||
(10 rows)
|
||||
|
||||
ROLLBACK;
|
||||
BEGIN;
|
||||
ROLLBACK TO one;
|
||||
FETCH 10 FROM c;
|
||||
unique2
|
||||
---------
|
||||
@ -227,7 +344,7 @@ BEGIN;
|
||||
19
|
||||
(10 rows)
|
||||
|
||||
COMMIT;
|
||||
RELEASE one;
|
||||
FETCH 10 FROM c;
|
||||
unique2
|
||||
---------
|
||||
@ -245,15 +362,15 @@ BEGIN;
|
||||
|
||||
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;
|
||||
|
@ -61,13 +61,14 @@ SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE;
|
||||
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;
|
||||
@ -80,76 +81,156 @@ SELECT * FROM baz; -- should be empty
|
||||
-- inserts
|
||||
BEGIN;
|
||||
INSERT INTO foo VALUES (1);
|
||||
BEGIN;
|
||||
SAVEPOINT one;
|
||||
INSERT into bar VALUES (1);
|
||||
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
|
||||
SELECT * FROM barbaz; -- should have 1
|
||||
|
||||
-- check that starting a subxact in a failed xact or subxact works
|
||||
-- test whole-tree commit
|
||||
BEGIN;
|
||||
SELECT 0/0; -- fail the outer xact
|
||||
BEGIN;
|
||||
SELECT 1; -- this should NOT work
|
||||
COMMIT;
|
||||
SELECT 1; -- this should NOT work
|
||||
BEGIN;
|
||||
SELECT 1; -- this should NOT work
|
||||
ROLLBACK;
|
||||
SELECT 1; -- this should NOT work
|
||||
SAVEPOINT one;
|
||||
SELECT foo;
|
||||
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
|
||||
SELECT * FROM savepoints;
|
||||
|
||||
-- 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
|
||||
|
||||
SELECT * FROM savepoints;
|
||||
|
||||
-- test whole-tree commit on an aborted subtransaction
|
||||
BEGIN;
|
||||
INSERT INTO savepoints VALUES (4);
|
||||
SAVEPOINT one;
|
||||
INSERT INTO savepoints VALUES (5);
|
||||
SELECT foo;
|
||||
COMMIT;
|
||||
SELECT * FROM savepoints;
|
||||
|
||||
BEGIN;
|
||||
BEGIN;
|
||||
SELECT 1; -- this should work
|
||||
SELECT 0/0; -- fail the subxact
|
||||
SELECT 1; -- this should NOT work
|
||||
BEGIN;
|
||||
SELECT 1; -- this should NOT work
|
||||
ROLLBACK;
|
||||
BEGIN;
|
||||
SELECT 1; -- this should NOT work
|
||||
COMMIT;
|
||||
SELECT 1; -- this should NOT work
|
||||
ROLLBACK;
|
||||
SELECT 1; -- this should work
|
||||
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;
|
||||
-- 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;
|
||||
|
||||
BEGIN;
|
||||
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);
|
||||
-- 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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
DROP TABLE savepoints;
|
||||
|
||||
-- only in a transaction block:
|
||||
SAVEPOINT one;
|
||||
ROLLBACK TO one;
|
||||
RELEASE one;
|
||||
|
||||
-- Only "rollback to" allowed in aborted state
|
||||
BEGIN;
|
||||
SAVEPOINT one;
|
||||
SELECT 0/0;
|
||||
SAVEPOINT two; -- ignored till the end of ...
|
||||
RELEASE one; -- ignored till the end of ...
|
||||
ROLLBACK TO one;
|
||||
SELECT 1;
|
||||
COMMIT;
|
||||
SELECT 1; -- this should work
|
||||
|
||||
-- check non-transactional behavior of cursors
|
||||
BEGIN;
|
||||
DECLARE c CURSOR FOR SELECT unique2 FROM tenk1;
|
||||
BEGIN;
|
||||
SAVEPOINT one;
|
||||
FETCH 10 FROM c;
|
||||
ROLLBACK;
|
||||
BEGIN;
|
||||
ROLLBACK TO one;
|
||||
FETCH 10 FROM c;
|
||||
COMMIT;
|
||||
RELEASE one;
|
||||
FETCH 10 FROM c;
|
||||
CLOSE c;
|
||||
DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1;
|
||||
BEGIN;
|
||||
SAVEPOINT two;
|
||||
FETCH 10 FROM c;
|
||||
ROLLBACK;
|
||||
ROLLBACK TO two;
|
||||
-- c is now dead to the world ...
|
||||
BEGIN;
|
||||
FETCH 10 FROM c;
|
||||
ROLLBACK;
|
||||
ROLLBACK TO two;
|
||||
RELEASE two;
|
||||
FETCH 10 FROM c;
|
||||
COMMIT;
|
||||
|
||||
|
||||
DROP TABLE foo;
|
||||
DROP TABLE baz;
|
||||
DROP TABLE barbaz;
|
||||
|
Loading…
x
Reference in New Issue
Block a user