Commit 312b1a98 authored by Tom Lane's avatar Tom Lane

Reduce the memory footprint of large pending-trigger-event lists, as per my

recent proposal.  In typical cases, we now need 12 bytes per insert or delete
event and 16 bytes per update event; previously we needed 40 bytes per
event on 32-bit hardware and 80 bytes per event on 64-bit hardware.  Even
in the worst case usage pattern with a large number of distinct triggers being
fired in one query, usage is at most 32 bytes per event.  It seems to be a
bit faster than the old code as well, due to reduction of palloc overhead.

This commit doesn't address the TODO item of allowing the event list to spill
to disk; rather it's trying to stave off the need for that.  However, it
probably makes that task a bit easier by reducing the data structure's
dependency on pointers.  It would now be practical to dump an event list to
disk by "chunks" instead of individual events.
parent 3ca5aa6c
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* IDENTIFICATION * IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.237 2008/09/01 20:42:44 tgl Exp $ * $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.238 2008/10/24 23:42:35 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -2234,12 +2234,14 @@ ltrmark:; ...@@ -2234,12 +2234,14 @@ ltrmark:;
* during the current transaction tree. (BEFORE triggers are fired * during the current transaction tree. (BEFORE triggers are fired
* immediately so we don't need any persistent state about them.) The struct * immediately so we don't need any persistent state about them.) The struct
* and most of its subsidiary data are kept in TopTransactionContext; however * and most of its subsidiary data are kept in TopTransactionContext; however
* the individual event records are kept in separate contexts, to make them * the individual event records are kept in a separate sub-context. This is
* easy to delete during subtransaction abort. * done mainly so that it's easy to tell from a memory context dump how much
* space is being eaten by trigger events.
* *
* Because the list of pending events can grow large, we go to some effort * Because the list of pending events can grow large, we go to some
* to minimize memory consumption. We do not use the generic List mechanism * considerable effort to minimize per-event memory consumption. The event
* but thread the events manually. * records are grouped into chunks and common data for similar events in the
* same chunk is only stored once.
* *
* XXX We need to be able to save the per-event data in a file if it grows too * XXX We need to be able to save the per-event data in a file if it grows too
* large. * large.
...@@ -2280,30 +2282,101 @@ typedef SetConstraintStateData *SetConstraintState; ...@@ -2280,30 +2282,101 @@ typedef SetConstraintStateData *SetConstraintState;
/* /*
* Per-trigger-event data * Per-trigger-event data
* *
* Note: ate_firing_id is meaningful when either AFTER_TRIGGER_DONE * The actual per-event data, AfterTriggerEventData, includes DONE/IN_PROGRESS
* or AFTER_TRIGGER_IN_PROGRESS is set. It indicates which trigger firing * status bits and one or two tuple CTIDs. Each event record also has an
* cycle the trigger was or will be fired in. * associated AfterTriggerSharedData that is shared across all instances
* of similar events within a "chunk".
*
* We arrange not to waste storage on ate_ctid2 for non-update events.
* We could go further and not store either ctid for statement-level triggers,
* but that seems unlikely to be worth the trouble.
*
* Note: ats_firing_id is initially zero and is set to something else when
* AFTER_TRIGGER_IN_PROGRESS is set. It indicates which trigger firing
* cycle the trigger will be fired in (or was fired in, if DONE is set).
* Although this is mutable state, we can keep it in AfterTriggerSharedData
* because all instances of the same type of event in a given event list will
* be fired at the same time, if they were queued between the same firing
* cycles. So we need only ensure that ats_firing_id is zero when attaching
* a new event to an existing AfterTriggerSharedData record.
*/ */
typedef uint32 TriggerFlags;
#define AFTER_TRIGGER_OFFSET 0x0FFFFFFF /* must be low-order bits */
#define AFTER_TRIGGER_2CTIDS 0x10000000
#define AFTER_TRIGGER_DONE 0x20000000
#define AFTER_TRIGGER_IN_PROGRESS 0x40000000
typedef struct AfterTriggerSharedData *AfterTriggerShared;
typedef struct AfterTriggerSharedData
{
TriggerEvent ats_event; /* event type indicator, see trigger.h */
Oid ats_tgoid; /* the trigger's ID */
Oid ats_relid; /* the relation it's on */
CommandId ats_firing_id; /* ID for firing cycle */
} AfterTriggerSharedData;
typedef struct AfterTriggerEventData *AfterTriggerEvent; typedef struct AfterTriggerEventData *AfterTriggerEvent;
typedef struct AfterTriggerEventData typedef struct AfterTriggerEventData
{ {
AfterTriggerEvent ate_next; /* list link */ TriggerFlags ate_flags; /* status bits and offset to shared data */
TriggerEvent ate_event; /* event type and status bits */ ItemPointerData ate_ctid1; /* inserted, deleted, or old updated tuple */
CommandId ate_firing_id; /* ID for firing cycle */ ItemPointerData ate_ctid2; /* new updated tuple */
Oid ate_tgoid; /* the trigger's ID */
Oid ate_relid; /* the relation it's on */
ItemPointerData ate_oldctid; /* specific tuple(s) involved */
ItemPointerData ate_newctid;
} AfterTriggerEventData; } AfterTriggerEventData;
/* This struct must exactly match the one above except for not having ctid2 */
typedef struct AfterTriggerEventDataOneCtid
{
TriggerFlags ate_flags; /* status bits and offset to shared data */
ItemPointerData ate_ctid1; /* inserted, deleted, or old updated tuple */
} AfterTriggerEventDataOneCtid;
#define SizeofTriggerEvent(evt) \
(((evt)->ate_flags & AFTER_TRIGGER_2CTIDS) ? \
sizeof(AfterTriggerEventData) : sizeof(AfterTriggerEventDataOneCtid))
#define GetTriggerSharedData(evt) \
((AfterTriggerShared) ((char *) (evt) + ((evt)->ate_flags & AFTER_TRIGGER_OFFSET)))
/*
* To avoid palloc overhead, we keep trigger events in arrays in successively-
* larger chunks (a slightly more sophisticated version of an expansible
* array). The space between CHUNK_DATA_START and freeptr is occupied by
* AfterTriggerEventData records; the space between endfree and endptr is
* occupied by AfterTriggerSharedData records.
*/
typedef struct AfterTriggerEventChunk
{
struct AfterTriggerEventChunk *next; /* list link */
char *freeptr; /* start of free space in chunk */
char *endfree; /* end of free space in chunk */
char *endptr; /* end of chunk */
/* event data follows here */
} AfterTriggerEventChunk;
#define CHUNK_DATA_START(cptr) ((char *) (cptr) + MAXALIGN(sizeof(AfterTriggerEventChunk)))
/* A list of events */ /* A list of events */
typedef struct AfterTriggerEventList typedef struct AfterTriggerEventList
{ {
AfterTriggerEvent head; AfterTriggerEventChunk *head;
AfterTriggerEvent tail; AfterTriggerEventChunk *tail;
char *tailfree; /* freeptr of tail chunk */
} AfterTriggerEventList; } AfterTriggerEventList;
/* Macros to help in iterating over a list of events */
#define for_each_chunk(cptr, evtlist) \
for (cptr = (evtlist).head; cptr != NULL; cptr = cptr->next)
#define for_each_event(eptr, cptr) \
for (eptr = (AfterTriggerEvent) CHUNK_DATA_START(cptr); \
(char *) eptr < (cptr)->freeptr; \
eptr = (AfterTriggerEvent) (((char *) eptr) + SizeofTriggerEvent(eptr)))
/* Use this if no special per-chunk processing is needed */
#define for_each_event_chunk(eptr, cptr, evtlist) \
for_each_chunk(cptr, evtlist) for_each_event(eptr, cptr)
/* /*
* All per-transaction data for the AFTER TRIGGERS module. * All per-transaction data for the AFTER TRIGGERS module.
...@@ -2323,9 +2396,7 @@ typedef struct AfterTriggerEventList ...@@ -2323,9 +2396,7 @@ typedef struct AfterTriggerEventList
* all subtransactions of the current transaction. In a subtransaction * all subtransactions of the current transaction. In a subtransaction
* abort, we know that the events added by the subtransaction are at the * abort, we know that the events added by the subtransaction are at the
* end of the list, so it is relatively easy to discard them. The event * end of the list, so it is relatively easy to discard them. The event
* structs themselves are stored in event_cxt if generated by the top-level * list chunks themselves are stored in event_cxt.
* transaction, else in per-subtransaction contexts identified by the
* entries in cxt_stack.
* *
* query_depth is the current depth of nested AfterTriggerBeginQuery calls * query_depth is the current depth of nested AfterTriggerBeginQuery calls
* (-1 when the stack is empty). * (-1 when the stack is empty).
...@@ -2367,8 +2438,7 @@ typedef struct AfterTriggersData ...@@ -2367,8 +2438,7 @@ typedef struct AfterTriggersData
int query_depth; /* current query list index */ int query_depth; /* current query list index */
AfterTriggerEventList *query_stack; /* events pending from each query */ AfterTriggerEventList *query_stack; /* events pending from each query */
int maxquerydepth; /* allocated len of above array */ int maxquerydepth; /* allocated len of above array */
MemoryContext event_cxt; /* top transaction's event context, if any */ MemoryContext event_cxt; /* memory context for events, if any */
MemoryContext *cxt_stack; /* per-subtransaction event contexts */
/* these fields are just for resetting at subtrans abort: */ /* these fields are just for resetting at subtrans abort: */
...@@ -2398,13 +2468,13 @@ static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, ...@@ -2398,13 +2468,13 @@ static SetConstraintState SetConstraintStateAddItem(SetConstraintState state,
/* ---------- /* ----------
* afterTriggerCheckState() * afterTriggerCheckState()
* *
* Returns true if the trigger identified by tgoid is actually * Returns true if the trigger event is actually in state DEFERRED.
* in state DEFERRED.
* ---------- * ----------
*/ */
static bool static bool
afterTriggerCheckState(Oid tgoid, TriggerEvent eventstate) afterTriggerCheckState(AfterTriggerShared evtshared)
{ {
Oid tgoid = evtshared->ats_tgoid;
SetConstraintState state = afterTriggers->state; SetConstraintState state = afterTriggers->state;
int i; int i;
...@@ -2412,7 +2482,7 @@ afterTriggerCheckState(Oid tgoid, TriggerEvent eventstate) ...@@ -2412,7 +2482,7 @@ afterTriggerCheckState(Oid tgoid, TriggerEvent eventstate)
* For not-deferrable triggers (i.e. normal AFTER ROW triggers and * For not-deferrable triggers (i.e. normal AFTER ROW triggers and
* constraints declared NOT DEFERRABLE), the state is always false. * constraints declared NOT DEFERRABLE), the state is always false.
*/ */
if ((eventstate & AFTER_TRIGGER_DEFERRABLE) == 0) if ((evtshared->ats_event & AFTER_TRIGGER_DEFERRABLE) == 0)
return false; return false;
/* /*
...@@ -2433,37 +2503,184 @@ afterTriggerCheckState(Oid tgoid, TriggerEvent eventstate) ...@@ -2433,37 +2503,184 @@ afterTriggerCheckState(Oid tgoid, TriggerEvent eventstate)
/* /*
* Otherwise return the default state for the trigger. * Otherwise return the default state for the trigger.
*/ */
return ((eventstate & AFTER_TRIGGER_INITDEFERRED) != 0); return ((evtshared->ats_event & AFTER_TRIGGER_INITDEFERRED) != 0);
} }
/* ---------- /* ----------
* afterTriggerAddEvent() * afterTriggerAddEvent()
* *
* Add a new trigger event to the current query's queue. * Add a new trigger event to the specified queue.
* The passed-in event data is copied.
* ---------- * ----------
*/ */
static void static void
afterTriggerAddEvent(AfterTriggerEvent event) afterTriggerAddEvent(AfterTriggerEventList *events,
AfterTriggerEvent event, AfterTriggerShared evtshared)
{ {
AfterTriggerEventList *events; Size eventsize = SizeofTriggerEvent(event);
Size needed = eventsize + sizeof(AfterTriggerSharedData);
AfterTriggerEventChunk *chunk;
AfterTriggerShared newshared;
AfterTriggerEvent newevent;
Assert(event->ate_next == NULL); /*
* If empty list or not enough room in the tail chunk, make a new chunk.
* We assume here that a new shared record will always be needed.
*/
chunk = events->tail;
if (chunk == NULL ||
chunk->endfree - chunk->freeptr < needed)
{
Size chunksize;
/* Must be inside a query */ /* Create event context if we didn't already */
Assert(afterTriggers->query_depth >= 0); if (afterTriggers->event_cxt == NULL)
afterTriggers->event_cxt =
AllocSetContextCreate(TopTransactionContext,
"AfterTriggerEvents",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
events = &afterTriggers->query_stack[afterTriggers->query_depth]; /*
if (events->tail == NULL) * Chunk size starts at 1KB and is allowed to increase up to 1MB.
* These numbers are fairly arbitrary, though there is a hard limit at
* AFTER_TRIGGER_OFFSET; else we couldn't link event records to their
* shared records using the available space in ate_flags. Another
* constraint is that if the chunk size gets too huge, the search loop
* below would get slow given a (not too common) usage pattern with
* many distinct event types in a chunk. Therefore, we double the
* preceding chunk size only if there weren't too many shared records
* in the preceding chunk; otherwise we halve it. This gives us some
* ability to adapt to the actual usage pattern of the current query
* while still having large chunk sizes in typical usage. All chunk
* sizes used should be MAXALIGN multiples, to ensure that the shared
* records will be aligned safely.
*/
#define MIN_CHUNK_SIZE 1024
#define MAX_CHUNK_SIZE (1024*1024)
#if MAX_CHUNK_SIZE > (AFTER_TRIGGER_OFFSET+1)
#error MAX_CHUNK_SIZE must not exceed AFTER_TRIGGER_OFFSET
#endif
if (chunk == NULL)
chunksize = MIN_CHUNK_SIZE;
else
{
/* preceding chunk size... */
chunksize = chunk->endptr - (char *) chunk;
/* check number of shared records in preceding chunk */
if ((chunk->endptr - chunk->endfree) <=
(100 * sizeof(AfterTriggerSharedData)))
chunksize *= 2; /* okay, double it */
else
chunksize /= 2; /* too many shared records */
chunksize = Min(chunksize, MAX_CHUNK_SIZE);
}
chunk = MemoryContextAlloc(afterTriggers->event_cxt, chunksize);
chunk->next = NULL;
chunk->freeptr = CHUNK_DATA_START(chunk);
chunk->endptr = chunk->endfree = (char *) chunk + chunksize;
Assert(chunk->endfree - chunk->freeptr >= needed);
if (events->head == NULL)
events->head = chunk;
else
events->tail->next = chunk;
events->tail = chunk;
}
/*
* Try to locate a matching shared-data record already in the chunk.
* If none, make a new one.
*/
for (newshared = ((AfterTriggerShared) chunk->endptr) - 1;
(char *) newshared >= chunk->endfree;
newshared--)
{
if (newshared->ats_tgoid == evtshared->ats_tgoid &&
newshared->ats_relid == evtshared->ats_relid &&
newshared->ats_event == evtshared->ats_event &&
newshared->ats_firing_id == 0)
break;
}
if ((char *) newshared < chunk->endfree)
{ {
/* first list entry */ *newshared = *evtshared;
events->head = event; newshared->ats_firing_id = 0; /* just to be sure */
events->tail = event; chunk->endfree = (char *) newshared;
}
/* Insert the data */
newevent = (AfterTriggerEvent) chunk->freeptr;
memcpy(newevent, event, eventsize);
/* ... and link the new event to its shared record */
newevent->ate_flags &= ~AFTER_TRIGGER_OFFSET;
newevent->ate_flags |= (char *) newshared - (char *) newevent;
chunk->freeptr += eventsize;
events->tailfree = chunk->freeptr;
}
/* ----------
* afterTriggerFreeEventList()
*
* Free all the event storage in the given list.
* ----------
*/
static void
afterTriggerFreeEventList(AfterTriggerEventList *events)
{
AfterTriggerEventChunk *chunk;
AfterTriggerEventChunk *next_chunk;
for (chunk = events->head; chunk != NULL; chunk = next_chunk)
{
next_chunk = chunk->next;
pfree(chunk);
}
events->head = NULL;
events->tail = NULL;
events->tailfree = NULL;
}
/* ----------
* afterTriggerRestoreEventList()
*
* Restore an event list to its prior length, removing all the events
* added since it had the value old_events.
* ----------
*/
static void
afterTriggerRestoreEventList(AfterTriggerEventList *events,
const AfterTriggerEventList *old_events)
{
AfterTriggerEventChunk *chunk;
AfterTriggerEventChunk *next_chunk;
if (old_events->tail == NULL)
{
/* restoring to a completely empty state, so free everything */
afterTriggerFreeEventList(events);
} }
else else
{ {
events->tail->ate_next = event; *events = *old_events;
events->tail = event; /* free any chunks after the last one we want to keep */
for (chunk = events->tail->next; chunk != NULL; chunk = next_chunk)
{
next_chunk = chunk->next;
pfree(chunk);
}
/* and clean up the tail chunk to be the right length */
events->tail->next = NULL;
events->tail->freeptr = events->tailfree;
/*
* We don't make any effort to remove now-unused shared data records.
* They might still be useful, anyway.
*/
} }
} }
...@@ -2494,13 +2711,14 @@ AfterTriggerExecute(AfterTriggerEvent event, ...@@ -2494,13 +2711,14 @@ AfterTriggerExecute(AfterTriggerEvent event,
FmgrInfo *finfo, Instrumentation *instr, FmgrInfo *finfo, Instrumentation *instr,
MemoryContext per_tuple_context) MemoryContext per_tuple_context)
{ {
Oid tgoid = event->ate_tgoid; AfterTriggerShared evtshared = GetTriggerSharedData(event);
Oid tgoid = evtshared->ats_tgoid;
TriggerData LocTriggerData; TriggerData LocTriggerData;
HeapTupleData oldtuple; HeapTupleData tuple1;
HeapTupleData newtuple; HeapTupleData tuple2;
HeapTuple rettuple; HeapTuple rettuple;
Buffer oldbuffer = InvalidBuffer; Buffer buffer1 = InvalidBuffer;
Buffer newbuffer = InvalidBuffer; Buffer buffer2 = InvalidBuffer;
int tgindx; int tgindx;
/* /*
...@@ -2526,37 +2744,36 @@ AfterTriggerExecute(AfterTriggerEvent event, ...@@ -2526,37 +2744,36 @@ AfterTriggerExecute(AfterTriggerEvent event,
InstrStartNode(instr + tgindx); InstrStartNode(instr + tgindx);
/* /*
* Fetch the required OLD and NEW tuples. * Fetch the required tuple(s).
*/ */
LocTriggerData.tg_trigtuple = NULL; if (ItemPointerIsValid(&(event->ate_ctid1)))
LocTriggerData.tg_newtuple = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
LocTriggerData.tg_newtuplebuf = InvalidBuffer;
if (ItemPointerIsValid(&(event->ate_oldctid)))
{ {
ItemPointerCopy(&(event->ate_oldctid), &(oldtuple.t_self)); ItemPointerCopy(&(event->ate_ctid1), &(tuple1.t_self));
if (!heap_fetch(rel, SnapshotAny, &oldtuple, &oldbuffer, false, NULL)) if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer1, false, NULL))
elog(ERROR, "failed to fetch old tuple for AFTER trigger"); elog(ERROR, "failed to fetch tuple1 for AFTER trigger");
LocTriggerData.tg_trigtuple = &oldtuple; LocTriggerData.tg_trigtuple = &tuple1;
LocTriggerData.tg_trigtuplebuf = oldbuffer; LocTriggerData.tg_trigtuplebuf = buffer1;
}
else
{
LocTriggerData.tg_trigtuple = NULL;
LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
} }
if (ItemPointerIsValid(&(event->ate_newctid))) /* don't touch ctid2 if not there */
if ((event->ate_flags & AFTER_TRIGGER_2CTIDS) &&
ItemPointerIsValid(&(event->ate_ctid2)))
{ {
ItemPointerCopy(&(event->ate_newctid), &(newtuple.t_self)); ItemPointerCopy(&(event->ate_ctid2), &(tuple2.t_self));
if (!heap_fetch(rel, SnapshotAny, &newtuple, &newbuffer, false, NULL)) if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer2, false, NULL))
elog(ERROR, "failed to fetch new tuple for AFTER trigger"); elog(ERROR, "failed to fetch tuple2 for AFTER trigger");
if (LocTriggerData.tg_trigtuple != NULL) LocTriggerData.tg_newtuple = &tuple2;
{ LocTriggerData.tg_newtuplebuf = buffer2;
LocTriggerData.tg_newtuple = &newtuple; }
LocTriggerData.tg_newtuplebuf = newbuffer; else
} {
else LocTriggerData.tg_newtuple = NULL;
{ LocTriggerData.tg_newtuplebuf = InvalidBuffer;
LocTriggerData.tg_trigtuple = &newtuple;
LocTriggerData.tg_trigtuplebuf = newbuffer;
}
} }
/* /*
...@@ -2564,7 +2781,7 @@ AfterTriggerExecute(AfterTriggerEvent event, ...@@ -2564,7 +2781,7 @@ AfterTriggerExecute(AfterTriggerEvent event,
*/ */
LocTriggerData.type = T_TriggerData; LocTriggerData.type = T_TriggerData;
LocTriggerData.tg_event = LocTriggerData.tg_event =
event->ate_event & (TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW); evtshared->ats_event & (TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW);
LocTriggerData.tg_relation = rel; LocTriggerData.tg_relation = rel;
MemoryContextReset(per_tuple_context); MemoryContextReset(per_tuple_context);
...@@ -2578,16 +2795,16 @@ AfterTriggerExecute(AfterTriggerEvent event, ...@@ -2578,16 +2795,16 @@ AfterTriggerExecute(AfterTriggerEvent event,
finfo, finfo,
NULL, NULL,
per_tuple_context); per_tuple_context);
if (rettuple != NULL && rettuple != &oldtuple && rettuple != &newtuple) if (rettuple != NULL && rettuple != &tuple1 && rettuple != &tuple2)
heap_freetuple(rettuple); heap_freetuple(rettuple);
/* /*
* Release buffers * Release buffers
*/ */
if (oldbuffer != InvalidBuffer) if (buffer1 != InvalidBuffer)
ReleaseBuffer(oldbuffer); ReleaseBuffer(buffer1);
if (newbuffer != InvalidBuffer) if (buffer2 != InvalidBuffer)
ReleaseBuffer(newbuffer); ReleaseBuffer(buffer2);
/* /*
* If doing EXPLAIN ANALYZE, stop charging time to this trigger, and count * If doing EXPLAIN ANALYZE, stop charging time to this trigger, and count
...@@ -2605,7 +2822,7 @@ AfterTriggerExecute(AfterTriggerEvent event, ...@@ -2605,7 +2822,7 @@ AfterTriggerExecute(AfterTriggerEvent event,
* that can be invoked now with the current firing ID. * that can be invoked now with the current firing ID.
* *
* If move_list isn't NULL, events that are not to be invoked now are * If move_list isn't NULL, events that are not to be invoked now are
* removed from the given list and appended to move_list. * transferred to move_list.
* *
* When immediate_only is TRUE, do not invoke currently-deferred triggers. * When immediate_only is TRUE, do not invoke currently-deferred triggers.
* (This will be FALSE only at main transaction exit.) * (This will be FALSE only at main transaction exit.)
...@@ -2618,26 +2835,22 @@ afterTriggerMarkEvents(AfterTriggerEventList *events, ...@@ -2618,26 +2835,22 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
bool immediate_only) bool immediate_only)
{ {
bool found = false; bool found = false;
AfterTriggerEvent event, AfterTriggerEvent event;
prev_event; AfterTriggerEventChunk *chunk;
prev_event = NULL;
event = events->head;
while (event != NULL) for_each_event_chunk(event, chunk, *events)
{ {
AfterTriggerShared evtshared = GetTriggerSharedData(event);
bool defer_it = false; bool defer_it = false;
AfterTriggerEvent next_event;
if (!(event->ate_event & if (!(event->ate_flags &
(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS))) (AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS)))
{ {
/* /*
* This trigger hasn't been called or scheduled yet. Check if we * This trigger hasn't been called or scheduled yet. Check if we
* should call it now. * should call it now.
*/ */
if (immediate_only && if (immediate_only && afterTriggerCheckState(evtshared))
afterTriggerCheckState(event->ate_tgoid, event->ate_event))
{ {
defer_it = true; defer_it = true;
} }
...@@ -2646,8 +2859,8 @@ afterTriggerMarkEvents(AfterTriggerEventList *events, ...@@ -2646,8 +2859,8 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
/* /*
* Mark it as to be fired in this firing cycle. * Mark it as to be fired in this firing cycle.
*/ */
event->ate_firing_id = afterTriggers->firing_counter; evtshared->ats_firing_id = afterTriggers->firing_counter;
event->ate_event |= AFTER_TRIGGER_IN_PROGRESS; event->ate_flags |= AFTER_TRIGGER_IN_PROGRESS;
found = true; found = true;
} }
} }
...@@ -2655,45 +2868,19 @@ afterTriggerMarkEvents(AfterTriggerEventList *events, ...@@ -2655,45 +2868,19 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
/* /*
* If it's deferred, move it to move_list, if requested. * If it's deferred, move it to move_list, if requested.
*/ */
next_event = event->ate_next;
if (defer_it && move_list != NULL) if (defer_it && move_list != NULL)
{ {
/* Delink it from input list */ /* add it to move_list */
if (prev_event) afterTriggerAddEvent(move_list, event, evtshared);
prev_event->ate_next = next_event; /* mark original copy "done" so we don't do it again */
else event->ate_flags |= AFTER_TRIGGER_DONE;
events->head = next_event;
/* and add it to move_list */
event->ate_next = NULL;
if (move_list->tail == NULL)
{
/* first list entry */
move_list->head = event;
move_list->tail = event;
}
else
{
move_list->tail->ate_next = event;
move_list->tail = event;
}
}
else
{
/* Keep it in input list */
prev_event = event;
} }
event = next_event;
} }
/* Update list tail pointer in case we moved tail event */
events->tail = prev_event;
return found; return found;
} }
/* ---------- /*
* afterTriggerInvokeEvents() * afterTriggerInvokeEvents()
* *
* Scan the given event list for events that are marked as to be fired * Scan the given event list for events that are marked as to be fired
...@@ -2704,18 +2891,24 @@ afterTriggerMarkEvents(AfterTriggerEventList *events, ...@@ -2704,18 +2891,24 @@ afterTriggerMarkEvents(AfterTriggerEventList *events,
* make one locally to cache the info in case there are multiple trigger * make one locally to cache the info in case there are multiple trigger
* events per rel. * events per rel.
* *
* When delete_ok is TRUE, it's okay to delete fully-processed events. * When delete_ok is TRUE, it's safe to delete fully-processed events.
* The events list pointers are updated. * (We are not very tense about that: we simply reset a chunk to be empty
* ---------- * if all its events got fired. The objective here is just to avoid useless
* rescanning of events when a trigger queues new events during transaction
* end, so it's not necessary to worry much about the case where only
* some events are fired.)
*
* Returns TRUE if no unfired events remain in the list (this allows us
* to avoid repeating afterTriggerMarkEvents).
*/ */
static void static bool
afterTriggerInvokeEvents(AfterTriggerEventList *events, afterTriggerInvokeEvents(AfterTriggerEventList *events,
CommandId firing_id, CommandId firing_id,
EState *estate, EState *estate,
bool delete_ok) bool delete_ok)
{ {
AfterTriggerEvent event, bool all_fired = true;
prev_event; AfterTriggerEventChunk *chunk;
MemoryContext per_tuple_context; MemoryContext per_tuple_context;
bool local_estate = false; bool local_estate = false;
Relation rel = NULL; Relation rel = NULL;
...@@ -2738,83 +2931,68 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, ...@@ -2738,83 +2931,68 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE); ALLOCSET_DEFAULT_MAXSIZE);
prev_event = NULL; for_each_chunk(chunk, *events)
event = events->head;
while (event != NULL)
{ {
AfterTriggerEvent next_event; AfterTriggerEvent event;
bool all_fired_in_chunk = true;
/* for_each_event(event, chunk)
* Is it one for me to fire?
*/
if ((event->ate_event & AFTER_TRIGGER_IN_PROGRESS) &&
event->ate_firing_id == firing_id)
{ {
AfterTriggerShared evtshared = GetTriggerSharedData(event);
/* /*
* So let's fire it... but first, find the correct relation if * Is it one for me to fire?
* this is not the same relation as before.
*/ */
if (rel == NULL || RelationGetRelid(rel) != event->ate_relid) if ((event->ate_flags & AFTER_TRIGGER_IN_PROGRESS) &&
evtshared->ats_firing_id == firing_id)
{ {
ResultRelInfo *rInfo; /*
* So let's fire it... but first, find the correct relation if
rInfo = ExecGetTriggerResultRel(estate, event->ate_relid); * this is not the same relation as before.
rel = rInfo->ri_RelationDesc; */
trigdesc = rInfo->ri_TrigDesc; if (rel == NULL || RelationGetRelid(rel) != evtshared->ats_relid)
finfo = rInfo->ri_TrigFunctions; {
instr = rInfo->ri_TrigInstrument; ResultRelInfo *rInfo;
if (trigdesc == NULL) /* should not happen */
elog(ERROR, "relation %u has no triggers", rInfo = ExecGetTriggerResultRel(estate, evtshared->ats_relid);
event->ate_relid); rel = rInfo->ri_RelationDesc;
} trigdesc = rInfo->ri_TrigDesc;
finfo = rInfo->ri_TrigFunctions;
instr = rInfo->ri_TrigInstrument;
if (trigdesc == NULL) /* should not happen */
elog(ERROR, "relation %u has no triggers",
evtshared->ats_relid);
}
/* /*
* Fire it. Note that the AFTER_TRIGGER_IN_PROGRESS flag is still * Fire it. Note that the AFTER_TRIGGER_IN_PROGRESS flag is
* set, so recursive examinations of the event list won't try to * still set, so recursive examinations of the event list
* re-fire it. * won't try to re-fire it.
*/ */
AfterTriggerExecute(event, rel, trigdesc, finfo, instr, AfterTriggerExecute(event, rel, trigdesc, finfo, instr,
per_tuple_context); per_tuple_context);
/* /*
* Mark the event as done. * Mark the event as done.
*/ */
event->ate_event &= ~AFTER_TRIGGER_IN_PROGRESS; event->ate_flags &= ~AFTER_TRIGGER_IN_PROGRESS;
event->ate_event |= AFTER_TRIGGER_DONE; event->ate_flags |= AFTER_TRIGGER_DONE;
}
else if (!(event->ate_flags & AFTER_TRIGGER_DONE))
{
/* something remains to be done */
all_fired = all_fired_in_chunk = false;
}
} }
/* /* Clear the chunk if delete_ok and nothing left of interest */
* If it's now done, throw it away, if allowed. if (delete_ok && all_fired_in_chunk)
*
* NB: it's possible the trigger call above added more events to the
* queue, or that calls we will do later will want to add more, so we
* have to be careful about maintaining list validity at all points
* here.
*/
next_event = event->ate_next;
if ((event->ate_event & AFTER_TRIGGER_DONE) && delete_ok)
{ {
/* Delink it from list and free it */ chunk->freeptr = CHUNK_DATA_START(chunk);
if (prev_event) chunk->endfree = chunk->endptr;
prev_event->ate_next = next_event;
else
events->head = next_event;
pfree(event);
}
else
{
/* Keep it in list */
prev_event = event;
} }
event = next_event;
} }
/* Update list tail pointer in case we just deleted tail event */
events->tail = prev_event;
/* Release working resources */ /* Release working resources */
MemoryContextDelete(per_tuple_context); MemoryContextDelete(per_tuple_context);
...@@ -2832,6 +3010,8 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, ...@@ -2832,6 +3010,8 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
} }
FreeExecutorState(estate); FreeExecutorState(estate);
} }
return all_fired;
} }
...@@ -2854,10 +3034,11 @@ AfterTriggerBeginXact(void) ...@@ -2854,10 +3034,11 @@ AfterTriggerBeginXact(void)
MemoryContextAlloc(TopTransactionContext, MemoryContextAlloc(TopTransactionContext,
sizeof(AfterTriggersData)); sizeof(AfterTriggersData));
afterTriggers->firing_counter = FirstCommandId; afterTriggers->firing_counter = (CommandId) 1; /* mustn't be 0 */
afterTriggers->state = SetConstraintStateCreate(8); afterTriggers->state = SetConstraintStateCreate(8);
afterTriggers->events.head = NULL; afterTriggers->events.head = NULL;
afterTriggers->events.tail = NULL; afterTriggers->events.tail = NULL;
afterTriggers->events.tailfree = NULL;
afterTriggers->query_depth = -1; afterTriggers->query_depth = -1;
/* We initialize the query stack to a reasonable size */ /* We initialize the query stack to a reasonable size */
...@@ -2870,7 +3051,6 @@ AfterTriggerBeginXact(void) ...@@ -2870,7 +3051,6 @@ AfterTriggerBeginXact(void)
afterTriggers->event_cxt = NULL; afterTriggers->event_cxt = NULL;
/* Subtransaction stack is empty until/unless needed */ /* Subtransaction stack is empty until/unless needed */
afterTriggers->cxt_stack = NULL;
afterTriggers->state_stack = NULL; afterTriggers->state_stack = NULL;
afterTriggers->events_stack = NULL; afterTriggers->events_stack = NULL;
afterTriggers->depth_stack = NULL; afterTriggers->depth_stack = NULL;
...@@ -2891,6 +3071,8 @@ AfterTriggerBeginXact(void) ...@@ -2891,6 +3071,8 @@ AfterTriggerBeginXact(void)
void void
AfterTriggerBeginQuery(void) AfterTriggerBeginQuery(void)
{ {
AfterTriggerEventList *events;
/* Must be inside a transaction */ /* Must be inside a transaction */
Assert(afterTriggers != NULL); Assert(afterTriggers != NULL);
...@@ -2912,8 +3094,10 @@ AfterTriggerBeginQuery(void) ...@@ -2912,8 +3094,10 @@ AfterTriggerBeginQuery(void)
} }
/* Initialize this query's list to empty */ /* Initialize this query's list to empty */
afterTriggers->query_stack[afterTriggers->query_depth].head = NULL; events = &afterTriggers->query_stack[afterTriggers->query_depth];
afterTriggers->query_stack[afterTriggers->query_depth].tail = NULL; events->head = NULL;
events->tail = NULL;
events->tailfree = NULL;
} }
...@@ -2950,20 +3134,32 @@ AfterTriggerEndQuery(EState *estate) ...@@ -2950,20 +3134,32 @@ AfterTriggerEndQuery(EState *estate)
* IMMEDIATE: all events we have decided to defer will be available for it * IMMEDIATE: all events we have decided to defer will be available for it
* to fire. * to fire.
* *
* We loop in case a trigger queues more events. * We loop in case a trigger queues more events at the same query level
* (is that even possible?). Be careful here: firing a trigger could
* result in query_stack being repalloc'd, so we can't save its address
* across afterTriggerInvokeEvents calls.
* *
* If we find no firable events, we don't have to increment * If we find no firable events, we don't have to increment
* firing_counter. * firing_counter.
*/ */
events = &afterTriggers->query_stack[afterTriggers->query_depth]; for (;;)
while (afterTriggerMarkEvents(events, &afterTriggers->events, true))
{ {
CommandId firing_id = afterTriggers->firing_counter++; events = &afterTriggers->query_stack[afterTriggers->query_depth];
if (afterTriggerMarkEvents(events, &afterTriggers->events, true))
{
CommandId firing_id = afterTriggers->firing_counter++;
/* OK to delete the immediate events after processing them */ /* OK to delete the immediate events after processing them */
afterTriggerInvokeEvents(events, firing_id, estate, true); if (afterTriggerInvokeEvents(events, firing_id, estate, true))
break; /* all fired */
}
else
break;
} }
/* Release query-local storage for events */
afterTriggerFreeEventList(&afterTriggers->query_stack[afterTriggers->query_depth]);
afterTriggers->query_depth--; afterTriggers->query_depth--;
} }
...@@ -3011,13 +3207,17 @@ AfterTriggerFireDeferred(void) ...@@ -3011,13 +3207,17 @@ AfterTriggerFireDeferred(void)
{ {
CommandId firing_id = afterTriggers->firing_counter++; CommandId firing_id = afterTriggers->firing_counter++;
afterTriggerInvokeEvents(events, firing_id, NULL, true); if (afterTriggerInvokeEvents(events, firing_id, NULL, true))
break; /* all fired */
} }
/*
* We don't bother freeing the event list, since it will go away anyway
* (and more efficiently than via pfree) in AfterTriggerEndXact.
*/
if (snap_pushed) if (snap_pushed)
PopActiveSnapshot(); PopActiveSnapshot();
Assert(events->head == NULL);
} }
...@@ -3045,10 +3245,6 @@ AfterTriggerEndXact(bool isCommit) ...@@ -3045,10 +3245,6 @@ AfterTriggerEndXact(bool isCommit)
* pending-events list could be large, and so it's useful to discard it as * pending-events list could be large, and so it's useful to discard it as
* 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!
*
* (Note: any event_cxts of child subtransactions could also be deleted
* here, but we have no convenient way to find them, so we leave it to
* TopTransactionContext reset to clean them up.)
*/ */
if (afterTriggers && afterTriggers->event_cxt) if (afterTriggers && afterTriggers->event_cxt)
MemoryContextDelete(afterTriggers->event_cxt); MemoryContextDelete(afterTriggers->event_cxt);
...@@ -3087,8 +3283,6 @@ AfterTriggerBeginSubXact(void) ...@@ -3087,8 +3283,6 @@ AfterTriggerBeginSubXact(void)
old_cxt = MemoryContextSwitchTo(TopTransactionContext); old_cxt = MemoryContextSwitchTo(TopTransactionContext);
#define DEFTRIG_INITALLOC 8 #define DEFTRIG_INITALLOC 8
afterTriggers->cxt_stack = (MemoryContext *)
palloc(DEFTRIG_INITALLOC * sizeof(MemoryContext));
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 *)
...@@ -3106,9 +3300,6 @@ AfterTriggerBeginSubXact(void) ...@@ -3106,9 +3300,6 @@ AfterTriggerBeginSubXact(void)
/* 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->cxt_stack = (MemoryContext *)
repalloc(afterTriggers->cxt_stack,
new_alloc * sizeof(MemoryContext));
afterTriggers->state_stack = (SetConstraintState *) afterTriggers->state_stack = (SetConstraintState *)
repalloc(afterTriggers->state_stack, repalloc(afterTriggers->state_stack,
new_alloc * sizeof(SetConstraintState)); new_alloc * sizeof(SetConstraintState));
...@@ -3130,7 +3321,6 @@ AfterTriggerBeginSubXact(void) ...@@ -3130,7 +3321,6 @@ 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->cxt_stack[my_level] = NULL;
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;
...@@ -3148,6 +3338,7 @@ AfterTriggerEndSubXact(bool isCommit) ...@@ -3148,6 +3338,7 @@ AfterTriggerEndSubXact(bool isCommit)
int my_level = GetCurrentTransactionNestLevel(); int my_level = GetCurrentTransactionNestLevel();
SetConstraintState state; SetConstraintState state;
AfterTriggerEvent event; AfterTriggerEvent event;
AfterTriggerEventChunk *chunk;
CommandId subxact_firing_id; CommandId subxact_firing_id;
/* /*
...@@ -3172,55 +3363,27 @@ AfterTriggerEndSubXact(bool isCommit) ...@@ -3172,55 +3363,27 @@ AfterTriggerEndSubXact(bool isCommit)
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]);
/*
* It's entirely possible that the subxact created an event_cxt but
* there is not anything left in it (because all the triggers were
* fired at end-of-statement). If so, we should release the context
* to prevent memory leakage in a long sequence of subtransactions. We
* can detect whether there's anything of use in the context by seeing
* if anything was added to the global events list since subxact
* start. (This test doesn't catch every case where the context is
* deletable; for instance maybe the only additions were from a
* sub-sub-xact. But it handles the common case.)
*/
if (afterTriggers->cxt_stack[my_level] &&
afterTriggers->events.tail == afterTriggers->events_stack[my_level].tail)
{
MemoryContextDelete(afterTriggers->cxt_stack[my_level]);
/* avoid double delete if abort later */
afterTriggers->cxt_stack[my_level] = NULL;
}
} }
else else
{ {
/* /*
* Aborting. We don't really need to release the subxact's event_cxt, * Aborting. Release any event lists from queries being aborted,
* since it will go away anyway when CurTransactionContext gets reset, * and restore query_depth to its pre-subxact value.
* but doing so early in subxact abort helps free space we might need.
*
* (Note: any event_cxts of child subtransactions could also be
* deleted here, but we have no convenient way to find them, so we
* leave it to CurTransactionContext reset to clean them up.)
*/ */
if (afterTriggers->cxt_stack[my_level]) while (afterTriggers->query_depth > afterTriggers->depth_stack[my_level])
{ {
MemoryContextDelete(afterTriggers->cxt_stack[my_level]); afterTriggerFreeEventList(&afterTriggers->query_stack[afterTriggers->query_depth]);
/* avoid double delete if repeated aborts */ afterTriggers->query_depth--;
afterTriggers->cxt_stack[my_level] = NULL;
} }
Assert(afterTriggers->query_depth ==
afterTriggers->depth_stack[my_level]);
/* /*
* Restore the pointers from the stacks. * Restore the global deferred-event list to its former length,
*/ * discarding any events queued by the subxact.
afterTriggers->events = afterTriggers->events_stack[my_level];
afterTriggers->query_depth = afterTriggers->depth_stack[my_level];
/*
* Cleanup the tail of the list.
*/ */
if (afterTriggers->events.tail != NULL) afterTriggerRestoreEventList(&afterTriggers->events,
afterTriggers->events.tail->ate_next = NULL; &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
...@@ -3244,15 +3407,15 @@ AfterTriggerEndSubXact(bool isCommit) ...@@ -3244,15 +3407,15 @@ AfterTriggerEndSubXact(bool isCommit)
* 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 (event = afterTriggers->events.head; for_each_event_chunk(event, chunk, afterTriggers->events)
event != NULL;
event = event->ate_next)
{ {
if (event->ate_event & AfterTriggerShared evtshared = GetTriggerSharedData(event);
if (event->ate_flags &
(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS)) (AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS))
{ {
if (event->ate_firing_id >= subxact_firing_id) if (evtshared->ats_firing_id >= subxact_firing_id)
event->ate_event &= event->ate_flags &=
~(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS); ~(AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS);
} }
} }
...@@ -3579,8 +3742,9 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt) ...@@ -3579,8 +3742,9 @@ AfterTriggerSetState(ConstraintsSetStmt *stmt)
* but we'd better not if inside a subtransaction, since the * but we'd better not if inside a subtransaction, since the
* subtransaction could later get rolled back. * subtransaction could later get rolled back.
*/ */
afterTriggerInvokeEvents(events, firing_id, NULL, if (afterTriggerInvokeEvents(events, firing_id, NULL,
!IsSubTransaction()); !IsSubTransaction()))
break; /* all fired */
} }
} }
} }
...@@ -3604,6 +3768,7 @@ bool ...@@ -3604,6 +3768,7 @@ bool
AfterTriggerPendingOnRel(Oid relid) AfterTriggerPendingOnRel(Oid relid)
{ {
AfterTriggerEvent event; AfterTriggerEvent event;
AfterTriggerEventChunk *chunk;
int depth; int depth;
/* No-op if we aren't in a transaction. (Shouldn't happen?) */ /* No-op if we aren't in a transaction. (Shouldn't happen?) */
...@@ -3611,19 +3776,19 @@ AfterTriggerPendingOnRel(Oid relid) ...@@ -3611,19 +3776,19 @@ AfterTriggerPendingOnRel(Oid relid)
return false; return false;
/* Scan queued events */ /* Scan queued events */
for (event = afterTriggers->events.head; for_each_event_chunk(event, chunk, afterTriggers->events)
event != NULL;
event = event->ate_next)
{ {
AfterTriggerShared evtshared = GetTriggerSharedData(event);
/* /*
* We can ignore completed events. (Even if a DONE flag is rolled * We can ignore completed events. (Even if a DONE flag is rolled
* back by subxact abort, it's OK because the effects of the TRUNCATE * back by subxact abort, it's OK because the effects of the TRUNCATE
* or whatever must get rolled back too.) * or whatever must get rolled back too.)
*/ */
if (event->ate_event & AFTER_TRIGGER_DONE) if (event->ate_flags & AFTER_TRIGGER_DONE)
continue; continue;
if (event->ate_relid == relid) if (evtshared->ats_relid == relid)
return true; return true;
} }
...@@ -3634,14 +3799,14 @@ AfterTriggerPendingOnRel(Oid relid) ...@@ -3634,14 +3799,14 @@ AfterTriggerPendingOnRel(Oid relid)
*/ */
for (depth = 0; depth <= afterTriggers->query_depth; depth++) for (depth = 0; depth <= afterTriggers->query_depth; depth++)
{ {
for (event = afterTriggers->query_stack[depth].head; for_each_event_chunk(event, chunk, afterTriggers->query_stack[depth])
event != NULL;
event = event->ate_next)
{ {
if (event->ate_event & AFTER_TRIGGER_DONE) AfterTriggerShared evtshared = GetTriggerSharedData(event);
if (event->ate_flags & AFTER_TRIGGER_DONE)
continue; continue;
if (event->ate_relid == relid) if (evtshared->ats_relid == relid)
return true; return true;
} }
} }
...@@ -3665,35 +3830,85 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, ...@@ -3665,35 +3830,85 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
{ {
Relation rel = relinfo->ri_RelationDesc; Relation rel = relinfo->ri_RelationDesc;
TriggerDesc *trigdesc = relinfo->ri_TrigDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
int my_level = GetCurrentTransactionNestLevel(); AfterTriggerEventData new_event;
MemoryContext *cxtptr; AfterTriggerSharedData new_shared;
AfterTriggerEvent new_event;
int i; int i;
int ntriggers; int ntriggers;
int *tgindx; int *tgindx;
ItemPointerData oldctid;
ItemPointerData newctid;
if (afterTriggers == NULL) if (afterTriggers == NULL)
elog(ERROR, "AfterTriggerSaveEvent() called outside of transaction"); elog(ERROR, "AfterTriggerSaveEvent() called outside of transaction");
Assert(afterTriggers->query_depth >= 0);
/* /*
* event is used both as a bitmask and an array offset, * Validate the event code and collect the associated tuple CTIDs.
* so make sure we don't walk off the edge of our arrays *
*/ * The event code will be used both as a bitmask and an array offset, so
Assert(event >= 0 && event < TRIGGER_NUM_EVENT_CLASSES); * validation is important to make sure we don't walk off the edge of our
* arrays.
/*
* Get the CTID's of OLD and NEW
*/ */
if (oldtup != NULL) new_event.ate_flags = 0;
ItemPointerCopy(&(oldtup->t_self), &(oldctid)); switch (event)
else {
ItemPointerSetInvalid(&(oldctid)); case TRIGGER_EVENT_INSERT:
if (newtup != NULL) if (row_trigger)
ItemPointerCopy(&(newtup->t_self), &(newctid)); {
else Assert(oldtup == NULL);
ItemPointerSetInvalid(&(newctid)); Assert(newtup != NULL);
ItemPointerCopy(&(newtup->t_self), &(new_event.ate_ctid1));
ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
else
{
Assert(oldtup == NULL);
Assert(newtup == NULL);
ItemPointerSetInvalid(&(new_event.ate_ctid1));
ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
break;
case TRIGGER_EVENT_DELETE:
if (row_trigger)
{
Assert(oldtup != NULL);
Assert(newtup == NULL);
ItemPointerCopy(&(oldtup->t_self), &(new_event.ate_ctid1));
ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
else
{
Assert(oldtup == NULL);
Assert(newtup == NULL);
ItemPointerSetInvalid(&(new_event.ate_ctid1));
ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
break;
case TRIGGER_EVENT_UPDATE:
if (row_trigger)
{
Assert(oldtup != NULL);
Assert(newtup != NULL);
ItemPointerCopy(&(oldtup->t_self), &(new_event.ate_ctid1));
ItemPointerCopy(&(newtup->t_self), &(new_event.ate_ctid2));
new_event.ate_flags |= AFTER_TRIGGER_2CTIDS;
}
else
{
Assert(oldtup == NULL);
Assert(newtup == NULL);
ItemPointerSetInvalid(&(new_event.ate_ctid1));
ItemPointerSetInvalid(&(new_event.ate_ctid2));
}
break;
case TRIGGER_EVENT_TRUNCATE:
Assert(oldtup == NULL);
Assert(newtup == NULL);
ItemPointerSetInvalid(&(new_event.ate_ctid1));
ItemPointerSetInvalid(&(new_event.ate_ctid2));
break;
default:
elog(ERROR, "invalid after-trigger event code: %d", event);
break;
}
/* /*
* Scan the appropriate set of triggers * Scan the appropriate set of triggers
...@@ -3772,44 +3987,18 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, ...@@ -3772,44 +3987,18 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
} }
/* /*
* If we don't yet have an event context for the current (sub)xact, * Fill in event structure and add it to the current query's queue.
* create one. Make it a child of CurTransactionContext to ensure it
* will go away if the subtransaction aborts.
*/
if (my_level > 1) /* subtransaction? */
{
Assert(my_level < afterTriggers->maxtransdepth);
cxtptr = &afterTriggers->cxt_stack[my_level];
}
else
cxtptr = &afterTriggers->event_cxt;
if (*cxtptr == NULL)
*cxtptr = AllocSetContextCreate(CurTransactionContext,
"AfterTriggerEvents",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
/*
* Create a new event.
*/ */
new_event = (AfterTriggerEvent) new_shared.ats_event =
MemoryContextAlloc(*cxtptr, sizeof(AfterTriggerEventData));
new_event->ate_next = NULL;
new_event->ate_event =
(event & TRIGGER_EVENT_OPMASK) | (event & TRIGGER_EVENT_OPMASK) |
(row_trigger ? TRIGGER_EVENT_ROW : 0) | (row_trigger ? TRIGGER_EVENT_ROW : 0) |
(trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) | (trigger->tgdeferrable ? AFTER_TRIGGER_DEFERRABLE : 0) |
(trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0); (trigger->tginitdeferred ? AFTER_TRIGGER_INITDEFERRED : 0);
new_event->ate_firing_id = 0; new_shared.ats_tgoid = trigger->tgoid;
new_event->ate_tgoid = trigger->tgoid; new_shared.ats_relid = RelationGetRelid(rel);
new_event->ate_relid = rel->rd_id; new_shared.ats_firing_id = 0;
ItemPointerCopy(&oldctid, &(new_event->ate_oldctid));
ItemPointerCopy(&newctid, &(new_event->ate_newctid));
/* afterTriggerAddEvent(&afterTriggers->query_stack[afterTriggers->query_depth],
* Add the new event to the queue. &new_event, &new_shared);
*/
afterTriggerAddEvent(new_event);
} }
} }
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
* Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.68 2008/09/19 14:43:46 mha Exp $ * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.69 2008/10/24 23:42:35 tgl Exp $
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
...@@ -56,10 +56,8 @@ typedef struct TriggerData ...@@ -56,10 +56,8 @@ typedef struct TriggerData
/* More TriggerEvent flags, used only within trigger.c */ /* More TriggerEvent flags, used only within trigger.c */
#define AFTER_TRIGGER_DONE 0x00000010 #define AFTER_TRIGGER_DEFERRABLE 0x00000010
#define AFTER_TRIGGER_IN_PROGRESS 0x00000020 #define AFTER_TRIGGER_INITDEFERRED 0x00000020
#define AFTER_TRIGGER_DEFERRABLE 0x00000040
#define AFTER_TRIGGER_INITDEFERRED 0x00000080
#define TRIGGER_FIRED_BY_INSERT(event) \ #define TRIGGER_FIRED_BY_INSERT(event) \
(((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \ (((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \
......
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