Commit 2781b4be authored by Robert Haas's avatar Robert Haas

Perform less setup work for AFTER triggers at transaction start.

Testing reveals that the memory allocation we do at transaction start
has small but measurable overhead on simple transactions.  To cut
down on that overhead, defer some of that work to the point when
AFTER triggers are first used, thus avoiding it altogether if they
never are.

Patch by me.  Review by Andres Freund.
parent efbbbbc8
...@@ -90,6 +90,7 @@ static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, ...@@ -90,6 +90,7 @@ static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
int event, bool row_trigger, int event, bool row_trigger,
HeapTuple oldtup, HeapTuple newtup, HeapTuple oldtup, HeapTuple newtup,
List *recheckIndexes, Bitmapset *modifiedCols); List *recheckIndexes, Bitmapset *modifiedCols);
static void AfterTriggerEnlargeQueryState(void);
/* /*
...@@ -3203,9 +3204,7 @@ typedef struct AfterTriggersData ...@@ -3203,9 +3204,7 @@ typedef struct AfterTriggersData
int maxtransdepth; /* allocated len of above arrays */ int maxtransdepth; /* allocated len of above arrays */
} AfterTriggersData; } AfterTriggersData;
typedef AfterTriggersData *AfterTriggers; static AfterTriggersData afterTriggers;
static AfterTriggers afterTriggers;
static void AfterTriggerExecute(AfterTriggerEvent event, static void AfterTriggerExecute(AfterTriggerEvent event,
Relation rel, TriggerDesc *trigdesc, Relation rel, TriggerDesc *trigdesc,
...@@ -3228,7 +3227,7 @@ GetCurrentFDWTuplestore() ...@@ -3228,7 +3227,7 @@ GetCurrentFDWTuplestore()
{ {
Tuplestorestate *ret; Tuplestorestate *ret;
ret = afterTriggers->fdw_tuplestores[afterTriggers->query_depth]; ret = afterTriggers.fdw_tuplestores[afterTriggers.query_depth];
if (ret == NULL) if (ret == NULL)
{ {
MemoryContext oldcxt; MemoryContext oldcxt;
...@@ -3255,7 +3254,7 @@ GetCurrentFDWTuplestore() ...@@ -3255,7 +3254,7 @@ GetCurrentFDWTuplestore()
CurrentResourceOwner = saveResourceOwner; CurrentResourceOwner = saveResourceOwner;
MemoryContextSwitchTo(oldcxt); MemoryContextSwitchTo(oldcxt);
afterTriggers->fdw_tuplestores[afterTriggers->query_depth] = ret; afterTriggers.fdw_tuplestores[afterTriggers.query_depth] = ret;
} }
return ret; return ret;
...@@ -3271,7 +3270,7 @@ static bool ...@@ -3271,7 +3270,7 @@ static bool
afterTriggerCheckState(AfterTriggerShared evtshared) afterTriggerCheckState(AfterTriggerShared evtshared)
{ {
Oid tgoid = evtshared->ats_tgoid; Oid tgoid = evtshared->ats_tgoid;
SetConstraintState state = afterTriggers->state; SetConstraintState state = afterTriggers.state;
int i; int i;
/* /*
...@@ -3282,19 +3281,22 @@ afterTriggerCheckState(AfterTriggerShared evtshared) ...@@ -3282,19 +3281,22 @@ afterTriggerCheckState(AfterTriggerShared evtshared)
return false; return false;
/* /*
* Check if SET CONSTRAINTS has been executed for this specific trigger. * If constraint state exists, SET CONSTRAINTS might have been executed
* either for this trigger or for all triggers.
*/ */
if (state != NULL)
{
/* Check for SET CONSTRAINTS for this specific trigger. */
for (i = 0; i < state->numstates; i++) for (i = 0; i < state->numstates; i++)
{ {
if (state->trigstates[i].sct_tgoid == tgoid) if (state->trigstates[i].sct_tgoid == tgoid)
return state->trigstates[i].sct_tgisdeferred; return state->trigstates[i].sct_tgisdeferred;
} }
/* /* Check for SET CONSTRAINTS ALL. */
* Check if SET CONSTRAINTS ALL has been executed; if so use that.
*/
if (state->all_isset) if (state->all_isset)
return state->all_isdeferred; return state->all_isdeferred;
}
/* /*
* Otherwise return the default state for the trigger. * Otherwise return the default state for the trigger.
...@@ -3331,8 +3333,8 @@ afterTriggerAddEvent(AfterTriggerEventList *events, ...@@ -3331,8 +3333,8 @@ afterTriggerAddEvent(AfterTriggerEventList *events,
Size chunksize; Size chunksize;
/* Create event context if we didn't already */ /* Create event context if we didn't already */
if (afterTriggers->event_cxt == NULL) if (afterTriggers.event_cxt == NULL)
afterTriggers->event_cxt = afterTriggers.event_cxt =
AllocSetContextCreate(TopTransactionContext, AllocSetContextCreate(TopTransactionContext,
"AfterTriggerEvents", "AfterTriggerEvents",
ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_MINSIZE,
...@@ -3375,7 +3377,7 @@ afterTriggerAddEvent(AfterTriggerEventList *events, ...@@ -3375,7 +3377,7 @@ afterTriggerAddEvent(AfterTriggerEventList *events,
chunksize /= 2; /* too many shared records */ chunksize /= 2; /* too many shared records */
chunksize = Min(chunksize, MAX_CHUNK_SIZE); chunksize = Min(chunksize, MAX_CHUNK_SIZE);
} }
chunk = MemoryContextAlloc(afterTriggers->event_cxt, chunksize); chunk = MemoryContextAlloc(afterTriggers.event_cxt, chunksize);
chunk->next = NULL; chunk->next = NULL;
chunk->freeptr = CHUNK_DATA_START(chunk); chunk->freeptr = CHUNK_DATA_START(chunk);
chunk->endptr = chunk->endfree = (char *) chunk + chunksize; chunk->endptr = chunk->endfree = (char *) chunk + chunksize;
...@@ -3706,7 +3708,7 @@ afterTriggerMarkEvents(AfterTriggerEventList *events, ...@@ -3706,7 +3708,7 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
/* /*
* Mark it as to be fired in this firing cycle. * Mark it as to be fired in this firing cycle.
*/ */
evtshared->ats_firing_id = afterTriggers->firing_counter; evtshared->ats_firing_id = afterTriggers.firing_counter;
event->ate_flags |= AFTER_TRIGGER_IN_PROGRESS; event->ate_flags |= AFTER_TRIGGER_IN_PROGRESS;
found = true; found = true;
} }
...@@ -3898,40 +3900,28 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, ...@@ -3898,40 +3900,28 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
void void
AfterTriggerBeginXact(void) AfterTriggerBeginXact(void)
{ {
Assert(afterTriggers == NULL);
/* /*
* Build empty after-trigger state structure * Initialize after-trigger state structure to empty
*/ */
afterTriggers = (AfterTriggers) afterTriggers.firing_counter = (CommandId) 1; /* mustn't be 0 */
MemoryContextAlloc(TopTransactionContext, afterTriggers.query_depth = -1;
sizeof(AfterTriggersData));
afterTriggers->firing_counter = (CommandId) 1; /* mustn't be 0 */
afterTriggers->state = SetConstraintStateCreate(8);
afterTriggers->events.head = NULL;
afterTriggers->events.tail = NULL;
afterTriggers->events.tailfree = NULL;
afterTriggers->query_depth = -1;
/* We initialize the arrays to a reasonable size */ /*
afterTriggers->query_stack = (AfterTriggerEventList *) * Verify that there is no leftover state remaining. If these assertions
MemoryContextAlloc(TopTransactionContext, * trip, it means that AfterTriggerEndXact wasn't called or didn't clean
8 * sizeof(AfterTriggerEventList)); * up properly.
afterTriggers->fdw_tuplestores = (Tuplestorestate **) */
MemoryContextAllocZero(TopTransactionContext, Assert(afterTriggers.state == NULL);
8 * sizeof(Tuplestorestate *)); Assert(afterTriggers.query_stack == NULL);
afterTriggers->maxquerydepth = 8; Assert(afterTriggers.fdw_tuplestores == NULL);
Assert(afterTriggers.maxquerydepth == 0);
/* Context for events is created only when needed */ Assert(afterTriggers.event_cxt == NULL);
afterTriggers->event_cxt = NULL; Assert(afterTriggers.events.head == NULL);
Assert(afterTriggers.state_stack == NULL);
/* Subtransaction stack is empty until/unless needed */ Assert(afterTriggers.events_stack == NULL);
afterTriggers->state_stack = NULL; Assert(afterTriggers.depth_stack == NULL);
afterTriggers->events_stack = NULL; Assert(afterTriggers.firing_stack == NULL);
afterTriggers->depth_stack = NULL; Assert(afterTriggers.maxtransdepth == 0);
afterTriggers->firing_stack = NULL;
afterTriggers->maxtransdepth = 0;
} }
...@@ -3939,48 +3929,15 @@ AfterTriggerBeginXact(void) ...@@ -3939,48 +3929,15 @@ AfterTriggerBeginXact(void)
* AfterTriggerBeginQuery() * AfterTriggerBeginQuery()
* *
* Called just before we start processing a single query within a * Called just before we start processing a single query within a
* transaction (or subtransaction). Set up to record AFTER trigger * transaction (or subtransaction). Most of the real work gets deferred
* events queued by the query. Note that it is allowed to have * until somebody actually tries to queue a trigger event.
* nested queries within a (sub)transaction.
* ---------- * ----------
*/ */
void void
AfterTriggerBeginQuery(void) AfterTriggerBeginQuery(void)
{ {
AfterTriggerEventList *events;
/* Must be inside a transaction */
Assert(afterTriggers != NULL);
/* Increase the query stack depth */ /* Increase the query stack depth */
afterTriggers->query_depth++; afterTriggers.query_depth++;
/*
* Allocate more space in the query stack if needed.
*/
if (afterTriggers->query_depth >= afterTriggers->maxquerydepth)
{
/* repalloc will keep the stack in the same context */
int old_alloc = afterTriggers->maxquerydepth;
int new_alloc = old_alloc * 2;
afterTriggers->query_stack = (AfterTriggerEventList *)
repalloc(afterTriggers->query_stack,
new_alloc * sizeof(AfterTriggerEventList));
afterTriggers->fdw_tuplestores = (Tuplestorestate **)
repalloc(afterTriggers->fdw_tuplestores,
new_alloc * sizeof(Tuplestorestate *));
/* Clear newly-allocated slots for subsequent lazy initialization. */
memset(afterTriggers->fdw_tuplestores + old_alloc,
0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *));
afterTriggers->maxquerydepth = new_alloc;
}
/* Initialize this query's list to empty */
events = &afterTriggers->query_stack[afterTriggers->query_depth];
events->head = NULL;
events->tail = NULL;
events->tailfree = NULL;
} }
...@@ -4002,11 +3959,18 @@ AfterTriggerEndQuery(EState *estate) ...@@ -4002,11 +3959,18 @@ AfterTriggerEndQuery(EState *estate)
AfterTriggerEventList *events; AfterTriggerEventList *events;
Tuplestorestate *fdw_tuplestore; Tuplestorestate *fdw_tuplestore;
/* Must be inside a transaction */
Assert(afterTriggers != NULL);
/* Must be inside a query, too */ /* Must be inside a query, too */
Assert(afterTriggers->query_depth >= 0); Assert(afterTriggers.query_depth >= 0);
/*
* If we never even got as far as initializing the event stack, there
* certainly won't be any events, so exit quickly.
*/
if (afterTriggers.query_depth >= afterTriggers.maxquerydepth)
{
afterTriggers.query_depth--;
return;
}
/* /*
* Process all immediate-mode triggers queued by the query, and move the * Process all immediate-mode triggers queued by the query, and move the
...@@ -4032,10 +3996,10 @@ AfterTriggerEndQuery(EState *estate) ...@@ -4032,10 +3996,10 @@ AfterTriggerEndQuery(EState *estate)
*/ */
for (;;) for (;;)
{ {
events = &afterTriggers->query_stack[afterTriggers->query_depth]; events = &afterTriggers.query_stack[afterTriggers.query_depth];
if (afterTriggerMarkEvents(events, &afterTriggers->events, true)) if (afterTriggerMarkEvents(events, &afterTriggers.events, true))
{ {
CommandId firing_id = afterTriggers->firing_counter++; CommandId firing_id = afterTriggers.firing_counter++;
/* OK to delete the immediate events after processing them */ /* OK to delete the immediate events after processing them */
if (afterTriggerInvokeEvents(events, firing_id, estate, true)) if (afterTriggerInvokeEvents(events, firing_id, estate, true))
...@@ -4046,15 +4010,15 @@ AfterTriggerEndQuery(EState *estate) ...@@ -4046,15 +4010,15 @@ AfterTriggerEndQuery(EState *estate)
} }
/* Release query-local storage for events, including tuplestore if any */ /* Release query-local storage for events, including tuplestore if any */
fdw_tuplestore = afterTriggers->fdw_tuplestores[afterTriggers->query_depth]; fdw_tuplestore = afterTriggers.fdw_tuplestores[afterTriggers.query_depth];
if (fdw_tuplestore) if (fdw_tuplestore)
{ {
tuplestore_end(fdw_tuplestore); tuplestore_end(fdw_tuplestore);
afterTriggers->fdw_tuplestores[afterTriggers->query_depth] = NULL; afterTriggers.fdw_tuplestores[afterTriggers.query_depth] = NULL;
} }
afterTriggerFreeEventList(&afterTriggers->query_stack[afterTriggers->query_depth]); afterTriggerFreeEventList(&afterTriggers.query_stack[afterTriggers.query_depth]);
afterTriggers->query_depth--; afterTriggers.query_depth--;
} }
...@@ -4075,18 +4039,15 @@ AfterTriggerFireDeferred(void) ...@@ -4075,18 +4039,15 @@ AfterTriggerFireDeferred(void)
AfterTriggerEventList *events; AfterTriggerEventList *events;
bool snap_pushed = false; bool snap_pushed = false;
/* Must be inside a transaction */ /* Must not be inside a query */
Assert(afterTriggers != NULL); Assert(afterTriggers.query_depth == -1);
/* ... but not inside a query */
Assert(afterTriggers->query_depth == -1);
/* /*
* If there are any triggers to fire, make sure we have set a snapshot for * If there are any triggers to fire, make sure we have set a snapshot for
* them to use. (Since PortalRunUtility doesn't set a snap for COMMIT, we * them to use. (Since PortalRunUtility doesn't set a snap for COMMIT, we
* can't assume ActiveSnapshot is valid on entry.) * can't assume ActiveSnapshot is valid on entry.)
*/ */
events = &afterTriggers->events; events = &afterTriggers.events;
if (events->head != NULL) if (events->head != NULL)
{ {
PushActiveSnapshot(GetTransactionSnapshot()); PushActiveSnapshot(GetTransactionSnapshot());
...@@ -4099,7 +4060,7 @@ AfterTriggerFireDeferred(void) ...@@ -4099,7 +4060,7 @@ AfterTriggerFireDeferred(void)
*/ */
while (afterTriggerMarkEvents(events, NULL, false)) while (afterTriggerMarkEvents(events, NULL, false))
{ {
CommandId firing_id = afterTriggers->firing_counter++; CommandId firing_id = afterTriggers.firing_counter++;
if (afterTriggerInvokeEvents(events, firing_id, NULL, true)) if (afterTriggerInvokeEvents(events, firing_id, NULL, true))
break; /* all fired */ break; /* all fired */
...@@ -4132,7 +4093,7 @@ void ...@@ -4132,7 +4093,7 @@ void
AfterTriggerEndXact(bool isCommit) AfterTriggerEndXact(bool isCommit)
{ {
/* /*
* Forget everything we know about AFTER triggers. * Forget the pending-events list.
* *
* Since all the info is in TopTransactionContext or children thereof, we * Since all the info is in TopTransactionContext or children thereof, we
* don't really need to do anything to reclaim memory. However, the * don't really need to do anything to reclaim memory. However, the
...@@ -4140,10 +4101,39 @@ AfterTriggerEndXact(bool isCommit) ...@@ -4140,10 +4101,39 @@ AfterTriggerEndXact(bool isCommit)
* soon as possible --- especially if we are aborting because we ran out * soon as possible --- especially if we are aborting because we ran out
* of memory for the list! * of memory for the list!
*/ */
if (afterTriggers && afterTriggers->event_cxt) if (afterTriggers.event_cxt)
MemoryContextDelete(afterTriggers->event_cxt); {
MemoryContextDelete(afterTriggers.event_cxt);
afterTriggers.event_cxt = NULL;
afterTriggers.events.head = NULL;
afterTriggers.events.tail = NULL;
afterTriggers.events.tailfree = NULL;
}
/*
* Forget any subtransaction state as well. Since this can't be very
* large, we let the eventual reset of TopTransactionContext free the
* memory instead of doing it here.
*/
afterTriggers.state_stack = NULL;
afterTriggers.events_stack = NULL;
afterTriggers.depth_stack = NULL;
afterTriggers.firing_stack = NULL;
afterTriggers.maxtransdepth = 0;
afterTriggers = NULL; /*
* Forget the query stack and constrant-related state information. As
* with the subtransaction state information, we don't bother freeing the
* memory here.
*/
afterTriggers.query_stack = NULL;
afterTriggers.fdw_tuplestores = NULL;
afterTriggers.maxquerydepth = 0;
afterTriggers.state = NULL;
/* No more afterTriggers manipulation until next transaction starts. */
afterTriggers.query_depth = -1;
} }
/* /*
...@@ -4156,57 +4146,50 @@ AfterTriggerBeginSubXact(void) ...@@ -4156,57 +4146,50 @@ AfterTriggerBeginSubXact(void)
{ {
int my_level = GetCurrentTransactionNestLevel(); int my_level = GetCurrentTransactionNestLevel();
/*
* Ignore call if the transaction is in aborted state. (Probably
* shouldn't happen?)
*/
if (afterTriggers == NULL)
return;
/* /*
* Allocate more space in the stacks if needed. (Note: because the * Allocate more space in the stacks if needed. (Note: because the
* minimum nest level of a subtransaction is 2, we waste the first couple * minimum nest level of a subtransaction is 2, we waste the first couple
* entries of each array; not worth the notational effort to avoid it.) * entries of each array; not worth the notational effort to avoid it.)
*/ */
while (my_level >= afterTriggers->maxtransdepth) while (my_level >= afterTriggers.maxtransdepth)
{ {
if (afterTriggers->maxtransdepth == 0) if (afterTriggers.maxtransdepth == 0)
{ {
MemoryContext old_cxt; MemoryContext old_cxt;
old_cxt = MemoryContextSwitchTo(TopTransactionContext); old_cxt = MemoryContextSwitchTo(TopTransactionContext);
#define DEFTRIG_INITALLOC 8 #define DEFTRIG_INITALLOC 8
afterTriggers->state_stack = (SetConstraintState *) afterTriggers.state_stack = (SetConstraintState *)
palloc(DEFTRIG_INITALLOC * sizeof(SetConstraintState)); palloc(DEFTRIG_INITALLOC * sizeof(SetConstraintState));
afterTriggers->events_stack = (AfterTriggerEventList *) afterTriggers.events_stack = (AfterTriggerEventList *)
palloc(DEFTRIG_INITALLOC * sizeof(AfterTriggerEventList)); palloc(DEFTRIG_INITALLOC * sizeof(AfterTriggerEventList));
afterTriggers->depth_stack = (int *) afterTriggers.depth_stack = (int *)
palloc(DEFTRIG_INITALLOC * sizeof(int)); palloc(DEFTRIG_INITALLOC * sizeof(int));
afterTriggers->firing_stack = (CommandId *) afterTriggers.firing_stack = (CommandId *)
palloc(DEFTRIG_INITALLOC * sizeof(CommandId)); palloc(DEFTRIG_INITALLOC * sizeof(CommandId));
afterTriggers->maxtransdepth = DEFTRIG_INITALLOC; afterTriggers.maxtransdepth = DEFTRIG_INITALLOC;
MemoryContextSwitchTo(old_cxt); MemoryContextSwitchTo(old_cxt);
} }
else else
{ {
/* repalloc will keep the stacks in the same context */ /* repalloc will keep the stacks in the same context */
int new_alloc = afterTriggers->maxtransdepth * 2; int new_alloc = afterTriggers.maxtransdepth * 2;
afterTriggers->state_stack = (SetConstraintState *) afterTriggers.state_stack = (SetConstraintState *)
repalloc(afterTriggers->state_stack, repalloc(afterTriggers.state_stack,
new_alloc * sizeof(SetConstraintState)); new_alloc * sizeof(SetConstraintState));
afterTriggers->events_stack = (AfterTriggerEventList *) afterTriggers.events_stack = (AfterTriggerEventList *)
repalloc(afterTriggers->events_stack, repalloc(afterTriggers.events_stack,
new_alloc * sizeof(AfterTriggerEventList)); new_alloc * sizeof(AfterTriggerEventList));
afterTriggers->depth_stack = (int *) afterTriggers.depth_stack = (int *)
repalloc(afterTriggers->depth_stack, repalloc(afterTriggers.depth_stack,
new_alloc * sizeof(int)); new_alloc * sizeof(int));
afterTriggers->firing_stack = (CommandId *) afterTriggers.firing_stack = (CommandId *)
repalloc(afterTriggers->firing_stack, repalloc(afterTriggers.firing_stack,
new_alloc * sizeof(CommandId)); new_alloc * sizeof(CommandId));
afterTriggers->maxtransdepth = new_alloc; afterTriggers.maxtransdepth = new_alloc;
} }
} }
...@@ -4215,10 +4198,10 @@ AfterTriggerBeginSubXact(void) ...@@ -4215,10 +4198,10 @@ AfterTriggerBeginSubXact(void)
* is not saved until/unless changed. Likewise, we don't make a * is not saved until/unless changed. Likewise, we don't make a
* per-subtransaction event context until needed. * per-subtransaction event context until needed.
*/ */
afterTriggers->state_stack[my_level] = NULL; afterTriggers.state_stack[my_level] = NULL;
afterTriggers->events_stack[my_level] = afterTriggers->events; afterTriggers.events_stack[my_level] = afterTriggers.events;
afterTriggers->depth_stack[my_level] = afterTriggers->query_depth; afterTriggers.depth_stack[my_level] = afterTriggers.query_depth;
afterTriggers->firing_stack[my_level] = afterTriggers->firing_counter; afterTriggers.firing_stack[my_level] = afterTriggers.firing_counter;
} }
/* /*
...@@ -4235,27 +4218,20 @@ AfterTriggerEndSubXact(bool isCommit) ...@@ -4235,27 +4218,20 @@ AfterTriggerEndSubXact(bool isCommit)
AfterTriggerEventChunk *chunk; AfterTriggerEventChunk *chunk;
CommandId subxact_firing_id; CommandId subxact_firing_id;
/*
* Ignore call if the transaction is in aborted state. (Probably
* unneeded)
*/
if (afterTriggers == NULL)
return;
/* /*
* Pop the prior state if needed. * Pop the prior state if needed.
*/ */
if (isCommit) if (isCommit)
{ {
Assert(my_level < afterTriggers->maxtransdepth); Assert(my_level < afterTriggers.maxtransdepth);
/* If we saved a prior state, we don't need it anymore */ /* If we saved a prior state, we don't need it anymore */
state = afterTriggers->state_stack[my_level]; state = afterTriggers.state_stack[my_level];
if (state != NULL) if (state != NULL)
pfree(state); pfree(state);
/* this avoids double pfree if error later: */ /* this avoids double pfree if error later: */
afterTriggers->state_stack[my_level] = NULL; afterTriggers.state_stack[my_level] = NULL;
Assert(afterTriggers->query_depth == Assert(afterTriggers.query_depth ==
afterTriggers->depth_stack[my_level]); afterTriggers.depth_stack[my_level]);
} }
else else
{ {
...@@ -4264,7 +4240,7 @@ AfterTriggerEndSubXact(bool isCommit) ...@@ -4264,7 +4240,7 @@ AfterTriggerEndSubXact(bool isCommit)
* AfterTriggerBeginSubXact, in which case we mustn't risk touching * AfterTriggerBeginSubXact, in which case we mustn't risk touching
* stack levels that aren't there. * stack levels that aren't there.
*/ */
if (my_level >= afterTriggers->maxtransdepth) if (my_level >= afterTriggers.maxtransdepth)
return; return;
/* /*
...@@ -4273,42 +4249,46 @@ AfterTriggerEndSubXact(bool isCommit) ...@@ -4273,42 +4249,46 @@ AfterTriggerEndSubXact(bool isCommit)
* subtransaction will not add events to query levels started in a * subtransaction will not add events to query levels started in a
* earlier transaction state. * earlier transaction state.
*/ */
while (afterTriggers->query_depth > afterTriggers->depth_stack[my_level]) while (afterTriggers.query_depth > afterTriggers.depth_stack[my_level])
{
if (afterTriggers.query_depth < afterTriggers.maxquerydepth)
{ {
Tuplestorestate *ts; Tuplestorestate *ts;
ts = afterTriggers->fdw_tuplestores[afterTriggers->query_depth]; ts = afterTriggers.fdw_tuplestores[afterTriggers.query_depth];
if (ts) if (ts)
{ {
tuplestore_end(ts); tuplestore_end(ts);
afterTriggers->fdw_tuplestores[afterTriggers->query_depth] = NULL; afterTriggers.fdw_tuplestores[afterTriggers.query_depth] = NULL;
} }
afterTriggerFreeEventList(&afterTriggers->query_stack[afterTriggers->query_depth]); afterTriggerFreeEventList(&afterTriggers.query_stack[afterTriggers.query_depth]);
afterTriggers->query_depth--;
} }
Assert(afterTriggers->query_depth ==
afterTriggers->depth_stack[my_level]); afterTriggers.query_depth--;
}
Assert(afterTriggers.query_depth ==
afterTriggers.depth_stack[my_level]);
/* /*
* Restore the global deferred-event list to its former length, * Restore the global deferred-event list to its former length,
* discarding any events queued by the subxact. * discarding any events queued by the subxact.
*/ */
afterTriggerRestoreEventList(&afterTriggers->events, afterTriggerRestoreEventList(&afterTriggers.events,
&afterTriggers->events_stack[my_level]); &afterTriggers.events_stack[my_level]);
/* /*
* Restore the trigger state. If the saved state is NULL, then this * Restore the trigger state. If the saved state is NULL, then this
* subxact didn't save it, so it doesn't need restoring. * subxact didn't save it, so it doesn't need restoring.
*/ */
state = afterTriggers->state_stack[my_level]; state = afterTriggers.state_stack[my_level];
if (state != NULL) if (state != NULL)
{ {
pfree(afterTriggers->state); pfree(afterTriggers.state);
afterTriggers->state = state; afterTriggers.state = state;
} }
/* this avoids double pfree if error later: */ /* this avoids double pfree if error later: */
afterTriggers->state_stack[my_level] = NULL; afterTriggers.state_stack[my_level] = NULL;
/* /*
* Scan for any remaining deferred events that were marked DONE or IN * Scan for any remaining deferred events that were marked DONE or IN
...@@ -4318,8 +4298,8 @@ AfterTriggerEndSubXact(bool isCommit) ...@@ -4318,8 +4298,8 @@ AfterTriggerEndSubXact(bool isCommit)
* (This essentially assumes that the current subxact includes all * (This essentially assumes that the current subxact includes all
* subxacts started after it.) * subxacts started after it.)
*/ */
subxact_firing_id = afterTriggers->firing_stack[my_level]; subxact_firing_id = afterTriggers.firing_stack[my_level];
for_each_event_chunk(event, chunk, afterTriggers->events) for_each_event_chunk(event, chunk, afterTriggers.events)
{ {
AfterTriggerShared evtshared = GetTriggerSharedData(event); AfterTriggerShared evtshared = GetTriggerSharedData(event);
...@@ -4334,6 +4314,66 @@ AfterTriggerEndSubXact(bool isCommit) ...@@ -4334,6 +4314,66 @@ AfterTriggerEndSubXact(bool isCommit)
} }
} }
/* ----------
* AfterTriggerEnlargeQueryState()
*
* Prepare the necessary state so that we can record AFTER trigger events
* queued by a query. It is allowed to have nested queries within a
* (sub)transaction, so we need to have separate state for each query
* nesting level.
* ----------
*/
static void
AfterTriggerEnlargeQueryState(void)
{
int init_depth = afterTriggers.maxquerydepth;
Assert(afterTriggers.query_depth >= afterTriggers.maxquerydepth);
if (afterTriggers.maxquerydepth == 0)
{
int new_alloc = Max(afterTriggers.query_depth, 8);
afterTriggers.query_stack = (AfterTriggerEventList *)
MemoryContextAlloc(TopTransactionContext,
new_alloc * sizeof(AfterTriggerEventList));
afterTriggers.fdw_tuplestores = (Tuplestorestate **)
MemoryContextAllocZero(TopTransactionContext,
new_alloc * sizeof(Tuplestorestate *));
afterTriggers.maxquerydepth = new_alloc;
}
else
{
/* repalloc will keep the stack in the same context */
int old_alloc = afterTriggers.maxquerydepth;
int new_alloc = Max(afterTriggers.query_depth, old_alloc * 2);
afterTriggers.query_stack = (AfterTriggerEventList *)
repalloc(afterTriggers.query_stack,
new_alloc * sizeof(AfterTriggerEventList));
afterTriggers.fdw_tuplestores = (Tuplestorestate **)
repalloc(afterTriggers.fdw_tuplestores,
new_alloc * sizeof(Tuplestorestate *));
/* Clear newly-allocated slots for subsequent lazy initialization. */
memset(afterTriggers.fdw_tuplestores + old_alloc,
0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *));
afterTriggers.maxquerydepth = new_alloc;
}
/* Initialize new query lists to empty */
while (init_depth < afterTriggers.maxquerydepth)
{
AfterTriggerEventList *events;
events = &afterTriggers.query_stack[init_depth];
events->head = NULL;
events->tail = NULL;
events->tailfree = NULL;
++init_depth;
}
}
/* /*
* Create an empty SetConstraintState with room for numalloc trigstates * Create an empty SetConstraintState with room for numalloc trigstates
*/ */
...@@ -4417,21 +4457,19 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -4417,21 +4457,19 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
{ {
int my_level = GetCurrentTransactionNestLevel(); int my_level = GetCurrentTransactionNestLevel();
/* /* If we haven't already done so, initialize our state. */
* Ignore call if we aren't in a transaction. (Shouldn't happen?) if (afterTriggers.state == NULL)
*/ afterTriggers.state = SetConstraintStateCreate(8);
if (afterTriggers == NULL)
return;
/* /*
* If in a subtransaction, and we didn't save the current state already, * If in a subtransaction, and we didn't save the current state already,
* save it so it can be restored if the subtransaction aborts. * save it so it can be restored if the subtransaction aborts.
*/ */
if (my_level > 1 && if (my_level > 1 &&
afterTriggers->state_stack[my_level] == NULL) afterTriggers.state_stack[my_level] == NULL)
{ {
afterTriggers->state_stack[my_level] = afterTriggers.state_stack[my_level] =
SetConstraintStateCopy(afterTriggers->state); SetConstraintStateCopy(afterTriggers.state);
} }
/* /*
...@@ -4442,13 +4480,13 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -4442,13 +4480,13 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
/* /*
* Forget any previous SET CONSTRAINTS commands in this transaction. * Forget any previous SET CONSTRAINTS commands in this transaction.
*/ */
afterTriggers->state->numstates = 0; afterTriggers.state->numstates = 0;
/* /*
* Set the per-transaction ALL state to known. * Set the per-transaction ALL state to known.
*/ */
afterTriggers->state->all_isset = true; afterTriggers.state->all_isset = true;
afterTriggers->state->all_isdeferred = stmt->deferred; afterTriggers.state->all_isdeferred = stmt->deferred;
} }
else else
{ {
...@@ -4622,7 +4660,7 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -4622,7 +4660,7 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
foreach(lc, tgoidlist) foreach(lc, tgoidlist)
{ {
Oid tgoid = lfirst_oid(lc); Oid tgoid = lfirst_oid(lc);
SetConstraintState state = afterTriggers->state; SetConstraintState state = afterTriggers.state;
bool found = false; bool found = false;
int i; int i;
...@@ -4637,7 +4675,7 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -4637,7 +4675,7 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
} }
if (!found) if (!found)
{ {
afterTriggers->state = afterTriggers.state =
SetConstraintStateAddItem(state, tgoid, stmt->deferred); SetConstraintStateAddItem(state, tgoid, stmt->deferred);
} }
} }
...@@ -4656,12 +4694,12 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -4656,12 +4694,12 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
*/ */
if (!stmt->deferred) if (!stmt->deferred)
{ {
AfterTriggerEventList *events = &afterTriggers->events; AfterTriggerEventList *events = &afterTriggers.events;
bool snapshot_set = false; bool snapshot_set = false;
while (afterTriggerMarkEvents(events, NULL, true)) while (afterTriggerMarkEvents(events, NULL, true))
{ {
CommandId firing_id = afterTriggers->firing_counter++; CommandId firing_id = afterTriggers.firing_counter++;
/* /*
* Make sure a snapshot has been established in case trigger * Make sure a snapshot has been established in case trigger
...@@ -4715,12 +4753,8 @@ AfterTriggerPendingOnRel(Oid relid) ...@@ -4715,12 +4753,8 @@ AfterTriggerPendingOnRel(Oid relid)
AfterTriggerEventChunk *chunk; AfterTriggerEventChunk *chunk;
int depth; int depth;
/* No-op if we aren't in a transaction. (Shouldn't happen?) */
if (afterTriggers == NULL)
return false;
/* Scan queued events */ /* Scan queued events */
for_each_event_chunk(event, chunk, afterTriggers->events) for_each_event_chunk(event, chunk, afterTriggers.events)
{ {
AfterTriggerShared evtshared = GetTriggerSharedData(event); AfterTriggerShared evtshared = GetTriggerSharedData(event);
...@@ -4741,9 +4775,9 @@ AfterTriggerPendingOnRel(Oid relid) ...@@ -4741,9 +4775,9 @@ AfterTriggerPendingOnRel(Oid relid)
* if TRUNCATE/etc is executed by a function or trigger within an updating * if TRUNCATE/etc is executed by a function or trigger within an updating
* query on the same relation, which is pretty perverse, but let's check. * query on the same relation, which is pretty perverse, but let's check.
*/ */
for (depth = 0; depth <= afterTriggers->query_depth; depth++) for (depth = 0; depth <= afterTriggers.query_depth; depth++)
{ {
for_each_event_chunk(event, chunk, afterTriggers->query_stack[depth]) for_each_event_chunk(event, chunk, afterTriggers.query_stack[depth])
{ {
AfterTriggerShared evtshared = GetTriggerSharedData(event); AfterTriggerShared evtshared = GetTriggerSharedData(event);
...@@ -4787,15 +4821,17 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, ...@@ -4787,15 +4821,17 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
Tuplestorestate *fdw_tuplestore = NULL; Tuplestorestate *fdw_tuplestore = NULL;
/* /*
* Check state. We use normal tests not Asserts because it is possible to * Check state. We use a normal test not Assert because it is possible to
* reach here in the wrong state given misconfigured RI triggers, in * reach here in the wrong state given misconfigured RI triggers, in
* particular deferring a cascade action trigger. * particular deferring a cascade action trigger.
*/ */
if (afterTriggers == NULL) if (afterTriggers.query_depth < 0)
elog(ERROR, "AfterTriggerSaveEvent() called outside of transaction");
if (afterTriggers->query_depth < 0)
elog(ERROR, "AfterTriggerSaveEvent() called outside of query"); elog(ERROR, "AfterTriggerSaveEvent() called outside of query");
/* Be sure we have enough space to record events at this query depth. */
if (afterTriggers.query_depth >= afterTriggers.maxquerydepth)
AfterTriggerEnlargeQueryState();
/* /*
* Validate the event code and collect the associated tuple CTIDs. * Validate the event code and collect the associated tuple CTIDs.
* *
...@@ -4959,7 +4995,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, ...@@ -4959,7 +4995,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
new_shared.ats_relid = RelationGetRelid(rel); new_shared.ats_relid = RelationGetRelid(rel);
new_shared.ats_firing_id = 0; new_shared.ats_firing_id = 0;
afterTriggerAddEvent(&afterTriggers->query_stack[afterTriggers->query_depth], afterTriggerAddEvent(&afterTriggers.query_stack[afterTriggers.query_depth],
&new_event, &new_shared); &new_event, &new_shared);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment