Index: src/backend/access/transam/xact.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/access/transam/xact.c,v retrieving revision 1.170 diff -c -r1.170 xact.c *** src/backend/access/transam/xact.c 1 Jul 2004 20:11:02 -0000 1.170 --- src/backend/access/transam/xact.c 14 Jul 2004 04:07:10 -0000 *************** *** 173,215 **** #include "pgstat.h" ! static void AbortTransaction(void); ! static void AtAbort_Cache(void); ! static void AtAbort_Locks(void); ! static void AtAbort_Memory(void); ! static void AtCleanup_Memory(void); ! static void AtCommit_Cache(void); ! static void AtCommit_LocalCache(void); ! static void AtCommit_Locks(void); ! static void AtCommit_Memory(void); ! static void AtStart_Cache(void); ! static void AtStart_Locks(void); ! static void AtStart_Memory(void); ! static void CallEOXactCallbacks(bool isCommit); ! static void CleanupTransaction(void); ! static void CommitTransaction(void); ! static void RecordTransactionAbort(void); ! static void StartTransaction(void); ! ! static void RecordSubTransactionCommit(void); ! static void StartSubTransaction(void); ! static void CommitSubTransaction(void); ! static void AbortSubTransaction(void); ! static void CleanupSubTransaction(void); ! static void StartAbortedSubTransaction(void); ! static void PushTransaction(void); ! static void PopTransaction(void); ! ! static void AtSubAbort_Locks(void); ! static void AtSubAbort_Memory(void); ! static void AtSubCleanup_Memory(void); ! static void AtSubCommit_Memory(void); ! static void AtSubStart_Memory(void); ! static void ShowTransactionState(const char *str); ! static void ShowTransactionStateRec(TransactionState state); ! static const char *BlockStateAsString(TBlockState blockState); ! static const char *TransStateAsString(TransState state); /* * CurrentTransactionState always points to the current transaction state --- 173,234 ---- #include "pgstat.h" ! /* ! * transaction states - transaction state from server perspective ! */ ! typedef enum TransState ! { ! TRANS_DEFAULT, ! TRANS_START, ! TRANS_INPROGRESS, ! TRANS_COMMIT, ! TRANS_ABORT ! } TransState; ! ! /* ! * transaction block states - transaction state of client queries ! */ ! 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; ! ! /* ! * transaction state structure ! */ ! 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 */ ! int nestingLevel; /* nest depth */ ! MemoryContext curTransactionContext; /* my xact-lifetime context */ ! List *childXids; /* subcommitted child XIDs */ ! AclId currentUser; /* subxact start current_user */ ! struct TransactionStateData *parent; /* back link to parent */ ! } TransactionStateData; ! typedef TransactionStateData *TransactionState; /* * CurrentTransactionState always points to the current transaction state *************** *** 218,223 **** --- 237,244 ---- */ 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 *************** *** 270,275 **** --- 291,347 ---- 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 means unset. This is OK since transaction nesting levels start at 1. + */ + static int AbortToLevel = 0; + + /* private functions declarations */ + static void AbortTransaction(void); + static void AtAbort_Cache(void); + static void AtAbort_Locks(void); + static void AtAbort_Memory(void); + static void AtCleanup_Memory(void); + static void AtCommit_Cache(void); + static void AtCommit_LocalCache(void); + static void AtCommit_Locks(void); + static void AtCommit_Memory(void); + static void AtStart_Cache(void); + static void AtStart_Locks(void); + static void AtStart_Memory(void); + static void CallEOXactCallbacks(bool isCommit); + static void CleanupTransaction(void); + static void CommitTransaction(void); + static void RecordTransactionAbort(void); + static void StartTransaction(void); + static void CommitTransactionToLevel(int level); + static void AbortTransactionToLevel(int level); + + static void RecordSubTransactionCommit(void); + static void StartSubTransaction(void); + static void CommitSubTransaction(void); + static void AbortSubTransaction(void); + static void CleanupSubTransaction(void); + static void StartAbortedSubTransaction(void); + static void PushTransaction(void); + static void PopTransaction(void); + + static void AtSubAbort_Locks(void); + static void AtSubAbort_Memory(void); + static void AtSubCleanup_Memory(void); + static void AtSubCommit_Memory(void); + static void AtSubStart_Memory(void); + + static void ShowTransactionState(const char *str); + static void ShowTransactionStateRec(TransactionState state); + static const char *BlockStateAsString(TBlockState blockState); + static const char *TransStateAsString(TransState state); + /* ---------------------------------------------------------------- * transaction state accessors * ---------------------------------------------------------------- *************** *** 461,466 **** --- 533,540 ---- if (SerializableSnapshot) SerializableSnapshot->curcid = s->commandId; + ShowTransactionState("CommandCounterIncrement"); + /* * make cache changes visible to me. AtCommit_LocalCache() instead of * AtCommit_Cache() is called here. *************** *** 1602,1612 **** 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)); --- 1676,1685 ---- 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)); *************** *** 1674,1679 **** --- 1747,1757 ---- * 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; *************** *** 1698,1704 **** 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.) --- 1776,1791 ---- break; /* ! * Ditto, but in a subtransaction. AbortOutOfAnyTransaction ! * will do the dirty work. ! */ ! case TBLOCK_SUBENDABORT_ALL: ! 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.) *************** *** 1709,1723 **** 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: --- 1796,1801 ---- *************** *** 1725,1731 **** break; /* ! * We were issued a COMMIT command, so we end the current * subtransaction and return to the parent transaction. */ case TBLOCK_SUBEND: --- 1803,1809 ---- break; /* ! * We were issued a RELEASE command, so we end the current * subtransaction and return to the parent transaction. */ case TBLOCK_SUBEND: *************** *** 1741,1766 **** 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; } } --- 1819,1868 ---- 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; + } } *************** *** 1851,1857 **** * in aborted state. */ case TBLOCK_SUBBEGIN: - case TBLOCK_SUBBEGINABORT: StartAbortedSubTransaction(); s->blockState = TBLOCK_SUBABORT; break; --- 1953,1958 ---- *************** *** 1871,1894 **** 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; } } --- 1972,2000 ---- 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; } } *************** *** 2099,2105 **** { TransactionState s = CurrentTransactionState; ! switch (s->blockState) { /* * We are not inside a transaction block, so allow one * to begin. --- 2205,2212 ---- { TransactionState s = CurrentTransactionState; ! switch (s->blockState) ! { /* * We are not inside a transaction block, so allow one * to begin. *************** *** 2110,2144 **** /* * 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)); --- 2217,2240 ---- /* * 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)); *************** *** 2155,2182 **** { 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 --- 2251,2271 ---- { 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 *************** *** 2187,2199 **** 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: --- 2276,2287 ---- 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: *************** *** 2216,2225 **** 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; --- 2304,2312 ---- 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; *************** *** 2235,2261 **** { 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(). --- 2322,2353 ---- { 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(). *************** *** 2265,2281 **** 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, --- 2357,2378 ---- 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, *************** *** 2285,2305 **** 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; } } /* --- 2382,2606 ---- 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; } /* *************** *** 2339,2345 **** 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. --- 2640,2645 ---- *************** *** 2356,2363 **** 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(); --- 2656,2663 ---- s = CurrentTransactionState; /* changed by pop */ break; case TBLOCK_SUBABORT: ! case TBLOCK_SUBENDABORT_ALL: ! case TBLOCK_SUBENDABORT: /* As above, but AbortSubTransaction already done */ CleanupSubTransaction(); PopTransaction(); *************** *** 2371,2376 **** --- 2671,2770 ---- } /* + * 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 *************** *** 2425,2433 **** 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 */ } --- 2819,2826 ---- case TBLOCK_ABORT: case TBLOCK_ENDABORT: case TBLOCK_SUBABORT: ! case TBLOCK_SUBENDABORT_ALL: ! case TBLOCK_SUBENDABORT: return 'E'; /* in failed transaction */ } *************** *** 2445,2451 **** { TransactionState s = CurrentTransactionState; ! switch (s->blockState) { case TBLOCK_DEFAULT: case TBLOCK_STARTED: case TBLOCK_BEGIN: --- 2838,2845 ---- { TransactionState s = CurrentTransactionState; ! switch (s->blockState) ! { case TBLOCK_DEFAULT: case TBLOCK_STARTED: case TBLOCK_BEGIN: *************** *** 2455,2466 **** 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; } --- 2849,2859 ---- 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; } *************** *** 2490,2495 **** --- 2883,2890 ---- SubTransSetParent(s->transactionIdData, s->parent->transactionIdData); + XactLockTableInsert(s->transactionIdData); + /* * Finish setup of other transaction state fields. */ *************** *** 2565,2570 **** --- 2960,2968 ---- ShowTransactionState("AbortSubTransaction"); + if (s->state != TRANS_INPROGRESS) + elog(WARNING, "AbortSubTransaction and not in in-progress state"); + HOLD_INTERRUPTS(); s->state = TRANS_ABORT; *************** *** 2696,2701 **** --- 3094,3102 ---- /* * 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) *************** *** 2731,2736 **** --- 3132,3140 ---- /* * 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) *************** *** 2783,2789 **** /* 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, --- 3187,3194 ---- /* 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, *************** *** 2799,2805 **** static const char * BlockStateAsString(TBlockState blockState) { ! switch (blockState) { case TBLOCK_DEFAULT: return "DEFAULT"; case TBLOCK_STARTED: --- 3204,3211 ---- static const char * BlockStateAsString(TBlockState blockState) { ! switch (blockState) ! { case TBLOCK_DEFAULT: return "DEFAULT"; case TBLOCK_STARTED: *************** *** 2816,2833 **** 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"; } --- 3222,3237 ---- 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"; } *************** *** 2839,2845 **** static const char * TransStateAsString(TransState state) { ! switch (state) { case TRANS_DEFAULT: return "DEFAULT"; case TRANS_START: --- 3243,3250 ---- 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 11 Jul 2004 00:03:45 -0000 *************** *** 103,108 **** --- 103,109 ---- _SPI_current->processed = 0; _SPI_current->tuptable = NULL; _SPI_current->connectXid = GetCurrentTransactionId(); + _SPI_current->nestLevel = GetCurrentTransactionNestLevel(); /* * Create memory contexts for this procedure *************** *** 1181,1186 **** --- 1182,1211 ---- 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->nestLevel >= GetCurrentTransactionNestLevel()) + { + res = SPI_ERROR_TRANSACTION; + goto fail; + } + } res = SPI_OK_UTILITY; if (plan == NULL) { *************** *** 1306,1311 **** --- 1331,1361 ---- 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->nestLevel >= GetCurrentTransactionNestLevel()) + { + 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.465 diff -c -r2.465 gram.y *** src/backend/parser/gram.y 28 Jun 2004 01:19:11 -0000 2.465 --- src/backend/parser/gram.y 14 Jul 2004 03:53:20 -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 *************** *** 3954,3959 **** --- 3954,3983 ---- 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 {} *************** *** 7682,7687 **** --- 7706,7712 ---- | RECHECK | REINDEX | RELATIVE_P + | RELEASE | RENAME | REPEATABLE | REPLACE *************** *** 7693,7698 **** --- 7718,7724 ---- | 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.150 diff -c -r1.150 keywords.c *** src/backend/parser/keywords.c 18 Jun 2004 06:13:31 -0000 1.150 --- src/backend/parser/keywords.c 11 Jul 2004 00:24:31 -0000 *************** *** 256,261 **** --- 256,262 ---- {"references", REFERENCES}, {"reindex", REINDEX}, {"relative", RELATIVE_P}, + {"release", RELEASE}, {"rename", RENAME}, {"repeatable", REPEATABLE}, {"replace", REPLACE}, *************** *** 269,274 **** --- 270,276 ---- {"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.422 diff -c -r1.422 postgres.c *** src/backend/tcop/postgres.c 1 Jul 2004 00:51:11 -0000 1.422 --- src/backend/tcop/postgres.c 12 Jul 2004 02:49:22 -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/backend/utils/mmgr/portalmem.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/utils/mmgr/portalmem.c,v retrieving revision 1.66 diff -c -r1.66 portalmem.c *** src/backend/utils/mmgr/portalmem.c 1 Jul 2004 00:51:29 -0000 1.66 --- src/backend/utils/mmgr/portalmem.c 4 Jul 2004 05:48:12 -0000 *************** *** 302,308 **** /* Not sure if this case can validly happen or not... */ if (portal->portalActive) ! elog(ERROR, "cannot drop active portal"); /* * Remove portal from hash table. Because we do this first, we will --- 302,308 ---- /* Not sure if this case can validly happen or not... */ if (portal->portalActive) ! elog(FATAL, "cannot drop active portal"); /* * Remove portal from hash table. Because we do this first, we will Index: src/include/access/xact.h =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/include/access/xact.h,v retrieving revision 1.64 diff -c -r1.64 xact.h *** src/include/access/xact.h 1 Jul 2004 00:51:38 -0000 1.64 --- src/include/access/xact.h 14 Jul 2004 03:58:28 -0000 *************** *** 41,103 **** extern bool XactReadOnly; /* - * transaction states - transaction state from server perspective - */ - typedef enum TransState - { - TRANS_DEFAULT, - TRANS_START, - TRANS_INPROGRESS, - TRANS_COMMIT, - TRANS_ABORT - } TransState; - - /* - * transaction block states - transaction state of client queries - */ - 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; - - /* * end-of-transaction cleanup callbacks for dynamically loaded modules */ typedef void (*EOXactCallback) (bool isCommit, void *arg); - /* - * transaction state structure - */ - typedef struct TransactionStateData - { - TransactionId transactionIdData; /* my XID */ - CommandId commandId; /* current CID */ - TransState state; /* low-level state */ - TBlockState blockState; /* high-level state */ - int nestingLevel; /* nest depth */ - MemoryContext curTransactionContext; /* my xact-lifetime context */ - List *childXids; /* subcommitted child XIDs */ - AclId currentUser; /* subxact start current_user */ - struct TransactionStateData *parent; /* back link to parent */ - } TransactionStateData; - - typedef TransactionStateData *TransactionState; - - /* ---------------- * transaction-related XLOG entries * ---------------- --- 41,50 ---- *************** *** 154,164 **** 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/executor/spi_priv.h =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/include/executor/spi_priv.h,v retrieving revision 1.19 diff -c -r1.19 spi_priv.h *** src/include/executor/spi_priv.h 1 Jul 2004 00:51:42 -0000 1.19 --- src/include/executor/spi_priv.h 4 Jul 2004 21:40:25 -0000 *************** *** 24,29 **** --- 24,30 ---- MemoryContext execCxt; /* executor context */ MemoryContext savedcxt; TransactionId connectXid; /* Xid of connecting transaction */ + int nestLevel; /* nesting level of connecting transaction */ } _SPI_connection; typedef struct Index: src/include/nodes/parsenodes.h =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/include/nodes/parsenodes.h,v retrieving revision 1.260 diff -c -r1.260 parsenodes.h *** src/include/nodes/parsenodes.h 25 Jun 2004 21:55:59 -0000 1.260 --- src/include/nodes/parsenodes.h 14 Jul 2004 03:59:10 -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.5 diff -c -r1.5 transactions.out *** src/test/regress/expected/transactions.out 1 Jul 2004 20:11:02 -0000 1.5 --- src/test/regress/expected/transactions.out 14 Jul 2004 18:48:45 -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,196 **** 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 (1 row) COMMIT; ! SELECT 1; -- this should work ?column? ---------- 1 (1 row) DROP TABLE foo; DROP TABLE baz; DROP TABLE barbaz; --- 135,307 ---- 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 (1 row) + COMMIT; DROP TABLE foo; DROP TABLE baz; DROP TABLE barbaz; Index: src/test/regress/sql/transactions.sql =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/test/regress/sql/transactions.sql,v retrieving revision 1.5 diff -c -r1.5 transactions.sql *** src/test/regress/sql/transactions.sql 1 Jul 2004 20:11:03 -0000 1.5 --- src/test/regress/sql/transactions.sql 14 Jul 2004 18:32:36 -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,132 **** -- 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 DROP TABLE foo; DROP TABLE baz; --- 81,211 ---- -- 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; DROP TABLE foo; DROP TABLE baz;