Index: src/backend/access/transam/xact.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/access/transam/xact.c,v retrieving revision 1.165 diff -c -r1.165 xact.c *** src/backend/access/transam/xact.c 5 Apr 2004 03:11:39 -0000 1.165 --- src/backend/access/transam/xact.c 26 Apr 2004 13:25:30 -0000 *************** *** 189,194 **** --- 189,227 ---- static void RecordTransactionAbort(void); static void StartTransaction(void); + #ifdef SUBTRANSACTIONS + static void StartSubTransaction(void); + static void CommitSubTransaction(void); + static void AbortSubTransaction(void); + static void CleanupSubTransaction(void); + static void PushCurrentTransaction(void); + static void PopTransaction(void); + static bool IsSubTransaction(void); + static void ShowTransactionState(char *); + static void ShowTransactionStateRec(TransactionState, int); + + static void AtSubStart_Memory(void); + static void AtSubCommit_Memory(void); + static void AtSubAbort_Memory(void); + static void AtSubCleanup_Memory(void); + static void AtSubCommit_Notifies(void); + + static char *TransStateAsString(TransState); + + #else /* SUBTRANSACTIONS */ + #define StartSubTransaction() + #define CommitSubTransaction() + #define AbortSubTransaction() + #define CleanupSubTransaction() + #define PushCurrentTransaction() + #define PopTransaction() + #define IsSubTransaction() (false) + #define ShowTransactionState(foo) + #define ShowTransactionStateRec() + #endif /* SUBTRANSACTIONS */ + + static char *BlockStateAsString(TBlockState); + /* * global variables holding the current transaction state. */ *************** *** 198,205 **** 0, /* scan command id */ 0x0, /* start time */ TRANS_DEFAULT, /* transaction state */ ! TBLOCK_DEFAULT /* transaction block state from the client * perspective */ }; static TransactionState CurrentTransactionState = &CurrentTransactionStateData; --- 231,251 ---- 0, /* scan command id */ 0x0, /* start time */ TRANS_DEFAULT, /* transaction state */ ! TBLOCK_DEFAULT, /* transaction block state from the client * perspective */ + #ifdef SUBTRANSACTIONS + 0, /* nesting level */ + NULL, /* commit memory context */ + NULL, /* deferred trigger queue */ + #endif /* SUBTRANSACTIONS */ + NIL, /* smgr pending deletes */ + NIL /* async notifies */ + #ifdef SUBTRANSACTIONS + , + NULL, /* lightweight locks */ + NIL, /* regular locks */ + NULL /* parent transaction */ + #endif }; static TransactionState CurrentTransactionState = &CurrentTransactionStateData; *************** *** 281,287 **** { TransactionState s = CurrentTransactionState; ! if (s->blockState == TBLOCK_ABORT) return true; return false; --- 327,334 ---- { TransactionState s = CurrentTransactionState; ! if (s->blockState == TBLOCK_ABORT || ! s->blockState == TBLOCK_SUBABORT) return true; return false; *************** *** 337,342 **** --- 384,484 ---- return s->startTime; } + /* + * TransactionGetPendingDeletes + */ + List * + TransactionGetPendingDeletes(void) + { + TransactionState s = CurrentTransactionState; + + return s->pendingDeletes; + } + + /* + * TransactionSetPendingDeletes + */ + void + TransactionSetPendingDeletes(List *pending) + { + TransactionState s = CurrentTransactionState; + + s->pendingDeletes = pending; + } + + #ifdef SUBTRANSACTIONS + /* + * TransactionGetParentPendingDeletes + */ + List * + TransactionGetParentPendingDeletes(void) + { + TransactionState s = CurrentTransactionState; + + Assert(s->parent != NULL); + return s->parent->pendingDeletes; + } + + /* + * TransactionSetParentPendingDeletes + */ + void + TransactionSetParentPendingDeletes(List *pending) + { + TransactionState s = CurrentTransactionState; + + Assert(s->parent != NULL); + s->parent->pendingDeletes = pending; + } + #endif /* SUBTRANSACTIONS */ + + /* + * TransactionGetNotifies + */ + List * + TransactionGetNotifies(void) + { + TransactionState s = CurrentTransactionState; + + return s->notifies; + } + + /* + * TransactionSetNotifies + */ + void + TransactionSetNotifies(List *notifies) + { + TransactionState s = CurrentTransactionState; + + s->notifies = notifies; + } + + #ifdef SUBTRANSACTIONS + /* + * TransactionGetParentNotifies + */ + List * + TransactionGetParentNotifies(void) + { + TransactionState s = CurrentTransactionState; + + Assert(s->parent != NULL); + return s->parent->notifies; + } + + /* + * TransactionSetParentNotifies + */ + void + TransactionSetParentNotifies(List *notifies) + { + TransactionState s = CurrentTransactionState; + + Assert(s->parent != NULL); + s->parent->notifies = notifies; + } + #endif /* SUBTRANSACTIONS */ /* * TransactionIdIsCurrentTransactionId *************** *** 436,441 **** --- 578,587 ---- static void AtStart_Memory(void) { + #ifdef SUBTRANSACTIONS + TransactionState s = CurrentTransactionState; + #endif /* SUBTRANSACTIONS */ + /* * We shouldn't have a transaction context already. */ *************** *** 452,459 **** --- 598,652 ---- ALLOCSET_DEFAULT_MAXSIZE); MemoryContextSwitchTo(TopTransactionContext); + + /* + * This context will be used to hold data that survives + * subtransaction commit but disappears on subtransaction end. + * Most if not all of the transaction-local structures should + * live here. + */ + CommitContext = AllocSetContextCreate(TopTransactionContext, + "CommitContext", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + #ifdef SUBTRANSACTIONS + s->commitContext = CommitContext; + #endif /* SUBTRANSACTIONS */ } + #ifdef SUBTRANSACTIONS + /* ---------------------------------------------------------------- + * StartSubTransaction stuff + * ---------------------------------------------------------------- + */ + + /* + * AtSubStart_Memory + */ + static void + AtSubStart_Memory(void) + { + TransactionState s = CurrentTransactionState; + + elog(DEBUG2, "AtSubStart_Memory"); + Assert(TopTransactionContext != NULL); + + /* + * This context will be used to hold data that survives + * subtransaction commit but disappears on subtransaction end. + * Most if not all of the transaction-local structures should + * live here. + */ + CommitContext = AllocSetContextCreate(TopTransactionContext, + "CommitContext", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + s->commitContext = CommitContext; + } + #endif /* SUBTRANSACTIONS */ /* ---------------------------------------------------------------- * CommitTransaction stuff *************** *** 637,644 **** --- 830,893 ---- Assert(TopTransactionContext != NULL); MemoryContextDelete(TopTransactionContext); TopTransactionContext = NULL; + CommitContext = NULL; } + #ifdef SUBTRANSACTIONS + /* ---------------------------------------------------------------- + * StartSubTransaction stuff + * ---------------------------------------------------------------- + */ + + /* + * AtSubCommit_Memory + */ + static void + AtSubCommit_Memory(void) + { + elog(DEBUG2, "AtSubCommit_Memory"); + Assert(TopTransactionContext != NULL); + + /* Return to a known-good memory context. */ + MemoryContextSwitchTo(TopTransactionContext); + } + + /* + * AtSubCommit_childXids + * + * Save the child XIDs in the transaction state so we can mark them + * aborted in pg_clog if their parent aborts. + */ + static void + AtSubCommit_childXids(void) + { + TransactionState s = CurrentTransactionState; + MemoryContext old_cxt; + + old_cxt = MemoryContextSwitchTo(TopTransactionContext); + + s->parent->childXids = + lconsi(s->transactionIdData, + nconc(s->parent->childXids, s->childXids)); + + MemoryContextSwitchTo(old_cxt); + } + + /* + * AtSubCommit_Notifies + * + * Reassign the pending notifies to the parent transaction. + */ + static void + AtSubCommit_Notifies(void) + { + TransactionState s = CurrentTransactionState; + + s->parent->notifies = s->notifies; + } + + #endif /* SUBTRANSACTIONS */ + /* ---------------------------------------------------------------- * AbortTransaction stuff * ---------------------------------------------------------------- *************** *** 778,783 **** --- 1027,1044 ---- MemoryContextSwitchTo(TopMemoryContext); } + #ifdef SUBTRANSACTIONS + /* + * AtSubAbort_Memory + */ + static void + AtSubAbort_Memory(void) + { + Assert(TopTransactionContext != NULL); + + MemoryContextSwitchTo(TopTransactionContext); + } + #endif /* ---------------------------------------------------------------- * CleanupTransaction stuff *************** *** 806,811 **** --- 1067,1100 ---- } + #ifdef SUBTRANSACTIONS + /* ---------------------------------------------------------------- + * CleanupSubTransaction stuff + * ---------------------------------------------------------------- + */ + + /* + * AtSubCleanup_Memory + */ + static void + AtSubCleanup_Memory(void) + { + elog(DEBUG2, "AtSubEnd_Memory"); + Assert(TopTransactionContext != NULL); + + /* Return to a known-good memory context. */ + MemoryContextSwitchTo(TopTransactionContext); + + /* + * Delete the subxact local memory contexts. The CommitContext + * can go too. + */ + MemoryContextDelete(CommitContext); + + CommitContext = NULL; + } + #endif /* SUBTRANSACTIONS */ + /* ---------------------------------------------------------------- * interface routines * ---------------------------------------------------------------- *************** *** 851,856 **** --- 1140,1149 ---- s->commandId = FirstCommandId; s->startTime = GetCurrentAbsoluteTimeUsec(&(s->startTimeUsec)); + #ifdef SUBTRANSACTIONS + s->childXids = NIL; + #endif + /* * initialize the various transaction subsystems */ *************** *** 868,873 **** --- 1161,1167 ---- * progress" */ s->state = TRANS_INPROGRESS; + ShowTransactionState("StartTransaction"); } *************** *** 879,884 **** --- 1173,1180 ---- { TransactionState s = CurrentTransactionState; + ShowTransactionState("CommitTransaction"); + /* * check the current transaction state */ *************** *** 1145,1189 **** break; /* - * We should never experience this -- it means the STARTED state - * was not changed in the previous CommitTransactionCommand. - */ - case TBLOCK_STARTED: - elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_STARTED"); - break; - - /* - * We should never experience this -- if we do it means the - * BEGIN state was not changed in the previous - * CommitTransactionCommand(). If we get it, we print a - * warning and change to the in-progress state. - */ - case TBLOCK_BEGIN: - elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_BEGIN"); - s->blockState = TBLOCK_INPROGRESS; - break; - - /* * This is the case when are somewhere in a transaction block * and about to start a new command. For now we do nothing * but someday we may do command-local resource * initialization. */ case TBLOCK_INPROGRESS: ! break; ! ! /* ! * As with BEGIN, we should never experience this if we do it ! * means the END state was not changed in the previous ! * CommitTransactionCommand(). If we get it, we print a ! * warning, commit the transaction, start a new transaction ! * and change to the default state. ! */ ! case TBLOCK_END: ! elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_END"); ! CommitTransaction(); ! StartTransaction(); ! s->blockState = TBLOCK_DEFAULT; break; /* --- 1441,1453 ---- break; /* * This is the case when are somewhere in a transaction block * and about to start a new command. For now we do nothing * but someday we may do command-local resource * initialization. */ case TBLOCK_INPROGRESS: ! case TBLOCK_SUBINPROGRESS: break; /* *************** *** 1193,1209 **** * TRANSACTION" which will set things straight. */ case TBLOCK_ABORT: break; ! /* ! * This means we somehow aborted and the last call to ! * CommitTransactionCommand() didn't clear the state so we ! * remain in the ENDABORT state and maybe next time we get to ! * CommitTransactionCommand() the state will get reset to ! * default. ! */ case TBLOCK_ENDABORT: ! elog(WARNING, "StartTransactionCommand: unexpected TBLOCK_ENDABORT"); break; } --- 1457,1477 ---- * TRANSACTION" which will set things straight. */ case TBLOCK_ABORT: + case TBLOCK_SUBABORT: break; ! /* These cases are invalid. */ ! 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 block state %s", ! BlockStateAsString(s->blockState)); break; } *************** *** 1231,1237 **** * appropiately. */ case TBLOCK_DEFAULT: ! elog(WARNING, "CommitTransactionCommand: unexpected TBLOCK_DEFAULT"); break; /* --- 1499,1505 ---- * appropiately. */ case TBLOCK_DEFAULT: ! elog(FATAL, "CommitTransactionCommand: unexpected TBLOCK_DEFAULT"); break; /* *************** *** 1290,1295 **** --- 1558,1633 ---- CleanupTransaction(); s->blockState = TBLOCK_DEFAULT; break; + + /* + * We were just issued a BEGIN inside a transaction block. + * Start a subtransaction. + */ + case TBLOCK_SUBBEGIN: + StartSubTransaction(); + s->blockState = TBLOCK_SUBINPROGRESS; + break; + + /* + * We were issued a BEGIN inside an aborted transaction block. + * Start a subtransaction, and put it in aborted state. + */ + case TBLOCK_SUBBEGINABORT: + StartSubTransaction(); + AbortSubTransaction(); + s->blockState = TBLOCK_SUBABORT; + break; + + /* + * Inside a subtransaction, increment the command counter. + */ + case TBLOCK_SUBINPROGRESS: + CommandCounterIncrement(); + break; + + /* + * We where issued a COMMIT command, so we end the current + * subtransaction and return to the parent transaction. + */ + case TBLOCK_SUBEND: + CommitSubTransaction(); + PopTransaction(); + break; + + /* + * If we are in an aborted subtransaction, do nothing. + */ + case TBLOCK_SUBABORT: + break; + + /* + * We are ending a subtransaction that aborted nicely, + * so the parent can be allowed to live. + */ + case TBLOCK_SUBENDABORT_OK: + CleanupSubTransaction(); + PopTransaction(); + break; + + /* + * We are ending a subtransaction that aborted in a unclean + * way (e.g. the user issued COMMIT in an aborted subtrasaction.) + * Abort the subtransaction, and abort the parent too. + */ + case TBLOCK_SUBENDABORT_ERROR: + CleanupSubTransaction(); + PopTransaction(); + if (IsSubTransaction()) + { + AbortSubTransaction(); + s->blockState = TBLOCK_SUBABORT; + } + else + { + AbortTransaction(); + s->blockState = TBLOCK_ABORT; + } + break; } } *************** *** 1361,1366 **** --- 1699,1705 ---- * state. */ case TBLOCK_ABORT: + case TBLOCK_SUBABORT: break; /* *************** *** 1373,1378 **** --- 1712,1758 ---- CleanupTransaction(); s->blockState = TBLOCK_DEFAULT; break; + + /* + * If we are just starting a subtransaction, put it + * in aborted state. + */ + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBBEGINABORT: + PushCurrentTransaction(); + StartSubTransaction(); + AbortSubTransaction(); + s->blockState = TBLOCK_SUBABORT; + break; + + case TBLOCK_SUBINPROGRESS: + AbortSubTransaction(); + s->blockState = TBLOCK_SUBABORT; + break; + + /* + * If we are aborting an ending transaction, + * we have to abort the parent transaction too. + */ + case TBLOCK_SUBEND: + AbortSubTransaction(); + CleanupSubTransaction(); + PopTransaction(); + if (IsSubTransaction()) + AbortSubTransaction(); + else + AbortTransaction(); + break; + + case TBLOCK_SUBENDABORT_OK: + case TBLOCK_SUBENDABORT_ERROR: + CleanupSubTransaction(); + PopTransaction(); + if (IsSubTransaction()) + AbortSubTransaction(); + else + AbortTransaction(); + break; } } *************** *** 1391,1396 **** --- 1771,1777 ---- * stmtNode: pointer to parameter block for statement; this is used in * a very klugy way to determine whether we are inside a function. * stmtType: statement type name for error messages. + * */ void PreventTransactionChain(void *stmtNode, const char *stmtType) *************** *** 1418,1424 **** /* If we got past IsTransactionBlock test, should be in default state */ if (CurrentTransactionState->blockState != TBLOCK_DEFAULT && CurrentTransactionState->blockState != TBLOCK_STARTED) ! elog(ERROR, "cannot prevent transaction chain"); /* all okay */ } --- 1799,1805 ---- /* If we got past IsTransactionBlock test, should be in default state */ if (CurrentTransactionState->blockState != TBLOCK_DEFAULT && CurrentTransactionState->blockState != TBLOCK_STARTED) ! elog(FATAL, "cannot prevent transaction chain"); /* all okay */ } *************** *** 1540,1557 **** s->blockState = TBLOCK_BEGIN; break; ! /* Already a transaction block in progress. */ case TBLOCK_INPROGRESS: ereport(WARNING, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), errmsg("there is already a transaction in progress"))); /* ! * This shouldn't happen, because a transaction in aborted state ! * will not be allowed to call BeginTransactionBlock. */ case TBLOCK_ABORT: ! elog(WARNING, "BeginTransactionBlock: unexpected TBLOCK_ABORT"); break; /* These cases are invalid. Reject them altogether. */ --- 1921,1951 ---- s->blockState = TBLOCK_BEGIN; break; ! /* ! * Already a transaction block in progress. ! * Start a subtransaction, or squeak if we don't ! * have subtransactions compiled in. ! */ case TBLOCK_INPROGRESS: + #ifndef SUBTRANSACTIONS ereport(WARNING, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), errmsg("there is already a transaction in progress"))); + break; + #endif + case TBLOCK_SUBINPROGRESS: + PushCurrentTransaction(); + 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: ! PushCurrentTransaction(); ! s->blockState = TBLOCK_SUBBEGINABORT; break; /* These cases are invalid. Reject them altogether. */ *************** *** 1559,1565 **** case TBLOCK_BEGIN: case TBLOCK_ENDABORT: case TBLOCK_END: ! elog(FATAL, "BeginTransactionBlock: not in a user-allowed state!"); break; } } --- 1953,1965 ---- case TBLOCK_BEGIN: case TBLOCK_ENDABORT: case TBLOCK_END: ! case TBLOCK_SUBBEGIN: ! case TBLOCK_SUBBEGINABORT: ! case TBLOCK_SUBENDABORT_OK: ! case TBLOCK_SUBENDABORT_ERROR: ! case TBLOCK_SUBEND: ! elog(FATAL, "BeginTransactionBlock: unexpected %s", ! BlockStateAsString(s->blockState)); break; } } *************** *** 1584,1589 **** --- 1984,1998 ---- 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 and since the * AbortTransaction() was already done, we do whatever is needed * and change to the special "END ABORT" state. The upcoming *************** *** 1594,1599 **** --- 2003,2017 ---- s->blockState = TBLOCK_ENDABORT; break; + /* + * here we are in an aborted subtransaction. Signal + * CommitTransactionCommand() to clean up and return to the + * parent transaction. + */ + case TBLOCK_SUBABORT: + s->blockState = TBLOCK_SUBENDABORT_ERROR; + break; + case TBLOCK_STARTED: /* * here, the user issued COMMIT when not inside a transaction. Issue a *************** *** 1613,1619 **** case TBLOCK_BEGIN: case TBLOCK_ENDABORT: case TBLOCK_END: ! elog(FATAL, "EndTransactionBlock and not in a user-allowed state"); break; } } --- 2031,2043 ---- case TBLOCK_BEGIN: 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 %s", ! BlockStateAsString(s->blockState)); break; } } *************** *** 1626,1667 **** { TransactionState s = CurrentTransactionState; ! /* ! * if the transaction has already been automatically aborted with an ! * error, and the user subsequently types 'abort', allow it. (the ! * behavior is the same as if they had typed 'end'.) ! */ ! if (s->blockState == TBLOCK_ABORT) ! { ! s->blockState = TBLOCK_ENDABORT; ! return; ! } ! ! if (s->blockState == TBLOCK_INPROGRESS) ! { /* * here we were 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(). */ ! s->blockState = TBLOCK_ABORT; ! AbortTransaction(); ! s->blockState = TBLOCK_ENDABORT; ! return; } - /* - * 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. - */ - ereport(WARNING, - (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), - errmsg("there is no transaction in progress"))); - AbortTransaction(); - s->blockState = TBLOCK_ENDABORT; } /* --- 2050,2118 ---- { TransactionState s = CurrentTransactionState; ! switch (s->blockState) { /* * here we were 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(). */ ! case TBLOCK_ABORT: ! s->blockState = TBLOCK_ENDABORT; ! break; ! ! /* Ditto, for a subtransaction. */ ! case TBLOCK_SUBABORT: ! AbortSubTransaction(); ! s->blockState = TBLOCK_SUBENDABORT_OK; ! break; ! ! /* ! * here we were 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(). ! */ ! case TBLOCK_INPROGRESS: ! AbortTransaction(); ! 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, ! (errcode(ERRCODE_NO_ACTIVE_SQL_TRANSACTION), ! errmsg("there is no transaction in progress"))); ! AbortTransaction(); ! 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 %s", ! BlockStateAsString(s->blockState)); ! break; } } /* *************** *** 1697,1702 **** --- 2148,2177 ---- /* AbortTransaction already done, still need Cleanup */ CleanupTransaction(); break; + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBBEGINABORT: + /* + * Just starting a new transaction -- return to parent. + * FIXME -- Is this correct? + */ + PopTransaction(); + AbortOutOfAnyTransaction(); + break; + case TBLOCK_SUBINPROGRESS: + case TBLOCK_SUBEND: + /* In a subtransaction, so clean it up and abort parent too */ + AbortSubTransaction(); + CleanupSubTransaction(); + PopTransaction(); + AbortOutOfAnyTransaction(); + break; + case TBLOCK_SUBABORT: + case TBLOCK_SUBENDABORT_OK: + case TBLOCK_SUBENDABORT_ERROR: + CleanupSubTransaction(); + PopTransaction(); + AbortOutOfAnyTransaction(); + break; } /* *************** *** 1753,1770 **** case TBLOCK_BEGIN: case TBLOCK_INPROGRESS: case TBLOCK_END: return 'T'; /* in transaction */ case TBLOCK_ABORT: case TBLOCK_ENDABORT: return 'E'; /* in failed transaction */ } /* should never get here */ ! elog(ERROR, "invalid transaction block state: %d", ! (int) s->blockState); return 0; /* keep compiler quiet */ } /* * XLOG support routines --- 2228,2561 ---- case TBLOCK_BEGIN: case TBLOCK_INPROGRESS: case TBLOCK_END: + case TBLOCK_SUBINPROGRESS: + case TBLOCK_SUBBEGIN: + case TBLOCK_SUBEND: return 'T'; /* in transaction */ 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 */ } /* should never get here */ ! elog(FATAL, "invalid transaction block state: %s", ! BlockStateAsString(s->blockState)); return 0; /* keep compiler quiet */ } + #ifdef SUBTRANSACTIONS + /* + * IsSubTransaction + */ + bool + IsSubTransaction(void) + { + TransactionState s = CurrentTransactionState; + + switch (s->blockState) { + case TBLOCK_DEFAULT: + case TBLOCK_STARTED: + case TBLOCK_BEGIN: + case TBLOCK_INPROGRESS: + case TBLOCK_END: + case TBLOCK_ABORT: + 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; + } + + /* Should not get here */ + Assert(false); + + /* Keep compiler quiet */ + return false; + } + + /* + * StartSubTransaction + */ + void + StartSubTransaction(void) + { + TransactionState s = CurrentTransactionState; + + /* + * generate a new transaction id + */ + s->transactionIdData = GetNewTransactionId(); + + s->childXids = NIL; + + CommandCounterIncrement(); + + /* Initialize the memory subsystem */ + AtSubStart_Memory(); + + ShowTransactionState("StartSubTransaction"); + } + + /* + * CommitSubTransaction + */ + void + CommitSubTransaction(void) + { + TransactionState s = CurrentTransactionState; + ShowTransactionState("CommitSubTransaction"); + + CommandCounterIncrement(); + + Assert(s->parent != NULL); + + smgrCommitSubTransaction(); + + AtSubCommit_Portals(s->parent->transactionIdData); + AtSubCommit_Memory(); + AtSubCommit_childXids(); + AtSubCommit_Notifies(); + } + + /* + * AbortSubTransaction + */ + void + AbortSubTransaction(void) + { + ShowTransactionState("AbortSubTransaction"); + + AtSubAbort_Portals(); + AtSubAbort_Memory(); + + /* I'm not sure this is needed. */ + CommandCounterIncrement(); + + smgrAbortSubTransaction(); + } + + /* + * CleanupSubTransaction + */ + void + CleanupSubTransaction(void) + { + ShowTransactionState("CleanupSubTransaction"); + + AtSubCleanup_Portals(); + AtSubCleanup_Memory(); + } + + /* + * PushCurrentTransaction + */ + void + PushCurrentTransaction(void) + { + TransactionState parent; + TransactionState s = CurrentTransactionState; + MemoryContext old_cxt; + + /* + * Make sure the transaction state node is in a long-lived context + */ + old_cxt = MemoryContextSwitchTo(TopMemoryContext); + + parent = (TransactionState) palloc(sizeof(TransactionStateData)); + memcpy(parent, s, sizeof(TransactionStateData)); + + s->parent = parent; + s->nestingLevel = s->parent->nestingLevel + 1; + s->commandId = s->parent->commandId; + + MemoryContextSwitchTo(old_cxt); + } + + /* + * PopTransaction + */ + void + PopTransaction(void) + { + TransactionState s = CurrentTransactionState; + TransactionState p; + + Assert(s->parent != NULL); + + s->parent->commandId = s->commandId; + + p = s->parent; + + memcpy(s, p, sizeof(TransactionStateData)); + CommitContext = s->commitContext; + + /* Free the old parent structure */ + pfree(p); + } + + /* + * ShowTransactionState + */ + void + ShowTransactionState(char *str) + { + TransactionState s = CurrentTransactionState; + + elog(DEBUG2, "%s", str); + ShowTransactionStateRec(s, 2 * s->nestingLevel); + } + + /* + * ShowTransactionStateRec + */ + void + ShowTransactionStateRec(TransactionState s, int indent) + { + char *blockState = BlockStateAsString(s->blockState); + char *state = TransStateAsString(s->state); + List *child; + char *childStr; + char *tmp; + int len; + + if (s->parent) + ShowTransactionStateRec(s->parent, indent - 2); + + len = length(s->childXids); + if (len == 0) + childStr = "(none)"; + else + { + childStr = (char *)calloc(13, len); + tmp = (char *)malloc(13); + + foreach (child, s->childXids) + { + /* there will be an extra space at the end but ... */ + snprintf(tmp, 12, "%d ", lfirsti(child)); + strncat(childStr, tmp, 12); + } + free(tmp); + } + + tmp = (char *)malloc(sizeof(char) * indent + 1); + memset(tmp, ' ', indent); + memset(tmp + indent, '\0', 1); + + elog(DEBUG2, "%sblockState: %19s; state: %10s, xid/cid: %u/%u, nestlvl: %d, childs: %s", + tmp, + blockState, + state, + (unsigned int)s->transactionIdData, + (unsigned int)s->commandId, + s->nestingLevel, + childStr); + free(tmp); + + if (len != 0) + free(childStr); + pfree(blockState); + pfree(state); + } + + #endif /* SUBTRANSACTIONS */ + + /* + * BlockStateAsString + * + * Returns the block state as a palloc'ed string + */ + char * + BlockStateAsString(TBlockState blockState) + { + char *tmp = ""; + switch (blockState) { + case TBLOCK_DEFAULT: + tmp = "DEFAULT"; + break; + case TBLOCK_STARTED: + tmp = "STARTED"; + break; + case TBLOCK_BEGIN: + tmp = "BEGIN"; + break; + case TBLOCK_INPROGRESS: + tmp = "INPROGRESS"; + break; + case TBLOCK_END: + tmp = "END"; + break; + case TBLOCK_ABORT: + tmp = "ABORT"; + break; + case TBLOCK_ENDABORT: + tmp = "ENDABORT"; + break; + case TBLOCK_SUBBEGIN: + tmp = "SUB BEGIN"; + break; + case TBLOCK_SUBBEGINABORT: + tmp = "SUB BEGIN ABORT"; + break; + case TBLOCK_SUBINPROGRESS: + tmp = "SUB INPROGRESS"; + break; + case TBLOCK_SUBEND: + tmp = "SUB END"; + break; + case TBLOCK_SUBABORT: + tmp = "SUB ABORT"; + break; + case TBLOCK_SUBENDABORT_OK: + tmp = "SUB ENDABORT OK"; + break; + case TBLOCK_SUBENDABORT_ERROR: + tmp = "SUB ENDABORT ERROR"; + break; + } + return pstrdup(tmp); + } + + #ifdef SUBTRANSACTIONS + /* + * TransStateAsString + * + * Returns the transaction internal state as a palloc'ed string + */ + char * + TransStateAsString(TransState state) + { + char *tmp = ""; + + switch (state) { + case TRANS_DEFAULT: + tmp = "DEFAULT"; + break; + case TRANS_START: + tmp = "START"; + break; + case TRANS_COMMIT: + tmp = "COMMIT"; + break; + case TRANS_ABORT: + tmp = "ABORT"; + break; + case TRANS_INPROGRESS: + tmp = "INPROGRESS"; + break; + } + return pstrdup(tmp); + } + #endif /* SUBTRANSACTIONS */ /* * XLOG support routines Index: src/backend/commands/async.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/commands/async.c,v retrieving revision 1.109 diff -c -r1.109 async.c *** src/backend/commands/async.c 8 Feb 2004 22:28:56 -0000 1.109 --- src/backend/commands/async.c 26 Apr 2004 02:54:03 -0000 *************** *** 96,104 **** * in the current transaction. We do not actually perform a NOTIFY until * and unless the transaction commits. pendingNotifies is NIL if no * NOTIFYs have been done in the current transaction. The List nodes and ! * referenced strings are all palloc'd in TopTransactionContext. */ - static List *pendingNotifies = NIL; /* * State for inbound notifies consists of two flags: one saying whether --- 96,105 ---- * in the current transaction. We do not actually perform a NOTIFY until * and unless the transaction commits. pendingNotifies is NIL if no * NOTIFYs have been done in the current transaction. The List nodes and ! * referenced strings are all palloc'd in CommitContext. The List is ! * kept in the TransactionState struct, and is handled by ! * access/transam/xact.c; we get and set it from there whenever needed. */ /* * State for inbound notifies consists of two flags: one saying whether *************** *** 159,165 **** oldcontext = MemoryContextSwitchTo(TopTransactionContext); ! pendingNotifies = lcons(pstrdup(relname), pendingNotifies); MemoryContextSwitchTo(oldcontext); } --- 160,167 ---- oldcontext = MemoryContextSwitchTo(TopTransactionContext); ! TransactionSetNotifies(lcons(pstrdup(relname), ! TransactionGetNotifies())); MemoryContextSwitchTo(oldcontext); } *************** *** 433,439 **** char repl[Natts_pg_listener], nulls[Natts_pg_listener]; ! if (pendingNotifies == NIL) return; /* no NOTIFY statements in this * transaction */ --- 435,441 ---- char repl[Natts_pg_listener], nulls[Natts_pg_listener]; ! if (TransactionGetNotifies() == NIL) return; /* no NOTIFY statements in this * transaction */ *************** *** 915,927 **** elog(INFO, "NOTIFY for %s", relname); } ! /* Does pendingNotifies include the given relname? */ static bool AsyncExistsPendingNotify(const char *relname) { List *p; ! foreach(p, pendingNotifies) { /* Use NAMEDATALEN for relname comparison. DZ - 26-08-1996 */ if (strncmp((const char *) lfirst(p), relname, NAMEDATALEN) == 0) --- 917,929 ---- elog(INFO, "NOTIFY for %s", relname); } ! /* Does the pendingNotifies list include the given relname? */ static bool AsyncExistsPendingNotify(const char *relname) { List *p; ! foreach(p, TransactionGetNotifies()) { /* Use NAMEDATALEN for relname comparison. DZ - 26-08-1996 */ if (strncmp((const char *) lfirst(p), relname, NAMEDATALEN) == 0) *************** *** 938,946 **** /* * We used to have to explicitly deallocate the list members and * nodes, because they were malloc'd. Now, since we know they are ! * palloc'd in TopTransactionContext, we need not do that --- they'll * go away automatically at transaction exit. We need only reset the * list head pointer. */ ! pendingNotifies = NIL; } --- 940,948 ---- /* * We used to have to explicitly deallocate the list members and * nodes, because they were malloc'd. Now, since we know they are ! * palloc'd in CommitContext, we need not do that --- they'll * go away automatically at transaction exit. We need only reset the * list head pointer. */ ! TransactionSetNotifies(NIL); } Index: src/backend/storage/smgr/smgr.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/storage/smgr/smgr.c,v retrieving revision 1.70 diff -c -r1.70 smgr.c *** src/backend/storage/smgr/smgr.c 11 Feb 2004 22:55:25 -0000 1.70 --- src/backend/storage/smgr/smgr.c 26 Apr 2004 04:32:27 -0000 *************** *** 17,22 **** --- 17,23 ---- */ #include "postgres.h" + #include "access/xact.h" #include "storage/bufmgr.h" #include "storage/freespace.h" #include "storage/ipc.h" *************** *** 77,98 **** * executed immediately, but is just entered in the list. When and if * the transaction commits, we can delete the physical file. * ! * NOTE: the list is kept in TopMemoryContext to be sure it won't disappear ! * unbetimes. It'd probably be OK to keep it in TopTransactionContext, ! * but I'm being paranoid. */ ! typedef struct PendingRelDelete { RelFileNode relnode; /* relation that may need to be deleted */ int which; /* which storage manager? */ bool isTemp; /* is it a temporary relation? */ bool atCommit; /* T=delete at commit; F=delete at abort */ ! struct PendingRelDelete *next; /* linked-list link */ ! } PendingRelDelete; ! ! static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */ /* * Declarations for smgr-related XLOG records --- 78,95 ---- * executed immediately, but is just entered in the list. When and if * the transaction commits, we can delete the physical file. * ! * The list is kept in CommitContext. */ ! typedef struct PendingRelDeleteData { RelFileNode relnode; /* relation that may need to be deleted */ int which; /* which storage manager? */ bool isTemp; /* is it a temporary relation? */ bool atCommit; /* T=delete at commit; F=delete at abort */ ! } PendingRelDeleteData; + typedef PendingRelDeleteData *PendingRelDelete; /* * Declarations for smgr-related XLOG records *************** *** 300,306 **** XLogRecPtr lsn; XLogRecData rdata; xl_smgr_create xlrec; ! PendingRelDelete *pending; if (! (*(smgrsw[reln->smgr_which].smgr_create)) (reln, isRedo)) ereport(ERROR, --- 297,304 ---- XLogRecPtr lsn; XLogRecData rdata; xl_smgr_create xlrec; ! PendingRelDelete pending; ! MemoryContext old_cxt; if (! (*(smgrsw[reln->smgr_which].smgr_create)) (reln, isRedo)) ereport(ERROR, *************** *** 326,340 **** lsn = XLogInsert(RM_SMGR_ID, XLOG_SMGR_CREATE | XLOG_NO_TRAN, &rdata); /* Add the relation to the list of stuff to delete at abort */ ! pending = (PendingRelDelete *) ! MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); pending->relnode = reln->smgr_rnode; pending->which = reln->smgr_which; pending->isTemp = isTemp; pending->atCommit = false; /* delete if abort */ ! pending->next = pendingDeletes; ! pendingDeletes = pending; } /* --- 324,342 ---- lsn = XLogInsert(RM_SMGR_ID, XLOG_SMGR_CREATE | XLOG_NO_TRAN, &rdata); + old_cxt = MemoryContextSwitchTo(CommitContext); + /* Add the relation to the list of stuff to delete at abort */ ! pending = (PendingRelDelete) palloc(sizeof(PendingRelDeleteData)); pending->relnode = reln->smgr_rnode; pending->which = reln->smgr_which; pending->isTemp = isTemp; pending->atCommit = false; /* delete if abort */ ! ! TransactionSetPendingDeletes(lcons(pending, ! TransactionGetPendingDeletes())); ! ! MemoryContextSwitchTo(old_cxt); } /* *************** *** 348,364 **** void smgrscheduleunlink(SMgrRelation reln, bool isTemp) { ! PendingRelDelete *pending; /* Add the relation to the list of stuff to delete at commit */ ! pending = (PendingRelDelete *) ! MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); pending->relnode = reln->smgr_rnode; pending->which = reln->smgr_which; pending->isTemp = isTemp; pending->atCommit = true; /* delete if commit */ ! pending->next = pendingDeletes; ! pendingDeletes = pending; /* * NOTE: if the relation was created in this transaction, it will now --- 350,369 ---- void smgrscheduleunlink(SMgrRelation reln, bool isTemp) { ! PendingRelDelete pending; ! MemoryContext old_cxt; ! ! old_cxt = MemoryContextSwitchTo(CommitContext); /* Add the relation to the list of stuff to delete at commit */ ! pending = (PendingRelDelete) palloc(sizeof(PendingRelDeleteData)); pending->relnode = reln->smgr_rnode; pending->which = reln->smgr_which; pending->isTemp = isTemp; pending->atCommit = true; /* delete if commit */ ! ! TransactionSetPendingDeletes(lcons(pending, ! TransactionGetPendingDeletes())); /* * NOTE: if the relation was created in this transaction, it will now *************** *** 372,377 **** --- 377,384 ---- /* Now close the file and throw away the hashtable entry */ smgrclose(reln); + + MemoryContextSwitchTo(old_cxt); } /* *************** *** 573,592 **** void smgrDoPendingDeletes(bool isCommit) { ! while (pendingDeletes != NULL) { ! PendingRelDelete *pending = pendingDeletes; - pendingDeletes = pending->next; if (pending->atCommit == isCommit) smgr_internal_unlink(pending->relnode, pending->which, pending->isTemp, false); - pfree(pending); } } /* * smgrGetPendingDeletes() -- Get a list of relations to be deleted. * --- 580,644 ---- void smgrDoPendingDeletes(bool isCommit) { ! List *p; ! ! foreach (p, TransactionGetPendingDeletes()) { ! PendingRelDelete pending = lfirst(p); if (pending->atCommit == isCommit) smgr_internal_unlink(pending->relnode, pending->which, pending->isTemp, false); } + + TransactionSetPendingDeletes(NIL); } + #ifdef SUBTRANSACTIONS + /* + * smgrAbortSubTransaction() --- Take care of subtransaction abort. + * + * Delete created relations and forget about deleted relations. + * We don't care about these operations anymore because we know this + * subtransaction will not commit. + */ + void + smgrAbortSubTransaction(void) + { + List *p; + + foreach (p, TransactionGetPendingDeletes()) + { + PendingRelDelete pending = lfirst(p); + + if (pending->atCommit == false) + smgr_internal_unlink(pending->relnode, + pending->which, + pending->isTemp, + false); + } + } + + /* + * smgrCommitSubTransaction() --- Take care of subtransaction commit. + * + * Reassign all items in the pending deletes list to the parent transaction. + */ + void + smgrCommitSubTransaction(void) + { + List *pending, + *parentPending; + + pending = TransactionGetPendingDeletes(); + parentPending = TransactionGetParentPendingDeletes(); + + TransactionSetParentPendingDeletes(nconc(parentPending, pending)); + } + #endif /* SUBTRANSACTIONS */ + /* * smgrGetPendingDeletes() -- Get a list of relations to be deleted. * *************** *** 599,609 **** { int nrels; RelFileNode *rptr; ! PendingRelDelete *pending; nrels = 0; ! for (pending = pendingDeletes; pending != NULL; pending = pending->next) { if (pending->atCommit == forCommit) nrels++; } --- 651,663 ---- { int nrels; RelFileNode *rptr; ! List *p; nrels = 0; ! foreach (p, TransactionGetPendingDeletes()) { + PendingRelDelete pending = lfirst(p); + if (pending->atCommit == forCommit) nrels++; } *************** *** 614,621 **** } rptr = (RelFileNode *) palloc(nrels * sizeof(RelFileNode)); *ptr = rptr; ! for (pending = pendingDeletes; pending != NULL; pending = pending->next) { if (pending->atCommit == forCommit) *rptr++ = pending->relnode; } --- 668,677 ---- } rptr = (RelFileNode *) palloc(nrels * sizeof(RelFileNode)); *ptr = rptr; ! foreach (p, TransactionGetPendingDeletes()) { + PendingRelDelete pending = lfirst(p); + if (pending->atCommit == forCommit) *rptr++ = pending->relnode; } Index: src/backend/tcop/postgres.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/tcop/postgres.c,v retrieving revision 1.401 diff -c -r1.401 postgres.c *** src/backend/tcop/postgres.c 25 Apr 2004 18:23:56 -0000 1.401 --- src/backend/tcop/postgres.c 25 Apr 2004 21:49:59 -0000 *************** *** 841,846 **** --- 841,849 ---- TransactionStmt *stmt = (TransactionStmt *) parsetree; if (stmt->kind == TRANS_STMT_COMMIT || + #ifdef SUBTRANSACTIONS + stmt->kind == TRANS_STMT_BEGIN || + #endif stmt->kind == TRANS_STMT_ROLLBACK) allowit = true; } Index: src/backend/utils/mmgr/README =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/utils/mmgr/README,v retrieving revision 1.5 diff -c -r1.5 README *** src/backend/utils/mmgr/README 29 Nov 2003 19:52:04 -0000 1.5 --- src/backend/utils/mmgr/README 26 Apr 2004 03:48:13 -0000 *************** *** 70,76 **** We could even consider getting rid of CurrentMemoryContext entirely, instead requiring the target memory context for allocation to be specified explicitly. But I think that would be too much notational overhead --- ! we'd have to pass an apppropriate memory context to called routines in many places. For example, the copyObject routines would need to be passed a context, as would function execution routines that return a pass-by-reference datatype. And what of routines that temporarily --- 70,76 ---- We could even consider getting rid of CurrentMemoryContext entirely, instead requiring the target memory context for allocation to be specified explicitly. But I think that would be too much notational overhead --- ! we'd have to pass an appropriate memory context to called routines in many places. For example, the copyObject routines would need to be passed a context, as would function execution routines that return a pass-by-reference datatype. And what of routines that temporarily *************** *** 157,170 **** single transaction or portal. TopTransactionContext --- this holds everything that lives until end of ! transaction (longer than one statement within a transaction!). An example ! of what has to be here is the list of pending NOTIFY messages to be sent ! at xact commit. This context will be reset, and all its children deleted, ! at conclusion of each transaction cycle. Note: this context is NOT ! cleared immediately upon error; its contents will survive until the ! transaction block is exited by COMMIT/ROLLBACK. ! (If we ever implement nested transactions, TopTransactionContext may need ! to be split into a true "top" pointer and a "current transaction" pointer.) QueryContext --- this is not actually a separate context, but a global variable pointing to the context that holds the current command's parse --- 157,173 ---- single transaction or portal. TopTransactionContext --- this holds everything that lives until end of ! transaction (longer than one statement within a transaction!). This context ! will be reset, and all its children deleted, at conclusion of each ! transaction cycle. Note: this context is NOT cleared immediately upon ! error; its contents will survive until the transaction block is exited by ! COMMIT/ROLLBACK. ! ! CommitContext --- this holds data local to a transaction that has to be ! freed whenever a subtransaction aborts, but which should survive if it ! commits. For example, the list of pending NOTIFY messages has to live here, ! because we don't need to keep messages issued within aborted ! subtransactions. QueryContext --- this is not actually a separate context, but a global variable pointing to the context that holds the current command's parse Index: src/backend/utils/mmgr/mcxt.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/utils/mmgr/mcxt.c,v retrieving revision 1.44 diff -c -r1.44 mcxt.c *** src/backend/utils/mmgr/mcxt.c 29 Nov 2003 19:52:04 -0000 1.44 --- src/backend/utils/mmgr/mcxt.c 21 Apr 2004 14:26:41 -0000 *************** *** 45,50 **** --- 45,51 ---- MemoryContext CacheMemoryContext = NULL; MemoryContext MessageContext = NULL; MemoryContext TopTransactionContext = NULL; + MemoryContext CommitContext = NULL; /* These two are transient links to contexts owned by other objects: */ MemoryContext QueryContext = NULL; Index: src/backend/utils/mmgr/portalmem.c =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/backend/utils/mmgr/portalmem.c,v retrieving revision 1.64 diff -c -r1.64 portalmem.c *** src/backend/utils/mmgr/portalmem.c 3 Feb 2004 17:34:03 -0000 1.64 --- src/backend/utils/mmgr/portalmem.c 25 Apr 2004 16:51:24 -0000 *************** *** 511,513 **** --- 511,603 ---- PortalDrop(portal, true); } } + + #ifdef SUBTRANSACTIONS + /* + * Subtransaction commit handling for portals. + * + * Reassign all portals created by the current subtransaction to the + * parent transaction. + */ + void + AtSubCommit_Portals(TransactionId parentXid) + { + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + TransactionId curXid = GetCurrentTransactionId(); + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + if (portal->createXact == curXid) + portal->createXact = parentXid; + } + } + + /* + * Subtransaction abort handling for portals. + * + * Deactivate all portals created during to the finishing subtransaction. + * Note that per AtSubCommit_Portals, this will catch portals created + * in descendents of the finishing subtransaction too. + */ + void + AtSubAbort_Portals(void) + { + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + TransactionId curXid = GetCurrentTransactionId(); + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + if (portal->createXact != curXid) + continue; + + portal->portalActive = false; + if (PointerIsValid(portal->cleanup)) + { + (*portal->cleanup) (portal, true); + portal->cleanup = NULL; + } + } + } + + /* + * Subtransaction cleanup handling for portals. + * + * Drop all portals created in the aborting subtransaction and all + * its descendents. + */ + void + AtSubCleanup_Portals(void) + { + HASH_SEQ_STATUS status; + PortalHashEnt *hentry; + TransactionId curXid = GetCurrentTransactionId(); + + hash_seq_init(&status, PortalHashTable); + + while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL) + { + Portal portal = hentry->portal; + + if (portal->createXact != curXid) + continue; + + /* + * Let's just make sure no one's active... + */ + portal->portalActive = false; + + /* Zap it with prejudice. */ + PortalDrop(portal, true); + } + } + #endif /* SUBTRANSACTIONS */ Index: src/include/pg_config_manual.h =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/include/pg_config_manual.h,v retrieving revision 1.12 diff -c -r1.12 pg_config_manual.h *** src/include/pg_config_manual.h 24 Mar 2004 22:40:29 -0000 1.12 --- src/include/pg_config_manual.h 26 Apr 2004 14:53:01 -0000 *************** *** 235,237 **** --- 235,239 ---- /* #define ACLDEBUG */ /* #define RTDEBUG */ /* #define GISTDEBUG */ + + #define SUBTRANSACTIONS Index: src/include/access/xact.h =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/include/access/xact.h,v retrieving revision 1.62 diff -c -r1.62 xact.h *** src/include/access/xact.h 5 Apr 2004 03:11:39 -0000 1.62 --- src/include/access/xact.h 26 Apr 2004 04:19:06 -0000 *************** *** 63,69 **** TBLOCK_INPROGRESS, TBLOCK_END, TBLOCK_ABORT, ! TBLOCK_ENDABORT } TBlockState; /* --- 63,78 ---- 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; /* *************** *** 82,87 **** --- 91,109 ---- int startTimeUsec; TransState state; TBlockState blockState; + #ifdef SUBTRANSACTIONS + int nestingLevel; + MemoryContext commitContext; + void *deferredTriggers; + #endif /* SUBTRANSACTIONS */ + List *pendingDeletes; + List *notifies; + #ifdef SUBTRANSACTIONS + LWLockId *LWLocks; + List *locks; + List *childXids; + struct TransactionStateData *parent; + #endif /* SUBTRANSACTIONS */ } TransactionStateData; typedef TransactionStateData *TransactionState; *************** *** 130,135 **** --- 152,168 ---- extern CommandId GetCurrentCommandId(void); extern AbsoluteTime GetCurrentTransactionStartTime(void); extern AbsoluteTime GetCurrentTransactionStartTimeUsec(int *usec); + + extern List *TransactionGetPendingDeletes(void); + extern void TransactionSetPendingDeletes(List *); + extern List *TransactionGetParentPendingDeletes(void); + extern void TransactionSetParentPendingDeletes(List *); + + extern List *TransactionGetNotifies(void); + extern void TransactionSetNotifies(List *); + extern List *TransactionGetParentNotifies(void); + extern void TransactionSetParentNotifies(List *); + extern bool TransactionIdIsCurrentTransactionId(TransactionId xid); extern bool CommandIdIsCurrentCommandId(CommandId cid); extern void CommandCounterIncrement(void); Index: src/include/storage/smgr.h =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/include/storage/smgr.h,v retrieving revision 1.41 diff -c -r1.41 smgr.h *** src/include/storage/smgr.h 11 Feb 2004 22:55:26 -0000 1.41 --- src/include/storage/smgr.h 21 Apr 2004 15:33:45 -0000 *************** *** 61,66 **** --- 61,68 ---- extern BlockNumber smgrnblocks(SMgrRelation reln); extern BlockNumber smgrtruncate(SMgrRelation reln, BlockNumber nblocks); extern void smgrDoPendingDeletes(bool isCommit); + extern void smgrAbortSubTransaction(void); + extern void smgrCommitSubTransaction(void); extern int smgrGetPendingDeletes(bool forCommit, RelFileNode **ptr); extern void smgrcommit(void); extern void smgrabort(void); Index: src/include/utils/memutils.h =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/include/utils/memutils.h,v retrieving revision 1.54 diff -c -r1.54 memutils.h *** src/include/utils/memutils.h 29 Nov 2003 22:41:15 -0000 1.54 --- src/include/utils/memutils.h 21 Apr 2004 14:26:44 -0000 *************** *** 70,75 **** --- 70,76 ---- extern DLLIMPORT MemoryContext CacheMemoryContext; extern DLLIMPORT MemoryContext MessageContext; extern DLLIMPORT MemoryContext TopTransactionContext; + extern DLLIMPORT MemoryContext CommitContext; /* These two are transient links to contexts owned by other objects: */ extern DLLIMPORT MemoryContext QueryContext; Index: src/include/utils/portal.h =================================================================== RCS file: /home/alvherre/cvs/pgsql-server/src/include/utils/portal.h,v retrieving revision 1.48 diff -c -r1.48 portal.h *** src/include/utils/portal.h 29 Nov 2003 22:41:16 -0000 1.48 --- src/include/utils/portal.h 25 Apr 2004 16:51:03 -0000 *************** *** 167,172 **** --- 167,177 ---- extern void AtCommit_Portals(void); extern void AtAbort_Portals(void); extern void AtCleanup_Portals(void); + #ifdef SUBTRANSACTIONS + extern void AtSubCommit_Portals(TransactionId); + extern void AtSubAbort_Portals(void); + extern void AtSubCleanup_Portals(void); + #endif /* SUBTRANSACTIONS */ extern Portal CreatePortal(const char *name, bool allowDup, bool dupSilent); extern Portal CreateNewPortal(void); extern void PortalDrop(Portal portal, bool isError);