diff options
Diffstat (limited to 'src/backend/commands/trigger.c')
-rw-r--r-- | src/backend/commands/trigger.c | 385 |
1 files changed, 271 insertions, 114 deletions
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 8f6de3fc69..576fa19705 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -25,6 +25,7 @@ #include "catalog/objectaccess.h" #include "catalog/pg_constraint.h" #include "catalog/pg_constraint_fn.h" +#include "catalog/pg_inherits_fn.h" #include "catalog/pg_proc.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" @@ -100,7 +101,8 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata, static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, int event, bool row_trigger, HeapTuple oldtup, HeapTuple newtup, - List *recheckIndexes, Bitmapset *modifiedCols); + List *recheckIndexes, Bitmapset *modifiedCols, + TransitionCaptureState *transition_capture); static void AfterTriggerEnlargeQueryState(void); #ifdef XCP @@ -361,13 +363,6 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, * adjustments will be needed below. */ - if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is a partitioned table", - RelationGetRelationName(rel)), - errdetail("Triggers on partitioned tables cannot have transition tables."))); - if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -382,6 +377,27 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, RelationGetRelationName(rel)), errdetail("Triggers on views cannot have transition tables."))); + /* + * We currently don't allow row-level triggers with transition + * tables on partition or inheritance children. Such triggers + * would somehow need to see tuples converted to the format of the + * table they're attached to, and it's not clear which subset of + * tuples each child should see. See also the prohibitions in + * ATExecAttachPartition() and ATExecAddInherit(). + */ + if (TRIGGER_FOR_ROW(tgtype) && has_superclass(rel->rd_id)) + { + /* Use appropriate error message. */ + if (rel->rd_rel->relispartition) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ROW triggers with transition tables are not supported on partitions"))); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("ROW triggers with transition tables are not supported on inheritance children"))); + } + if (stmt->timing != TRIGGER_TYPE_AFTER) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), @@ -392,6 +408,23 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("TRUNCATE triggers with transition tables are not supported"))); + /* + * We currently don't allow multi-event triggers ("INSERT OR + * UPDATE") with transition tables, because it's not clear how to + * handle INSERT ... ON CONFLICT statements which can fire both + * INSERT and UPDATE triggers. We show the inserted tuples to + * INSERT triggers and the updated tuples to UPDATE triggers, but + * it's not yet clear what INSERT OR UPDATE trigger should see. + * This restriction could be lifted if we can decide on the right + * semantics in a later release. + */ + if (((TRIGGER_FOR_INSERT(tgtype) ? 1 : 0) + + (TRIGGER_FOR_UPDATE(tgtype) ? 1 : 0) + + (TRIGGER_FOR_DELETE(tgtype) ? 1 : 0)) != 1) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Transition tables cannot be specified for triggers with more than one event"))); + if (tt->isNew) { if (!(TRIGGER_FOR_INSERT(tgtype) || @@ -2038,6 +2071,120 @@ equalTriggerDescs(TriggerDesc *trigdesc1, TriggerDesc *trigdesc2) #endif /* NOT_USED */ /* + * Check if there is a row-level trigger with transition tables that prevents + * a table from becoming an inheritance child or partition. Return the name + * of the first such incompatible trigger, or NULL if there is none. + */ +const char * +FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc) +{ + if (trigdesc != NULL) + { + int i; + + for (i = 0; i < trigdesc->numtriggers; ++i) + { + Trigger *trigger = &trigdesc->triggers[i]; + + if (trigger->tgoldtable != NULL || trigger->tgnewtable != NULL) + return trigger->tgname; + } + } + + return NULL; +} + +/* + * Make a TransitionCaptureState object from a given TriggerDesc. The + * resulting object holds the flags which control whether transition tuples + * are collected when tables are modified, and the tuplestores themselves. + * Note that we copy the flags from a parent table into this struct (rather + * than using each relation's TriggerDesc directly) so that we can use it to + * control the collection of transition tuples from child tables. + * + * If there are no triggers with transition tables configured for 'trigdesc', + * then return NULL. + * + * The resulting object can be passed to the ExecAR* functions. The caller + * should set tcs_map or tcs_original_insert_tuple as appropriate when dealing + * with child tables. + */ +TransitionCaptureState * +MakeTransitionCaptureState(TriggerDesc *trigdesc) +{ + TransitionCaptureState *state = NULL; + + if (trigdesc != NULL && + (trigdesc->trig_delete_old_table || trigdesc->trig_update_old_table || + trigdesc->trig_update_new_table || trigdesc->trig_insert_new_table)) + { + MemoryContext oldcxt; + ResourceOwner saveResourceOwner; + + /* + * Normally DestroyTransitionCaptureState should be called after + * executing all AFTER triggers for the current statement. + * + * To handle error cleanup, TransitionCaptureState and the tuplestores + * it contains will live in the current [sub]transaction's memory + * context. Likewise for the current resource owner, because we also + * want to clean up temporary files spilled to disk by the tuplestore + * in that scenario. This scope is sufficient, because AFTER triggers + * with transition tables cannot be deferred (only constraint triggers + * can be deferred, and constraint triggers cannot have transition + * tables). The AFTER trigger queue may contain pointers to this + * TransitionCaptureState, but any such entries will be processed or + * discarded before the end of the current [sub]transaction. + * + * If a future release allows deferred triggers with transition + * tables, we'll need to reconsider the scope of the + * TransitionCaptureState object. + */ + oldcxt = MemoryContextSwitchTo(CurTransactionContext); + saveResourceOwner = CurrentResourceOwner; + + state = (TransitionCaptureState *) + palloc0(sizeof(TransitionCaptureState)); + state->tcs_delete_old_table = trigdesc->trig_delete_old_table; + state->tcs_update_old_table = trigdesc->trig_update_old_table; + state->tcs_update_new_table = trigdesc->trig_update_new_table; + state->tcs_insert_new_table = trigdesc->trig_insert_new_table; + PG_TRY(); + { + CurrentResourceOwner = CurTransactionResourceOwner; + if (trigdesc->trig_delete_old_table || trigdesc->trig_update_old_table) + state->tcs_old_tuplestore = tuplestore_begin_heap(false, false, work_mem); + if (trigdesc->trig_insert_new_table) + state->tcs_insert_tuplestore = tuplestore_begin_heap(false, false, work_mem); + if (trigdesc->trig_update_new_table) + state->tcs_update_tuplestore = tuplestore_begin_heap(false, false, work_mem); + } + PG_CATCH(); + { + CurrentResourceOwner = saveResourceOwner; + PG_RE_THROW(); + } + PG_END_TRY(); + CurrentResourceOwner = saveResourceOwner; + MemoryContextSwitchTo(oldcxt); + } + + return state; +} + +void +DestroyTransitionCaptureState(TransitionCaptureState *tcs) +{ + if (tcs->tcs_insert_tuplestore != NULL) + tuplestore_end(tcs->tcs_insert_tuplestore); + if (tcs->tcs_update_tuplestore != NULL) + tuplestore_end(tcs->tcs_update_tuplestore); + if (tcs->tcs_old_tuplestore != NULL) + tuplestore_end(tcs->tcs_old_tuplestore); + pfree(tcs); +} + +/* * Call a trigger function. * * trigdata: trigger descriptor. @@ -2195,13 +2342,14 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo) } void -ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo) +ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo, + TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->trig_insert_after_statement) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT, - false, NULL, NULL, NIL, NULL); + false, NULL, NULL, NIL, NULL, transition_capture); } TupleTableSlot * @@ -2272,14 +2420,17 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, void ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, - HeapTuple trigtuple, List *recheckIndexes) + HeapTuple trigtuple, List *recheckIndexes, + TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; - if (trigdesc && - (trigdesc->trig_insert_after_row || trigdesc->trig_insert_new_table)) + if ((trigdesc && trigdesc->trig_insert_after_row) || + (transition_capture && transition_capture->tcs_insert_new_table)) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT, - true, NULL, trigtuple, recheckIndexes, NULL); + true, NULL, trigtuple, + recheckIndexes, NULL, + transition_capture); } TupleTableSlot * @@ -2401,13 +2552,14 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo) } void -ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo) +ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo, + TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->trig_delete_after_statement) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE, - false, NULL, NULL, NIL, NULL); + false, NULL, NULL, NIL, NULL, transition_capture); } bool @@ -2482,12 +2634,13 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, void ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, - HeapTuple fdw_trigtuple) + HeapTuple fdw_trigtuple, + TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; - if (trigdesc && - (trigdesc->trig_delete_after_row || trigdesc->trig_delete_old_table)) + if ((trigdesc && trigdesc->trig_delete_after_row) || + (transition_capture && transition_capture->tcs_delete_old_table)) { HeapTuple trigtuple; @@ -2503,7 +2656,8 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo, trigtuple = fdw_trigtuple; AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE, - true, trigtuple, NULL, NIL, NULL); + true, trigtuple, NULL, NIL, NULL, + transition_capture); if (trigtuple != fdw_trigtuple) heap_freetuple(trigtuple); } @@ -2612,14 +2766,16 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo) } void -ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo) +ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo, + TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; if (trigdesc && trigdesc->trig_update_after_statement) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, false, NULL, NULL, NIL, - GetUpdatedColumns(relinfo, estate)); + GetUpdatedColumns(relinfo, estate), + transition_capture); } TupleTableSlot * @@ -2744,12 +2900,15 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, HeapTuple newtuple, - List *recheckIndexes) + List *recheckIndexes, + TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; - if (trigdesc && (trigdesc->trig_update_after_row || - trigdesc->trig_update_old_table || trigdesc->trig_update_new_table)) + if ((trigdesc && trigdesc->trig_update_after_row) || + (transition_capture && + (transition_capture->tcs_update_old_table || + transition_capture->tcs_update_new_table))) { HeapTuple trigtuple; @@ -2766,7 +2925,8 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, true, trigtuple, newtuple, recheckIndexes, - GetUpdatedColumns(relinfo, estate)); + GetUpdatedColumns(relinfo, estate), + transition_capture); if (trigtuple != fdw_trigtuple) heap_freetuple(trigtuple); } @@ -2897,7 +3057,7 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo) if (trigdesc && trigdesc->trig_truncate_after_statement) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_TRUNCATE, - false, NULL, NULL, NIL, NULL); + false, NULL, NULL, NIL, NULL, NULL); } @@ -3285,6 +3445,7 @@ typedef struct AfterTriggerSharedData Oid ats_tgoid; /* the trigger's ID */ Oid ats_relid; /* the relation it's on */ CommandId ats_firing_id; /* ID for firing cycle */ + TransitionCaptureState *ats_transition_capture; } AfterTriggerSharedData; typedef struct AfterTriggerEventData *AfterTriggerEvent; @@ -3390,9 +3551,6 @@ typedef struct AfterTriggerEventList * fdw_tuplestores[query_depth] is a tuplestore containing the foreign tuples * needed for the current query. * - * old_tuplestores[query_depth] and new_tuplestores[query_depth] hold the - * transition relations for the current query. - * * maxquerydepth is just the allocated length of query_stack and the * tuplestores. * @@ -3425,8 +3583,6 @@ typedef struct AfterTriggersData AfterTriggerEventList *query_stack; /* events pending from each query */ Tuplestorestate **fdw_tuplestores; /* foreign tuples for one row from * each query */ - Tuplestorestate **old_tuplestores; /* all old tuples from each query */ - Tuplestorestate **new_tuplestores; /* all new tuples from each query */ int maxquerydepth; /* allocated len of above array */ MemoryContext event_cxt; /* memory context for events, if any */ @@ -3447,7 +3603,8 @@ static void AfterTriggerExecute(AfterTriggerEvent event, Instrumentation *instr, MemoryContext per_tuple_context, TupleTableSlot *trig_tuple_slot1, - TupleTableSlot *trig_tuple_slot2); + TupleTableSlot *trig_tuple_slot2, + TransitionCaptureState *transition_capture); static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCopy(SetConstraintState state); static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, @@ -3456,8 +3613,6 @@ static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, /* * Gets a current query transition tuplestore and initializes it if necessary. - * This can be holding a single transition row tuple (in the case of an FDW) - * or a transition table (for an AFTER trigger). */ static Tuplestorestate * GetTriggerTransitionTuplestore(Tuplestorestate **tss) @@ -3637,6 +3792,7 @@ afterTriggerAddEvent(AfterTriggerEventList *events, if (newshared->ats_tgoid == evtshared->ats_tgoid && newshared->ats_relid == evtshared->ats_relid && newshared->ats_event == evtshared->ats_event && + newshared->ats_transition_capture == evtshared->ats_transition_capture && newshared->ats_firing_id == 0) break; } @@ -3748,7 +3904,8 @@ AfterTriggerExecute(AfterTriggerEvent event, FmgrInfo *finfo, Instrumentation *instr, MemoryContext per_tuple_context, TupleTableSlot *trig_tuple_slot1, - TupleTableSlot *trig_tuple_slot2) + TupleTableSlot *trig_tuple_slot2, + TransitionCaptureState *transition_capture) { AfterTriggerShared evtshared = GetTriggerSharedData(event); Oid tgoid = evtshared->ats_tgoid; @@ -3863,16 +4020,36 @@ AfterTriggerExecute(AfterTriggerEvent event, /* * Set up the tuplestore information. */ - if (LocTriggerData.tg_trigger->tgoldtable) - LocTriggerData.tg_oldtable = - GetTriggerTransitionTuplestore(afterTriggers.old_tuplestores); - else - LocTriggerData.tg_oldtable = NULL; - if (LocTriggerData.tg_trigger->tgnewtable) - LocTriggerData.tg_newtable = - GetTriggerTransitionTuplestore(afterTriggers.new_tuplestores); - else - LocTriggerData.tg_newtable = NULL; + LocTriggerData.tg_oldtable = LocTriggerData.tg_newtable = NULL; + if (transition_capture != NULL) + { + if (LocTriggerData.tg_trigger->tgoldtable) + LocTriggerData.tg_oldtable = transition_capture->tcs_old_tuplestore; + if (LocTriggerData.tg_trigger->tgnewtable) + { + /* + * Currently a trigger with transition tables may only be defined + * for a single event type (here AFTER INSERT or AFTER UPDATE, but + * not AFTER INSERT OR ...). + */ + Assert((TRIGGER_FOR_INSERT(LocTriggerData.tg_trigger->tgtype) != 0) ^ + (TRIGGER_FOR_UPDATE(LocTriggerData.tg_trigger->tgtype) != 0)); + + /* + * Show either the insert or update new tuple images, depending on + * which event type the trigger was registered for. A single + * statement may have produced both in the case of INSERT ... ON + * CONFLICT ... DO UPDATE, and in that case the event determines + * which tuplestore the trigger sees as the NEW TABLE. + */ + if (TRIGGER_FOR_INSERT(LocTriggerData.tg_trigger->tgtype)) + LocTriggerData.tg_newtable = + transition_capture->tcs_insert_tuplestore; + else + LocTriggerData.tg_newtable = + transition_capture->tcs_update_tuplestore; + } + } /* * Setup the remaining trigger information @@ -4080,7 +4257,8 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, * won't try to re-fire it. */ AfterTriggerExecute(event, rel, trigdesc, finfo, instr, - per_tuple_context, slot1, slot2); + per_tuple_context, slot1, slot2, + evtshared->ats_transition_capture); /* * Mark the event as done. @@ -4154,8 +4332,6 @@ AfterTriggerBeginXact(void) Assert(afterTriggers.state == NULL); Assert(afterTriggers.query_stack == NULL); Assert(afterTriggers.fdw_tuplestores == NULL); - Assert(afterTriggers.old_tuplestores == NULL); - Assert(afterTriggers.new_tuplestores == NULL); Assert(afterTriggers.maxquerydepth == 0); Assert(afterTriggers.event_cxt == NULL); Assert(afterTriggers.events.head == NULL); @@ -4224,8 +4400,6 @@ AfterTriggerEndQuery(EState *estate) { AfterTriggerEventList *events; Tuplestorestate *fdw_tuplestore; - Tuplestorestate *old_tuplestore; - Tuplestorestate *new_tuplestore; /* Must be inside a query, too */ Assert(afterTriggers.query_depth >= 0); @@ -4284,18 +4458,6 @@ AfterTriggerEndQuery(EState *estate) tuplestore_end(fdw_tuplestore); afterTriggers.fdw_tuplestores[afterTriggers.query_depth] = NULL; } - old_tuplestore = afterTriggers.old_tuplestores[afterTriggers.query_depth]; - if (old_tuplestore) - { - tuplestore_end(old_tuplestore); - afterTriggers.old_tuplestores[afterTriggers.query_depth] = NULL; - } - new_tuplestore = afterTriggers.new_tuplestores[afterTriggers.query_depth]; - if (new_tuplestore) - { - tuplestore_end(new_tuplestore); - afterTriggers.new_tuplestores[afterTriggers.query_depth] = NULL; - } afterTriggerFreeEventList(&afterTriggers.query_stack[afterTriggers.query_depth]); afterTriggers.query_depth--; @@ -4409,8 +4571,6 @@ AfterTriggerEndXact(bool isCommit) */ afterTriggers.query_stack = NULL; afterTriggers.fdw_tuplestores = NULL; - afterTriggers.old_tuplestores = NULL; - afterTriggers.new_tuplestores = NULL; afterTriggers.maxquerydepth = 0; afterTriggers.state = NULL; @@ -4543,18 +4703,6 @@ AfterTriggerEndSubXact(bool isCommit) tuplestore_end(ts); afterTriggers.fdw_tuplestores[afterTriggers.query_depth] = NULL; } - ts = afterTriggers.old_tuplestores[afterTriggers.query_depth]; - if (ts) - { - tuplestore_end(ts); - afterTriggers.old_tuplestores[afterTriggers.query_depth] = NULL; - } - ts = afterTriggers.new_tuplestores[afterTriggers.query_depth]; - if (ts) - { - tuplestore_end(ts); - afterTriggers.new_tuplestores[afterTriggers.query_depth] = NULL; - } afterTriggerFreeEventList(&afterTriggers.query_stack[afterTriggers.query_depth]); } @@ -4634,12 +4782,6 @@ AfterTriggerEnlargeQueryState(void) afterTriggers.fdw_tuplestores = (Tuplestorestate **) MemoryContextAllocZero(TopTransactionContext, new_alloc * sizeof(Tuplestorestate *)); - afterTriggers.old_tuplestores = (Tuplestorestate **) - MemoryContextAllocZero(TopTransactionContext, - new_alloc * sizeof(Tuplestorestate *)); - afterTriggers.new_tuplestores = (Tuplestorestate **) - MemoryContextAllocZero(TopTransactionContext, - new_alloc * sizeof(Tuplestorestate *)); afterTriggers.maxquerydepth = new_alloc; } else @@ -4655,19 +4797,9 @@ AfterTriggerEnlargeQueryState(void) afterTriggers.fdw_tuplestores = (Tuplestorestate **) repalloc(afterTriggers.fdw_tuplestores, new_alloc * sizeof(Tuplestorestate *)); - afterTriggers.old_tuplestores = (Tuplestorestate **) - repalloc(afterTriggers.old_tuplestores, - new_alloc * sizeof(Tuplestorestate *)); - afterTriggers.new_tuplestores = (Tuplestorestate **) - repalloc(afterTriggers.new_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 *)); - memset(afterTriggers.old_tuplestores + old_alloc, - 0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *)); - memset(afterTriggers.new_tuplestores + old_alloc, - 0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *)); afterTriggers.maxquerydepth = new_alloc; } @@ -5134,7 +5266,8 @@ static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, int event, bool row_trigger, HeapTuple oldtup, HeapTuple newtup, - List *recheckIndexes, Bitmapset *modifiedCols) + List *recheckIndexes, Bitmapset *modifiedCols, + TransitionCaptureState *transition_capture) { Relation rel = relinfo->ri_RelationDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc; @@ -5159,40 +5292,63 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, AfterTriggerEnlargeQueryState(); /* - * If the relation has AFTER ... FOR EACH ROW triggers, capture rows into - * transition tuplestores for this depth. + * If the directly named relation has any triggers with transition tables, + * then we need to capture transition tuples. */ - if (row_trigger) + if (row_trigger && transition_capture != NULL) { - if ((event == TRIGGER_EVENT_DELETE && - trigdesc->trig_delete_old_table) || - (event == TRIGGER_EVENT_UPDATE && - trigdesc->trig_update_old_table)) + HeapTuple original_insert_tuple = transition_capture->tcs_original_insert_tuple; + TupleConversionMap *map = transition_capture->tcs_map; + bool delete_old_table = transition_capture->tcs_delete_old_table; + bool update_old_table = transition_capture->tcs_update_old_table; + bool update_new_table = transition_capture->tcs_update_new_table; + bool insert_new_table = transition_capture->tcs_insert_new_table;; + + if ((event == TRIGGER_EVENT_DELETE && delete_old_table) || + (event == TRIGGER_EVENT_UPDATE && update_old_table)) { Tuplestorestate *old_tuplestore; Assert(oldtup != NULL); - old_tuplestore = - GetTriggerTransitionTuplestore - (afterTriggers.old_tuplestores); - tuplestore_puttuple(old_tuplestore, oldtup); + old_tuplestore = transition_capture->tcs_old_tuplestore; + + if (map != NULL) + { + HeapTuple converted = do_convert_tuple(oldtup, map); + + tuplestore_puttuple(old_tuplestore, converted); + pfree(converted); + } + else + tuplestore_puttuple(old_tuplestore, oldtup); } - if ((event == TRIGGER_EVENT_INSERT && - trigdesc->trig_insert_new_table) || - (event == TRIGGER_EVENT_UPDATE && - trigdesc->trig_update_new_table)) + if ((event == TRIGGER_EVENT_INSERT && insert_new_table) || + (event == TRIGGER_EVENT_UPDATE && update_new_table)) { Tuplestorestate *new_tuplestore; Assert(newtup != NULL); - new_tuplestore = - GetTriggerTransitionTuplestore - (afterTriggers.new_tuplestores); - tuplestore_puttuple(new_tuplestore, newtup); + if (event == TRIGGER_EVENT_INSERT) + new_tuplestore = transition_capture->tcs_insert_tuplestore; + else + new_tuplestore = transition_capture->tcs_update_tuplestore; + + if (original_insert_tuple != NULL) + tuplestore_puttuple(new_tuplestore, original_insert_tuple); + else if (map != NULL) + { + HeapTuple converted = do_convert_tuple(newtup, map); + + tuplestore_puttuple(new_tuplestore, converted); + pfree(converted); + } + else + tuplestore_puttuple(new_tuplestore, newtup); } /* If transition tables are the only reason we're here, return. */ - if ((event == TRIGGER_EVENT_DELETE && !trigdesc->trig_delete_after_row) || + if (trigdesc == NULL || + (event == TRIGGER_EVENT_DELETE && !trigdesc->trig_delete_after_row) || (event == TRIGGER_EVENT_INSERT && !trigdesc->trig_insert_after_row) || (event == TRIGGER_EVENT_UPDATE && !trigdesc->trig_update_after_row)) return; @@ -5362,6 +5518,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, new_shared.ats_tgoid = trigger->tgoid; new_shared.ats_relid = RelationGetRelid(rel); new_shared.ats_firing_id = 0; + new_shared.ats_transition_capture = transition_capture; afterTriggerAddEvent(&afterTriggers.query_stack[afterTriggers.query_depth], &new_event, &new_shared); |