mirror of
https://github.com/postgres/postgres.git
synced 2025-08-22 21:53:06 +03:00
Nested transactions. There is still much left to do, especially on the
performance front, but with feature freeze upon us I think it's time to drive a stake in the ground and say that this will be in 7.5. Alvaro Herrera, with some help from Tom Lane.
This commit is contained in:
@@ -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.
|
||||
* ----------
|
||||
*/
|
||||
|
||||
/* Per-item data */
|
||||
typedef struct DeferredTriggerEventItem
|
||||
{
|
||||
Oid dti_tgoid;
|
||||
TransactionId dti_done_xid;
|
||||
int32 dti_state;
|
||||
} DeferredTriggerEventItem;
|
||||
|
||||
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.
|
||||
*
|
||||
* We make this a single palloc'd object so it can be copied and freed easily.
|
||||
*
|
||||
* all_isset and all_isdeferred are used to keep track
|
||||
* of SET CONSTRAINTS ALL {DEFERRED, IMMEDIATE}.
|
||||
*
|
||||
* 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
|
||||
{
|
||||
/* 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;
|
||||
DeferredTriggerState state;
|
||||
DeferredTriggerEvent events;
|
||||
DeferredTriggerEvent tail_thisxact;
|
||||
DeferredTriggerEvent events_imm;
|
||||
DeferredTriggerEvent *tail_stack;
|
||||
DeferredTriggerEvent *imm_stack;
|
||||
DeferredTriggerState *state_stack;
|
||||
int numpushed;
|
||||
int numalloc;
|
||||
} DeferredTriggersData;
|
||||
|
||||
/* ----------
|
||||
* deftrig_events, deftrig_event_tail:
|
||||
* The list of pending deferred trigger events during the current transaction.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* XXX Need to be able to shove this data out to a file if it grows too
|
||||
* large...
|
||||
* ----------
|
||||
*/
|
||||
|
||||
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;
|
||||
Assert(deferredTriggers == NULL);
|
||||
|
||||
deferredTriggers = (DeferredTriggers) MemoryContextAlloc(TopTransactionContext,
|
||||
sizeof(DeferredTriggersData));
|
||||
|
||||
/*
|
||||
* 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,14 +2456,23 @@ DeferredTriggerAbortXact(void)
|
||||
void
|
||||
DeferredTriggerSetState(ConstraintsSetStmt *stmt)
|
||||
{
|
||||
ListCell *l;
|
||||
|
||||
/*
|
||||
* Ignore call if we aren't in a transaction.
|
||||
*/
|
||||
if (deferredTriggers == NULL)
|
||||
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 ...
|
||||
*/
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user