Commit cc813fc2 authored by Tom Lane's avatar Tom Lane

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.
parent b1ee9388
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * NOTES
* Transaction aborts can now occur two ways: * Transaction aborts can now occur two ways:
...@@ -186,21 +186,26 @@ typedef enum TransState ...@@ -186,21 +186,26 @@ typedef enum TransState
*/ */
typedef enum TBlockState typedef enum TBlockState
{ {
/* not-in-transaction-block states */
TBLOCK_DEFAULT, TBLOCK_DEFAULT,
TBLOCK_STARTED, TBLOCK_STARTED,
/* transaction block states */
TBLOCK_BEGIN, TBLOCK_BEGIN,
TBLOCK_INPROGRESS, TBLOCK_INPROGRESS,
TBLOCK_END, TBLOCK_END,
TBLOCK_ABORT, TBLOCK_ABORT,
TBLOCK_ENDABORT, TBLOCK_ENDABORT,
/* subtransaction states */
TBLOCK_SUBBEGIN, TBLOCK_SUBBEGIN,
TBLOCK_SUBBEGINABORT,
TBLOCK_SUBINPROGRESS, TBLOCK_SUBINPROGRESS,
TBLOCK_SUBEND, TBLOCK_SUBEND,
TBLOCK_SUBABORT, TBLOCK_SUBABORT,
TBLOCK_SUBENDABORT_OK, TBLOCK_SUBABORT_PENDING,
TBLOCK_SUBENDABORT_ERROR TBLOCK_SUBENDABORT_ALL,
TBLOCK_SUBENDABORT_RELEASE,
TBLOCK_SUBENDABORT
} TBlockState; } TBlockState;
/* /*
...@@ -209,6 +214,8 @@ typedef enum TBlockState ...@@ -209,6 +214,8 @@ typedef enum TBlockState
typedef struct TransactionStateData typedef struct TransactionStateData
{ {
TransactionId transactionIdData; /* my XID */ TransactionId transactionIdData; /* my XID */
char *name; /* savepoint name, if any */
int savepointLevel; /* savepoint level */
CommandId commandId; /* current CID */ CommandId commandId; /* current CID */
TransState state; /* low-level state */ TransState state; /* low-level state */
TBlockState blockState; /* high-level state */ TBlockState blockState; /* high-level state */
...@@ -245,6 +252,8 @@ static void CleanupSubTransaction(void); ...@@ -245,6 +252,8 @@ static void CleanupSubTransaction(void);
static void StartAbortedSubTransaction(void); static void StartAbortedSubTransaction(void);
static void PushTransaction(void); static void PushTransaction(void);
static void PopTransaction(void); static void PopTransaction(void);
static void CommitTransactionToLevel(int level);
static char *CleanupAbortedSubTransactions(bool returnName);
static void AtSubAbort_Memory(void); static void AtSubAbort_Memory(void);
static void AtSubCleanup_Memory(void); static void AtSubCleanup_Memory(void);
...@@ -264,6 +273,8 @@ static const char *TransStateAsString(TransState state); ...@@ -264,6 +273,8 @@ static const char *TransStateAsString(TransState state);
*/ */
static TransactionStateData TopTransactionStateData = { static TransactionStateData TopTransactionStateData = {
0, /* transaction id */ 0, /* transaction id */
NULL, /* savepoint name */
0, /* savepoint level */
FirstCommandId, /* command id */ FirstCommandId, /* command id */
TRANS_DEFAULT, /* transaction state */ TRANS_DEFAULT, /* transaction state */
TBLOCK_DEFAULT, /* transaction block state from the client TBLOCK_DEFAULT, /* transaction block state from the client
...@@ -1638,11 +1649,12 @@ StartTransactionCommand(void) ...@@ -1638,11 +1649,12 @@ StartTransactionCommand(void)
case TBLOCK_STARTED: case TBLOCK_STARTED:
case TBLOCK_BEGIN: case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN: case TBLOCK_SUBBEGIN:
case TBLOCK_SUBBEGINABORT:
case TBLOCK_END: case TBLOCK_END:
case TBLOCK_SUBEND: case TBLOCK_SUBEND:
case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
case TBLOCK_ENDABORT: case TBLOCK_ENDABORT:
elog(FATAL, "StartTransactionCommand: unexpected state %s", elog(FATAL, "StartTransactionCommand: unexpected state %s",
BlockStateAsString(s->blockState)); BlockStateAsString(s->blockState));
...@@ -1670,10 +1682,13 @@ CommitTransactionCommand(void) ...@@ -1670,10 +1682,13 @@ CommitTransactionCommand(void)
/* /*
* This shouldn't happen, because it means the previous * This shouldn't happen, because it means the previous
* StartTransactionCommand didn't set the STARTED state * StartTransactionCommand didn't set the STARTED state
* appropiately. * appropriately, or we didn't manage previous pending
* abort states.
*/ */
case TBLOCK_DEFAULT: case TBLOCK_DEFAULT:
elog(FATAL, "CommitTransactionCommand: unexpected TBLOCK_DEFAULT"); case TBLOCK_SUBABORT_PENDING:
elog(FATAL, "CommitTransactionCommand: unexpected state %s",
BlockStateAsString(s->blockState));
break; break;
/* /*
...@@ -1710,6 +1725,12 @@ CommitTransactionCommand(void) ...@@ -1710,6 +1725,12 @@ CommitTransactionCommand(void)
* default state. * default state.
*/ */
case TBLOCK_END: 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(); CommitTransaction();
s->blockState = TBLOCK_DEFAULT; s->blockState = TBLOCK_DEFAULT;
break; break;
...@@ -1734,7 +1755,17 @@ CommitTransactionCommand(void) ...@@ -1734,7 +1755,17 @@ CommitTransactionCommand(void)
break; break;
/* /*
* We were just issued a BEGIN inside a transaction block. * Ditto, but in a subtransaction. AbortOutOfAnyTransaction
* will do the dirty work.
*/
case TBLOCK_SUBENDABORT_ALL:
AbortOutOfAnyTransaction();
s = CurrentTransactionState; /* changed by AbortOutOfAnyTransaction */
/* AbortOutOfAnyTransaction sets the blockState */
break;
/*
* We were just issued a SAVEPOINT inside a transaction block.
* Start a subtransaction. (BeginTransactionBlock already * Start a subtransaction. (BeginTransactionBlock already
* did PushTransaction, so as to have someplace to put the * did PushTransaction, so as to have someplace to put the
* SUBBEGIN state.) * SUBBEGIN state.)
...@@ -1744,15 +1775,6 @@ CommitTransactionCommand(void) ...@@ -1744,15 +1775,6 @@ CommitTransactionCommand(void)
s->blockState = TBLOCK_SUBINPROGRESS; s->blockState = TBLOCK_SUBINPROGRESS;
break; 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. * Inside a subtransaction, increment the command counter.
*/ */
...@@ -1761,7 +1783,7 @@ CommitTransactionCommand(void) ...@@ -1761,7 +1783,7 @@ CommitTransactionCommand(void)
break; 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. * subtransaction and return to the parent transaction.
*/ */
case TBLOCK_SUBEND: case TBLOCK_SUBEND:
...@@ -1777,27 +1799,78 @@ CommitTransactionCommand(void) ...@@ -1777,27 +1799,78 @@ CommitTransactionCommand(void)
break; break;
/* /*
* We are ending an aborted subtransaction via ROLLBACK, * The current subtransaction is ending. Do the equivalent
* so the parent can be allowed to live. * of a ROLLBACK TO followed by a RELEASE command.
*/ */
case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_RELEASE:
CleanupSubTransaction(); CleanupAbortedSubTransactions(false);
PopTransaction();
s = CurrentTransactionState; /* changed by pop */
break; break;
/* /*
* We are ending an aborted subtransaction via COMMIT. * The current subtransaction is ending due to a ROLLBACK
* End the subtransaction, and abort the parent too. * TO command, so close all savepoints up to the target
* level. When finished, recreate the savepoint.
*/ */
case TBLOCK_SUBENDABORT_ERROR: 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(); CleanupSubTransaction();
PopTransaction(); PopTransaction();
s = CurrentTransactionState; /* changed by pop */ s = CurrentTransactionState; /* changed by pop */
Assert(s->blockState != TBLOCK_SUBENDABORT_ERROR);
AbortCurrentTransaction(); while (s->blockState == TBLOCK_SUBABORT_PENDING)
break; {
AbortSubTransaction();
if (returnName && s->parent->blockState != TBLOCK_SUBABORT_PENDING)
name = MemoryContextStrdup(TopMemoryContext, s->name);
CleanupSubTransaction();
PopTransaction();
s = CurrentTransactionState;
} }
return name;
} }
/* /*
...@@ -1887,7 +1960,6 @@ AbortCurrentTransaction(void) ...@@ -1887,7 +1960,6 @@ AbortCurrentTransaction(void)
* in aborted state. * in aborted state.
*/ */
case TBLOCK_SUBBEGIN: case TBLOCK_SUBBEGIN:
case TBLOCK_SUBBEGINABORT:
StartAbortedSubTransaction(); StartAbortedSubTransaction();
s->blockState = TBLOCK_SUBABORT; s->blockState = TBLOCK_SUBABORT;
break; break;
...@@ -1902,29 +1974,36 @@ AbortCurrentTransaction(void) ...@@ -1902,29 +1974,36 @@ AbortCurrentTransaction(void)
* we have to abort the parent transaction too. * we have to abort the parent transaction too.
*/ */
case TBLOCK_SUBEND: case TBLOCK_SUBEND:
case TBLOCK_SUBABORT_PENDING:
AbortSubTransaction(); AbortSubTransaction();
CleanupSubTransaction(); CleanupSubTransaction();
PopTransaction(); PopTransaction();
s = CurrentTransactionState; /* changed by pop */ s = CurrentTransactionState; /* changed by pop */
Assert(s->blockState != TBLOCK_SUBEND && Assert(s->blockState != TBLOCK_SUBEND &&
s->blockState != TBLOCK_SUBENDABORT_OK && s->blockState != TBLOCK_SUBENDABORT);
s->blockState != TBLOCK_SUBENDABORT_ERROR);
AbortCurrentTransaction(); AbortCurrentTransaction();
break; break;
/* /*
* Same as above, except the Abort() was already done. * Same as above, except the Abort() was already done.
*/ */
case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT:
case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_SUBENDABORT_RELEASE:
CleanupSubTransaction(); CleanupSubTransaction();
PopTransaction(); PopTransaction();
s = CurrentTransactionState; /* changed by pop */ s = CurrentTransactionState; /* changed by pop */
Assert(s->blockState != TBLOCK_SUBEND && Assert(s->blockState != TBLOCK_SUBEND &&
s->blockState != TBLOCK_SUBENDABORT_OK && s->blockState != TBLOCK_SUBENDABORT);
s->blockState != TBLOCK_SUBENDABORT_ERROR);
AbortCurrentTransaction(); AbortCurrentTransaction();
break; 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) ...@@ -2135,7 +2214,8 @@ BeginTransactionBlock(void)
{ {
TransactionState s = CurrentTransactionState; TransactionState s = CurrentTransactionState;
switch (s->blockState) { switch (s->blockState)
{
/* /*
* We are not inside a transaction block, so allow one * We are not inside a transaction block, so allow one
* to begin. * to begin.
...@@ -2146,35 +2226,26 @@ BeginTransactionBlock(void) ...@@ -2146,35 +2226,26 @@ BeginTransactionBlock(void)
/* /*
* Already a transaction block in progress. * Already a transaction block in progress.
* Start a subtransaction.
*/ */
case TBLOCK_INPROGRESS: case TBLOCK_INPROGRESS:
case TBLOCK_SUBINPROGRESS: 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_ABORT:
case TBLOCK_SUBABORT: case TBLOCK_SUBABORT:
PushTransaction(); ereport(WARNING,
s = CurrentTransactionState; /* changed by push */ (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION),
s->blockState = TBLOCK_SUBBEGINABORT; errmsg("there is already a transaction in progress")));
break; break;
/* These cases are invalid. Reject them altogether. */ /* These cases are invalid. Reject them altogether. */
case TBLOCK_DEFAULT: case TBLOCK_DEFAULT:
case TBLOCK_BEGIN: case TBLOCK_BEGIN:
case TBLOCK_SUBBEGIN: case TBLOCK_SUBBEGIN:
case TBLOCK_SUBBEGINABORT:
case TBLOCK_ENDABORT: case TBLOCK_ENDABORT:
case TBLOCK_END: case TBLOCK_END:
case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
case TBLOCK_SUBEND: case TBLOCK_SUBEND:
elog(FATAL, "BeginTransactionBlock: unexpected state %s", elog(FATAL, "BeginTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState)); BlockStateAsString(s->blockState));
...@@ -2185,34 +2256,32 @@ BeginTransactionBlock(void) ...@@ -2185,34 +2256,32 @@ BeginTransactionBlock(void)
/* /*
* EndTransactionBlock * EndTransactionBlock
* This executes a COMMIT command. * 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) EndTransactionBlock(void)
{ {
TransactionState s = CurrentTransactionState; 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 * get to the upcoming CommitTransactionCommand() so we set the
* state to "END". CommitTransactionCommand() will recognize this * 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: 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: case TBLOCK_SUBINPROGRESS:
s->blockState = TBLOCK_SUBEND; s->blockState = TBLOCK_END;
result = true;
break; 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 * AbortTransaction() was already done, we need only
* change to the special "END ABORT" state. The upcoming * change to the special "END ABORT" state. The upcoming
* CommitTransactionCommand() will recognise this and then put us * CommitTransactionCommand() will recognise this and then put us
...@@ -2223,13 +2292,12 @@ EndTransactionBlock(void) ...@@ -2223,13 +2292,12 @@ EndTransactionBlock(void)
break; break;
/* /*
* here we are in an aborted subtransaction. Signal * Here we are inside an aborted subtransaction. Go to the "abort
* CommitTransactionCommand() to clean up and return to the * the whole tree" state so that CommitTransactionCommand() calls
* parent transaction. Since the user said COMMIT, we must * AbortOutOfAnyTransaction.
* fail the parent transaction.
*/ */
case TBLOCK_SUBABORT: case TBLOCK_SUBABORT:
s->blockState = TBLOCK_SUBENDABORT_ERROR; s->blockState = TBLOCK_SUBENDABORT_ALL;
break; break;
case TBLOCK_STARTED: case TBLOCK_STARTED:
...@@ -2252,14 +2320,17 @@ EndTransactionBlock(void) ...@@ -2252,14 +2320,17 @@ EndTransactionBlock(void)
case TBLOCK_ENDABORT: case TBLOCK_ENDABORT:
case TBLOCK_END: case TBLOCK_END:
case TBLOCK_SUBBEGIN: case TBLOCK_SUBBEGIN:
case TBLOCK_SUBBEGINABORT:
case TBLOCK_SUBEND: case TBLOCK_SUBEND:
case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
elog(FATAL, "EndTransactionBlock: unexpected state %s", elog(FATAL, "EndTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState)); BlockStateAsString(s->blockState));
break; break;
} }
return result;
} }
/* /*
...@@ -2271,27 +2342,32 @@ UserAbortTransactionBlock(void) ...@@ -2271,27 +2342,32 @@ UserAbortTransactionBlock(void)
{ {
TransactionState s = CurrentTransactionState; TransactionState s = CurrentTransactionState;
switch (s->blockState) { switch (s->blockState)
{
/* /*
* here we are inside a failed transaction block and we got an abort * We are inside a failed transaction block and we got an
* command from the user. Abort processing is already done, we just * abort command from the user. Abort processing is already
* need to move to the ENDABORT state so we will end up in the default * done, we just need to move to the ENDABORT state so we will
* state after the upcoming CommitTransactionCommand(). * end up in the default state after the upcoming
* CommitTransactionCommand().
*/ */
case TBLOCK_ABORT: case TBLOCK_ABORT:
s->blockState = TBLOCK_ENDABORT; s->blockState = TBLOCK_ENDABORT;
break; break;
/* /*
* Ditto, for a subtransaction. Here it is okay to allow the * We are inside a failed subtransaction and we got an
* parent transaction to continue. * 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: case TBLOCK_SUBABORT:
s->blockState = TBLOCK_SUBENDABORT_OK; s->blockState = TBLOCK_SUBENDABORT_ALL;
break; 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 * command from the user, so we move to the ENDABORT state and
* do abort processing so we will end up in the default state * do abort processing so we will end up in the default state
* after the upcoming CommitTransactionCommand(). * after the upcoming CommitTransactionCommand().
...@@ -2301,17 +2377,22 @@ UserAbortTransactionBlock(void) ...@@ -2301,17 +2377,22 @@ UserAbortTransactionBlock(void)
s->blockState = TBLOCK_ENDABORT; s->blockState = TBLOCK_ENDABORT;
break; 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: case TBLOCK_SUBINPROGRESS:
AbortSubTransaction(); AbortSubTransaction();
s->blockState = TBLOCK_SUBENDABORT_OK; s->blockState = TBLOCK_SUBENDABORT_ALL;
break; break;
/* /*
* here, the user issued ABORT when not inside a * The user issued ABORT when not inside a transaction. Issue
* transaction. Issue a WARNING and go to abort state. The * a WARNING and go to abort state. The upcoming call to
* upcoming call to CommitTransactionCommand() will then put us * CommitTransactionCommand() will then put us back into the
* back into the default state. * default state.
*/ */
case TBLOCK_STARTED: case TBLOCK_STARTED:
ereport(WARNING, ereport(WARNING,
...@@ -2321,21 +2402,265 @@ UserAbortTransactionBlock(void) ...@@ -2321,21 +2402,265 @@ UserAbortTransactionBlock(void)
s->blockState = TBLOCK_ENDABORT; s->blockState = TBLOCK_ENDABORT;
break; break;
/* these cases are invalid. */ /* These cases are invalid. */
case TBLOCK_DEFAULT: case TBLOCK_DEFAULT:
case TBLOCK_BEGIN: case TBLOCK_BEGIN:
case TBLOCK_END: case TBLOCK_END:
case TBLOCK_ENDABORT: case TBLOCK_ENDABORT:
case TBLOCK_SUBEND: case TBLOCK_SUBEND:
case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
case TBLOCK_SUBBEGIN: case TBLOCK_SUBBEGIN:
case TBLOCK_SUBBEGINABORT:
elog(FATAL, "UserAbortTransactionBlock: unexpected state %s", elog(FATAL, "UserAbortTransactionBlock: unexpected state %s",
BlockStateAsString(s->blockState)); BlockStateAsString(s->blockState));
break; 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) ...@@ -2375,7 +2700,6 @@ AbortOutOfAnyTransaction(void)
s->blockState = TBLOCK_DEFAULT; s->blockState = TBLOCK_DEFAULT;
break; break;
case TBLOCK_SUBBEGIN: case TBLOCK_SUBBEGIN:
case TBLOCK_SUBBEGINABORT:
/* /*
* We didn't get as far as starting the subxact, so there's * We didn't get as far as starting the subxact, so there's
* nothing to abort. Just pop back to parent. * nothing to abort. Just pop back to parent.
...@@ -2385,6 +2709,7 @@ AbortOutOfAnyTransaction(void) ...@@ -2385,6 +2709,7 @@ AbortOutOfAnyTransaction(void)
break; break;
case TBLOCK_SUBINPROGRESS: case TBLOCK_SUBINPROGRESS:
case TBLOCK_SUBEND: case TBLOCK_SUBEND:
case TBLOCK_SUBABORT_PENDING:
/* In a subtransaction, so clean it up and abort parent too */ /* In a subtransaction, so clean it up and abort parent too */
AbortSubTransaction(); AbortSubTransaction();
CleanupSubTransaction(); CleanupSubTransaction();
...@@ -2392,8 +2717,9 @@ AbortOutOfAnyTransaction(void) ...@@ -2392,8 +2717,9 @@ AbortOutOfAnyTransaction(void)
s = CurrentTransactionState; /* changed by pop */ s = CurrentTransactionState; /* changed by pop */
break; break;
case TBLOCK_SUBABORT: case TBLOCK_SUBABORT:
case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_SUBENDABORT:
case TBLOCK_SUBENDABORT_RELEASE:
/* As above, but AbortSubTransaction already done */ /* As above, but AbortSubTransaction already done */
CleanupSubTransaction(); CleanupSubTransaction();
PopTransaction(); PopTransaction();
...@@ -2406,6 +2732,28 @@ AbortOutOfAnyTransaction(void) ...@@ -2406,6 +2732,28 @@ AbortOutOfAnyTransaction(void)
Assert(s->parent == NULL); 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? * IsTransactionBlock --- are we within a transaction block?
*/ */
...@@ -2461,9 +2809,10 @@ TransactionBlockStatusCode(void) ...@@ -2461,9 +2809,10 @@ TransactionBlockStatusCode(void)
case TBLOCK_ABORT: case TBLOCK_ABORT:
case TBLOCK_ENDABORT: case TBLOCK_ENDABORT:
case TBLOCK_SUBABORT: case TBLOCK_SUBABORT:
case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_SUBENDABORT:
case TBLOCK_SUBBEGINABORT: case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
return 'E'; /* in failed transaction */ return 'E'; /* in failed transaction */
} }
...@@ -2481,7 +2830,8 @@ IsSubTransaction(void) ...@@ -2481,7 +2830,8 @@ IsSubTransaction(void)
{ {
TransactionState s = CurrentTransactionState; TransactionState s = CurrentTransactionState;
switch (s->blockState) { switch (s->blockState)
{
case TBLOCK_DEFAULT: case TBLOCK_DEFAULT:
case TBLOCK_STARTED: case TBLOCK_STARTED:
case TBLOCK_BEGIN: case TBLOCK_BEGIN:
...@@ -2491,12 +2841,13 @@ IsSubTransaction(void) ...@@ -2491,12 +2841,13 @@ IsSubTransaction(void)
case TBLOCK_ENDABORT: case TBLOCK_ENDABORT:
return false; return false;
case TBLOCK_SUBBEGIN: case TBLOCK_SUBBEGIN:
case TBLOCK_SUBBEGINABORT:
case TBLOCK_SUBINPROGRESS: case TBLOCK_SUBINPROGRESS:
case TBLOCK_SUBABORT: case TBLOCK_SUBABORT:
case TBLOCK_SUBEND: case TBLOCK_SUBEND:
case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ALL:
case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_SUBENDABORT:
case TBLOCK_SUBABORT_PENDING:
case TBLOCK_SUBENDABORT_RELEASE:
return true; return true;
} }
...@@ -2532,6 +2883,8 @@ StartSubTransaction(void) ...@@ -2532,6 +2883,8 @@ StartSubTransaction(void)
SubTransSetParent(s->transactionIdData, s->parent->transactionIdData); SubTransSetParent(s->transactionIdData, s->parent->transactionIdData);
XactLockTableInsert(s->transactionIdData);
/* /*
* Finish setup of other transaction state fields. * Finish setup of other transaction state fields.
*/ */
...@@ -2619,6 +2972,9 @@ AbortSubTransaction(void) ...@@ -2619,6 +2972,9 @@ AbortSubTransaction(void)
ShowTransactionState("AbortSubTransaction"); ShowTransactionState("AbortSubTransaction");
if (s->state != TRANS_INPROGRESS)
elog(WARNING, "AbortSubTransaction and not in in-progress state");
HOLD_INTERRUPTS(); HOLD_INTERRUPTS();
s->state = TRANS_ABORT; s->state = TRANS_ABORT;
...@@ -2762,6 +3118,9 @@ StartAbortedSubTransaction(void) ...@@ -2762,6 +3118,9 @@ StartAbortedSubTransaction(void)
/* /*
* PushTransaction * PushTransaction
* Set up transaction state for a subtransaction * 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 static void
PushTransaction(void) PushTransaction(void)
...@@ -2777,6 +3136,7 @@ PushTransaction(void) ...@@ -2777,6 +3136,7 @@ PushTransaction(void)
sizeof(TransactionStateData)); sizeof(TransactionStateData));
s->parent = p; s->parent = p;
s->nestingLevel = p->nestingLevel + 1; s->nestingLevel = p->nestingLevel + 1;
s->savepointLevel = p->savepointLevel;
s->state = TRANS_DEFAULT; s->state = TRANS_DEFAULT;
s->blockState = TBLOCK_SUBBEGIN; s->blockState = TBLOCK_SUBBEGIN;
...@@ -2798,6 +3158,9 @@ PushTransaction(void) ...@@ -2798,6 +3158,9 @@ PushTransaction(void)
/* /*
* PopTransaction * PopTransaction
* Pop back to parent transaction state * 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 static void
PopTransaction(void) PopTransaction(void)
...@@ -2824,6 +3187,8 @@ PopTransaction(void) ...@@ -2824,6 +3187,8 @@ PopTransaction(void)
CurrentResourceOwner = s->parent->curTransactionOwner; CurrentResourceOwner = s->parent->curTransactionOwner;
/* Free the old child structure */ /* Free the old child structure */
if (s->name)
pfree(s->name);
pfree(s); pfree(s);
} }
...@@ -2854,7 +3219,8 @@ ShowTransactionStateRec(TransactionState s) ...@@ -2854,7 +3219,8 @@ ShowTransactionStateRec(TransactionState s)
/* use ereport to suppress computation if msg will not be printed */ /* use ereport to suppress computation if msg will not be printed */
ereport(DEBUG2, 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), BlockStateAsString(s->blockState),
TransStateAsString(s->state), TransStateAsString(s->state),
(unsigned int) s->transactionIdData, (unsigned int) s->transactionIdData,
...@@ -2870,7 +3236,8 @@ ShowTransactionStateRec(TransactionState s) ...@@ -2870,7 +3236,8 @@ ShowTransactionStateRec(TransactionState s)
static const char * static const char *
BlockStateAsString(TBlockState blockState) BlockStateAsString(TBlockState blockState)
{ {
switch (blockState) { switch (blockState)
{
case TBLOCK_DEFAULT: case TBLOCK_DEFAULT:
return "DEFAULT"; return "DEFAULT";
case TBLOCK_STARTED: case TBLOCK_STARTED:
...@@ -2887,18 +3254,20 @@ BlockStateAsString(TBlockState blockState) ...@@ -2887,18 +3254,20 @@ BlockStateAsString(TBlockState blockState)
return "ENDABORT"; return "ENDABORT";
case TBLOCK_SUBBEGIN: case TBLOCK_SUBBEGIN:
return "SUB BEGIN"; return "SUB BEGIN";
case TBLOCK_SUBBEGINABORT:
return "SUB BEGIN AB";
case TBLOCK_SUBINPROGRESS: case TBLOCK_SUBINPROGRESS:
return "SUB INPROGRS"; return "SUB INPROGRS";
case TBLOCK_SUBEND: case TBLOCK_SUBEND:
return "SUB END"; return "SUB END";
case TBLOCK_SUBABORT: case TBLOCK_SUBABORT:
return "SUB ABORT"; return "SUB ABORT";
case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ALL:
return "SUB ENDAB OK"; return "SUB ENDAB ALL";
case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_SUBENDABORT:
return "SUB ENDAB ERR"; return "SUB ENDAB";
case TBLOCK_SUBABORT_PENDING:
return "SUB ABRT PEND";
case TBLOCK_SUBENDABORT_RELEASE:
return "SUB ENDAB REL";
} }
return "UNRECOGNIZED"; return "UNRECOGNIZED";
} }
...@@ -2910,7 +3279,8 @@ BlockStateAsString(TBlockState blockState) ...@@ -2910,7 +3279,8 @@ BlockStateAsString(TBlockState blockState)
static const char * static const char *
TransStateAsString(TransState state) TransStateAsString(TransState state)
{ {
switch (state) { switch (state)
{
case TRANS_DEFAULT: case TRANS_DEFAULT:
return "DEFAULT"; return "DEFAULT";
case TRANS_START: case TRANS_START:
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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,17 +1181,15 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan) ...@@ -1181,17 +1181,15 @@ _SPI_execute(const char *src, int tcount, _SPI_plan *plan)
res = SPI_ERROR_CURSOR; res = SPI_ERROR_CURSOR;
goto fail; goto fail;
} }
else if (IsA(queryTree->utilityStmt, TransactionStmt))
{
res = SPI_ERROR_TRANSACTION;
goto fail;
}
res = SPI_OK_UTILITY; res = SPI_OK_UTILITY;
if (plan == NULL) if (plan == NULL)
{ {
ProcessUtility(queryTree->utilityStmt, dest, NULL); ProcessUtility(queryTree->utilityStmt, dest, NULL);
if (IsA(queryTree->utilityStmt, TransactionStmt))
{
CommitTransactionCommand();
StartTransactionCommand();
}
else
CommandCounterIncrement(); CommandCounterIncrement();
} }
} }
...@@ -1308,13 +1306,6 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls, ...@@ -1308,13 +1306,6 @@ _SPI_execute_plan(_SPI_plan *plan, Datum *Values, const char *Nulls,
{ {
ProcessUtility(queryTree->utilityStmt, dest, NULL); ProcessUtility(queryTree->utilityStmt, dest, NULL);
res = SPI_OK_UTILITY; res = SPI_OK_UTILITY;
if (IsA(queryTree->utilityStmt, TransactionStmt))
{
CommitTransactionCommand();
StartTransactionCommand();
}
else
CommandCounterIncrement(); CommandCounterIncrement();
} }
else else
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
* *
* *
* IDENTIFICATION * 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 * HISTORY
* AUTHOR DATE MAJOR EVENT * AUTHOR DATE MAJOR EVENT
...@@ -386,11 +386,11 @@ static void doNegateFloat(Value *v); ...@@ -386,11 +386,11 @@ static void doNegateFloat(Value *v);
QUOTE QUOTE
READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RENAME REPEATABLE REPLACE READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RELEASE RENAME
RESET RESTART RESTRICT RETURNS REVOKE RIGHT ROLLBACK ROW ROWS REPEATABLE REPLACE RESET RESTART RESTRICT RETURNS REVOKE RIGHT
RULE 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 SERIALIZABLE SESSION SESSION_USER SET SETOF SHARE
SHOW SIMILAR SIMPLE SMALLINT SOME STABLE START STATEMENT SHOW SIMILAR SIMPLE SMALLINT SOME STABLE START STATEMENT
STATISTICS STDIN STDOUT STORAGE STRICT_P SUBSTRING SYSID STATISTICS STDIN STDOUT STORAGE STRICT_P SUBSTRING SYSID
...@@ -3961,6 +3961,30 @@ TransactionStmt: ...@@ -3961,6 +3961,30 @@ TransactionStmt:
n->options = NIL; n->options = NIL;
$$ = (Node *)n; $$ = (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 {} opt_transaction: WORK {}
...@@ -7688,6 +7712,7 @@ unreserved_keyword: ...@@ -7688,6 +7712,7 @@ unreserved_keyword:
| RECHECK | RECHECK
| REINDEX | REINDEX
| RELATIVE_P | RELATIVE_P
| RELEASE
| RENAME | RENAME
| REPEATABLE | REPEATABLE
| REPLACE | REPLACE
...@@ -7699,6 +7724,7 @@ unreserved_keyword: ...@@ -7699,6 +7724,7 @@ unreserved_keyword:
| ROLLBACK | ROLLBACK
| ROWS | ROWS
| RULE | RULE
| SAVEPOINT
| SCHEMA | SCHEMA
| SCROLL | SCROLL
| SECOND_P | SECOND_P
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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[] = { ...@@ -254,6 +254,7 @@ static const ScanKeyword ScanKeywords[] = {
{"references", REFERENCES}, {"references", REFERENCES},
{"reindex", REINDEX}, {"reindex", REINDEX},
{"relative", RELATIVE_P}, {"relative", RELATIVE_P},
{"release", RELEASE},
{"rename", RENAME}, {"rename", RENAME},
{"repeatable", REPEATABLE}, {"repeatable", REPEATABLE},
{"replace", REPLACE}, {"replace", REPLACE},
...@@ -267,6 +268,7 @@ static const ScanKeyword ScanKeywords[] = { ...@@ -267,6 +268,7 @@ static const ScanKeyword ScanKeywords[] = {
{"row", ROW}, {"row", ROW},
{"rows", ROWS}, {"rows", ROWS},
{"rule", RULE}, {"rule", RULE},
{"savepoint", SAVEPOINT},
{"schema", SCHEMA}, {"schema", SCHEMA},
{"scroll", SCROLL}, {"scroll", SCROLL},
{"second", SECOND_P}, {"second", SECOND_P},
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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) ...@@ -334,21 +334,23 @@ XactLockTableInsert(TransactionId xid)
* XactLockTableWait * XactLockTableWait
* *
* Wait for the specified transaction to commit or abort. * 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 void
XactLockTableWait(TransactionId xid) XactLockTableWait(TransactionId xid)
{ {
LOCKTAG tag; LOCKTAG tag;
TransactionId myxid = GetCurrentTransactionId(); TransactionId myxid = GetCurrentTransactionId();
TransactionId waitXid = SubTransGetTopmostTransaction(xid);
Assert(!SubTransXidsHaveCommonAncestor(waitXid, myxid)); Assert(!SubTransXidsHaveCommonAncestor(xid, myxid));
MemSet(&tag, 0, sizeof(tag)); MemSet(&tag, 0, sizeof(tag));
tag.relId = XactLockTableId; tag.relId = XactLockTableId;
tag.dbId = InvalidOid; tag.dbId = InvalidOid;
tag.objId.xid = waitXid; tag.objId.xid = xid;
if (!LockAcquire(LockTableId, &tag, myxid, if (!LockAcquire(LockTableId, &tag, myxid,
ShareLock, false)) ShareLock, false))
...@@ -358,13 +360,8 @@ XactLockTableWait(TransactionId xid) ...@@ -358,13 +360,8 @@ XactLockTableWait(TransactionId xid)
/* /*
* Transaction was committed/aborted/crashed - we have to update * Transaction was committed/aborted/crashed - we have to update
* pg_clog if transaction is still marked as running. If it's a * pg_clog if transaction is still marked as running.
* subtransaction, we can update the parent status too.
*/ */
if (!TransactionIdDidCommit(waitXid) && !TransactionIdDidAbort(waitXid)) if (!TransactionIdDidCommit(xid) && !TransactionIdDidAbort(xid))
{
TransactionIdAbort(waitXid);
if (waitXid != xid)
TransactionIdAbort(xid); TransactionIdAbort(xid);
}
} }
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
* *
* *
* IDENTIFICATION * 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 * NOTES
* this is the "main" module of the postgres backend and * this is the "main" module of the postgres backend and
...@@ -841,8 +841,8 @@ exec_simple_query(const char *query_string) ...@@ -841,8 +841,8 @@ exec_simple_query(const char *query_string)
TransactionStmt *stmt = (TransactionStmt *) parsetree; TransactionStmt *stmt = (TransactionStmt *) parsetree;
if (stmt->kind == TRANS_STMT_COMMIT || 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; allowit = true;
} }
...@@ -1162,8 +1162,8 @@ exec_parse_message(const char *query_string, /* string to execute */ ...@@ -1162,8 +1162,8 @@ exec_parse_message(const char *query_string, /* string to execute */
TransactionStmt *stmt = (TransactionStmt *) parsetree; TransactionStmt *stmt = (TransactionStmt *) parsetree;
if (stmt->kind == TRANS_STMT_COMMIT || 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; allowit = true;
} }
...@@ -1625,8 +1625,8 @@ exec_execute_message(const char *portal_name, long max_rows) ...@@ -1625,8 +1625,8 @@ exec_execute_message(const char *portal_name, long max_rows)
is_trans_stmt = true; is_trans_stmt = true;
if (stmt->kind == TRANS_STMT_COMMIT || 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; is_trans_exit = true;
} }
} }
...@@ -2810,6 +2810,9 @@ PostgresMain(int argc, char *argv[], const char *username) ...@@ -2810,6 +2810,9 @@ PostgresMain(int argc, char *argv[], const char *username)
*/ */
MemoryContextSwitchTo(ErrorContext); MemoryContextSwitchTo(ErrorContext);
/* Make sure we are using a sane ResourceOwner, too */
CurrentResourceOwner = CurTransactionResourceOwner;
/* Do the recovery */ /* Do the recovery */
ereport(DEBUG2, ereport(DEBUG2,
(errmsg_internal("AbortCurrentTransaction"))); (errmsg_internal("AbortCurrentTransaction")));
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
* *
* *
* IDENTIFICATION * 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, ...@@ -354,12 +354,51 @@ ProcessUtility(Node *parsetree,
break; break;
case TRANS_STMT_COMMIT: case TRANS_STMT_COMMIT:
EndTransactionBlock(); if (!EndTransactionBlock())
{
/* report unsuccessful commit in completionTag */
if (completionTag)
strcpy(completionTag, "ROLLBACK");
}
break; break;
case TRANS_STMT_ROLLBACK: case TRANS_STMT_ROLLBACK:
UserAbortTransactionBlock(); UserAbortTransactionBlock();
break; 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; break;
...@@ -1114,9 +1153,18 @@ CreateCommandTag(Node *parsetree) ...@@ -1114,9 +1153,18 @@ CreateCommandTag(Node *parsetree)
break; break;
case TRANS_STMT_ROLLBACK: case TRANS_STMT_ROLLBACK:
case TRANS_STMT_ROLLBACK_TO:
tag = "ROLLBACK"; tag = "ROLLBACK";
break; break;
case TRANS_STMT_SAVEPOINT:
tag = "SAVEPOINT";
break;
case TRANS_STMT_RELEASE:
tag = "RELEASE";
break;
default: default:
tag = "???"; tag = "???";
break; break;
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* *
* Copyright (c) 2000-2003, PostgreSQL Global Development Group * 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) ...@@ -463,8 +463,8 @@ psql_completion(char *text, int start, int end)
"ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", "COMMENT", "ABORT", "ALTER", "ANALYZE", "BEGIN", "CHECKPOINT", "CLOSE", "CLUSTER", "COMMENT",
"COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", "DELETE", "DROP", "EXECUTE", "COMMIT", "COPY", "CREATE", "DEALLOCATE", "DECLARE", "DELETE", "DROP", "EXECUTE",
"EXPLAIN", "FETCH", "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY", "EXPLAIN", "FETCH", "GRANT", "INSERT", "LISTEN", "LOAD", "LOCK", "MOVE", "NOTIFY",
"PREPARE", "REINDEX", "RESET", "REVOKE", "ROLLBACK", "SELECT", "SET", "SHOW", "START", "PREPARE", "REINDEX", "RELEASE", "RESET", "REVOKE", "ROLLBACK", "SAVEPOINT",
"TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", NULL "SELECT", "SET", "SHOW", "START", "TRUNCATE", "UNLISTEN", "UPDATE", "VACUUM", NULL
}; };
static const char * const pgsql_variables[] = { static const char * const pgsql_variables[] = {
...@@ -722,16 +722,23 @@ psql_completion(char *text, int start, int end) ...@@ -722,16 +722,23 @@ psql_completion(char *text, int start, int end)
else if (pg_strcasecmp(prev2_wd, "ANALYZE") == 0) else if (pg_strcasecmp(prev2_wd, "ANALYZE") == 0)
COMPLETE_WITH_CONST(";"); COMPLETE_WITH_CONST(";");
/* BEGIN, COMMIT, ROLLBACK, ABORT, */ /* BEGIN, COMMIT, ABORT */
else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 || else if (pg_strcasecmp(prev_wd, "BEGIN") == 0 ||
pg_strcasecmp(prev_wd, "END") == 0 || pg_strcasecmp(prev_wd, "END") == 0 ||
pg_strcasecmp(prev_wd, "COMMIT") == 0 || pg_strcasecmp(prev_wd, "COMMIT") == 0 ||
pg_strcasecmp(prev_wd, "ROLLBACK") == 0 ||
pg_strcasecmp(prev_wd, "ABORT") == 0) pg_strcasecmp(prev_wd, "ABORT") == 0)
{ {
static const char * const list_TRANS[] = static const char * const list_TRANS[] =
{"WORK", "TRANSACTION", NULL}; {"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); COMPLETE_WITH_LIST(list_TRANS);
} }
/* CLUSTER */ /* CLUSTER */
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 @@ ...@@ -16,6 +16,7 @@
#include "access/xlog.h" #include "access/xlog.h"
#include "storage/relfilenode.h" #include "storage/relfilenode.h"
#include "nodes/pg_list.h"
#include "utils/nabstime.h" #include "utils/nabstime.h"
...@@ -101,12 +102,16 @@ extern void StartTransactionCommand(void); ...@@ -101,12 +102,16 @@ extern void StartTransactionCommand(void);
extern void CommitTransactionCommand(void); extern void CommitTransactionCommand(void);
extern void AbortCurrentTransaction(void); extern void AbortCurrentTransaction(void);
extern void BeginTransactionBlock(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 IsSubTransaction(void);
extern bool IsTransactionBlock(void); extern bool IsTransactionBlock(void);
extern bool IsTransactionOrTransactionBlock(void); extern bool IsTransactionOrTransactionBlock(void);
extern char TransactionBlockStatusCode(void); extern char TransactionBlockStatusCode(void);
extern void UserAbortTransactionBlock(void);
extern void AbortOutOfAnyTransaction(void); extern void AbortOutOfAnyTransaction(void);
extern void PreventTransactionChain(void *stmtNode, const char *stmtType); extern void PreventTransactionChain(void *stmtNode, const char *stmtType);
extern void RequireTransactionChain(void *stmtNode, const char *stmtType); extern void RequireTransactionChain(void *stmtNode, const char *stmtType);
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * 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 ...@@ -1514,14 +1514,17 @@ typedef enum TransactionStmtKind
TRANS_STMT_BEGIN, TRANS_STMT_BEGIN,
TRANS_STMT_START, /* semantically identical to BEGIN */ TRANS_STMT_START, /* semantically identical to BEGIN */
TRANS_STMT_COMMIT, TRANS_STMT_COMMIT,
TRANS_STMT_ROLLBACK TRANS_STMT_ROLLBACK,
TRANS_STMT_SAVEPOINT,
TRANS_STMT_RELEASE,
TRANS_STMT_ROLLBACK_TO
} TransactionStmtKind; } TransactionStmtKind;
typedef struct TransactionStmt typedef struct TransactionStmt
{ {
NodeTag type; NodeTag type;
TransactionStmtKind kind; /* see above */ TransactionStmtKind kind; /* see above */
List *options; /* for BEGIN/START only */ List *options; /* for BEGIN/START and savepoint commands */
} TransactionStmt; } TransactionStmt;
/* ---------------------- /* ----------------------
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
* *
* Copyright (c) 2003, PostgreSQL Global Development Group * 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 @@ ...@@ -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_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') #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 */ /* Class 3D - Invalid Catalog Name */
#define ERRCODE_INVALID_CATALOG_NAME MAKE_SQLSTATE('3','D', '0','0','0') #define ERRCODE_INVALID_CATALOG_NAME MAKE_SQLSTATE('3','D', '0','0','0')
......
...@@ -74,13 +74,14 @@ SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE; ...@@ -74,13 +74,14 @@ SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE;
CREATE TABLE foobar (a int); CREATE TABLE foobar (a int);
BEGIN; BEGIN;
CREATE TABLE foo (a int); CREATE TABLE foo (a int);
BEGIN; SAVEPOINT one;
DROP TABLE foo; DROP TABLE foo;
CREATE TABLE bar (a int); CREATE TABLE bar (a int);
ROLLBACK; ROLLBACK TO one;
BEGIN; RELEASE one;
SAVEPOINT two;
CREATE TABLE baz (a int); CREATE TABLE baz (a int);
COMMIT; RELEASE two;
drop TABLE foobar; drop TABLE foobar;
CREATE TABLE barbaz (a int); CREATE TABLE barbaz (a int);
COMMIT; COMMIT;
...@@ -105,18 +106,20 @@ SELECT * FROM baz; -- should be empty ...@@ -105,18 +106,20 @@ SELECT * FROM baz; -- should be empty
-- inserts -- inserts
BEGIN; BEGIN;
INSERT INTO foo VALUES (1); INSERT INTO foo VALUES (1);
BEGIN; SAVEPOINT one;
INSERT into bar VALUES (1); INSERT into bar VALUES (1);
ERROR: relation "bar" does not exist ERROR: relation "bar" does not exist
ROLLBACK; ROLLBACK TO one;
BEGIN; RELEASE one;
SAVEPOINT two;
INSERT into barbaz VALUES (1); INSERT into barbaz VALUES (1);
COMMIT; RELEASE two;
BEGIN; SAVEPOINT three;
BEGIN; SAVEPOINT four;
INSERT INTO foo VALUES (2); INSERT INTO foo VALUES (2);
COMMIT; RELEASE four;
ROLLBACK; ROLLBACK TO three;
RELEASE three;
INSERT INTO foo VALUES (3); INSERT INTO foo VALUES (3);
COMMIT; COMMIT;
SELECT * FROM foo; -- should have 1 and 3 SELECT * FROM foo; -- should have 1 and 3
...@@ -132,53 +135,168 @@ SELECT * FROM barbaz; -- should have 1 ...@@ -132,53 +135,168 @@ SELECT * FROM barbaz; -- should have 1
1 1
(1 row) (1 row)
-- check that starting a subxact in a failed xact or subxact works -- test whole-tree commit
BEGIN; BEGIN;
SELECT 0/0; -- fail the outer xact SAVEPOINT one;
ERROR: division by zero SELECT foo;
BEGIN; ERROR: column "foo" does not exist
SELECT 1; -- this should NOT work ROLLBACK TO one;
ERROR: current transaction is aborted, commands ignored until end of transaction block RELEASE one;
COMMIT; SAVEPOINT two;
SELECT 1; -- this should NOT work CREATE TABLE savepoints (a int);
ERROR: current transaction is aborted, commands ignored until end of transaction block SAVEPOINT three;
BEGIN; INSERT INTO savepoints VALUES (1);
SELECT 1; -- this should NOT work SAVEPOINT four;
ERROR: current transaction is aborted, commands ignored until end of transaction block INSERT INTO savepoints VALUES (2);
ROLLBACK; SAVEPOINT five;
SELECT 1; -- this should NOT work INSERT INTO savepoints VALUES (3);
ERROR: current transaction is aborted, commands ignored until end of transaction block ROLLBACK TO five;
COMMIT; 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? ?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) (1 row)
BEGIN; BEGIN;
BEGIN; INSERT INTO savepoints VALUES (9);
SELECT 1; -- this should work 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? ?column?
---------- ----------
1 f
(1 row) (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 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
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 ERROR: current transaction is aborted, commands ignored until end of transaction block
COMMIT; RELEASE one; -- ignored till the end of ...
SELECT 1; -- this should NOT work
ERROR: current transaction is aborted, commands ignored until end of transaction block ERROR: current transaction is aborted, commands ignored until end of transaction block
ROLLBACK; ROLLBACK TO one;
SELECT 1; -- this should work SELECT 1;
?column? ?column?
---------- ----------
1 1
...@@ -194,7 +312,7 @@ SELECT 1; -- this should work ...@@ -194,7 +312,7 @@ SELECT 1; -- this should work
-- check non-transactional behavior of cursors -- check non-transactional behavior of cursors
BEGIN; BEGIN;
DECLARE c CURSOR FOR SELECT unique2 FROM tenk1; DECLARE c CURSOR FOR SELECT unique2 FROM tenk1;
BEGIN; SAVEPOINT one;
FETCH 10 FROM c; FETCH 10 FROM c;
unique2 unique2
--------- ---------
...@@ -210,8 +328,7 @@ BEGIN; ...@@ -210,8 +328,7 @@ BEGIN;
9 9
(10 rows) (10 rows)
ROLLBACK; ROLLBACK TO one;
BEGIN;
FETCH 10 FROM c; FETCH 10 FROM c;
unique2 unique2
--------- ---------
...@@ -227,7 +344,7 @@ BEGIN; ...@@ -227,7 +344,7 @@ BEGIN;
19 19
(10 rows) (10 rows)
COMMIT; RELEASE one;
FETCH 10 FROM c; FETCH 10 FROM c;
unique2 unique2
--------- ---------
...@@ -245,15 +362,15 @@ BEGIN; ...@@ -245,15 +362,15 @@ BEGIN;
CLOSE c; CLOSE c;
DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1; DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1;
BEGIN; SAVEPOINT two;
FETCH 10 FROM c; FETCH 10 FROM c;
ERROR: division by zero ERROR: division by zero
ROLLBACK; ROLLBACK TO two;
-- c is now dead to the world ... -- c is now dead to the world ...
BEGIN;
FETCH 10 FROM c; FETCH 10 FROM c;
ERROR: portal "c" cannot be run ERROR: portal "c" cannot be run
ROLLBACK; ROLLBACK TO two;
RELEASE two;
FETCH 10 FROM c; FETCH 10 FROM c;
ERROR: portal "c" cannot be run ERROR: portal "c" cannot be run
COMMIT; COMMIT;
......
...@@ -61,13 +61,14 @@ SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE; ...@@ -61,13 +61,14 @@ SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE;
CREATE TABLE foobar (a int); CREATE TABLE foobar (a int);
BEGIN; BEGIN;
CREATE TABLE foo (a int); CREATE TABLE foo (a int);
BEGIN; SAVEPOINT one;
DROP TABLE foo; DROP TABLE foo;
CREATE TABLE bar (a int); CREATE TABLE bar (a int);
ROLLBACK; ROLLBACK TO one;
BEGIN; RELEASE one;
SAVEPOINT two;
CREATE TABLE baz (a int); CREATE TABLE baz (a int);
COMMIT; RELEASE two;
drop TABLE foobar; drop TABLE foobar;
CREATE TABLE barbaz (a int); CREATE TABLE barbaz (a int);
COMMIT; COMMIT;
...@@ -80,76 +81,156 @@ SELECT * FROM baz; -- should be empty ...@@ -80,76 +81,156 @@ SELECT * FROM baz; -- should be empty
-- inserts -- inserts
BEGIN; BEGIN;
INSERT INTO foo VALUES (1); INSERT INTO foo VALUES (1);
BEGIN; SAVEPOINT one;
INSERT into bar VALUES (1); INSERT into bar VALUES (1);
ROLLBACK; ROLLBACK TO one;
BEGIN; RELEASE one;
SAVEPOINT two;
INSERT into barbaz VALUES (1); INSERT into barbaz VALUES (1);
COMMIT; RELEASE two;
BEGIN; SAVEPOINT three;
BEGIN; SAVEPOINT four;
INSERT INTO foo VALUES (2); INSERT INTO foo VALUES (2);
COMMIT; RELEASE four;
ROLLBACK; ROLLBACK TO three;
RELEASE three;
INSERT INTO foo VALUES (3); INSERT INTO foo VALUES (3);
COMMIT; COMMIT;
SELECT * FROM foo; -- should have 1 and 3 SELECT * FROM foo; -- should have 1 and 3
SELECT * FROM barbaz; -- should have 1 SELECT * FROM barbaz; -- should have 1
-- check that starting a subxact in a failed xact or subxact works -- test whole-tree commit
BEGIN; BEGIN;
SELECT 0/0; -- fail the outer xact SAVEPOINT one;
BEGIN; SELECT foo;
SELECT 1; -- this should NOT work ROLLBACK TO one;
COMMIT; RELEASE one;
SELECT 1; -- this should NOT work SAVEPOINT two;
BEGIN; CREATE TABLE savepoints (a int);
SELECT 1; -- this should NOT work SAVEPOINT three;
ROLLBACK; INSERT INTO savepoints VALUES (1);
SELECT 1; -- this should NOT work SAVEPOINT four;
INSERT INTO savepoints VALUES (2);
SAVEPOINT five;
INSERT INTO savepoints VALUES (3);
ROLLBACK TO five;
COMMIT; 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;
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; BEGIN;
BEGIN; SAVEPOINT one;
SELECT 1; -- this should work SELECT 0/0;
SELECT 0/0; -- fail the subxact SAVEPOINT two; -- ignored till the end of ...
SELECT 1; -- this should NOT work RELEASE one; -- ignored till the end of ...
BEGIN; ROLLBACK TO one;
SELECT 1; -- this should NOT work SELECT 1;
ROLLBACK;
BEGIN;
SELECT 1; -- this should NOT work
COMMIT;
SELECT 1; -- this should NOT work
ROLLBACK;
SELECT 1; -- this should work
COMMIT; COMMIT;
SELECT 1; -- this should work SELECT 1; -- this should work
-- check non-transactional behavior of cursors -- check non-transactional behavior of cursors
BEGIN; BEGIN;
DECLARE c CURSOR FOR SELECT unique2 FROM tenk1; DECLARE c CURSOR FOR SELECT unique2 FROM tenk1;
BEGIN; SAVEPOINT one;
FETCH 10 FROM c; FETCH 10 FROM c;
ROLLBACK; ROLLBACK TO one;
BEGIN;
FETCH 10 FROM c; FETCH 10 FROM c;
COMMIT; RELEASE one;
FETCH 10 FROM c; FETCH 10 FROM c;
CLOSE c; CLOSE c;
DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1; DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1;
BEGIN; SAVEPOINT two;
FETCH 10 FROM c; FETCH 10 FROM c;
ROLLBACK; ROLLBACK TO two;
-- c is now dead to the world ... -- c is now dead to the world ...
BEGIN;
FETCH 10 FROM c; FETCH 10 FROM c;
ROLLBACK; ROLLBACK TO two;
RELEASE two;
FETCH 10 FROM c; FETCH 10 FROM c;
COMMIT; COMMIT;
DROP TABLE foo; DROP TABLE foo;
DROP TABLE baz; DROP TABLE baz;
DROP TABLE barbaz; DROP TABLE barbaz;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment