diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index d88f7164d3..55d5ef9b80 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -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 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: diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index 19dbfc13d0..f2fa0a4316 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -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 { diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 519bcce718..1c7faa2c99 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -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 diff --git a/src/backend/parser/keywords.c b/src/backend/parser/keywords.c index cae1ed159b..80ae597feb 100644 --- a/src/backend/parser/keywords.c +++ b/src/backend/parser/keywords.c @@ -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}, diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c index 45305b4dea..176767507c 100644 --- a/src/backend/storage/lmgr/lmgr.c +++ b/src/backend/storage/lmgr/lmgr.c @@ -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); } diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 36fb347de3..a353122fc2 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -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"))); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index a3e727472a..6c32c6c3d7 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -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; diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index f26c3e7533..0dfaebe38b 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -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 */ diff --git a/src/include/access/xact.h b/src/include/access/xact.h index 458b3012ad..7bf92b0153 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -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); diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 47b09b42a7..9abfdb8605 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -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; /* ---------------------- diff --git a/src/include/utils/errcodes.h b/src/include/utils/errcodes.h index 270eb2073b..f3ac7f6c0b 100644 --- a/src/include/utils/errcodes.h +++ b/src/include/utils/errcodes.h @@ -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') diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out index cc3004dbb2..d5a2c0c5fa 100644 --- a/src/test/regress/expected/transactions.out +++ b/src/test/regress/expected/transactions.out @@ -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; diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql index f2a206979f..d101ff305d 100644 --- a/src/test/regress/sql/transactions.sql +++ b/src/test/regress/sql/transactions.sql @@ -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;