diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2004-07-27 05:11:48 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2004-07-27 05:11:48 +0000 |
commit | cc813fc2b8d9293bbd4d0e0d6a6f3b9cf02fe32f (patch) | |
tree | 9f7e6635c94bb61cb9d6340c3647c429dca9504b /src/backend | |
parent | b1ee93884d528672fbce446a38659954a86219e1 (diff) | |
download | postgresql-cc813fc2b8d9293bbd4d0e0d6a6f3b9cf02fe32f.tar.gz postgresql-cc813fc2b8d9293bbd4d0e0d6a6f3b9cf02fe32f.zip |
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.
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/access/transam/xact.c | 610 | ||||
-rw-r--r-- | src/backend/executor/spi.c | 25 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 36 | ||||
-rw-r--r-- | src/backend/parser/keywords.c | 4 | ||||
-rw-r--r-- | src/backend/storage/lmgr/lmgr.c | 23 | ||||
-rw-r--r-- | src/backend/tcop/postgres.c | 17 | ||||
-rw-r--r-- | src/backend/tcop/utility.c | 52 |
7 files changed, 602 insertions, 165 deletions
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index d88f7164d34..55d5ef9b80a 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.) @@ -1745,15 +1776,6 @@ CommitTransactionCommand(void) 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. */ case TBLOCK_SUBINPROGRESS: @@ -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,30 +1799,81 @@ 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 */ void @@ -1887,7 +1960,6 @@ AbortCurrentTransaction(void) * in aborted state. */ case TBLOCK_SUBBEGIN: - case TBLOCK_SUBBEGINABORT: StartAbortedSubTransaction(); s->blockState = TBLOCK_SUBABORT; break; @@ -1902,29 +1974,36 @@ AbortCurrentTransaction(void) * we have to abort the parent transaction too. */ case TBLOCK_SUBEND: + case TBLOCK_SUBABORT_PENDING: AbortSubTransaction(); CleanupSubTransaction(); PopTransaction(); s = CurrentTransactionState; /* changed by pop */ Assert(s->blockState != TBLOCK_SUBEND && - s->blockState != TBLOCK_SUBENDABORT_OK && - s->blockState != TBLOCK_SUBENDABORT_ERROR); + s->blockState != TBLOCK_SUBENDABORT); AbortCurrentTransaction(); break; /* * Same as above, except the Abort() was already done. */ - case TBLOCK_SUBENDABORT_OK: - case TBLOCK_SUBENDABORT_ERROR: + case TBLOCK_SUBENDABORT: + case TBLOCK_SUBENDABORT_RELEASE: CleanupSubTransaction(); PopTransaction(); s = CurrentTransactionState; /* changed by pop */ Assert(s->blockState != TBLOCK_SUBEND && - s->blockState != TBLOCK_SUBENDABORT_OK && - s->blockState != TBLOCK_SUBENDABORT_ERROR); + s->blockState != TBLOCK_SUBENDABORT); AbortCurrentTransaction(); break; + + /* + * We are already aborting the whole transaction tree. + * Do nothing, CommitTransactionCommand will call + * AbortOutOfAnyTransaction and set things straight. + */ + case TBLOCK_SUBENDABORT_ALL: + break; } } @@ -2135,7 +2214,8 @@ BeginTransactionBlock(void) { TransactionState s = CurrentTransactionState; - switch (s->blockState) { + switch (s->blockState) + { /* * We are not inside a transaction block, so allow one * to begin. @@ -2146,35 +2226,26 @@ BeginTransactionBlock(void) /* * Already a transaction block in progress. - * Start a subtransaction. */ case TBLOCK_INPROGRESS: case TBLOCK_SUBINPROGRESS: - PushTransaction(); - s = CurrentTransactionState; /* changed by push */ - s->blockState = TBLOCK_SUBBEGIN; - break; - - /* - * An aborted transaction block should be allowed to start - * a subtransaction, but it must put it in aborted state. - */ case TBLOCK_ABORT: case TBLOCK_SUBABORT: - PushTransaction(); - s = CurrentTransactionState; /* changed by push */ - s->blockState = TBLOCK_SUBBEGINABORT; + ereport(WARNING, + (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), + errmsg("there is already a transaction in progress"))); break; /* These cases are invalid. Reject them altogether. */ case TBLOCK_DEFAULT: case TBLOCK_BEGIN: case TBLOCK_SUBBEGIN: - case TBLOCK_SUBBEGINABORT: case TBLOCK_ENDABORT: case TBLOCK_END: - case TBLOCK_SUBENDABORT_OK: - case TBLOCK_SUBENDABORT_ERROR: + case TBLOCK_SUBENDABORT_ALL: + case TBLOCK_SUBENDABORT: + case TBLOCK_SUBABORT_PENDING: + case TBLOCK_SUBENDABORT_RELEASE: case TBLOCK_SUBEND: elog(FATAL, "BeginTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); @@ -2185,34 +2256,32 @@ BeginTransactionBlock(void) /* * EndTransactionBlock * This executes a COMMIT command. + * + * Since COMMIT may actually do a ROLLBACK, the result indicates what + * happened: TRUE for COMMIT, FALSE for ROLLBACK. */ -void +bool EndTransactionBlock(void) { TransactionState s = CurrentTransactionState; + bool result = false; - switch (s->blockState) { + switch (s->blockState) + { /* - * here we are in a transaction block which should commit when we + * We are in a transaction block which should commit when we * get to the upcoming CommitTransactionCommand() so we set the * state to "END". CommitTransactionCommand() will recognize this - * and commit the transaction and return us to the default state + * and commit the transaction and return us to the default state. */ case TBLOCK_INPROGRESS: - s->blockState = TBLOCK_END; - break; - - /* - * here we are in a subtransaction block. Signal - * CommitTransactionCommand() to end it and return to the - * parent transaction. - */ case TBLOCK_SUBINPROGRESS: - s->blockState = TBLOCK_SUBEND; + s->blockState = TBLOCK_END; + result = true; break; /* - * here, we are in a transaction block which aborted. Since the + * We are in a transaction block which aborted. Since the * AbortTransaction() was already done, we need only * change to the special "END ABORT" state. The upcoming * CommitTransactionCommand() will recognise this and then put us @@ -2223,13 +2292,12 @@ EndTransactionBlock(void) break; /* - * here we are in an aborted subtransaction. Signal - * CommitTransactionCommand() to clean up and return to the - * parent transaction. Since the user said COMMIT, we must - * fail the parent transaction. + * Here we are inside an aborted subtransaction. Go to the "abort + * the whole tree" state so that CommitTransactionCommand() calls + * AbortOutOfAnyTransaction. */ case TBLOCK_SUBABORT: - s->blockState = TBLOCK_SUBENDABORT_ERROR; + s->blockState = TBLOCK_SUBENDABORT_ALL; break; case TBLOCK_STARTED: @@ -2252,14 +2320,17 @@ EndTransactionBlock(void) case TBLOCK_ENDABORT: case TBLOCK_END: case TBLOCK_SUBBEGIN: - case TBLOCK_SUBBEGINABORT: case TBLOCK_SUBEND: - case TBLOCK_SUBENDABORT_OK: - case TBLOCK_SUBENDABORT_ERROR: + case TBLOCK_SUBENDABORT_ALL: + case TBLOCK_SUBENDABORT: + case TBLOCK_SUBABORT_PENDING: + case TBLOCK_SUBENDABORT_RELEASE: elog(FATAL, "EndTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); break; } + + return result; } /* @@ -2271,27 +2342,32 @@ UserAbortTransactionBlock(void) { TransactionState s = CurrentTransactionState; - switch (s->blockState) { - /* - * here we are inside a failed transaction block and we got an abort - * command from the user. Abort processing is already done, we just - * need to move to the ENDABORT state so we will end up in the default - * state after the upcoming CommitTransactionCommand(). - */ + switch (s->blockState) + { + /* + * We are inside a failed transaction block and we got an + * abort command from the user. Abort processing is already + * done, we just need to move to the ENDABORT state so we will + * end up in the default state after the upcoming + * CommitTransactionCommand(). + */ case TBLOCK_ABORT: s->blockState = TBLOCK_ENDABORT; break; /* - * Ditto, for a subtransaction. Here it is okay to allow the - * parent transaction to continue. + * We are inside a failed subtransaction and we got an + * abort command from the user. Abort processing is already + * done, so go to the "abort all" state and + * CommitTransactionCommand will call AbortOutOfAnyTransaction + * to set things straight. */ case TBLOCK_SUBABORT: - s->blockState = TBLOCK_SUBENDABORT_OK; + s->blockState = TBLOCK_SUBENDABORT_ALL; break; /* - * here we are inside a transaction block and we got an abort + * We are inside a transaction block and we got an abort * command from the user, so we move to the ENDABORT state and * do abort processing so we will end up in the default state * after the upcoming CommitTransactionCommand(). @@ -2301,17 +2377,22 @@ UserAbortTransactionBlock(void) s->blockState = TBLOCK_ENDABORT; break; - /* Ditto, for a subtransaction. */ + /* + * We are inside a subtransaction. Abort the current + * subtransaction and go to the "abort all" state, so + * CommitTransactionCommand will call AbortOutOfAnyTransaction + * to set things straight. + */ case TBLOCK_SUBINPROGRESS: AbortSubTransaction(); - s->blockState = TBLOCK_SUBENDABORT_OK; + s->blockState = TBLOCK_SUBENDABORT_ALL; break; /* - * here, the user issued ABORT when not inside a - * transaction. Issue a WARNING and go to abort state. The - * upcoming call to CommitTransactionCommand() will then put us - * back into the default state. + * The user issued ABORT when not inside a transaction. Issue + * a WARNING and go to abort state. The upcoming call to + * CommitTransactionCommand() will then put us back into the + * default state. */ case TBLOCK_STARTED: ereport(WARNING, @@ -2321,21 +2402,265 @@ UserAbortTransactionBlock(void) s->blockState = TBLOCK_ENDABORT; break; - /* these cases are invalid. */ + /* These cases are invalid. */ case TBLOCK_DEFAULT: case TBLOCK_BEGIN: case TBLOCK_END: case TBLOCK_ENDABORT: case TBLOCK_SUBEND: - case TBLOCK_SUBENDABORT_OK: - case TBLOCK_SUBENDABORT_ERROR: + case TBLOCK_SUBENDABORT_ALL: + case TBLOCK_SUBENDABORT: + case TBLOCK_SUBABORT_PENDING: + case TBLOCK_SUBENDABORT_RELEASE: case TBLOCK_SUBBEGIN: - case TBLOCK_SUBBEGINABORT: elog(FATAL, "UserAbortTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); break; } +} + +/* + * DefineSavepoint + * This executes a SAVEPOINT command. + */ +void +DefineSavepoint(char *name) +{ + TransactionState s = CurrentTransactionState; + + switch (s->blockState) + { + case TBLOCK_INPROGRESS: + case TBLOCK_SUBINPROGRESS: + /* Normal subtransaction start */ + PushTransaction(); + s = CurrentTransactionState; /* changed by push */ + /* + * Note that we are allocating the savepoint name in the + * parent transaction's CurTransactionContext, since we + * don't yet have a transaction context for the new guy. + */ + s->name = MemoryContextStrdup(CurTransactionContext, name); + s->blockState = TBLOCK_SUBBEGIN; + break; + + /* These cases are invalid. Reject them altogether. */ + case TBLOCK_DEFAULT: + case TBLOCK_STARTED: + case TBLOCK_BEGIN: + case TBLOCK_SUBBEGIN: + case TBLOCK_ABORT: + case TBLOCK_SUBABORT: + case TBLOCK_ENDABORT: + case TBLOCK_END: + case TBLOCK_SUBENDABORT_ALL: + case TBLOCK_SUBENDABORT: + case TBLOCK_SUBABORT_PENDING: + case TBLOCK_SUBENDABORT_RELEASE: + case TBLOCK_SUBEND: + elog(FATAL, "BeginTransactionBlock: unexpected state %s", + BlockStateAsString(s->blockState)); + break; + } +} + +/* + * ReleaseSavepoint + * This executes a RELEASE command. + */ +void +ReleaseSavepoint(List *options) +{ + TransactionState s = CurrentTransactionState; + TransactionState target = s; + char *name = NULL; + ListCell *cell; + + /* + * Check valid block state transaction status. + */ + switch (s->blockState) + { + case TBLOCK_INPROGRESS: + case TBLOCK_ABORT: + ereport(ERROR, + (errcode(ERRCODE_S_E_INVALID_SPECIFICATION), + errmsg("no such savepoint"))); + break; + + /* + * We are in a non-aborted subtransaction. This is + * the only valid case. + */ + case TBLOCK_SUBINPROGRESS: + break; + + /* these cases are invalid. */ + case TBLOCK_DEFAULT: + case TBLOCK_STARTED: + case TBLOCK_BEGIN: + case TBLOCK_ENDABORT: + case TBLOCK_END: + case TBLOCK_SUBABORT: + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_ALL: + case TBLOCK_SUBENDABORT: + case TBLOCK_SUBABORT_PENDING: + case TBLOCK_SUBENDABORT_RELEASE: + elog(FATAL, "ReleaseSavepoint: unexpected state %s", + BlockStateAsString(s->blockState)); + break; + } + foreach (cell, options) + { + DefElem *elem = lfirst(cell); + + if (strcmp(elem->defname, "savepoint_name") == 0) + name = strVal(elem->arg); + } + + Assert(PointerIsValid(name)); + + while (target != NULL) + { + if (PointerIsValid(target->name) && strcmp(target->name, name) == 0) + break; + target = target->parent; + } + + if (!PointerIsValid(target)) + ereport(ERROR, + (errcode(ERRCODE_S_E_INVALID_SPECIFICATION), + errmsg("no such savepoint"))); + + CommitTransactionToLevel(target->nestingLevel); +} + +/* + * RollbackToSavepoint + * This executes a ROLLBACK TO <savepoint> command. + */ +void +RollbackToSavepoint(List *options) +{ + TransactionState s = CurrentTransactionState; + TransactionState target, + xact; + ListCell *cell; + char *name = NULL; + + switch (s->blockState) + { + /* + * We can't rollback to a savepoint if there is no saveopint + * defined. + */ + case TBLOCK_ABORT: + case TBLOCK_INPROGRESS: + ereport(ERROR, + (errcode(ERRCODE_S_E_INVALID_SPECIFICATION), + errmsg("no such savepoint"))); + break; + + /* + * There is at least one savepoint, so proceed. + */ + case TBLOCK_SUBABORT: + case TBLOCK_SUBINPROGRESS: + /* + * Have to do AbortSubTransaction, but first check + * if this is the right subtransaction + */ + break; + + /* these cases are invalid. */ + case TBLOCK_DEFAULT: + case TBLOCK_STARTED: + case TBLOCK_BEGIN: + case TBLOCK_END: + case TBLOCK_ENDABORT: + case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_ALL: + case TBLOCK_SUBENDABORT: + case TBLOCK_SUBABORT_PENDING: + case TBLOCK_SUBENDABORT_RELEASE: + case TBLOCK_SUBBEGIN: + elog(FATAL, "RollbackToSavepoint: unexpected state %s", + BlockStateAsString(s->blockState)); + break; + } + + foreach (cell, options) + { + DefElem *elem = lfirst(cell); + + if (strcmp(elem->defname, "savepoint_name") == 0) + name = strVal(elem->arg); + } + + Assert(PointerIsValid(name)); + + target = CurrentTransactionState; + + while (target != NULL) + { + if (PointerIsValid(target->name) && strcmp(target->name, name) == 0) + break; + target = target->parent; + + /* we don't cross savepoint level boundaries */ + if (target->savepointLevel != s->savepointLevel) + ereport(ERROR, + (errcode(ERRCODE_S_E_INVALID_SPECIFICATION), + errmsg("no such savepoint"))); + } + + if (!PointerIsValid(target)) + ereport(ERROR, + (errcode(ERRCODE_S_E_INVALID_SPECIFICATION), + errmsg("no such savepoint"))); + + /* + * Abort the current subtransaction, if needed. We can't Cleanup the + * savepoint yet, so signal CommitTransactionCommand to do it and + * close all savepoints up to the target level. + */ + if (s->blockState == TBLOCK_SUBINPROGRESS) + AbortSubTransaction(); + s->blockState = TBLOCK_SUBENDABORT; + + /* + * Mark "abort pending" all subtransactions up to the target + * subtransaction. (Except the current subtransaction!) + */ + xact = CurrentTransactionState; + + while (xact != target) + { + xact = xact->parent; + Assert(PointerIsValid(xact)); + Assert(xact->blockState == TBLOCK_SUBINPROGRESS); + xact->blockState = TBLOCK_SUBABORT_PENDING; + } +} + +/* + * RollbackAndReleaseSavepoint + * + * Executes a ROLLBACK TO command, immediately followed by a RELEASE + * of the same savepoint. + */ +void +RollbackAndReleaseSavepoint(List *options) +{ + TransactionState s; + + RollbackToSavepoint(options); + s = CurrentTransactionState; + Assert(s->blockState == TBLOCK_SUBENDABORT); + s->blockState = TBLOCK_SUBENDABORT_RELEASE; } /* @@ -2375,7 +2700,6 @@ AbortOutOfAnyTransaction(void) s->blockState = TBLOCK_DEFAULT; break; case TBLOCK_SUBBEGIN: - case TBLOCK_SUBBEGINABORT: /* * We didn't get as far as starting the subxact, so there's * nothing to abort. Just pop back to parent. @@ -2385,6 +2709,7 @@ AbortOutOfAnyTransaction(void) break; case TBLOCK_SUBINPROGRESS: case TBLOCK_SUBEND: + case TBLOCK_SUBABORT_PENDING: /* In a subtransaction, so clean it up and abort parent too */ AbortSubTransaction(); CleanupSubTransaction(); @@ -2392,8 +2717,9 @@ AbortOutOfAnyTransaction(void) s = CurrentTransactionState; /* changed by pop */ break; case TBLOCK_SUBABORT: - case TBLOCK_SUBENDABORT_OK: - case TBLOCK_SUBENDABORT_ERROR: + case TBLOCK_SUBENDABORT_ALL: + case TBLOCK_SUBENDABORT: + case TBLOCK_SUBENDABORT_RELEASE: /* As above, but AbortSubTransaction already done */ CleanupSubTransaction(); PopTransaction(); @@ -2407,6 +2733,28 @@ AbortOutOfAnyTransaction(void) } /* + * 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? */ bool @@ -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 19dbfc13d0f..f2fa0a43163 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 519bcce7184..1c7faa2c99d 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 cae1ed159b0..80ae597feb5 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 45305b4dea2..176767507c2 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 36fb347de3e..a353122fc26 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 a3e727472ab..6c32c6c3d78 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; |