diff options
Diffstat (limited to 'src/backend/commands')
-rw-r--r-- | src/backend/commands/async.c | 72 | ||||
-rw-r--r-- | src/backend/commands/tablecmds.c | 71 | ||||
-rw-r--r-- | src/backend/commands/trigger.c | 577 | ||||
-rw-r--r-- | src/backend/commands/vacuum.c | 6 | ||||
-rw-r--r-- | src/backend/commands/variable.c | 11 |
5 files changed, 559 insertions, 178 deletions
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c index 847f73ff06a..8e53d6af7d7 100644 --- a/src/backend/commands/async.c +++ b/src/backend/commands/async.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/async.c,v 1.112 2004/05/26 04:41:10 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/commands/async.c,v 1.113 2004/07/01 00:50:10 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -97,11 +97,17 @@ * State for outbound notifies consists of a list of all relnames NOTIFYed * 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. + * NOTIFYs have been done in the current transaction. + * + * The list is kept in CurTransactionContext. In subtransactions, each + * subtransaction has its own list in its own CurTransactionContext, but + * successful subtransactions attach their lists to their parent's list. + * Failed subtransactions simply discard their lists. */ static List *pendingNotifies = NIL; +static List *upperPendingNotifies = NIL; /* list of upper-xact lists */ + /* * State for inbound notifies consists of two flags: one saying whether * the signal handler is currently allowed to call ProcessIncomingNotify @@ -155,11 +161,11 @@ Async_Notify(char *relname) { /* * The name list needs to live until end of transaction, so store - * it in the top transaction context. + * it in the transaction context. */ MemoryContext oldcontext; - oldcontext = MemoryContextSwitchTo(TopTransactionContext); + oldcontext = MemoryContextSwitchTo(CurTransactionContext); pendingNotifies = lcons(pstrdup(relname), pendingNotifies); @@ -607,6 +613,60 @@ AtAbort_Notify(void) } /* + * AtSubStart_Notify() --- Take care of subtransaction start. + * + * Push empty state for the new subtransaction. + */ +void +AtSubStart_Notify(void) +{ + MemoryContext old_cxt; + + /* Keep the list-of-lists in TopTransactionContext for simplicity */ + old_cxt = MemoryContextSwitchTo(TopTransactionContext); + + upperPendingNotifies = lcons(pendingNotifies, upperPendingNotifies); + + pendingNotifies = NIL; + + MemoryContextSwitchTo(old_cxt); +} + +/* + * AtSubCommit_Notify() --- Take care of subtransaction commit. + * + * Reassign all items in the pending notifies list to the parent transaction. + */ +void +AtSubCommit_Notify(void) +{ + List *parentPendingNotifies; + + parentPendingNotifies = (List *) linitial(upperPendingNotifies); + upperPendingNotifies = list_delete_first(upperPendingNotifies); + + /* + * We could try to eliminate duplicates here, but it seems not worthwhile. + */ + pendingNotifies = list_concat(parentPendingNotifies, pendingNotifies); +} + +/* + * AtSubAbort_Notify() --- Take care of subtransaction abort. + */ +void +AtSubAbort_Notify(void) +{ + /* + * All we have to do is pop the stack --- the notifies made in this + * subxact are no longer interesting, and the space will be freed when + * CurTransactionContext is recycled. + */ + pendingNotifies = (List *) linitial(upperPendingNotifies); + upperPendingNotifies = list_delete_first(upperPendingNotifies); +} + +/* *-------------------------------------------------------------- * NotifyInterruptHandler * @@ -951,7 +1011,7 @@ ClearPendingNotifies(void) /* * 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 + * palloc'd in CurTransactionContext, we need not do that --- they'll * go away automatically at transaction exit. We need only reset the * list head pointer. */ diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index cfd8bd80cc0..392822abf50 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.117 2004/06/25 21:55:53 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.118 2004/07/01 00:50:10 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -76,8 +76,8 @@ typedef struct OnCommitItem * entries in the list until commit so that we can roll back if * needed. */ - bool created_in_cur_xact; - bool deleted_in_cur_xact; + TransactionId creating_xid; + TransactionId deleting_xid; } OnCommitItem; static List *on_commits = NIL; @@ -5483,8 +5483,8 @@ register_on_commit_action(Oid relid, OnCommitAction action) oc = (OnCommitItem *) palloc(sizeof(OnCommitItem)); oc->relid = relid; oc->oncommit = action; - oc->created_in_cur_xact = true; - oc->deleted_in_cur_xact = false; + oc->creating_xid = GetCurrentTransactionId(); + oc->deleting_xid = InvalidTransactionId; on_commits = lcons(oc, on_commits); @@ -5507,7 +5507,7 @@ remove_on_commit_action(Oid relid) if (oc->relid == relid) { - oc->deleted_in_cur_xact = true; + oc->deleting_xid = GetCurrentTransactionId(); break; } } @@ -5522,6 +5522,7 @@ remove_on_commit_action(Oid relid) void PreCommit_on_commit_actions(void) { + TransactionId xid = GetCurrentTransactionId(); ListCell *l; foreach(l, on_commits) @@ -5529,7 +5530,7 @@ PreCommit_on_commit_actions(void) OnCommitItem *oc = (OnCommitItem *) lfirst(l); /* Ignore entry if already dropped in this xact */ - if (oc->deleted_in_cur_xact) + if (oc->deleting_xid == xid) continue; switch (oc->oncommit) @@ -5556,7 +5557,7 @@ PreCommit_on_commit_actions(void) * remove_on_commit_action, so the entry should get * marked as deleted. */ - Assert(oc->deleted_in_cur_xact); + Assert(oc->deleting_xid == xid); break; } } @@ -5572,7 +5573,7 @@ PreCommit_on_commit_actions(void) * during abort, remove those created during this transaction. */ void -AtEOXact_on_commit_actions(bool isCommit) +AtEOXact_on_commit_actions(bool isCommit, TransactionId xid) { ListCell *cur_item; ListCell *prev_item; @@ -5584,8 +5585,8 @@ AtEOXact_on_commit_actions(bool isCommit) { OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item); - if (isCommit ? oc->deleted_in_cur_xact : - oc->created_in_cur_xact) + if (isCommit ? TransactionIdEquals(oc->deleting_xid, xid) : + TransactionIdEquals(oc->creating_xid, xid)) { /* cur_item must be removed */ on_commits = list_delete_cell(on_commits, cur_item, prev_item); @@ -5598,8 +5599,52 @@ AtEOXact_on_commit_actions(bool isCommit) else { /* cur_item must be preserved */ - oc->deleted_in_cur_xact = false; - oc->created_in_cur_xact = false; + oc->creating_xid = InvalidTransactionId; + oc->deleting_xid = InvalidTransactionId; + prev_item = cur_item; + cur_item = lnext(prev_item); + } + } +} + +/* + * Post-subcommit or post-subabort cleanup for ON COMMIT management. + * + * During subabort, we can immediately remove entries created during this + * subtransaction. During subcommit, just relabel entries marked during + * this subtransaction as being the parent's responsibility. + */ +void +AtEOSubXact_on_commit_actions(bool isCommit, TransactionId childXid, + TransactionId parentXid) +{ + ListCell *cur_item; + ListCell *prev_item; + + prev_item = NULL; + cur_item = list_head(on_commits); + + while (cur_item != NULL) + { + OnCommitItem *oc = (OnCommitItem *) lfirst(cur_item); + + if (!isCommit && TransactionIdEquals(oc->creating_xid, childXid)) + { + /* cur_item must be removed */ + on_commits = list_delete_cell(on_commits, cur_item, prev_item); + pfree(oc); + if (prev_item) + cur_item = lnext(prev_item); + else + cur_item = list_head(on_commits); + } + else + { + /* cur_item must be preserved */ + if (TransactionIdEquals(oc->creating_xid, childXid)) + oc->creating_xid = parentXid; + if (TransactionIdEquals(oc->deleting_xid, childXid)) + oc->deleting_xid = isCommit ? parentXid : InvalidTransactionId; prev_item = cur_item; cur_item = lnext(prev_item); } diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index cfbd58e4282..15f4cfa8dcb 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.165 2004/05/26 04:41:12 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.166 2004/07/01 00:50:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -50,9 +50,6 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata, MemoryContext per_tuple_context); static void DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, HeapTuple oldtup, HeapTuple newtup); -static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno, - Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo, - MemoryContext per_tuple_context); /* @@ -1639,47 +1636,130 @@ ltrmark:; /* ---------- * Deferred trigger stuff + * + * The DeferredTriggersData struct holds data about pending deferred + * trigger events during the current transaction tree. The struct and + * most of its subsidiary data are kept in TopTransactionContext; however + * the individual event records are kept in CurTransactionContext, so that + * they will easily go away during subtransaction abort. + * + * DeferredTriggersData has the following fields: + * + * state keeps track of the deferred state of each trigger + * (including the global state). This is saved and restored across + * failed subtransactions. + * + * events is the head of the list of events. + * + * tail_thisxact points to the tail of the list, for the current + * transaction (whether main transaction or subtransaction). We always + * append to the list using this pointer. + * + * events_imm points to the last element scanned by the last + * deferredTriggerInvokeEvents call. We can use this to avoid rescanning + * unnecessarily; if it's NULL, the scan should start at the head of the + * list. Its name comes from the fact that it's set to the last event fired + * by the last call to immediate triggers. + * + * tail_stack and imm_stack are stacks of pointer, which hold the pointers + * to the tail and the "immediate" events as of the start of a subtransaction. + * We use to revert them when aborting the subtransaction. + * + * state_stack is a stack of pointers to saved copies of the deferred-trigger + * state data; each subtransaction level that modifies that state first + * saves a copy, which we use to restore the state if we abort. + * + * numpushed and numalloc keep control of allocation and storage in the above + * stacks. numpushed is essentially the current subtransaction nesting depth. + * + * XXX We need to be able to save the per-event data in a file if it grows too + * large. * ---------- */ -typedef struct DeferredTriggersData +/* Per-item data */ +typedef struct DeferredTriggerEventItem { - /* Internal data is held in a per-transaction memory context */ - MemoryContext deftrig_cxt; - /* ALL DEFERRED or ALL IMMEDIATE */ - bool deftrig_all_isset; - bool deftrig_all_isdeferred; - /* Per trigger state */ - List *deftrig_trigstates; - /* List of pending deferred triggers. Previous comment below */ - DeferredTriggerEvent deftrig_events; - DeferredTriggerEvent deftrig_events_imm; - DeferredTriggerEvent deftrig_event_tail; -} DeferredTriggersData; + Oid dti_tgoid; + TransactionId dti_done_xid; + int32 dti_state; +} DeferredTriggerEventItem; -/* ---------- - * deftrig_events, deftrig_event_tail: - * The list of pending deferred trigger events during the current transaction. +typedef struct DeferredTriggerEventData *DeferredTriggerEvent; + +/* Per-event data */ +typedef struct DeferredTriggerEventData +{ + DeferredTriggerEvent dte_next; /* list link */ + int32 dte_event; + Oid dte_relid; + TransactionId dte_done_xid; + ItemPointerData dte_oldctid; + ItemPointerData dte_newctid; + int32 dte_n_items; + /* dte_item is actually a variable-size array, of length dte_n_items */ + DeferredTriggerEventItem dte_item[1]; +} DeferredTriggerEventData; + +/* Per-trigger status data */ +typedef struct DeferredTriggerStatusData +{ + Oid dts_tgoid; + bool dts_tgisdeferred; +} DeferredTriggerStatusData; + +typedef struct DeferredTriggerStatusData *DeferredTriggerStatus; + + +/* + * Trigger deferral status data. * - * deftrig_events is the head, deftrig_event_tail is the last entry. - * Because this can grow pretty large, we don't use separate List nodes, - * but instead thread the list through the dte_next fields of the member - * nodes. Saves just a few bytes per entry, but that adds up. + * We make this a single palloc'd object so it can be copied and freed easily. * - * deftrig_events_imm holds the tail pointer as of the last - * deferredTriggerInvokeEvents call; we can use this to avoid rescanning - * entries unnecessarily. It is NULL if deferredTriggerInvokeEvents - * hasn't run since the last state change. + * all_isset and all_isdeferred are used to keep track + * of SET CONSTRAINTS ALL {DEFERRED, IMMEDIATE}. * - * XXX Need to be able to shove this data out to a file if it grows too - * large... - * ---------- + * trigstates[] stores per-trigger tgisdeferred settings. */ +typedef struct DeferredTriggerStateData +{ + bool all_isset; + bool all_isdeferred; + int numstates; /* number of trigstates[] entries in use */ + int numalloc; /* allocated size of trigstates[] */ + DeferredTriggerStatusData trigstates[1]; /* VARIABLE LENGTH ARRAY */ +} DeferredTriggerStateData; + +typedef DeferredTriggerStateData *DeferredTriggerState; + +/* Per-transaction data */ +typedef struct DeferredTriggersData +{ + DeferredTriggerState state; + DeferredTriggerEvent events; + DeferredTriggerEvent tail_thisxact; + DeferredTriggerEvent events_imm; + DeferredTriggerEvent *tail_stack; + DeferredTriggerEvent *imm_stack; + DeferredTriggerState *state_stack; + int numpushed; + int numalloc; +} DeferredTriggersData; typedef DeferredTriggersData *DeferredTriggers; static DeferredTriggers deferredTriggers; + +static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno, + Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo, + MemoryContext per_tuple_context); +static DeferredTriggerState DeferredTriggerStateCreate(int numalloc); +static DeferredTriggerState DeferredTriggerStateCopy(DeferredTriggerState state); +static DeferredTriggerState DeferredTriggerStateAddItem(DeferredTriggerState state, + Oid tgoid, bool tgisdeferred); + + /* ---------- * deferredTriggerCheckState() * @@ -1690,13 +1770,12 @@ static DeferredTriggers deferredTriggers; static bool deferredTriggerCheckState(Oid tgoid, int32 itemstate) { - MemoryContext oldcxt; - ListCell *sl; - DeferredTriggerStatus trigstate; + bool tgisdeferred; + int i; /* - * Not deferrable triggers (i.e. normal AFTER ROW triggers and - * constraints declared NOT DEFERRABLE, the state is always false. + * For not-deferrable triggers (i.e. normal AFTER ROW triggers and + * constraints declared NOT DEFERRABLE), the state is always false. */ if ((itemstate & TRIGGER_DEFERRED_DEFERRABLE) == 0) return false; @@ -1704,37 +1783,29 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate) /* * Lookup if we know an individual state for this trigger */ - foreach(sl, deferredTriggers->deftrig_trigstates) + for (i = 0; i < deferredTriggers->state->numstates; i++) { - trigstate = (DeferredTriggerStatus) lfirst(sl); - if (trigstate->dts_tgoid == tgoid) - return trigstate->dts_tgisdeferred; + if (deferredTriggers->state->trigstates[i].dts_tgoid == tgoid) + return deferredTriggers->state->trigstates[i].dts_tgisdeferred; } /* * No individual state known - so if the user issued a SET CONSTRAINT * ALL ..., we return that instead of the triggers default state. */ - if (deferredTriggers->deftrig_all_isset) - return deferredTriggers->deftrig_all_isdeferred; + if (deferredTriggers->state->all_isset) + return deferredTriggers->state->all_isdeferred; /* * No ALL state known either, remember the default state as the - * current and return that. + * current and return that. (XXX why do we bother making a state entry?) */ - oldcxt = MemoryContextSwitchTo(deferredTriggers->deftrig_cxt); + tgisdeferred = ((itemstate & TRIGGER_DEFERRED_INITDEFERRED) != 0); + deferredTriggers->state = + DeferredTriggerStateAddItem(deferredTriggers->state, + tgoid, tgisdeferred); - trigstate = (DeferredTriggerStatus) - palloc(sizeof(DeferredTriggerStatusData)); - trigstate->dts_tgoid = tgoid; - trigstate->dts_tgisdeferred = - ((itemstate & TRIGGER_DEFERRED_INITDEFERRED) != 0); - deferredTriggers->deftrig_trigstates = - lappend(deferredTriggers->deftrig_trigstates, trigstate); - - MemoryContextSwitchTo(oldcxt); - - return trigstate->dts_tgisdeferred; + return tgisdeferred; } @@ -1747,22 +1818,18 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate) static void deferredTriggerAddEvent(DeferredTriggerEvent event) { - /* - * Since the event list could grow quite long, we keep track of the - * list tail and append there, rather than just doing a stupid - * "lappend". This avoids O(N^2) behavior for large numbers of events. - */ - event->dte_next = NULL; - if (deferredTriggers->deftrig_event_tail == NULL) + Assert(event->dte_next == NULL); + + if (deferredTriggers->tail_thisxact == NULL) { /* first list entry */ - deferredTriggers->deftrig_events = event; - deferredTriggers->deftrig_event_tail = event; + deferredTriggers->events = event; + deferredTriggers->tail_thisxact = event; } else { - deferredTriggers->deftrig_event_tail->dte_next = event; - deferredTriggers->deftrig_event_tail = event; + deferredTriggers->tail_thisxact->dte_next = event; + deferredTriggers->tail_thisxact = event; } } @@ -1915,18 +1982,18 @@ deferredTriggerInvokeEvents(bool immediate_only) /* * If immediate_only is true, then the only events that could need - * firing are those since deftrig_events_imm. (But if - * deftrig_events_imm is NULL, we must scan the entire list.) + * firing are those since events_imm. (But if + * events_imm is NULL, we must scan the entire list.) */ - if (immediate_only && deferredTriggers->deftrig_events_imm != NULL) + if (immediate_only && deferredTriggers->events_imm != NULL) { - prev_event = deferredTriggers->deftrig_events_imm; + prev_event = deferredTriggers->events_imm; event = prev_event->dte_next; } else { prev_event = NULL; - event = deferredTriggers->deftrig_events; + event = deferredTriggers->events; } while (event != NULL) @@ -1936,10 +2003,13 @@ deferredTriggerInvokeEvents(bool immediate_only) int i; /* - * Check if event is already completely done. + * Skip executing cancelled events, and events done by transactions + * that are not aborted. */ - if (!(event->dte_event & (TRIGGER_DEFERRED_DONE | - TRIGGER_DEFERRED_CANCELED))) + if (!(event->dte_event & TRIGGER_DEFERRED_CANCELED) || + (event->dte_event & TRIGGER_DEFERRED_DONE && + TransactionIdIsValid(event->dte_done_xid) && + !TransactionIdDidAbort(event->dte_done_xid))) { MemoryContextReset(per_tuple_context); @@ -1948,7 +2018,9 @@ deferredTriggerInvokeEvents(bool immediate_only) */ for (i = 0; i < event->dte_n_items; i++) { - if (event->dte_item[i].dti_state & TRIGGER_DEFERRED_DONE) + if (event->dte_item[i].dti_state & TRIGGER_DEFERRED_DONE && + TransactionIdIsValid(event->dte_item[i].dti_done_xid) && + !(TransactionIdDidAbort(event->dte_item[i].dti_done_xid))) continue; /* @@ -2003,6 +2075,7 @@ deferredTriggerInvokeEvents(bool immediate_only) per_tuple_context); event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE; + event->dte_item[i].dti_done_xid = GetCurrentTransactionId(); } /* end loop over items within event */ } @@ -2022,23 +2095,27 @@ deferredTriggerInvokeEvents(bool immediate_only) } else { - /* Done */ - if (immediate_only) + /* + * We can drop an item if it's done, but only if we're not + * inside a subtransaction because it could abort later on. + * We will want to check the item again if it does. + */ + if (immediate_only && !IsSubTransaction()) { /* delink it from list and free it */ if (prev_event) prev_event->dte_next = next_event; else - deferredTriggers->deftrig_events = next_event; + deferredTriggers->events = next_event; pfree(event); } else { /* - * We will clean up later, but just for paranoia's sake, - * mark the event done. + * Mark the event done. */ event->dte_event |= TRIGGER_DEFERRED_DONE; + event->dte_done_xid = GetCurrentTransactionId(); } } @@ -2046,10 +2123,10 @@ deferredTriggerInvokeEvents(bool immediate_only) } /* Update list tail pointer in case we just deleted tail event */ - deferredTriggers->deftrig_event_tail = prev_event; + deferredTriggers->tail_thisxact = prev_event; /* Set the immediate event pointer for next time */ - deferredTriggers->deftrig_events_imm = prev_event; + deferredTriggers->events_imm = prev_event; /* Release working resources */ if (rel) @@ -2060,23 +2137,6 @@ deferredTriggerInvokeEvents(bool immediate_only) MemoryContextDelete(per_tuple_context); } - -/* ---------- - * DeferredTriggerInit() - * - * Initialize the deferred trigger mechanism. This is called during - * backend startup and is guaranteed to be before the first of all - * transactions. - * ---------- - */ -void -DeferredTriggerInit(void) -{ - /* Nothing to do */ - ; -} - - /* ---------- * DeferredTriggerBeginXact() * @@ -2087,34 +2147,24 @@ DeferredTriggerInit(void) void DeferredTriggerBeginXact(void) { - /* - * This will be changed to a special context when the nested - * transactions project moves forward. - */ - MemoryContext cxt = TopTransactionContext; - - deferredTriggers = (DeferredTriggers) MemoryContextAlloc(TopTransactionContext, - sizeof(DeferredTriggersData)); + Assert(deferredTriggers == NULL); - /* - * Create the per transaction memory context - */ - deferredTriggers->deftrig_cxt = AllocSetContextCreate(cxt, - "DeferredTriggerXact", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + deferredTriggers = (DeferredTriggers) + MemoryContextAlloc(TopTransactionContext, + sizeof(DeferredTriggersData)); /* * If unspecified, constraints default to IMMEDIATE, per SQL */ - deferredTriggers->deftrig_all_isdeferred = false; - deferredTriggers->deftrig_all_isset = false; - - deferredTriggers->deftrig_trigstates = NIL; - deferredTriggers->deftrig_events = NULL; - deferredTriggers->deftrig_events_imm = NULL; - deferredTriggers->deftrig_event_tail = NULL; + deferredTriggers->state = DeferredTriggerStateCreate(8); + deferredTriggers->events = NULL; + deferredTriggers->events_imm = NULL; + deferredTriggers->tail_thisxact = NULL; + deferredTriggers->tail_stack = NULL; + deferredTriggers->imm_stack = NULL; + deferredTriggers->state_stack = NULL; + deferredTriggers->numalloc = 0; + deferredTriggers->numpushed = 0; } @@ -2156,6 +2206,12 @@ DeferredTriggerEndXact(void) deferredTriggerInvokeEvents(false); + /* + * Forget everything we know about deferred triggers. + * + * Since all the info is in TopTransactionContext or children thereof, + * we need do nothing special to reclaim memory. + */ deferredTriggers = NULL; } @@ -2179,10 +2235,217 @@ DeferredTriggerAbortXact(void) /* * Forget everything we know about deferred triggers. + * + * Since all the info is in TopTransactionContext or children thereof, + * we need do nothing special to reclaim memory. */ deferredTriggers = NULL; } +/* + * DeferredTriggerBeginSubXact() + * + * Start a subtransaction. + */ +void +DeferredTriggerBeginSubXact(void) +{ + /* + * Ignore call if the transaction is in aborted state. + */ + if (deferredTriggers == NULL) + return; + + /* + * Allocate more space in the stacks if needed. + */ + if (deferredTriggers->numpushed == deferredTriggers->numalloc) + { + if (deferredTriggers->numalloc == 0) + { + MemoryContext old_cxt; + + old_cxt = MemoryContextSwitchTo(TopTransactionContext); + +#define DEFTRIG_INITALLOC 8 + deferredTriggers->tail_stack = (DeferredTriggerEvent *) + palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerEvent)); + deferredTriggers->imm_stack = (DeferredTriggerEvent *) + palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerEvent)); + deferredTriggers->state_stack = (DeferredTriggerState *) + palloc(DEFTRIG_INITALLOC * sizeof(DeferredTriggerState)); + deferredTriggers->numalloc = DEFTRIG_INITALLOC; + + MemoryContextSwitchTo(old_cxt); + } + else + { + /* repalloc will keep the stacks in the same context */ + deferredTriggers->numalloc *= 2; + + deferredTriggers->tail_stack = (DeferredTriggerEvent *) + repalloc(deferredTriggers->tail_stack, + deferredTriggers->numalloc * sizeof(DeferredTriggerEvent)); + deferredTriggers->imm_stack = (DeferredTriggerEvent *) + repalloc(deferredTriggers->imm_stack, + deferredTriggers->numalloc * sizeof(DeferredTriggerEvent)); + deferredTriggers->state_stack = (DeferredTriggerState *) + repalloc(deferredTriggers->state_stack, + deferredTriggers->numalloc * sizeof(DeferredTriggerState)); + } + } + + /* + * Push the current list position into the stack and reset the + * pointer. + */ + deferredTriggers->tail_stack[deferredTriggers->numpushed] = + deferredTriggers->tail_thisxact; + deferredTriggers->imm_stack[deferredTriggers->numpushed] = + deferredTriggers->events_imm; + /* State is not saved until/unless changed */ + deferredTriggers->state_stack[deferredTriggers->numpushed] = NULL; + + deferredTriggers->numpushed++; +} + +/* + * DeferredTriggerEndSubXact() + * + * The current subtransaction is ending. + */ +void +DeferredTriggerEndSubXact(bool isCommit) +{ + DeferredTriggerState state; + + /* + * Ignore call if the transaction is in aborted state. + */ + if (deferredTriggers == NULL) + return; + + /* + * Move back the "top of the stack." + */ + Assert(deferredTriggers->numpushed > 0); + + deferredTriggers->numpushed--; + + if (isCommit) + { + /* If we saved a prior state, we don't need it anymore */ + state = deferredTriggers->state_stack[deferredTriggers->numpushed]; + if (state != NULL) + pfree(state); + } + else + { + /* + * Aborting --- restore the pointers from the stacks. + */ + deferredTriggers->tail_thisxact = + deferredTriggers->tail_stack[deferredTriggers->numpushed]; + deferredTriggers->events_imm = + deferredTriggers->imm_stack[deferredTriggers->numpushed]; + + /* + * Cleanup the head and the tail of the list. + */ + if (deferredTriggers->tail_thisxact == NULL) + deferredTriggers->events = NULL; + else + deferredTriggers->tail_thisxact->dte_next = NULL; + + /* + * We don't need to free the items, since the CurTransactionContext + * will be reset shortly. + */ + + /* + * Restore the trigger state. If the saved state is NULL, then + * this subxact didn't save it, so it doesn't need restoring. + */ + state = deferredTriggers->state_stack[deferredTriggers->numpushed]; + if (state != NULL) + { + pfree(deferredTriggers->state); + deferredTriggers->state = state; + } + } +} + +/* + * Create an empty DeferredTriggerState with room for numalloc trigstates + */ +static DeferredTriggerState +DeferredTriggerStateCreate(int numalloc) +{ + DeferredTriggerState state; + + /* Behave sanely with numalloc == 0 */ + if (numalloc <= 0) + numalloc = 1; + + /* + * We assume that zeroing will correctly initialize the state values. + */ + state = (DeferredTriggerState) + MemoryContextAllocZero(TopTransactionContext, + sizeof(DeferredTriggerStateData) + + (numalloc - 1) * sizeof(DeferredTriggerStatusData)); + + state->numalloc = numalloc; + + return state; +} + +/* + * Copy a DeferredTriggerState + */ +static DeferredTriggerState +DeferredTriggerStateCopy(DeferredTriggerState origstate) +{ + DeferredTriggerState state; + + state = DeferredTriggerStateCreate(origstate->numstates); + + state->all_isset = origstate->all_isset; + state->all_isdeferred = origstate->all_isdeferred; + state->numstates = origstate->numstates; + memcpy(state->trigstates, origstate->trigstates, + origstate->numstates * sizeof(DeferredTriggerStatusData)); + + return state; +} + +/* + * Add a per-trigger item to a DeferredTriggerState. Returns possibly-changed + * pointer to the state object (it will change if we have to repalloc). + */ +static DeferredTriggerState +DeferredTriggerStateAddItem(DeferredTriggerState state, + Oid tgoid, bool tgisdeferred) +{ + if (state->numstates >= state->numalloc) + { + int newalloc = state->numalloc * 2; + + newalloc = Max(newalloc, 8); /* in case original has size 0 */ + state = (DeferredTriggerState) + repalloc(state, + sizeof(DeferredTriggerStateData) + + (newalloc - 1) * sizeof(DeferredTriggerStatusData)); + state->numalloc = newalloc; + Assert(state->numstates < state->numalloc); + } + + state->trigstates[state->numstates].dts_tgoid = tgoid; + state->trigstates[state->numstates].dts_tgisdeferred = tgisdeferred; + state->numstates++; + + return state; +} /* ---------- * DeferredTriggerSetState() @@ -2193,8 +2456,6 @@ DeferredTriggerAbortXact(void) void DeferredTriggerSetState(ConstraintsSetStmt *stmt) { - ListCell *l; - /* * Ignore call if we aren't in a transaction. */ @@ -2202,6 +2463,17 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) return; /* + * If in a subtransaction, and we didn't save the current state already, + * save it so it can be restored if the subtransaction aborts. + */ + if (deferredTriggers->numpushed > 0 && + deferredTriggers->state_stack[deferredTriggers->numpushed - 1] == NULL) + { + deferredTriggers->state_stack[deferredTriggers->numpushed - 1] = + DeferredTriggerStateCopy(deferredTriggers->state); + } + + /* * Handle SET CONSTRAINTS ALL ... */ if (stmt->constraints == NIL) @@ -2210,23 +2482,19 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) * Drop all per-transaction information about individual trigger * states. */ - list_free_deep(deferredTriggers->deftrig_trigstates); - deferredTriggers->deftrig_trigstates = NIL; + deferredTriggers->state->numstates = 0; /* * Set the per-transaction ALL state to known. */ - deferredTriggers->deftrig_all_isset = true; - deferredTriggers->deftrig_all_isdeferred = stmt->deferred; + deferredTriggers->state->all_isset = true; + deferredTriggers->state->all_isdeferred = stmt->deferred; } else { Relation tgrel; - MemoryContext oldcxt; - bool found; - DeferredTriggerStatus state; - ListCell *ls; - List *loid = NIL; + ListCell *l; + List *oidlist = NIL; /* ---------- * Handle SET CONSTRAINTS constraint-name [, ...] @@ -2241,6 +2509,7 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) ScanKeyData skey; SysScanDesc tgscan; HeapTuple htup; + bool found; /* * Check that only named constraints are set explicitly @@ -2285,7 +2554,7 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) cname))); constr_oid = HeapTupleGetOid(htup); - loid = lappend_oid(loid, constr_oid); + oidlist = lappend_oid(oidlist, constr_oid); found = true; } @@ -2305,34 +2574,28 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) * Inside of a transaction block set the trigger states of * individual triggers on transaction level. */ - oldcxt = MemoryContextSwitchTo(deferredTriggers->deftrig_cxt); - - foreach(l, loid) + foreach(l, oidlist) { - found = false; - foreach(ls, deferredTriggers->deftrig_trigstates) + Oid tgoid = lfirst_oid(l); + bool found = false; + int i; + + for (i = 0; i < deferredTriggers->state->numstates; i++) { - state = (DeferredTriggerStatus) lfirst(ls); - if (state->dts_tgoid == lfirst_oid(l)) + if (deferredTriggers->state->trigstates[i].dts_tgoid == tgoid) { - state->dts_tgisdeferred = stmt->deferred; + deferredTriggers->state->trigstates[i].dts_tgisdeferred = stmt->deferred; found = true; break; } } if (!found) { - state = (DeferredTriggerStatus) - palloc(sizeof(DeferredTriggerStatusData)); - state->dts_tgoid = lfirst_oid(l); - state->dts_tgisdeferred = stmt->deferred; - - deferredTriggers->deftrig_trigstates = - lappend(deferredTriggers->deftrig_trigstates, state); + deferredTriggers->state = + DeferredTriggerStateAddItem(deferredTriggers->state, + tgoid, stmt->deferred); } } - - MemoryContextSwitchTo(oldcxt); } /* @@ -2347,14 +2610,14 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt) * entire list, in case some deferred events are now immediately * invokable. */ - deferredTriggers->deftrig_events_imm = NULL; + deferredTriggers->events_imm = NULL; } /* ---------- * DeferredTriggerSaveEvent() * - * Called by ExecAR...Triggers() to add the event to the queue. + * Called by ExecA[RS]...Triggers() to add the event to the queue. * * NOTE: should be called only if we've determined that an event must * be added to the queue. @@ -2423,9 +2686,10 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, return; /* - * Create a new event + * Create a new event. We use the CurTransactionContext so the event + * will automatically go away if the subtransaction aborts. */ - oldcxt = MemoryContextSwitchTo(deferredTriggers->deftrig_cxt); + oldcxt = MemoryContextSwitchTo(CurTransactionContext); new_size = offsetof(DeferredTriggerEventData, dte_item[0]) + n_enabled_triggers * sizeof(DeferredTriggerEventItem); @@ -2433,6 +2697,7 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, new_event = (DeferredTriggerEvent) palloc(new_size); new_event->dte_next = NULL; new_event->dte_event = event & TRIGGER_EVENT_OPMASK; + new_event->dte_done_xid = InvalidTransactionId; if (row_trigger) new_event->dte_event |= TRIGGER_EVENT_ROW; new_event->dte_relid = rel->rd_id; @@ -2449,6 +2714,7 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, ev_item = &(new_event->dte_item[i]); ev_item->dti_tgoid = trigger->tgoid; + ev_item->dti_done_xid = InvalidTransactionId; ev_item->dti_state = ((trigger->tgdeferrable) ? TRIGGER_DEFERRED_DEFERRABLE : 0) | @@ -2517,6 +2783,7 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, * the trigger at all. */ new_event->dte_item[i].dti_state |= TRIGGER_DEFERRED_DONE; + new_event->dte_item[i].dti_done_xid = GetCurrentTransactionId(); } } diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 80a021487f9..c62bc6eaf1e 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -13,7 +13,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.281 2004/06/08 13:59:36 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.282 2004/07/01 00:50:11 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -25,6 +25,7 @@ #include "access/clog.h" #include "access/genam.h" #include "access/heapam.h" +#include "access/subtrans.h" #include "access/xlog.h" #include "catalog/catalog.h" #include "catalog/catname.h" @@ -798,8 +799,9 @@ vac_truncate_clog(TransactionId vacuumXID, TransactionId frozenXID) return; } - /* Truncate CLOG to the oldest vacuumxid */ + /* Truncate CLOG and SUBTRANS to the oldest vacuumxid */ TruncateCLOG(vacuumXID); + TruncateSUBTRANS(vacuumXID); /* Give warning about impending wraparound problems */ if (frozenAlreadyWrapped) diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c index 4a58419079a..dfa3f7121ed 100644 --- a/src/backend/commands/variable.c +++ b/src/backend/commands/variable.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/variable.c,v 1.97 2004/05/26 04:41:13 neilc Exp $ + * $PostgreSQL: pgsql/src/backend/commands/variable.c,v 1.98 2004/07/01 00:50:12 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -470,10 +470,17 @@ show_timezone(void) const char * assign_XactIsoLevel(const char *value, bool doit, GucSource source) { - if (doit && source >= PGC_S_INTERACTIVE && SerializableSnapshot != NULL) + if (doit && source >= PGC_S_INTERACTIVE) + { + if (SerializableSnapshot != NULL) ereport(ERROR, (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), errmsg("SET TRANSACTION ISOLATION LEVEL must be called before any query"))); + if (IsSubTransaction()) + ereport(ERROR, + (errcode(ERRCODE_ACTIVE_SQL_TRANSACTION), + errmsg("SET TRANSACTION ISOLATION LEVEL must not be called in a subtransaction"))); + } if (strcmp(value, "serializable") == 0) { |