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:
Tom Lane 2004-07-27 05:11:48 +00:00
parent b1ee93884d
commit cc813fc2b8
13 changed files with 929 additions and 275 deletions

View File

@ -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:

View File

@ -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
{

View File

@ -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

View File

@ -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},

View File

@ -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);
}

View File

@ -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")));

View File

@ -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;

View File

@ -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 */

View File

@ -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);

View File

@ -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;
/* ----------------------

View File

@ -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')

View File

@ -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;

View File

@ -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;