Hackers, Here is a patch covering the syntax change. This changes the current subtransaction-initiating command to SUBBEGIN instead of BEGIN; similarly SUBCOMMIT and SUBABORT.
I did not add a SUBROLLBACK command ... rather I want to use the standard syntax "SAVEPOINT <foo>" and "ROLLBACK TO <foo>" and keep our nonstandard syntax small. Note that with this patch it is possible to start a subtransaction when not in a transaction block. This makes the new commands to behave almost exactly like toplevel BEGIN/COMMIT (the difference is not visible to the user: the server is in nesting level 2 rather than 1, but the outer level will automatically commit or roll back). This also means that a single COMMIT will commit the whole transaction tree: BEGIN; create table foo (a int); SUBBEGIN; insert into foo values (1); SUBBEGIN; insert into foo values (2); COMMIT; Also a single ABORT/ROLLBACK aborts the whole thing. Included in this patch is the ability to "ignore errors" in a subcommit, so this works: begin; subbegin; drop table foo; -- error: table does not exist subcommit ignore errors; create table foo (...); commit; The point is that this can be executed in a dumb script without worrying about whether the subtransaction will cause an error or not. I'm not sure if the grammar modifications are good. I thought about using two Sconst and comparing them to "ignore errors" ... right now, "ignore" and "errors" are in the unreserved keywords list and I get no errors/warnings from bison, but please check this. I had to add a new transaction block state to support rolling back a whole transaction tree. Also I moved the TransactionState declaration to xact.c because it has no business being in the xact.h header file that I can see. I made some changes to SPI so that it forbids to close a subtransaction that the _SPI_connection did not open, by saving the nesting level at SPI_connect() time and checking when SPI_execute is called. I had thought that it would be easy to return to that nesting level if the function errored out, but I was quite wrong (because the SPI code stops executing immediately as soon as an error is encountered). Now I don't know how to do that at all. I also thought about adding something to the sigsetjmp() block but I don't have a clue how to handle this. Regression tests pass; I had to change the transaction test to adopt the new syntax. I also added a couple of tests to verify the new functionality. -- Alvaro Herrera (<alvherre[a]dcc.uchile.cl>) "La experiencia nos dice que el hombre peló millones de veces las patatas, pero era forzoso admitir la posibilidad de que en un caso entre millones, las patatas pelarían al hombre" (Ijon Tichy)
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 5 Jul 2004 03:40:48 -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_SUBBEGINABORT, ! TBLOCK_SUBINPROGRESS, ! TBLOCK_SUBEND, ! TBLOCK_SUBABORT, ! TBLOCK_SUBENDABORT_ALL, ! TBLOCK_SUBENDABORT_OK, ! TBLOCK_SUBENDABORT_ERROR ! } TBlockState; ! ! /* ! * 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; /* * CurrentTransactionState always points to the current transaction state *************** *** 270,275 **** --- 289,333 ---- static void *_RollbackData = NULL; + /* 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 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 * ---------------------------------------------------------------- *************** *** 1605,1610 **** --- 1663,1669 ---- case TBLOCK_SUBBEGINABORT: case TBLOCK_END: case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_ALL: case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_ENDABORT: *************** *** 1631,1641 **** switch (s->blockState) { ! /* ! * This shouldn't happen, because it means the previous ! * StartTransactionCommand didn't set the STARTED state ! * appropiately. ! */ case TBLOCK_DEFAULT: elog(FATAL, "CommitTransactionCommand: unexpected TBLOCK_DEFAULT"); break; --- 1690,1700 ---- switch (s->blockState) { ! /* ! * This shouldn't happen, because it means the previous ! * StartTransactionCommand didn't set the STARTED state ! * appropiately. ! */ case TBLOCK_DEFAULT: elog(FATAL, "CommitTransactionCommand: unexpected TBLOCK_DEFAULT"); break; *************** *** 1674,1679 **** --- 1733,1744 ---- * default state. */ case TBLOCK_END: + while (GetCurrentTransactionNestLevel() > 1) + { + CommitSubTransaction(); + PopTransaction(); + s = CurrentTransactionState; /* changed by pop */ + } 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.) --- 1763,1778 ---- 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 SUBBEGIN inside a transaction block. * Start a subtransaction. (BeginTransactionBlock already * did PushTransaction, so as to have someplace to put the * SUBBEGIN state.) *************** *** 1709,1715 **** break; /* ! * We were issued a BEGIN inside an aborted transaction block. * Start a subtransaction, and put it in aborted state. */ case TBLOCK_SUBBEGINABORT: --- 1783,1789 ---- break; /* ! * We were issued a SUBBEGIN inside an aborted transaction block. * Start a subtransaction, and put it in aborted state. */ case TBLOCK_SUBBEGINABORT: *************** *** 1725,1731 **** break; /* ! * We were issued a COMMIT command, so we end the current * subtransaction and return to the parent transaction. */ case TBLOCK_SUBEND: --- 1799,1805 ---- break; /* ! * We were issued a SUBCOMMIT command, so we end the current * subtransaction and return to the parent transaction. */ case TBLOCK_SUBEND: *************** *** 1741,1747 **** break; /* ! * We are ending an aborted subtransaction via ROLLBACK, * so the parent can be allowed to live. */ case TBLOCK_SUBENDABORT_OK: --- 1815,1821 ---- break; /* ! * We are ending an aborted subtransaction via SUBABORT, * so the parent can be allowed to live. */ case TBLOCK_SUBENDABORT_OK: *************** *** 1751,1764 **** 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; } --- 1825,1839 ---- break; /* ! * We are ending an aborted subtransaction via SUBCOMMIT ! * without IGNORE ERRORS. End the subtransaction, and ! * abort the parent too. */ case TBLOCK_SUBENDABORT_ERROR: CleanupSubTransaction(); PopTransaction(); s = CurrentTransactionState; /* changed by pop */ ! AssertState(s->blockState != TBLOCK_SUBENDABORT_ERROR); AbortCurrentTransaction(); break; } *************** *** 1889,1894 **** --- 1964,1977 ---- s->blockState != TBLOCK_SUBENDABORT_ERROR); 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; } } *************** *** 2091,2096 **** --- 2174,2227 ---- */ /* + * BeginSubTransactionBlock + * This executes a SUBBEGIN command. + */ + void + BeginSubTransactionBlock(void) + { + TransactionState s = CurrentTransactionState; + + 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; + + case TBLOCK_ABORT: + case TBLOCK_SUBABORT: + /* + * An aborted transaction block should be allowed to start + * a subtransaction, but it must put it in aborted state. + */ + 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_ALL: + case TBLOCK_SUBENDABORT_OK: + case TBLOCK_SUBENDABORT_ERROR: + case TBLOCK_SUBEND: + elog(FATAL, "BeginTransactionBlock: unexpected state %s", + BlockStateAsString(s->blockState)); + break; + } + } + + /* * BeginTransactionBlock * This executes a BEGIN command. */ *************** *** 2099,2105 **** { TransactionState s = CurrentTransactionState; ! switch (s->blockState) { /* * We are not inside a transaction block, so allow one * to begin. --- 2230,2237 ---- { TransactionState s = CurrentTransactionState; ! switch (s->blockState) ! { /* * We are not inside a transaction block, so allow one * to begin. *************** *** 2110,2133 **** /* * 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. */ --- 2242,2255 ---- /* * 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. */ *************** *** 2137,2142 **** --- 2259,2265 ---- case TBLOCK_SUBBEGINABORT: case TBLOCK_ENDABORT: case TBLOCK_END: + case TBLOCK_SUBENDABORT_ALL: case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_SUBEND: *************** *** 2147,2152 **** --- 2270,2348 ---- } /* + * EndSubTransactionBlock + * This executes a SUBEND command. + */ + void + EndSubTransactionBlock(List *options) + { + TransactionState s = CurrentTransactionState; + + switch (s->blockState) + { + case TBLOCK_INPROGRESS: + case TBLOCK_ABORT: + case TBLOCK_STARTED: + /* XXX is this the right ERRCODE? */ + ereport(WARNING, + (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), + errmsg("there is no subtransaction in progress"))); + 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 an aborted subtransaction. Signal + * CommitTransactionCommand() to clean up and return to the + * parent transaction. If the user didn't specify to ignore + * the errors then make the parent abort too; else it can be + * allowed to live. + */ + case TBLOCK_SUBABORT: + { + ListCell *cell; + bool ignore = false; + + foreach (cell, options) + { + DefElem *elem = lfirst(cell); + + if (strcmp(elem->defname, "ignore_errors") == 0) + ignore = true; + } + + if (ignore) + s->blockState = TBLOCK_SUBENDABORT_OK; + else + s->blockState = TBLOCK_SUBENDABORT_ERROR; + } + break; + + /* these cases are invalid. */ + case TBLOCK_DEFAULT: + case TBLOCK_BEGIN: + case TBLOCK_ENDABORT: + case TBLOCK_END: + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBBEGINABORT: + case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_ALL: + case TBLOCK_SUBENDABORT_OK: + case TBLOCK_SUBENDABORT_ERROR: + elog(FATAL, "EndTransactionBlock: unexpected state %s", + BlockStateAsString(s->blockState)); + break; + } + } + + /* * EndTransactionBlock * This executes a COMMIT command. */ *************** *** 2155,2161 **** { 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 --- 2351,2358 ---- { 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 *************** *** 2163,2178 **** * 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; /* --- 2360,2367 ---- * and commit the transaction and return us to the default state */ case TBLOCK_INPROGRESS: case TBLOCK_SUBINPROGRESS: ! s->blockState = TBLOCK_END; break; /* *************** *** 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: --- 2376,2387 ---- break; /* ! * Ditto, but in a 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: *************** *** 2218,2223 **** --- 2406,2412 ---- case TBLOCK_SUBBEGIN: case TBLOCK_SUBBEGINABORT: case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_ALL: case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ERROR: elog(FATAL, "EndTransactionBlock: unexpected state %s", *************** *** 2227,2232 **** --- 2416,2470 ---- } /* + * UserAbortSubTransactionBlock + * This executes a SUBABORT command. + */ + void + UserAbortSubTransactionBlock(void) + { + TransactionState s = CurrentTransactionState; + + switch (s->blockState) + { + case TBLOCK_ABORT: + case TBLOCK_INPROGRESS: + case TBLOCK_STARTED: + /* + * XXX is this the correct ERRCODE? + * Is an ERROR ok or should it be WARNING? + */ + ereport(ERROR, + (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), + errmsg("there is no subtransaction in progress"))); + break; + + case TBLOCK_SUBABORT: + s->blockState = TBLOCK_SUBENDABORT_OK; + break; + + case TBLOCK_SUBINPROGRESS: + AbortSubTransaction(); + s->blockState = TBLOCK_SUBENDABORT_OK; + 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_OK: + case TBLOCK_SUBENDABORT_ERROR: + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBBEGINABORT: + elog(FATAL, "UserAbortSubTransactionBlock: unexpected state %s", + BlockStateAsString(s->blockState)); + break; + } + } + + /* * UserAbortTransactionBlock * This executes a ROLLBACK command. */ *************** *** 2235,2241 **** { 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 --- 2473,2480 ---- { 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 *************** *** 2246,2257 **** 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; /* --- 2485,2498 ---- s->blockState = TBLOCK_ENDABORT; break; ! /* Here 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; /* *************** *** 2268,2274 **** /* Ditto, for a subtransaction. */ case TBLOCK_SUBINPROGRESS: AbortSubTransaction(); ! s->blockState = TBLOCK_SUBENDABORT_OK; break; /* --- 2509,2515 ---- /* Ditto, for a subtransaction. */ case TBLOCK_SUBINPROGRESS: AbortSubTransaction(); ! s->blockState = TBLOCK_SUBENDABORT_ALL; break; /* *************** *** 2291,2296 **** --- 2532,2538 ---- case TBLOCK_END: case TBLOCK_ENDABORT: case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_ALL: case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_SUBBEGIN: *************** *** 2299,2305 **** BlockStateAsString(s->blockState)); break; } - } /* --- 2541,2546 ---- *************** *** 2356,2361 **** --- 2597,2603 ---- s = CurrentTransactionState; /* changed by pop */ break; case TBLOCK_SUBABORT: + case TBLOCK_SUBENDABORT_ALL: case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ERROR: /* As above, but AbortSubTransaction already done */ *************** *** 2425,2430 **** --- 2667,2673 ---- case TBLOCK_ABORT: case TBLOCK_ENDABORT: case TBLOCK_SUBABORT: + case TBLOCK_SUBENDABORT_ALL: case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ERROR: case TBLOCK_SUBBEGINABORT: *************** *** 2445,2451 **** { TransactionState s = CurrentTransactionState; ! switch (s->blockState) { case TBLOCK_DEFAULT: case TBLOCK_STARTED: case TBLOCK_BEGIN: --- 2688,2695 ---- { TransactionState s = CurrentTransactionState; ! switch (s->blockState) ! { case TBLOCK_DEFAULT: case TBLOCK_STARTED: case TBLOCK_BEGIN: *************** *** 2459,2464 **** --- 2703,2709 ---- case TBLOCK_SUBINPROGRESS: case TBLOCK_SUBABORT: case TBLOCK_SUBEND: + case TBLOCK_SUBENDABORT_ALL: case TBLOCK_SUBENDABORT_OK: case TBLOCK_SUBENDABORT_ERROR: return true; *************** *** 2696,2701 **** --- 2941,2949 ---- /* * 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 **** --- 2979,2987 ---- /* * 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) *************** *** 2799,2805 **** static const char * BlockStateAsString(TBlockState blockState) { ! switch (blockState) { case TBLOCK_DEFAULT: return "DEFAULT"; case TBLOCK_STARTED: --- 3050,3057 ---- static const char * BlockStateAsString(TBlockState blockState) { ! switch (blockState) ! { case TBLOCK_DEFAULT: return "DEFAULT"; case TBLOCK_STARTED: *************** *** 2824,2829 **** --- 3076,3083 ---- return "SUB END"; case TBLOCK_SUBABORT: return "SUB ABORT"; + case TBLOCK_SUBENDABORT_ALL: + return "SUB ENDAB ALL"; case TBLOCK_SUBENDABORT_OK: return "SUB ENDAB OK"; case TBLOCK_SUBENDABORT_ERROR: *************** *** 2839,2845 **** static const char * TransStateAsString(TransState state) { ! switch (state) { case TRANS_DEFAULT: return "DEFAULT"; case TRANS_START: --- 3093,3100 ---- 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 5 Jul 2004 03:43:50 -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_SUBBEGIN && + XactStmt->kind != TRANS_STMT_SUBCOMMIT && + XactStmt->kind != TRANS_STMT_SUBABORT) + { + res = SPI_ERROR_TRANSACTION; + goto fail; + } + + /* + * Disallow closing the transaction that created the + * connection. + */ + if ((XactStmt->kind == TRANS_STMT_SUBCOMMIT || + XactStmt->kind == TRANS_STMT_SUBABORT) && + _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_SUBBEGIN && + XactStmt->kind != TRANS_STMT_SUBCOMMIT && + XactStmt->kind != TRANS_STMT_SUBABORT) + { + res = SPI_ERROR_TRANSACTION; + goto fail; + } + + /* + * Disallow closing the transaction that created the + * connection. + */ + if ((XactStmt->kind == TRANS_STMT_SUBCOMMIT || + XactStmt->kind == TRANS_STMT_SUBABORT) && + _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 4 Jul 2004 18:18:28 -0000 *************** *** 222,229 **** target_list update_target_list insert_column_list insert_target_list def_list indirection opt_indirection group_clause TriggerFuncArgs select_limit ! opt_select_limit opclass_item_list transaction_mode_list ! transaction_mode_list_or_empty TableFuncElementList prep_type_clause prep_type_list execute_param_clause --- 222,229 ---- target_list update_target_list insert_column_list insert_target_list def_list indirection opt_indirection group_clause TriggerFuncArgs select_limit ! opt_select_limit opclass_item_list transaction_subcommit_opts ! transaction_mode_list transaction_mode_list_or_empty TableFuncElementList prep_type_clause prep_type_list execute_param_clause *************** *** 348,354 **** DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC DISTINCT DO DOMAIN_P DOUBLE_P DROP ! EACH ELSE ENCODING ENCRYPTED END_P ESCAPE EXCEPT EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT FALSE_P FETCH FIRST_P FLOAT_P FOR FORCE FOREIGN FORWARD --- 348,354 ---- DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC DISTINCT DO DOMAIN_P DOUBLE_P DROP ! EACH ELSE ENCODING ENCRYPTED END_P ERRORS ESCAPE EXCEPT EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT FALSE_P FETCH FIRST_P FLOAT_P FOR FORCE FOREIGN FORWARD *************** *** 358,364 **** HANDLER HAVING HOLD HOUR_P ! ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION --- 358,364 ---- HANDLER HAVING HOLD HOUR_P ! IGNORE ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IN_P INCLUDING INCREMENT INDEX INHERITS INITIALLY INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION *************** *** 393,399 **** 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 TABLE TABLESPACE TEMP TEMPLATE TEMPORARY THEN TIME TIMESTAMP TO TOAST TRAILING TRANSACTION TREAT TRIGGER TRIM TRUE_P --- 393,400 ---- 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 SUBABORT SUBBEGIN SUBCOMMIT ! SUBSTRING SYSID TABLE TABLESPACE TEMP TEMPLATE TEMPORARY THEN TIME TIMESTAMP TO TOAST TRAILING TRANSACTION TREAT TRIGGER TRIM TRUE_P *************** *** 3954,3959 **** --- 3955,3981 ---- n->options = NIL; $$ = (Node *)n; } + | SUBBEGIN opt_transaction + { + TransactionStmt *n = makeNode(TransactionStmt); + n->kind = TRANS_STMT_SUBBEGIN; + n->options = NIL; + $$ = (Node *)n; + } + | SUBABORT opt_transaction + { + TransactionStmt *n = makeNode(TransactionStmt); + n->kind = TRANS_STMT_SUBABORT; + n->options = NIL; + $$ = (Node *)n; + } + | SUBCOMMIT opt_transaction transaction_subcommit_opts + { + TransactionStmt *n = makeNode(TransactionStmt); + n->kind = TRANS_STMT_SUBCOMMIT; + n->options = $3; + $$ = (Node *)n; + } ; opt_transaction: WORK {} *************** *** 3995,4000 **** --- 4017,4031 ---- | READ WRITE { $$ = FALSE; } ; + transaction_subcommit_opts: + IGNORE ERRORS + { + $$ = list_make1(makeDefElem("ignore_errors", + makeBoolConst(true, false))); + } + | /* EMPTY */ + { $$ = NIL; } + ; /***************************************************************************** * *************** *** 7608,7613 **** --- 7639,7645 ---- | EACH | ENCODING | ENCRYPTED + | ERRORS | ESCAPE | EXCLUDING | EXCLUSIVE *************** *** 7623,7628 **** --- 7655,7661 ---- | HANDLER | HOLD | HOUR_P + | IGNORE | IMMEDIATE | IMMUTABLE | IMPLICIT_P *************** *** 7713,7718 **** --- 7746,7754 ---- | STORAGE | SYSID | STRICT_P + | SUBABORT + | SUBBEGIN + | SUBCOMMIT | TABLESPACE | TEMP | TEMPLATE 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 4 Jul 2004 18:18:16 -0000 *************** *** 122,127 **** --- 122,128 ---- {"encoding", ENCODING}, {"encrypted", ENCRYPTED}, {"end", END_P}, + {"errors", ERRORS}, {"escape", ESCAPE}, {"except", EXCEPT}, {"excluding", EXCLUDING}, *************** *** 150,155 **** --- 151,157 ---- {"having", HAVING}, {"hold", HOLD}, {"hour", HOUR_P}, + {"ignore", IGNORE}, {"ilike", ILIKE}, {"immediate", IMMEDIATE}, {"immutable", IMMUTABLE}, *************** *** 294,299 **** --- 296,304 ---- {"stdout", STDOUT}, {"storage", STORAGE}, {"strict", STRICT_P}, + {"subabort", SUBABORT}, + {"subbegin", SUBBEGIN}, + {"subcommit", SUBCOMMIT}, {"substring", SUBSTRING}, {"sysid", SYSID}, {"table", TABLE}, 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 4 Jul 2004 00:03:41 -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,849 ---- TransactionStmt *stmt = (TransactionStmt *) parsetree; if (stmt->kind == TRANS_STMT_COMMIT || ! stmt->kind == TRANS_STMT_SUBBEGIN || ! stmt->kind == TRANS_STMT_SUBCOMMIT || ! stmt->kind == TRANS_STMT_SUBABORT || 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; } --- 1164,1172 ---- TransactionStmt *stmt = (TransactionStmt *) parsetree; if (stmt->kind == TRANS_STMT_COMMIT || ! stmt->kind == TRANS_STMT_SUBBEGIN || ! stmt->kind == TRANS_STMT_SUBCOMMIT || ! stmt->kind == TRANS_STMT_SUBABORT || 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; } --- 1629,1637 ---- is_trans_stmt = true; if (stmt->kind == TRANS_STMT_COMMIT || ! stmt->kind == TRANS_STMT_SUBBEGIN || ! stmt->kind == TRANS_STMT_SUBCOMMIT || ! stmt->kind == TRANS_STMT_SUBABORT || 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 4 Jul 2004 20:48:11 -0000 *************** *** 360,365 **** --- 360,378 ---- case TRANS_STMT_ROLLBACK: UserAbortTransactionBlock(); break; + + case TRANS_STMT_SUBBEGIN: + BeginSubTransactionBlock(); + break; + + case TRANS_STMT_SUBABORT: + UserAbortSubTransactionBlock(); + break; + + /* Let EndSubTransactionBlock parse the options list */ + case TRANS_STMT_SUBCOMMIT: + EndSubTransactionBlock(stmt->options); + break; } } break; *************** *** 1117,1122 **** --- 1130,1147 ---- tag = "ROLLBACK"; break; + case TRANS_STMT_SUBBEGIN: + tag = "SUBBEGIN"; + break; + + case TRANS_STMT_SUBABORT: + tag = "SUBABORT"; + break; + + case TRANS_STMT_SUBCOMMIT: + tag = "SUBCOMMIT"; + 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 5 Jul 2004 03:40:30 -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 ---- *************** *** 152,163 **** --- 99,113 ---- extern void StartTransactionCommand(void); extern void CommitTransactionCommand(void); extern void AbortCurrentTransaction(void); + extern void BeginSubTransactionBlock(void); extern void BeginTransactionBlock(void); + extern void EndSubTransactionBlock(List *options); extern void EndTransactionBlock(void); extern bool IsSubTransaction(void); extern bool IsTransactionBlock(void); extern bool IsTransactionOrTransactionBlock(void); extern char TransactionBlockStatusCode(void); + extern void UserAbortSubTransactionBlock(void); extern void UserAbortTransactionBlock(void); extern void AbortOutOfAnyTransaction(void); extern void PreventTransactionChain(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 3 Jul 2004 21:02:18 -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_SUBBEGIN, ! TRANS_STMT_SUBCOMMIT, ! TRANS_STMT_SUBABORT } TransactionStmtKind; typedef struct TransactionStmt { NodeTag type; TransactionStmtKind kind; /* see above */ ! List *options; /* for BEGIN/START/SUBBEGIN only */ } TransactionStmt; /* ---------------------- 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 4 Jul 2004 21:23:23 -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,86 ---- CREATE TABLE foobar (a int); BEGIN; CREATE TABLE foo (a int); ! SUBBEGIN; DROP TABLE foo; CREATE TABLE bar (a int); ! SUBABORT; ! SUBBEGIN; CREATE TABLE baz (a int); ! SUBCOMMIT; 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 --- 105,122 ---- -- inserts BEGIN; INSERT INTO foo VALUES (1); ! SUBBEGIN; INSERT into bar VALUES (1); ERROR: relation "bar" does not exist ! SUBABORT; ! SUBBEGIN; INSERT into barbaz VALUES (1); ! SUBCOMMIT; ! SUBBEGIN; ! SUBBEGIN; INSERT INTO foo VALUES (2); ! SUBCOMMIT; ! SUBABORT; INSERT INTO foo VALUES (3); COMMIT; SELECT * FROM foo; -- should have 1 and 3 *************** *** 136,151 **** 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; --- 136,151 ---- BEGIN; SELECT 0/0; -- fail the outer xact ERROR: division by zero ! SUBBEGIN; SELECT 1; -- this should NOT work ERROR: current transaction is aborted, commands ignored until end of transaction block ! SUBCOMMIT; SELECT 1; -- this should NOT work ERROR: current transaction is aborted, commands ignored until end of transaction block ! SUBBEGIN; SELECT 1; -- this should NOT work ERROR: current transaction is aborted, commands ignored until end of transaction block ! SUBABORT; SELECT 1; -- this should NOT work ERROR: current transaction is aborted, commands ignored until end of transaction block COMMIT; *************** *** 156,162 **** (1 row) BEGIN; ! BEGIN; SELECT 1; -- this should work ?column? ---------- --- 156,162 ---- (1 row) BEGIN; ! SUBBEGIN; SELECT 1; -- this should work ?column? ---------- *************** *** 167,183 **** 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? ---------- --- 167,183 ---- ERROR: division by zero SELECT 1; -- this should NOT work ERROR: current transaction is aborted, commands ignored until end of transaction block ! SUBBEGIN; SELECT 1; -- this should NOT work ERROR: current transaction is aborted, commands ignored until end of transaction block ! SUBABORT; ! SUBBEGIN; SELECT 1; -- this should NOT work ERROR: current transaction is aborted, commands ignored until end of transaction block ! SUBCOMMIT; SELECT 1; -- this should NOT work ERROR: current transaction is aborted, commands ignored until end of transaction block ! SUBABORT; SELECT 1; -- this should work ?column? ---------- *************** *** 191,196 **** --- 191,255 ---- 1 (1 row) + -- test "subcommit ignore errors" and whole-tree commit + BEGIN; + SUBBEGIN; + SELECT foo; + ERROR: column "foo" does not exist + SUBCOMMIT IGNORE ERRORS; + SUBBEGIN; + CREATE TABLE bazbaz (a int); + SUBBEGIN; + INSERT INTO bazbaz VALUES (1); + SUBBEGIN; + INSERT INTO bazbaz VALUES (2); + SUBBEGIN; + INSERT INTO bazbaz VALUES (3); + SUBABORT; + COMMIT; + COMMIT; -- should not be in a transaction block + WARNING: there is no transaction in progress + SELECT * FROM bazbaz; + a + --- + 1 + 2 + (2 rows) + + -- test whole-tree rollback + BEGIN; + SUBBEGIN; + DELETE FROM bazbaz WHERE a=1; + SUBCOMMIT; + SUBBEGIN; + DELETE FROM bazbaz WHERE a=1; + SUBBEGIN; + DELETE FROM bazbaz WHERE a=2; + ROLLBACK; + + SELECT * FROM bazbaz; + a + --- + 1 + 2 + (2 rows) + + -- test whole-tree commit on an aborted subtransaction + BEGIN; + INSERT INTO bazbaz VALUES (1); + SUBBEGIN; + INSERT INTO bazbaz VALUES (2); + SELECT foo; + ERROR: column "foo" does not exist + COMMIT; + SELECT * FROM bazbaz; + a + --- + 1 + 2 + (2 rows) + DROP TABLE foo; DROP TABLE baz; DROP TABLE barbaz; + DROP TABLE bazbaz; 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 4 Jul 2004 21:21:43 -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,73 ---- CREATE TABLE foobar (a int); BEGIN; CREATE TABLE foo (a int); ! SUBBEGIN; DROP TABLE foo; CREATE TABLE bar (a int); ! SUBABORT; ! SUBBEGIN; CREATE TABLE baz (a int); ! SUBCOMMIT; drop TABLE foobar; CREATE TABLE barbaz (a int); COMMIT; *************** *** 80,96 **** -- 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 --- 80,96 ---- -- inserts BEGIN; INSERT INTO foo VALUES (1); ! SUBBEGIN; INSERT into bar VALUES (1); ! SUBABORT; ! SUBBEGIN; INSERT into barbaz VALUES (1); ! SUBCOMMIT; ! SUBBEGIN; ! SUBBEGIN; INSERT INTO foo VALUES (2); ! SUBCOMMIT; ! SUBABORT; INSERT INTO foo VALUES (3); COMMIT; SELECT * FROM foo; -- should have 1 and 3 *************** *** 99,133 **** -- 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; DROP TABLE barbaz; --- 99,173 ---- -- check that starting a subxact in a failed xact or subxact works BEGIN; SELECT 0/0; -- fail the outer xact ! SUBBEGIN; SELECT 1; -- this should NOT work ! SUBCOMMIT; SELECT 1; -- this should NOT work ! SUBBEGIN; SELECT 1; -- this should NOT work ! SUBABORT; SELECT 1; -- this should NOT work COMMIT; SELECT 1; -- this should work BEGIN; ! SUBBEGIN; SELECT 1; -- this should work SELECT 0/0; -- fail the subxact SELECT 1; -- this should NOT work ! SUBBEGIN; SELECT 1; -- this should NOT work ! SUBABORT; ! SUBBEGIN; SELECT 1; -- this should NOT work ! SUBCOMMIT; SELECT 1; -- this should NOT work ! SUBABORT; SELECT 1; -- this should work COMMIT; SELECT 1; -- this should work + -- test "subcommit ignore errors" and whole-tree commit + BEGIN; + SUBBEGIN; + SELECT foo; + SUBCOMMIT IGNORE ERRORS; + SUBBEGIN; + CREATE TABLE bazbaz (a int); + SUBBEGIN; + INSERT INTO bazbaz VALUES (1); + SUBBEGIN; + INSERT INTO bazbaz VALUES (2); + SUBBEGIN; + INSERT INTO bazbaz VALUES (3); + SUBABORT; + COMMIT; + COMMIT; -- should not be in a transaction block + SELECT * FROM bazbaz; + + -- test whole-tree rollback + BEGIN; + SUBBEGIN; + DELETE FROM bazbaz WHERE a=1; + SUBCOMMIT; + SUBBEGIN; + DELETE FROM bazbaz WHERE a=1; + SUBBEGIN; + DELETE FROM bazbaz WHERE a=2; + ROLLBACK; + + SELECT * FROM bazbaz; + + -- test whole-tree commit on an aborted subtransaction + BEGIN; + INSERT INTO bazbaz VALUES (1); + SUBBEGIN; + INSERT INTO bazbaz VALUES (2); + SELECT foo; + COMMIT; + SELECT * FROM bazbaz; DROP TABLE foo; DROP TABLE baz; DROP TABLE barbaz; + DROP TABLE bazbaz;
---------------------------(end of broadcast)--------------------------- TIP 8: explain analyze is your friend