📄 trigger.c
字号:
* * maxquerydepth is just the allocated length of query_stack. * * state_stack is a stack of pointers to saved copies of the SET CONSTRAINTS * state data; each subtransaction level that modifies that state first * saves a copy, which we use to restore the state if we abort. * * events_stack is a stack of copies of the events head/tail pointers, * which we use to restore those values during subtransaction abort. * * depth_stack is a stack of copies of subtransaction-start-time query_depth, * which we similarly use to clean up at subtransaction abort. * * firing_stack is a stack of copies of subtransaction-start-time * firing_counter. We use this to recognize which deferred triggers were * fired (or marked for firing) within an aborted subtransaction. * * We use GetCurrentTransactionNestLevel() to determine the correct array * index in these stacks. maxtransdepth is the number of allocated entries in * each stack. (By not keeping our own stack pointer, we can avoid trouble * in cases where errors during subxact abort cause multiple invocations * of AfterTriggerEndSubXact() at the same nesting depth.) */typedef struct AfterTriggersData{ CommandId firing_counter; /* next firing ID to assign */ SetConstraintState state; /* the active S C state */ AfterTriggerEventList events; /* deferred-event list */ int query_depth; /* current query list index */ AfterTriggerEventList *query_stack; /* events pending from each query */ int maxquerydepth; /* allocated len of above array */ /* these fields are just for resetting at subtrans abort: */ SetConstraintState *state_stack; /* stacked S C states */ AfterTriggerEventList *events_stack; /* stacked list pointers */ int *depth_stack; /* stacked query_depths */ CommandId *firing_stack; /* stacked firing_counters */ int maxtransdepth; /* allocated len of above arrays */} AfterTriggersData;typedef AfterTriggersData *AfterTriggers;static AfterTriggers afterTriggers;static void AfterTriggerExecute(AfterTriggerEvent event, Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo, Instrumentation *instr, MemoryContext per_tuple_context);static SetConstraintState SetConstraintStateCreate(int numalloc);static SetConstraintState SetConstraintStateCopy(SetConstraintState state);static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, Oid tgoid, bool tgisdeferred);/* ---------- * afterTriggerCheckState() * * Returns true if the trigger identified by tgoid is actually * in state DEFERRED. * ---------- */static boolafterTriggerCheckState(Oid tgoid, TriggerEvent eventstate){ SetConstraintState state = afterTriggers->state; int i; /* * For not-deferrable triggers (i.e. normal AFTER ROW triggers and * constraints declared NOT DEFERRABLE), the state is always false. */ if ((eventstate & AFTER_TRIGGER_DEFERRABLE) == 0) return false; /* * Check if SET CONSTRAINTS has been executed for this specific trigger. */ for (i = 0; i < state->numstates; i++) { if (state->trigstates[i].sct_tgoid == tgoid) return state->trigstates[i].sct_tgisdeferred; } /* * Check if SET CONSTRAINTS ALL has been executed; if so use that. */ if (state->all_isset) return state->all_isdeferred; /* * Otherwise return the default state for the trigger. */ return ((eventstate & AFTER_TRIGGER_INITDEFERRED) != 0);}/* ---------- * afterTriggerAddEvent() * * Add a new trigger event to the current query's queue. * ---------- */static voidafterTriggerAddEvent(AfterTriggerEvent event){ AfterTriggerEventList *events; Assert(event->ate_next == NULL); /* Must be inside a query */ Assert(afterTriggers->query_depth >= 0); events = &afterTriggers->query_stack[afterTriggers->query_depth]; if (events->tail == NULL) { /* first list entry */ events->head = event; events->tail = event; } else { events->tail->ate_next = event; events->tail = event; }}/* ---------- * AfterTriggerExecute() * * Fetch the required tuples back from the heap and fire one * single trigger function. * * Frequently, this will be fired many times in a row for triggers of * a single relation. Therefore, we cache the open relation and provide * fmgr lookup cache space at the caller level. (For triggers fired at * the end of a query, we can even piggyback on the executor's state.) * * event: event currently being fired. * rel: open relation for event. * trigdesc: working copy of rel's trigger info. * finfo: array of fmgr lookup cache entries (one per trigger in trigdesc). * instr: array of EXPLAIN ANALYZE instrumentation nodes (one per trigger), * or NULL if no instrumentation is wanted. * per_tuple_context: memory context to call trigger function in. * ---------- */static voidAfterTriggerExecute(AfterTriggerEvent event, Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo, Instrumentation *instr, MemoryContext per_tuple_context){ Oid tgoid = event->ate_tgoid; TriggerData LocTriggerData; HeapTupleData oldtuple; HeapTupleData newtuple; HeapTuple rettuple; Buffer oldbuffer = InvalidBuffer; Buffer newbuffer = InvalidBuffer; int tgindx; /* * Locate trigger in trigdesc. */ LocTriggerData.tg_trigger = NULL; for (tgindx = 0; tgindx < trigdesc->numtriggers; tgindx++) { if (trigdesc->triggers[tgindx].tgoid == tgoid) { LocTriggerData.tg_trigger = &(trigdesc->triggers[tgindx]); break; } } if (LocTriggerData.tg_trigger == NULL) elog(ERROR, "could not find trigger %u", tgoid); /* * If doing EXPLAIN ANALYZE, start charging time to this trigger. We want * to include time spent re-fetching tuples in the trigger cost. */ if (instr) InstrStartNode(instr + tgindx); /* * Fetch the required OLD and NEW tuples. */ if (ItemPointerIsValid(&(event->ate_oldctid))) { ItemPointerCopy(&(event->ate_oldctid), &(oldtuple.t_self)); if (!heap_fetch(rel, SnapshotAny, &oldtuple, &oldbuffer, false, NULL)) elog(ERROR, "failed to fetch old tuple for AFTER trigger"); } if (ItemPointerIsValid(&(event->ate_newctid))) { ItemPointerCopy(&(event->ate_newctid), &(newtuple.t_self)); if (!heap_fetch(rel, SnapshotAny, &newtuple, &newbuffer, false, NULL)) elog(ERROR, "failed to fetch new tuple for AFTER trigger"); } /* * Setup the trigger information */ LocTriggerData.type = T_TriggerData; LocTriggerData.tg_event = event->ate_event & (TRIGGER_EVENT_OPMASK | TRIGGER_EVENT_ROW); LocTriggerData.tg_relation = rel; switch (event->ate_event & TRIGGER_EVENT_OPMASK) { case TRIGGER_EVENT_INSERT: LocTriggerData.tg_trigtuple = &newtuple; LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_trigtuplebuf = newbuffer; LocTriggerData.tg_newtuplebuf = InvalidBuffer; break; case TRIGGER_EVENT_UPDATE: LocTriggerData.tg_trigtuple = &oldtuple; LocTriggerData.tg_newtuple = &newtuple; LocTriggerData.tg_trigtuplebuf = oldbuffer; LocTriggerData.tg_newtuplebuf = newbuffer; break; case TRIGGER_EVENT_DELETE: LocTriggerData.tg_trigtuple = &oldtuple; LocTriggerData.tg_newtuple = NULL; LocTriggerData.tg_trigtuplebuf = oldbuffer; LocTriggerData.tg_newtuplebuf = InvalidBuffer; break; } MemoryContextReset(per_tuple_context); /* * Call the trigger and throw away any possibly returned updated tuple. * (Don't let ExecCallTriggerFunc measure EXPLAIN time.) */ rettuple = ExecCallTriggerFunc(&LocTriggerData, tgindx, finfo, NULL, per_tuple_context); if (rettuple != NULL && rettuple != &oldtuple && rettuple != &newtuple) heap_freetuple(rettuple); /* * Release buffers */ if (oldbuffer != InvalidBuffer) ReleaseBuffer(oldbuffer); if (newbuffer != InvalidBuffer) ReleaseBuffer(newbuffer); /* * If doing EXPLAIN ANALYZE, stop charging time to this trigger, and count * one "tuple returned" (really the number of firings). */ if (instr) InstrStopNode(instr + tgindx, true);}/* * afterTriggerMarkEvents() * * Scan the given event list for not yet invoked events. Mark the ones * 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 * removed from the given list and appended to move_list. * * When immediate_only is TRUE, do not invoke currently-deferred triggers. * (This will be FALSE only at main transaction exit.) * * Returns TRUE if any invokable events were found. */static boolafterTriggerMarkEvents(AfterTriggerEventList *events, AfterTriggerEventList *move_list, bool immediate_only){ bool found = false; AfterTriggerEvent event, prev_event; prev_event = NULL; event = events->head; while (event != NULL) { bool defer_it = false; AfterTriggerEvent next_event; if (!(event->ate_event & (AFTER_TRIGGER_DONE | AFTER_TRIGGER_IN_PROGRESS))) { /* * This trigger hasn't been called or scheduled yet. Check if we * should call it now. */ if (immediate_only && afterTriggerCheckState(event->ate_tgoid, event->ate_event)) { defer_it = true; } else { /* * Mark it as to be fired in this firing cycle. */ event->ate_firing_id = afterTriggers->firing_counter; event->ate_event |= AFTER_TRIGGER_IN_PROGRESS; found = true; } } /* * If it's deferred, move it to move_list, if requested. */ next_event = event->ate_next; if (defer_it && move_list != NULL) { /* Delink it from input list */ if (prev_event) prev_event->ate_next = next_event; else 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;}/* ---------- * afterTriggerInvokeEvents() * * Scan the given event list for events that are marked as to be fired * in the current firing cycle, and fire them. * * If estate isn't NULL, then we expect that all the firable events are * for triggers of the relations included in the estate's result relation * array. This allows us to re-use the estate's open relations and * trigger cache info. When estate is NULL, we have to find the relations * the hard way. * * When delete_ok is TRUE, it's okay to delete fully-processed events. * The events list pointers are updated. * ---------- */static voidafterTriggerInvokeEvents(AfterTriggerEventList *events, CommandId firing_id, EState *estate, bool delete_ok){ AfterTriggerEvent event, prev_event; MemoryContext per_tuple_context; Relation rel = NULL; TriggerDesc *trigdesc = NULL; FmgrInfo *finfo = NULL; Instrumentation *instr = NULL; /* Make a per-tuple memory context for trigger function calls */ per_tuple_context = AllocSetContextCreate(CurrentMemoryContext, "AfterTriggerTupleContext", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); prev_event = NULL; event = events->head; while (event != NULL) { AfterTriggerEvent next_event; /* * Is it one for me to fire? */ if ((event->ate_event & AFTER_TRIGGER_IN_PROGRESS) && event->ate_firing_id == firing_id) { /* * So let's fire it... but first, open the correct relation if * this is not the same relation as before. */ if (rel == NULL || rel->rd_id != event->ate_relid) { if (estate) { /* Find target relation among estate's result rels */ ResultRelInfo *rInfo; int nr; rInfo = estate->es_result_relations; nr = estate->es_num_result_relations; while (nr > 0) { if (rInfo->ri_RelationDesc->rd_id == event->ate_relid) break; rInfo++; nr--; } if (nr <= 0) /* should not happen */ elog(ERROR, "could not find relation %u among query result relations", event->ate_relid); rel = rInfo->ri_RelationDesc; trigdesc = rInfo->ri_TrigDesc; finfo = rInfo->ri_TrigFunctions; instr = rInfo->ri_TrigInstrument; } else { /* Hard way: we manage the resources for ourselves */ if (rel) heap_close(rel, NoLock); if (trigdesc) FreeTriggerDesc(trigdesc); if (finfo) pfree(finfo); Assert(instr == NULL); /* never used in this case */ /* * We assume that an appropriate lock is still held by the * executor, so grab no new lock here. */ rel = heap_open(event->ate_relid, NoLock); /* * Copy relation's trigger info so that we have a stable * copy no matter what the called triggers do. */ trigdesc = CopyTriggerDesc(rel->trigdesc); if (trigdesc == NULL) /* should not happen */ elog(ERROR, "relation %u has no triggers", event->ate_relid); /*
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -