Index: src/backend/access/transam/xact.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/access/transam/xact.c,v retrieving revision 1.171 diff -c -r1.171 xact.c *** src/backend/access/transam/xact.c 17 Jul 2004 03:28:23 -0000 1.171 --- src/backend/access/transam/xact.c 18 Jul 2004 05:13:22 -0000 *************** *** 186,206 **** */ typedef enum TBlockState { TBLOCK_DEFAULT, TBLOCK_STARTED, TBLOCK_BEGIN, TBLOCK_INPROGRESS, TBLOCK_END, TBLOCK_ABORT, TBLOCK_ENDABORT, TBLOCK_SUBBEGIN, - TBLOCK_SUBBEGINABORT, TBLOCK_SUBINPROGRESS, TBLOCK_SUBEND, TBLOCK_SUBABORT, ! TBLOCK_SUBENDABORT_OK, ! TBLOCK_SUBENDABORT_ERROR } TBlockState; /* --- 186,209 ---- */ 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_SUBINPROGRESS, TBLOCK_SUBEND, TBLOCK_SUBABORT, ! TBLOCK_SUBENDABORT_ALL, ! TBLOCK_SUBENDABORT, } TBlockState; /* *************** *** 209,214 **** --- 212,219 ---- 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,250 **** --- 250,257 ---- static void StartAbortedSubTransaction(void); static void PushTransaction(void); static void PopTransaction(void); + static void CommitTransactionToLevel(int level); + static void AbortTransactionToLevel(int level); static void AtSubAbort_Memory(void); static void AtSubCleanup_Memory(void); *************** *** 264,269 **** --- 271,278 ---- */ 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 *************** *** 317,322 **** --- 326,344 ---- static void *_RollbackData = NULL; + /* + * When we are rolling back to a savepoint, we need to store what is the + * target transaction nesting level. At CommitTransactionCommand, when + * a TBLOCK_SUBENDABORT is found, we close all nesting levels up to this + * one. + * + * 0 is not a meaningful level, since nesting levels start at 1. However + * 1 is also not a meaningful level, because we can't possibly try to + * rollback to the main transaction. + */ + static int AbortToLevel = 0; + + /* ---------------------------------------------------------------- * transaction state accessors * ---------------------------------------------------------------- *************** *** 508,513 **** --- 530,537 ---- if (SerializableSnapshot) SerializableSnapshot->curcid = s->commandId; + ShowTransactionState("CommandCounterIncrement"); + /* * make cache changes visible to me. */ *************** *** 1638,1648 **** 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_ENDABORT: elog(FATAL, "StartTransactionCommand: unexpected state %s", BlockStateAsString(s->blockState)); --- 1662,1671 ---- case TBLOCK_STARTED: case TBLOCK_BEGIN: case TBLOCK_SUBBEGIN: case TBLOCK_END: case TBLOCK_SUBEND: ! case TBLOCK_SUBENDABORT_ALL: ! case TBLOCK_SUBENDABORT: case TBLOCK_ENDABORT: elog(FATAL, "StartTransactionCommand: unexpected state %s", BlockStateAsString(s->blockState)); *************** *** 1710,1715 **** --- 1733,1743 ---- * default state. */ case TBLOCK_END: + if (s->nestingLevel > 1) + CommitTransactionToLevel(2); + s = CurrentTransactionState; + /* should be out of all subtransactions */ + Assert(s->nestingLevel == 1); CommitTransaction(); s->blockState = TBLOCK_DEFAULT; break; *************** *** 1734,1740 **** break; /* ! * We were just issued a BEGIN inside a transaction block. * Start a subtransaction. (BeginTransactionBlock already * did PushTransaction, so as to have someplace to put the * SUBBEGIN state.) --- 1762,1778 ---- break; /* ! * 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,1759 **** 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: --- 1783,1788 ---- *************** *** 1761,1767 **** break; /* ! * We were issued a COMMIT command, so we end the current * subtransaction and return to the parent transaction. */ case TBLOCK_SUBEND: --- 1790,1796 ---- break; /* ! * We were issued a RELEASE command, so we end the current * subtransaction and return to the parent transaction. */ case TBLOCK_SUBEND: *************** *** 1777,1802 **** break; /* ! * We are ending an aborted subtransaction via ROLLBACK, ! * so the parent can be allowed to live. */ ! case TBLOCK_SUBENDABORT_OK: ! CleanupSubTransaction(); ! PopTransaction(); ! s = CurrentTransactionState; /* changed by pop */ ! break; ! /* ! * We are ending an aborted subtransaction via COMMIT. ! * End the subtransaction, and abort the parent too. ! */ ! case TBLOCK_SUBENDABORT_ERROR: ! CleanupSubTransaction(); ! PopTransaction(); ! s = CurrentTransactionState; /* changed by pop */ ! Assert(s->blockState != TBLOCK_SUBENDABORT_ERROR); ! AbortCurrentTransaction(); break; } } --- 1806,1855 ---- break; /* ! * The current subtransaction is ending due to a ROLLBACK ! * TO command, so close all savepoint levels up to the target ! * level (recorded in AbortToLevel). Also open a new ! * savepoint with the same name as the target savepoint. */ ! case TBLOCK_SUBENDABORT: ! { ! char *name; ! /* First abort everything up to the target level */ ! AbortTransactionToLevel(AbortToLevel); ! AbortToLevel = 0; ! ! /* Copy the name string */ ! s = CurrentTransactionState; /* changed by AbortTransactionToLevel */ ! MemoryContextSwitchTo(TopTransactionContext); ! name = pstrdup(s->name); ! /* PopTransaction will return to a known good memory context */ ! ! /* ! * Clean up the last subtransaction, and return to the ! * parent. ! */ ! CleanupSubTransaction(); ! PopTransaction(); ! s = CurrentTransactionState; /* changed by pop */ ! ! /* ! * Define the savepoint again. DefineSavepoint copies the ! * name string, so we can free it afterwards. ! */ ! DefineSavepoint(name); ! s = CurrentTransactionState; /* changed by DefineSavepoint */ ! pfree(name); ! ! /* ! * now do what CommitTransactionCommand would do if a ! * subtransaction was just started ! */ ! StartSubTransaction(); ! s->blockState = TBLOCK_SUBINPROGRESS; ! } break; + } } *************** *** 1887,1893 **** * in aborted state. */ case TBLOCK_SUBBEGIN: - case TBLOCK_SUBBEGINABORT: StartAbortedSubTransaction(); s->blockState = TBLOCK_SUBABORT; break; --- 1940,1945 ---- *************** *** 1907,1930 **** PopTransaction(); s = CurrentTransactionState; /* changed by pop */ Assert(s->blockState != TBLOCK_SUBEND && ! s->blockState != TBLOCK_SUBENDABORT_OK && ! s->blockState != TBLOCK_SUBENDABORT_ERROR); AbortCurrentTransaction(); break; /* * Same as above, except the Abort() was already done. */ ! case TBLOCK_SUBENDABORT_OK: ! case TBLOCK_SUBENDABORT_ERROR: CleanupSubTransaction(); PopTransaction(); s = CurrentTransactionState; /* changed by pop */ Assert(s->blockState != TBLOCK_SUBEND && ! s->blockState != TBLOCK_SUBENDABORT_OK && ! s->blockState != TBLOCK_SUBENDABORT_ERROR); AbortCurrentTransaction(); break; } } --- 1959,1987 ---- PopTransaction(); s = CurrentTransactionState; /* changed by pop */ Assert(s->blockState != TBLOCK_SUBEND && ! s->blockState != TBLOCK_SUBENDABORT); AbortCurrentTransaction(); break; /* * Same as above, except the Abort() was already done. */ ! case TBLOCK_SUBENDABORT: CleanupSubTransaction(); PopTransaction(); s = CurrentTransactionState; /* changed by pop */ Assert(s->blockState != TBLOCK_SUBEND && ! 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,2141 **** { TransactionState s = CurrentTransactionState; ! switch (s->blockState) { /* * We are not inside a transaction block, so allow one * to begin. --- 2192,2199 ---- { TransactionState s = CurrentTransactionState; ! switch (s->blockState) ! { /* * We are not inside a transaction block, so allow one * to begin. *************** *** 2146,2180 **** /* * 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; 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_SUBEND: elog(FATAL, "BeginTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); --- 2204,2227 ---- /* * Already a transaction block in progress. */ case TBLOCK_INPROGRESS: case TBLOCK_SUBINPROGRESS: case TBLOCK_ABORT: case TBLOCK_SUBABORT: ! 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_ENDABORT: case TBLOCK_END: ! case TBLOCK_SUBENDABORT_ALL: ! case TBLOCK_SUBENDABORT: case TBLOCK_SUBEND: elog(FATAL, "BeginTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); *************** *** 2191,2218 **** { TransactionState s = CurrentTransactionState; ! switch (s->blockState) { /* ! * here 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 */ 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; break; /* ! * here, 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 --- 2238,2258 ---- { TransactionState s = CurrentTransactionState; ! switch (s->blockState) ! { /* ! * 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. */ case TBLOCK_INPROGRESS: case TBLOCK_SUBINPROGRESS: ! s->blockState = TBLOCK_END; break; /* ! * 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,2235 **** 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. */ case TBLOCK_SUBABORT: ! s->blockState = TBLOCK_SUBENDABORT_ERROR; break; case TBLOCK_STARTED: --- 2263,2274 ---- break; /* ! * 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_ALL; break; case TBLOCK_STARTED: *************** *** 2252,2261 **** case TBLOCK_ENDABORT: case TBLOCK_END: case TBLOCK_SUBBEGIN: - case TBLOCK_SUBBEGINABORT: case TBLOCK_SUBEND: ! case TBLOCK_SUBENDABORT_OK: ! case TBLOCK_SUBENDABORT_ERROR: elog(FATAL, "EndTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); break; --- 2291,2299 ---- case TBLOCK_ENDABORT: case TBLOCK_END: case TBLOCK_SUBBEGIN: case TBLOCK_SUBEND: ! case TBLOCK_SUBENDABORT_ALL: ! case TBLOCK_SUBENDABORT: elog(FATAL, "EndTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); break; *************** *** 2271,2297 **** { 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(). ! */ case TBLOCK_ABORT: s->blockState = TBLOCK_ENDABORT; break; /* ! * Ditto, for a subtransaction. Here it is okay to allow the ! * parent transaction to continue. */ case TBLOCK_SUBABORT: ! s->blockState = TBLOCK_SUBENDABORT_OK; break; /* ! * here 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(). --- 2309,2340 ---- { TransactionState s = CurrentTransactionState; ! 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; /* ! * 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_ALL; break; /* ! * 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,2317 **** s->blockState = TBLOCK_ENDABORT; break; ! /* Ditto, for a subtransaction. */ case TBLOCK_SUBINPROGRESS: AbortSubTransaction(); ! s->blockState = TBLOCK_SUBENDABORT_OK; 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. */ case TBLOCK_STARTED: ereport(WARNING, --- 2344,2365 ---- s->blockState = TBLOCK_ENDABORT; break; ! /* ! * We are inside a failed 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_ALL; break; /* ! * 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,2341 **** s->blockState = TBLOCK_ENDABORT; break; ! /* 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_SUBBEGIN: - case TBLOCK_SUBBEGINABORT: elog(FATAL, "UserAbortTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); break; } } /* --- 2369,2593 ---- s->blockState = TBLOCK_ENDABORT; break; ! /* These cases are invalid. */ case TBLOCK_DEFAULT: case TBLOCK_BEGIN: case TBLOCK_END: case TBLOCK_ENDABORT: case TBLOCK_SUBEND: ! case TBLOCK_SUBENDABORT_ALL: ! case TBLOCK_SUBENDABORT: case TBLOCK_SUBBEGIN: elog(FATAL, "UserAbortTransactionBlock: unexpected state %s", BlockStateAsString(s->blockState)); break; } + } + + /* + * DefineSavepoint + * This executes a SAVEPOINT command. + */ + void + DefineSavepoint(char *name) + { + TransactionState s = CurrentTransactionState; + MemoryContext old_cxt; + switch (s->blockState) + { + case TBLOCK_STARTED: + case TBLOCK_INPROGRESS: + case TBLOCK_SUBINPROGRESS: + /* Normal subtransaction start */ + PushTransaction(); + s = CurrentTransactionState; /* changed by push */ + s->blockState = TBLOCK_SUBBEGIN; + break; + + /* These cases are invalid. Reject them altogether. */ + case TBLOCK_DEFAULT: + 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_SUBEND: + elog(FATAL, "BeginTransactionBlock: unexpected state %s", + BlockStateAsString(s->blockState)); + break; + } + + old_cxt = MemoryContextSwitchTo(CurTransactionContext); + s->name = pstrdup(name); + MemoryContextSwitchTo(old_cxt); + } + + /* + * 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: + case TBLOCK_STARTED: + 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_BEGIN: + case TBLOCK_ENDABORT: + case TBLOCK_END: + case TBLOCK_SUBABORT: + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_ALL: + case TBLOCK_SUBENDABORT: + 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 = s; + 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: + case TBLOCK_STARTED: + 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_BEGIN: + case TBLOCK_END: + case TBLOCK_ENDABORT: + case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_ALL: + case TBLOCK_SUBENDABORT: + 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)); + + 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; + AbortToLevel = target->nestingLevel; } /* *************** *** 2375,2381 **** 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. --- 2627,2632 ---- *************** *** 2392,2399 **** s = CurrentTransactionState; /* changed by pop */ break; case TBLOCK_SUBABORT: ! case TBLOCK_SUBENDABORT_OK: ! case TBLOCK_SUBENDABORT_ERROR: /* As above, but AbortSubTransaction already done */ CleanupSubTransaction(); PopTransaction(); --- 2643,2650 ---- s = CurrentTransactionState; /* changed by pop */ break; case TBLOCK_SUBABORT: ! case TBLOCK_SUBENDABORT_ALL: ! case TBLOCK_SUBENDABORT: /* As above, but AbortSubTransaction already done */ CleanupSubTransaction(); PopTransaction(); *************** *** 2407,2412 **** --- 2658,2757 ---- } /* + * AbortTransactionToLevel + * + * Like AbortOutOfAnyTransaction, but stop aborting when we find + * the given level. + */ + static void + AbortTransactionToLevel(int level) + { + TransactionState s = CurrentTransactionState; + + /* + * The target can not be the topmost transaction -- we only + * accept subtransactions. + */ + AssertArg(level > 1); + AssertState(level <= s->nestingLevel); + + /* + * Get out of any subtransaction prior to the specified level + */ + while (s->nestingLevel > level) + { + switch (s->blockState) + { + case TBLOCK_DEFAULT: + case TBLOCK_STARTED: + case TBLOCK_BEGIN: + case TBLOCK_INPROGRESS: + case TBLOCK_END: + case TBLOCK_ABORT: + case TBLOCK_ENDABORT: + elog(FATAL, "AbortTransactionToLevel: invalid block state %s", + BlockStateAsString(s->blockState)); + break; + case TBLOCK_SUBBEGIN: + /* + * We didn't get as far as starting the subxact, so there's + * nothing to abort. Just pop back to parent. + */ + PopTransaction(); + s = CurrentTransactionState; /* changed by pop */ + break; + case TBLOCK_SUBINPROGRESS: + case TBLOCK_SUBEND: + /* In a subtransaction, so clean it up and abort parent too */ + AbortSubTransaction(); + CleanupSubTransaction(); + PopTransaction(); + s = CurrentTransactionState; /* changed by pop */ + break; + case TBLOCK_SUBABORT: + case TBLOCK_SUBENDABORT_ALL: + case TBLOCK_SUBENDABORT: + /* As above, but AbortSubTransaction already done */ + CleanupSubTransaction(); + PopTransaction(); + s = CurrentTransactionState; /* changed by pop */ + break; + } + } + + /* + * Now abort the target subtransaction, if needed. Note that it still + * needs Cleanup! + */ + if (s->blockState != TBLOCK_SUBABORT && + s->blockState != TBLOCK_SUBENDABORT && + s->blockState != TBLOCK_SUBENDABORT_ALL) + AbortSubTransaction(); + s->blockState = TBLOCK_SUBABORT; + } + + /* + * CommitTransactionToLevel + * + * Commit everything from the current transaction level + * up to the specified level. + */ + void + CommitTransactionToLevel(int level) + { + TransactionState s = CurrentTransactionState; + + Assert(s->state == TRANS_INPROGRESS); + + while (s->nestingLevel >= level) + { + CommitSubTransaction(); + PopTransaction(); + s = CurrentTransactionState; /* changed by pop */ + } + } + + /* * IsTransactionBlock --- are we within a transaction block? */ bool *************** *** 2461,2469 **** case TBLOCK_ABORT: case TBLOCK_ENDABORT: case TBLOCK_SUBABORT: ! case TBLOCK_SUBENDABORT_OK: ! case TBLOCK_SUBENDABORT_ERROR: ! case TBLOCK_SUBBEGINABORT: return 'E'; /* in failed transaction */ } --- 2806,2813 ---- case TBLOCK_ABORT: case TBLOCK_ENDABORT: case TBLOCK_SUBABORT: ! case TBLOCK_SUBENDABORT_ALL: ! case TBLOCK_SUBENDABORT: return 'E'; /* in failed transaction */ } *************** *** 2481,2487 **** { TransactionState s = CurrentTransactionState; ! switch (s->blockState) { case TBLOCK_DEFAULT: case TBLOCK_STARTED: case TBLOCK_BEGIN: --- 2825,2832 ---- { TransactionState s = CurrentTransactionState; ! switch (s->blockState) ! { case TBLOCK_DEFAULT: case TBLOCK_STARTED: case TBLOCK_BEGIN: *************** *** 2491,2502 **** 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: return true; } --- 2836,2846 ---- case TBLOCK_ENDABORT: return false; case TBLOCK_SUBBEGIN: case TBLOCK_SUBINPROGRESS: case TBLOCK_SUBABORT: case TBLOCK_SUBEND: ! case TBLOCK_SUBENDABORT_ALL: ! case TBLOCK_SUBENDABORT: return true; } *************** *** 2532,2537 **** --- 2876,2883 ---- SubTransSetParent(s->transactionIdData, s->parent->transactionIdData); + XactLockTableInsert(s->transactionIdData); + /* * Finish setup of other transaction state fields. */ *************** *** 2619,2624 **** --- 2965,2973 ---- ShowTransactionState("AbortSubTransaction"); + if (s->state != TRANS_INPROGRESS) + elog(WARNING, "AbortSubTransaction and not in in-progress state"); + HOLD_INTERRUPTS(); s->state = TRANS_ABORT; *************** *** 2762,2767 **** --- 3111,3119 ---- /* * 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) *************** *** 2798,2803 **** --- 3150,3158 ---- /* * 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) *************** *** 2854,2860 **** /* 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", BlockStateAsString(s->blockState), TransStateAsString(s->state), (unsigned int) s->transactionIdData, --- 3209,3216 ---- /* use ereport to suppress computation if msg will not be printed */ ereport(DEBUG2, ! (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,2876 **** static const char * BlockStateAsString(TBlockState blockState) { ! switch (blockState) { case TBLOCK_DEFAULT: return "DEFAULT"; case TBLOCK_STARTED: --- 3226,3233 ---- static const char * BlockStateAsString(TBlockState blockState) { ! switch (blockState) ! { case TBLOCK_DEFAULT: return "DEFAULT"; case TBLOCK_STARTED: *************** *** 2887,2904 **** 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"; } return "UNRECOGNIZED"; } --- 3244,3259 ---- return "ENDABORT"; case TBLOCK_SUBBEGIN: return "SUB BEGIN"; case TBLOCK_SUBINPROGRESS: return "SUB INPROGRS"; case TBLOCK_SUBEND: return "SUB END"; case TBLOCK_SUBABORT: return "SUB ABORT"; ! case TBLOCK_SUBENDABORT_ALL: ! return "SUB ENDAB ALL"; ! case TBLOCK_SUBENDABORT: ! return "SUB ENDAB"; } return "UNRECOGNIZED"; } *************** *** 2910,2916 **** static const char * TransStateAsString(TransState state) { ! switch (state) { case TRANS_DEFAULT: return "DEFAULT"; case TRANS_START: --- 3265,3272 ---- static const char * TransStateAsString(TransState state) { ! switch (state) ! { case TRANS_DEFAULT: return "DEFAULT"; case TRANS_START: Index: src/backend/executor/spi.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/executor/spi.c,v retrieving revision 1.120 diff -c -r1.120 spi.c *** src/backend/executor/spi.c 1 Jul 2004 21:17:13 -0000 1.120 --- src/backend/executor/spi.c 18 Jul 2004 05:21:33 -0000 *************** *** 1181,1186 **** --- 1181,1210 ---- res = SPI_ERROR_CURSOR; goto fail; } + else if (IsA(queryTree->utilityStmt, TransactionStmt)) + { + TransactionStmt *XactStmt = (TransactionStmt *)queryTree->utilityStmt; + + if (XactStmt->kind != TRANS_STMT_SAVEPOINT && + XactStmt->kind != TRANS_STMT_RELEASE && + XactStmt->kind != TRANS_STMT_RBACK_SAVEPT) + { + res = SPI_ERROR_TRANSACTION; + goto fail; + } + + /* + * Disallow closing the transaction that created the + * connection. + */ + if ((XactStmt->kind == TRANS_STMT_RELEASE || + XactStmt->kind == TRANS_STMT_RBACK_SAVEPT) && + _SPI_current->connectXid == GetCurrentTransactionId()) + { + res = SPI_ERROR_TRANSACTION; + goto fail; + } + } res = SPI_OK_UTILITY; if (plan == NULL) { *************** *** 1306,1311 **** --- 1330,1360 ---- dest = CreateDestReceiver(queryTree->canSetTag ? SPI : None, NULL); if (queryTree->commandType == CMD_UTILITY) { + if (IsA(queryTree->utilityStmt, TransactionStmt)) + { + TransactionStmt *XactStmt = (TransactionStmt *)queryTree->utilityStmt; + + if (XactStmt->kind != TRANS_STMT_SAVEPOINT && + XactStmt->kind != TRANS_STMT_RELEASE && + XactStmt->kind != TRANS_STMT_RBACK_SAVEPT) + { + res = SPI_ERROR_TRANSACTION; + goto fail; + } + + /* + * Disallow closing the transaction that created the + * connection. + */ + if ((XactStmt->kind == TRANS_STMT_RELEASE || + XactStmt->kind == TRANS_STMT_RBACK_SAVEPT) && + _SPI_current->connectXid == GetCurrentTransactionId()) + { + res = SPI_ERROR_TRANSACTION; + goto fail; + } + } + ProcessUtility(queryTree->utilityStmt, dest, NULL); res = SPI_OK_UTILITY; Index: src/backend/parser/gram.y =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/parser/gram.y,v retrieving revision 2.467 diff -c -r2.467 gram.y *** src/backend/parser/gram.y 12 Jul 2004 05:37:44 -0000 2.467 --- src/backend/parser/gram.y 14 Jul 2004 23:18:00 -0000 *************** *** 386,396 **** QUOTE ! READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RENAME REPEATABLE REPLACE RESET RESTART RESTRICT RETURNS REVOKE RIGHT ROLLBACK ROW ROWS RULE ! 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 --- 386,396 ---- QUOTE ! READ REAL RECHECK REFERENCES REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE RESET RESTART RESTRICT RETURNS REVOKE RIGHT ROLLBACK ROW ROWS RULE ! 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,3966 **** --- 3961,3990 ---- 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; + } + | ROLLBACK TO ColId + { + TransactionStmt *n = makeNode(TransactionStmt); + n->kind = TRANS_STMT_RBACK_SAVEPT; + n->options = list_make1(makeDefElem("savepoint_name", + (Node *)makeString($3))); + $$ = (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; + } ; opt_transaction: WORK {} *************** *** 7688,7693 **** --- 7712,7718 ---- | RECHECK | REINDEX | RELATIVE_P + | RELEASE | RENAME | REPEATABLE | REPLACE *************** *** 7699,7704 **** --- 7724,7730 ---- | ROLLBACK | ROWS | RULE + | SAVEPOINT | SCHEMA | SCROLL | SECOND_P Index: src/backend/parser/keywords.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/parser/keywords.c,v retrieving revision 1.151 diff -c -r1.151 keywords.c *** src/backend/parser/keywords.c 12 Jul 2004 05:37:44 -0000 1.151 --- src/backend/parser/keywords.c 14 Jul 2004 23:18:00 -0000 *************** *** 254,259 **** --- 254,260 ---- {"references", REFERENCES}, {"reindex", REINDEX}, {"relative", RELATIVE_P}, + {"release", RELEASE}, {"rename", RENAME}, {"repeatable", REPEATABLE}, {"replace", REPLACE}, *************** *** 267,272 **** --- 268,274 ---- {"row", ROW}, {"rows", ROWS}, {"rule", RULE}, + {"savepoint", SAVEPOINT}, {"schema", SCHEMA}, {"scroll", SCROLL}, {"second", SECOND_P}, Index: src/backend/storage/ipc/sinval.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/storage/ipc/sinval.c,v retrieving revision 1.66 diff -c -r1.66 sinval.c *** src/backend/storage/ipc/sinval.c 1 Jul 2004 03:13:05 -0000 1.66 --- src/backend/storage/ipc/sinval.c 14 Jul 2004 03:56:37 -0000 *************** *** 457,462 **** --- 457,468 ---- int nxids = 0; TransactionId *xids; + /* + * Don't bother checking a very old transaction. + */ + if (TransactionIdPrecedes(xid, RecentGlobalXmin)) + return false; + xids = (TransactionId *)palloc(sizeof(TransactionId) * segP->maxBackends); LWLockAcquire(SInvalLock, LW_SHARED); Index: src/backend/storage/lmgr/lmgr.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/storage/lmgr/lmgr.c,v retrieving revision 1.64 diff -c -r1.64 lmgr.c *** src/backend/storage/lmgr/lmgr.c 1 Jul 2004 00:50:59 -0000 1.64 --- src/backend/storage/lmgr/lmgr.c 8 Jul 2004 01:08:26 -0000 *************** *** 334,354 **** * XactLockTableWait * * Wait for the specified transaction to commit or abort. ! * We actually wait on the topmost transaction of the transaction tree. */ void XactLockTableWait(TransactionId xid) { LOCKTAG tag; TransactionId myxid = GetCurrentTransactionId(); - TransactionId waitXid = SubTransGetTopmostTransaction(xid); ! Assert(!SubTransXidsHaveCommonAncestor(waitXid, myxid)); MemSet(&tag, 0, sizeof(tag)); tag.relId = XactLockTableId; tag.dbId = InvalidOid; ! tag.objId.xid = waitXid; if (!LockAcquire(LockTableId, &tag, myxid, ShareLock, false)) --- 334,356 ---- * XactLockTableWait * * Wait for the specified transaction to commit or abort. ! * ! * 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(); ! Assert(!SubTransXidsHaveCommonAncestor(xid, myxid)); MemSet(&tag, 0, sizeof(tag)); tag.relId = XactLockTableId; tag.dbId = InvalidOid; ! tag.objId.xid = xid; if (!LockAcquire(LockTableId, &tag, myxid, ShareLock, false)) *************** *** 358,370 **** /* * 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. */ ! if (!TransactionIdDidCommit(waitXid) && !TransactionIdDidAbort(waitXid)) ! { ! TransactionIdAbort(waitXid); ! if (waitXid != xid) ! TransactionIdAbort(xid); ! } } --- 360,367 ---- /* * Transaction was committed/aborted/crashed - we have to update ! * pg_clog if transaction is still marked as running. */ ! if (!TransactionIdDidCommit(xid) && !TransactionIdDidAbort(xid)) ! TransactionIdAbort(xid); } Index: src/backend/tcop/postgres.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/tcop/postgres.c,v retrieving revision 1.424 diff -c -r1.424 postgres.c *** src/backend/tcop/postgres.c 17 Jul 2004 03:29:00 -0000 1.424 --- src/backend/tcop/postgres.c 17 Jul 2004 22:11:19 -0000 *************** *** 841,847 **** TransactionStmt *stmt = (TransactionStmt *) parsetree; if (stmt->kind == TRANS_STMT_COMMIT || ! stmt->kind == TRANS_STMT_BEGIN || stmt->kind == TRANS_STMT_ROLLBACK) allowit = true; } --- 841,847 ---- TransactionStmt *stmt = (TransactionStmt *) parsetree; if (stmt->kind == TRANS_STMT_COMMIT || ! stmt->kind == TRANS_STMT_RBACK_SAVEPT || stmt->kind == TRANS_STMT_ROLLBACK) allowit = true; } *************** *** 1162,1168 **** TransactionStmt *stmt = (TransactionStmt *) parsetree; if (stmt->kind == TRANS_STMT_COMMIT || ! stmt->kind == TRANS_STMT_BEGIN || stmt->kind == TRANS_STMT_ROLLBACK) allowit = true; } --- 1162,1168 ---- TransactionStmt *stmt = (TransactionStmt *) parsetree; if (stmt->kind == TRANS_STMT_COMMIT || ! stmt->kind == TRANS_STMT_RBACK_SAVEPT || stmt->kind == TRANS_STMT_ROLLBACK) allowit = true; } *************** *** 1625,1631 **** is_trans_stmt = true; if (stmt->kind == TRANS_STMT_COMMIT || ! stmt->kind == TRANS_STMT_BEGIN || stmt->kind == TRANS_STMT_ROLLBACK) is_trans_exit = true; } --- 1625,1631 ---- is_trans_stmt = true; if (stmt->kind == TRANS_STMT_COMMIT || ! stmt->kind == TRANS_STMT_RBACK_SAVEPT || stmt->kind == TRANS_STMT_ROLLBACK) is_trans_exit = true; } Index: src/backend/tcop/utility.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/tcop/utility.c,v retrieving revision 1.220 diff -c -r1.220 utility.c *** src/backend/tcop/utility.c 25 Jun 2004 21:55:57 -0000 1.220 --- src/backend/tcop/utility.c 14 Jul 2004 18:37:32 -0000 *************** *** 360,365 **** --- 360,395 ---- 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_RBACK_SAVEPT: + RequireTransactionChain((void *)stmt, "ROLLBACK TO"); + RollbackToSavepoint(stmt->options); + break; + + case TRANS_STMT_RELEASE: + RequireTransactionChain((void *)stmt, "RELEASE"); + ReleaseSavepoint(stmt->options); + break; } } break; *************** *** 1117,1122 **** --- 1147,1165 ---- tag = "ROLLBACK"; break; + case TRANS_STMT_SAVEPOINT: + tag = "SAVEPOINT"; + break; + + case TRANS_STMT_RELEASE: + tag = "RELEASE"; + break; + + /* Is this the right tag? */ + case TRANS_STMT_RBACK_SAVEPT: + tag = "ROLLBACK TO"; + break; + default: tag = "???"; break; Index: src/include/access/xact.h =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/include/access/xact.h,v retrieving revision 1.65 diff -c -r1.65 xact.h *** src/include/access/xact.h 17 Jul 2004 03:30:38 -0000 1.65 --- src/include/access/xact.h 17 Jul 2004 22:16:36 -0000 *************** *** 101,111 **** extern void AbortCurrentTransaction(void); extern void BeginTransactionBlock(void); extern void EndTransactionBlock(void); 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); --- 101,114 ---- extern void AbortCurrentTransaction(void); extern void BeginTransactionBlock(void); extern void EndTransactionBlock(void); + extern void UserAbortTransactionBlock(void); + extern void ReleaseSavepoint(List *options); + extern void DefineSavepoint(char *name); + extern void RollbackToSavepoint(List *options); extern bool IsSubTransaction(void); extern bool IsTransactionBlock(void); extern bool IsTransactionOrTransactionBlock(void); extern char TransactionBlockStatusCode(void); extern void AbortOutOfAnyTransaction(void); extern void PreventTransactionChain(void *stmtNode, const char *stmtType); extern void RequireTransactionChain(void *stmtNode, const char *stmtType); Index: src/include/nodes/parsenodes.h =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/include/nodes/parsenodes.h,v retrieving revision 1.262 diff -c -r1.262 parsenodes.h *** src/include/nodes/parsenodes.h 12 Jul 2004 05:38:11 -0000 1.262 --- src/include/nodes/parsenodes.h 14 Jul 2004 23:18:07 -0000 *************** *** 1514,1527 **** TRANS_STMT_BEGIN, TRANS_STMT_START, /* semantically identical to BEGIN */ TRANS_STMT_COMMIT, ! TRANS_STMT_ROLLBACK } TransactionStmtKind; typedef struct TransactionStmt { NodeTag type; TransactionStmtKind kind; /* see above */ ! List *options; /* for BEGIN/START only */ } TransactionStmt; /* ---------------------- --- 1514,1530 ---- TRANS_STMT_BEGIN, TRANS_STMT_START, /* semantically identical to BEGIN */ TRANS_STMT_COMMIT, ! TRANS_STMT_ROLLBACK, ! TRANS_STMT_SAVEPOINT, ! TRANS_STMT_RELEASE, ! TRANS_STMT_RBACK_SAVEPT } TransactionStmtKind; typedef struct TransactionStmt { NodeTag type; TransactionStmtKind kind; /* see above */ ! List *options; /* for BEGIN/START and savepoints commands */ } TransactionStmt; /* ---------------------- Index: src/include/utils/errcodes.h =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/include/utils/errcodes.h,v retrieving revision 1.12 diff -c -r1.12 errcodes.h *** src/include/utils/errcodes.h 1 Jun 2004 21:49:22 -0000 1.12 --- src/include/utils/errcodes.h 11 Jul 2004 03:23:23 -0000 *************** *** 213,218 **** --- 213,222 ---- #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 FIXME -- is this name correct? */ + #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') Index: src/test/regress/expected/transactions.out =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/test/regress/expected/transactions.out,v retrieving revision 1.6 diff -c -r1.6 transactions.out *** src/test/regress/expected/transactions.out 17 Jul 2004 03:32:05 -0000 1.6 --- src/test/regress/expected/transactions.out 18 Jul 2004 03:46:33 -0000 *************** *** 74,86 **** CREATE TABLE foobar (a int); BEGIN; CREATE TABLE foo (a int); ! BEGIN; DROP TABLE foo; CREATE TABLE bar (a int); ! ROLLBACK; ! BEGIN; CREATE TABLE baz (a int); ! COMMIT; drop TABLE foobar; CREATE TABLE barbaz (a int); COMMIT; --- 74,87 ---- CREATE TABLE foobar (a int); BEGIN; CREATE TABLE foo (a int); ! SAVEPOINT one; DROP TABLE foo; CREATE TABLE bar (a int); ! ROLLBACK TO one; ! RELEASE one; ! SAVEPOINT two; CREATE TABLE baz (a int); ! RELEASE two; drop TABLE foobar; CREATE TABLE barbaz (a int); COMMIT; *************** *** 105,122 **** -- inserts BEGIN; INSERT INTO foo VALUES (1); ! BEGIN; INSERT into bar VALUES (1); ERROR: relation "bar" does not exist ! ROLLBACK; ! BEGIN; INSERT into barbaz VALUES (1); ! COMMIT; ! BEGIN; ! BEGIN; INSERT INTO foo VALUES (2); ! COMMIT; ! ROLLBACK; INSERT INTO foo VALUES (3); COMMIT; SELECT * FROM foo; -- should have 1 and 3 --- 106,125 ---- -- inserts BEGIN; INSERT INTO foo VALUES (1); ! SAVEPOINT one; INSERT into bar VALUES (1); ERROR: relation "bar" does not exist ! ROLLBACK TO one; ! RELEASE one; ! SAVEPOINT two; INSERT into barbaz VALUES (1); ! RELEASE two; ! SAVEPOINT three; ! SAVEPOINT four; INSERT INTO foo VALUES (2); ! RELEASE four; ! ROLLBACK TO three; ! RELEASE three; INSERT INTO foo VALUES (3); COMMIT; SELECT * FROM foo; -- should have 1 and 3 *************** *** 132,184 **** 1 (1 row) ! -- check that starting a subxact in a failed xact or subxact works 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 COMMIT; ! SELECT 1; -- this should work ?column? ---------- ! 1 (1 row) BEGIN; ! BEGIN; ! SELECT 1; -- this should work ?column? ---------- ! 1 (1 row) ! SELECT 0/0; -- fail the subxact ERROR: division by zero ! 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; ! 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 ?column? ---------- 1 --- 135,301 ---- 1 (1 row) ! -- test whole-tree commit BEGIN; ! 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; ! 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? ---------- ! 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; ! 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? ---------- ! f (1 row) ! 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) ! ! -- 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 ! SAVEPOINT two; -- ignored till the end of ... ERROR: current transaction is aborted, commands ignored until end of transaction block ! RELEASE one; -- ignored till the end of ... ERROR: current transaction is aborted, commands ignored until end of transaction block ! ROLLBACK TO one; ! SELECT 1; ?column? ---------- 1 *************** *** 194,200 **** -- check non-transactional behavior of cursors BEGIN; DECLARE c CURSOR FOR SELECT unique2 FROM tenk1; ! BEGIN; FETCH 10 FROM c; unique2 --------- --- 311,317 ---- -- check non-transactional behavior of cursors BEGIN; DECLARE c CURSOR FOR SELECT unique2 FROM tenk1; ! SAVEPOINT one; FETCH 10 FROM c; unique2 --------- *************** *** 210,217 **** 9 (10 rows) ! ROLLBACK; ! BEGIN; FETCH 10 FROM c; unique2 --------- --- 327,333 ---- 9 (10 rows) ! ROLLBACK TO one; FETCH 10 FROM c; unique2 --------- *************** *** 227,233 **** 19 (10 rows) ! COMMIT; FETCH 10 FROM c; unique2 --------- --- 343,349 ---- 19 (10 rows) ! RELEASE one; FETCH 10 FROM c; unique2 --------- *************** *** 245,259 **** CLOSE c; DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1; ! BEGIN; FETCH 10 FROM c; ERROR: division by zero ! ROLLBACK; -- c is now dead to the world ... - BEGIN; FETCH 10 FROM c; ERROR: portal "c" cannot be run ! ROLLBACK; FETCH 10 FROM c; ERROR: portal "c" cannot be run COMMIT; --- 361,374 ---- CLOSE c; DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1; ! SAVEPOINT two; FETCH 10 FROM c; ERROR: division by zero ! ROLLBACK TO two; -- c is now dead to the world ... FETCH 10 FROM c; ERROR: portal "c" cannot be run ! ROLLBACK TO two; FETCH 10 FROM c; ERROR: portal "c" cannot be run COMMIT; Index: src/test/regress/output/misc.source =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/test/regress/output/misc.source,v retrieving revision 1.42 diff -c -r1.42 misc.source *** src/test/regress/output/misc.source 10 May 2004 22:44:49 -0000 1.42 --- src/test/regress/output/misc.source 16 Jul 2004 05:08:18 -0000 *************** *** 644,649 **** --- 644,650 ---- real_city reltime_tbl road + savepoints shighway slow_emp4000 street *************** *** 661,667 **** toyemp varchar_tbl xacttest ! (97 rows) SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer'))); name --- 662,668 ---- toyemp varchar_tbl xacttest ! (98 rows) SELECT name(equipment(hobby_construct(text 'skywalking', text 'mer'))); name Index: src/test/regress/sql/transactions.sql =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/test/regress/sql/transactions.sql,v retrieving revision 1.6 diff -c -r1.6 transactions.sql *** src/test/regress/sql/transactions.sql 17 Jul 2004 03:32:14 -0000 1.6 --- src/test/regress/sql/transactions.sql 18 Jul 2004 03:44:52 -0000 *************** *** 61,73 **** CREATE TABLE foobar (a int); BEGIN; CREATE TABLE foo (a int); ! BEGIN; DROP TABLE foo; CREATE TABLE bar (a int); ! ROLLBACK; ! BEGIN; CREATE TABLE baz (a int); ! COMMIT; drop TABLE foobar; CREATE TABLE barbaz (a int); COMMIT; --- 61,74 ---- CREATE TABLE foobar (a int); BEGIN; CREATE TABLE foo (a int); ! SAVEPOINT one; DROP TABLE foo; CREATE TABLE bar (a int); ! ROLLBACK TO one; ! RELEASE one; ! SAVEPOINT two; CREATE TABLE baz (a int); ! RELEASE two; drop TABLE foobar; CREATE TABLE barbaz (a int); COMMIT; *************** *** 80,155 **** -- inserts BEGIN; INSERT INTO foo VALUES (1); ! BEGIN; INSERT into bar VALUES (1); ! ROLLBACK; ! BEGIN; INSERT into barbaz VALUES (1); ! COMMIT; ! BEGIN; ! BEGIN; INSERT INTO foo VALUES (2); ! COMMIT; ! ROLLBACK; 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 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 COMMIT; ! SELECT 1; -- this should work 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 COMMIT; SELECT 1; -- this should work -- check non-transactional behavior of cursors BEGIN; DECLARE c CURSOR FOR SELECT unique2 FROM tenk1; ! BEGIN; FETCH 10 FROM c; ! ROLLBACK; ! BEGIN; FETCH 10 FROM c; ! COMMIT; FETCH 10 FROM c; CLOSE c; DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1; ! BEGIN; FETCH 10 FROM c; ! ROLLBACK; -- c is now dead to the world ... - BEGIN; FETCH 10 FROM c; ! ROLLBACK; FETCH 10 FROM c; COMMIT; - DROP TABLE foo; DROP TABLE baz; DROP TABLE barbaz; --- 81,233 ---- -- inserts BEGIN; INSERT INTO foo VALUES (1); ! SAVEPOINT one; INSERT into bar VALUES (1); ! ROLLBACK TO one; ! RELEASE one; ! SAVEPOINT two; INSERT into barbaz VALUES (1); ! RELEASE two; ! SAVEPOINT three; ! SAVEPOINT four; INSERT INTO foo VALUES (2); ! 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 ! -- test whole-tree commit BEGIN; ! 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; ! COMMIT; -- should not be in a transaction block ! SELECT * FROM savepoints; ! ! -- test whole-tree rollback ! BEGIN; ! SAVEPOINT one; ! DELETE FROM savepoints WHERE a=1; ! RELEASE one; ! SAVEPOINT two; ! DELETE FROM savepoints WHERE a=1; ! SAVEPOINT three; ! DELETE FROM savepoints WHERE a=2; ! ROLLBACK; ! COMMIT; -- should not be in a transaction block ! ! SELECT * FROM savepoints; ! ! -- test whole-tree commit on an aborted subtransaction ! BEGIN; ! INSERT INTO savepoints VALUES (4); ! SAVEPOINT one; ! INSERT INTO savepoints VALUES (5); ! SELECT foo; ! COMMIT; ! SELECT * FROM savepoints; ! ! BEGIN; ! INSERT INTO savepoints VALUES (6); ! SAVEPOINT one; ! INSERT INTO savepoints VALUES (7); ! RELEASE one; ! INSERT INTO savepoints VALUES (8); ! COMMIT; ! -- rows 6 and 8 should have been created by the same xact ! SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=8; ! -- rows 6 and 7 should have been created by different xacts ! SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=6 AND b.a=7; ! ! BEGIN; ! INSERT INTO savepoints VALUES (9); ! SAVEPOINT one; ! INSERT INTO savepoints VALUES (10); ! ROLLBACK TO one; ! INSERT INTO savepoints VALUES (11); ! COMMIT; ! SELECT a FROM savepoints WHERE a in (9, 10, 11); ! -- rows 9 and 11 should have been created by different xacts ! SELECT a.xmin = b.xmin FROM savepoints a, savepoints b WHERE a.a=9 AND b.a=11; ! ! BEGIN; ! INSERT INTO savepoints VALUES (12); ! SAVEPOINT one; ! INSERT INTO savepoints VALUES (13); ! SAVEPOINT two; ! INSERT INTO savepoints VALUES (14); ! ROLLBACK TO one; ! INSERT INTO savepoints VALUES (15); ! SAVEPOINT two; ! INSERT INTO savepoints VALUES (16); ! SAVEPOINT three; ! INSERT INTO savepoints VALUES (17); ! COMMIT; ! SELECT a FROM savepoints WHERE a BETWEEN 12 AND 17; BEGIN; ! INSERT INTO savepoints VALUES (18); ! SAVEPOINT one; ! INSERT INTO savepoints VALUES (19); ! SAVEPOINT two; ! INSERT INTO savepoints VALUES (20); ! ROLLBACK TO one; ! INSERT INTO savepoints VALUES (21); ! ROLLBACK TO one; ! INSERT INTO savepoints VALUES (22); ! COMMIT; ! SELECT a FROM savepoints WHERE a BETWEEN 18 AND 22; ! ! -- 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; ! SAVEPOINT one; FETCH 10 FROM c; ! ROLLBACK TO one; FETCH 10 FROM c; ! RELEASE one; FETCH 10 FROM c; CLOSE c; DECLARE c CURSOR FOR SELECT unique2/0 FROM tenk1; ! SAVEPOINT two; FETCH 10 FROM c; ! ROLLBACK TO two; -- c is now dead to the world ... FETCH 10 FROM c; ! ROLLBACK TO two; FETCH 10 FROM c; COMMIT; DROP TABLE foo; DROP TABLE baz; DROP TABLE barbaz;