*** a/doc/src/sgml/ref/create_table.sgml --- b/doc/src/sgml/ref/create_table.sgml *************** *** 1062,1067 **** CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } | UNLOGGED ] TABLE [ IF NOT EXI --- 1062,1079 ---- + + generate_deltas (boolean) + + + Declare that a table generates delta relations when modified. This + allows AFTER triggers to reference the set of rows modified + by a statement. See + for details. + + + + *** a/src/backend/access/common/reloptions.c --- b/src/backend/access/common/reloptions.c *************** *** 85,90 **** static relopt_bool boolRelOpts[] = --- 85,98 ---- }, false }, + { + { + "generate_deltas", + "Relation generates delta relations for use by AFTER triggers", + RELOPT_KIND_HEAP + }, + false + }, /* list terminator */ {{NULL}} }; *************** *** 1205,1211 **** default_reloptions(Datum reloptions, bool validate, relopt_kind kind) {"check_option", RELOPT_TYPE_STRING, offsetof(StdRdOptions, check_option_offset)}, {"user_catalog_table", RELOPT_TYPE_BOOL, ! offsetof(StdRdOptions, user_catalog_table)} }; options = parseRelOptions(reloptions, validate, kind, &numoptions); --- 1213,1221 ---- {"check_option", RELOPT_TYPE_STRING, offsetof(StdRdOptions, check_option_offset)}, {"user_catalog_table", RELOPT_TYPE_BOOL, ! offsetof(StdRdOptions, user_catalog_table)}, ! {"generate_deltas", RELOPT_TYPE_BOOL, ! offsetof(StdRdOptions, generate_deltas)} }; options = parseRelOptions(reloptions, validate, kind, &numoptions); *** a/src/backend/commands/trigger.c --- b/src/backend/commands/trigger.c *************** *** 2060,2066 **** ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; ! if (trigdesc && trigdesc->trig_insert_after_row) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT, true, NULL, trigtuple, recheckIndexes, NULL); } --- 2060,2069 ---- { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; ! if (trigdesc && ! (trigdesc->trig_insert_after_row || ! (trigdesc->trig_insert_after_statement && ! RelationGeneratesDeltas(relinfo->ri_RelationDesc)))) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT, true, NULL, trigtuple, recheckIndexes, NULL); } *************** *** 2263,2269 **** ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo, { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; ! if (trigdesc && trigdesc->trig_delete_after_row) { HeapTuple trigtuple; --- 2266,2275 ---- { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; ! if (trigdesc && ! (trigdesc->trig_delete_after_row || ! (trigdesc->trig_delete_after_statement && ! RelationGeneratesDeltas(relinfo->ri_RelationDesc)))) { HeapTuple trigtuple; *************** *** 2528,2534 **** ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; ! if (trigdesc && trigdesc->trig_update_after_row) { HeapTuple trigtuple; --- 2534,2543 ---- { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; ! if (trigdesc && ! (trigdesc->trig_update_after_row || ! (trigdesc->trig_update_after_statement && ! RelationGeneratesDeltas(relinfo->ri_RelationDesc)))) { HeapTuple trigtuple; *************** *** 3160,3167 **** typedef struct AfterTriggerEventList * fdw_tuplestores[query_depth] is a tuplestore containing the foreign tuples * needed for the current query. * ! * maxquerydepth is just the allocated length of query_stack and ! * fdw_tuplestores. * * state_stack is a stack of pointers to saved copies of the SET CONSTRAINTS * state data; each subtransaction level that modifies that state first --- 3169,3179 ---- * 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 ! * delta relations for the current query. ! * ! * maxquerydepth is just the allocated length of query_stack and the ! * tuplestores. * * state_stack is a stack of pointers to saved copies of the SET CONSTRAINTS * state data; each subtransaction level that modifies that state first *************** *** 3190,3196 **** typedef struct AfterTriggersData AfterTriggerEventList events; /* deferred-event list */ int query_depth; /* current query list index */ AfterTriggerEventList *query_stack; /* events pending from each query */ ! Tuplestorestate **fdw_tuplestores; /* foreign tuples from each query */ int maxquerydepth; /* allocated len of above array */ MemoryContext event_cxt; /* memory context for events, if any */ --- 3202,3210 ---- AfterTriggerEventList events; /* deferred-event list */ int query_depth; /* current query list index */ 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 */ *************** *** 3224,3234 **** static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, * Gets the current query fdw tuplestore and initializes it if necessary */ static Tuplestorestate * ! GetCurrentFDWTuplestore() { Tuplestorestate *ret; ! ret = afterTriggers->fdw_tuplestores[afterTriggers->query_depth]; if (ret == NULL) { MemoryContext oldcxt; --- 3238,3248 ---- * Gets the current query fdw tuplestore and initializes it if necessary */ static Tuplestorestate * ! GetCurrentTuplestore(Tuplestorestate **tss) { Tuplestorestate *ret; ! ret = tss[afterTriggers->query_depth]; if (ret == NULL) { MemoryContext oldcxt; *************** *** 3255,3261 **** GetCurrentFDWTuplestore() CurrentResourceOwner = saveResourceOwner; MemoryContextSwitchTo(oldcxt); ! afterTriggers->fdw_tuplestores[afterTriggers->query_depth] = ret; } return ret; --- 3269,3275 ---- CurrentResourceOwner = saveResourceOwner; MemoryContextSwitchTo(oldcxt); ! tss[afterTriggers->query_depth] = ret; } return ret; *************** *** 3515,3520 **** AfterTriggerExecute(AfterTriggerEvent event, --- 3529,3535 ---- { AfterTriggerShared evtshared = GetTriggerSharedData(event); Oid tgoid = evtshared->ats_tgoid; + int event_op = evtshared->ats_event & TRIGGER_EVENT_OPMASK; TriggerData LocTriggerData; HeapTupleData tuple1; HeapTupleData tuple2; *************** *** 3552,3565 **** AfterTriggerExecute(AfterTriggerEvent event, { case AFTER_TRIGGER_FDW_FETCH: { ! Tuplestorestate *fdw_tuplestore = GetCurrentFDWTuplestore(); if (!tuplestore_gettupleslot(fdw_tuplestore, true, false, trig_tuple_slot1)) elog(ERROR, "failed to fetch tuple1 for AFTER trigger"); ! if ((evtshared->ats_event & TRIGGER_EVENT_OPMASK) == ! TRIGGER_EVENT_UPDATE && !tuplestore_gettupleslot(fdw_tuplestore, true, false, trig_tuple_slot2)) elog(ERROR, "failed to fetch tuple2 for AFTER trigger"); --- 3567,3580 ---- { case AFTER_TRIGGER_FDW_FETCH: { ! Tuplestorestate *fdw_tuplestore = ! GetCurrentTuplestore(afterTriggers->fdw_tuplestores); if (!tuplestore_gettupleslot(fdw_tuplestore, true, false, trig_tuple_slot1)) elog(ERROR, "failed to fetch tuple1 for AFTER trigger"); ! if (event_op == TRIGGER_EVENT_UPDATE && !tuplestore_gettupleslot(fdw_tuplestore, true, false, trig_tuple_slot2)) elog(ERROR, "failed to fetch tuple2 for AFTER trigger"); *************** *** 3580,3588 **** AfterTriggerExecute(AfterTriggerEvent event, ExecMaterializeSlot(trig_tuple_slot1); LocTriggerData.tg_trigtuplebuf = InvalidBuffer; ! LocTriggerData.tg_newtuple = ! ((evtshared->ats_event & TRIGGER_EVENT_OPMASK) == ! TRIGGER_EVENT_UPDATE) ? ExecMaterializeSlot(trig_tuple_slot2) : NULL; LocTriggerData.tg_newtuplebuf = InvalidBuffer; --- 3595,3601 ---- ExecMaterializeSlot(trig_tuple_slot1); LocTriggerData.tg_trigtuplebuf = InvalidBuffer; ! LocTriggerData.tg_newtuple = (event_op == TRIGGER_EVENT_UPDATE) ? ExecMaterializeSlot(trig_tuple_slot2) : NULL; LocTriggerData.tg_newtuplebuf = InvalidBuffer; *************** *** 3622,3627 **** AfterTriggerExecute(AfterTriggerEvent event, --- 3635,3659 ---- } /* + * Set up the tuplestore information. + */ + if (RelationGeneratesDeltas(rel)) + { + if (event_op == TRIGGER_EVENT_DELETE || + event_op == TRIGGER_EVENT_UPDATE) + LocTriggerData.tg_olddelta = + GetCurrentTuplestore(afterTriggers->old_tuplestores); + else + LocTriggerData.tg_olddelta = NULL; + if (event_op == TRIGGER_EVENT_INSERT || + event_op == TRIGGER_EVENT_UPDATE) + LocTriggerData.tg_newdelta = + GetCurrentTuplestore(afterTriggers->new_tuplestores); + else + LocTriggerData.tg_newdelta = NULL; + } + + /* * Setup the remaining trigger information */ LocTriggerData.type = T_TriggerData; *************** *** 3921,3926 **** AfterTriggerBeginXact(void) --- 3953,3964 ---- afterTriggers->fdw_tuplestores = (Tuplestorestate **) MemoryContextAllocZero(TopTransactionContext, 8 * sizeof(Tuplestorestate *)); + afterTriggers->old_tuplestores = (Tuplestorestate **) + MemoryContextAllocZero(TopTransactionContext, + 8 * sizeof(Tuplestorestate *)); + afterTriggers->new_tuplestores = (Tuplestorestate **) + MemoryContextAllocZero(TopTransactionContext, + 8 * sizeof(Tuplestorestate *)); afterTriggers->maxquerydepth = 8; /* Context for events is created only when needed */ *************** *** 3970,3978 **** AfterTriggerBeginQuery(void) --- 4008,4026 ---- 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; } *************** *** 4001,4006 **** AfterTriggerEndQuery(EState *estate) --- 4049,4056 ---- { AfterTriggerEventList *events; Tuplestorestate *fdw_tuplestore; + Tuplestorestate *old_tuplestore; + Tuplestorestate *new_tuplestore; /* Must be inside a transaction */ Assert(afterTriggers != NULL); *************** *** 4045,4057 **** AfterTriggerEndQuery(EState *estate) break; } ! /* Release query-local storage for events, including tuplestore if any */ fdw_tuplestore = afterTriggers->fdw_tuplestores[afterTriggers->query_depth]; if (fdw_tuplestore) { tuplestore_end(fdw_tuplestore); afterTriggers->fdw_tuplestores[afterTriggers->query_depth] = NULL; } afterTriggerFreeEventList(&afterTriggers->query_stack[afterTriggers->query_depth]); afterTriggers->query_depth--; --- 4095,4119 ---- break; } ! /* Release query-local storage for events, including tuplestores, if any */ fdw_tuplestore = afterTriggers->fdw_tuplestores[afterTriggers->query_depth]; if (fdw_tuplestore) { 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--; *************** *** 4283,4288 **** AfterTriggerEndSubXact(bool isCommit) --- 4345,4362 ---- 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]); afterTriggers->query_depth--; *************** *** 4767,4773 **** AfterTriggerPendingOnRel(Oid relid) * * NOTE: this is called whenever there are any triggers associated with * the event (even if they are disabled). This function decides which ! * triggers actually need to be queued. * ---------- */ static void --- 4841,4854 ---- * * NOTE: this is called whenever there are any triggers associated with * the event (even if they are disabled). This function decides which ! * triggers actually need to be queued. It is also called after each row, ! * even if there are no triggers for that event, if the table has the ! * generate_deltas storage property set and there are any AFTER STATEMENT ! * triggers, so that the delta relations can be built. ! * ! * Delta tuplestores are built now, rather than when events are pulled off ! * of the queue because AFTER ROW triggers are allowed to select from the ! * delta relations for the statement. * ---------- */ static void *************** *** 4777,4782 **** AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, --- 4858,4864 ---- List *recheckIndexes, Bitmapset *modifiedCols) { Relation rel = relinfo->ri_RelationDesc; + bool generate_deltas = RelationGeneratesDeltas(rel); TriggerDesc *trigdesc = relinfo->ri_TrigDesc; AfterTriggerEventData new_event; AfterTriggerSharedData new_shared; *************** *** 4797,4807 **** AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, --- 4879,4924 ---- elog(ERROR, "AfterTriggerSaveEvent() called outside of query"); /* + * If the relation has enabled generate_deltas, capture rows into delta + * tuplestores for this depth. + */ + if (generate_deltas && row_trigger) + { + if (event == TRIGGER_EVENT_DELETE || event == TRIGGER_EVENT_UPDATE) + { + Tuplestorestate *old_tuplestore; + + Assert(oldtup != NULL); + old_tuplestore = + GetCurrentTuplestore(afterTriggers->old_tuplestores); + tuplestore_puttuple(old_tuplestore, oldtup); + } + if (event == TRIGGER_EVENT_INSERT || event == TRIGGER_EVENT_UPDATE) + { + Tuplestorestate *new_tuplestore; + + Assert(newtup != NULL); + new_tuplestore = + GetCurrentTuplestore(afterTriggers->new_tuplestores); + tuplestore_puttuple(new_tuplestore, newtup); + } + + /* If deltas were the only reason we're here, return. */ + if ((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; + } + + /* * Validate the event code and collect the associated tuple CTIDs. * * The event code will be used both as a bitmask and an array offset, so * validation is important to make sure we don't walk off the edge of our * arrays. + * + * If we are only saving a row for statement level delta relations, return + * early. */ switch (event) { *************** *** 4893,4899 **** AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, { if (fdw_tuplestore == NULL) { ! fdw_tuplestore = GetCurrentFDWTuplestore(); new_event.ate_flags = AFTER_TRIGGER_FDW_FETCH; } else --- 5010,5017 ---- { if (fdw_tuplestore == NULL) { ! fdw_tuplestore = ! GetCurrentTuplestore(afterTriggers->fdw_tuplestores); new_event.ate_flags = AFTER_TRIGGER_FDW_FETCH; } else *** a/src/include/commands/trigger.h --- b/src/include/commands/trigger.h *************** *** 36,41 **** typedef struct TriggerData --- 36,43 ---- Trigger *tg_trigger; Buffer tg_trigtuplebuf; Buffer tg_newtuplebuf; + Tuplestorestate *tg_olddelta; + Tuplestorestate *tg_newdelta; } TriggerData; /* *** a/src/include/utils/rel.h --- b/src/include/utils/rel.h *************** *** 220,225 **** typedef struct StdRdOptions --- 220,226 ---- int check_option_offset; /* for views */ bool user_catalog_table; /* use as an additional catalog * relation */ + bool generate_deltas; /* generate delta relations */ } StdRdOptions; #define HEAP_MIN_FILLFACTOR 10 *************** *** 298,303 **** typedef struct StdRdOptions --- 299,312 ---- ((StdRdOptions *) (relation)->rd_options)->user_catalog_table : false) /* + * RelationGeneratesDeltas + * Returns whether the relation captures delta information when changed. + */ + #define RelationGeneratesDeltas(relation) \ + ((relation)->rd_options ? \ + ((StdRdOptions *) (relation)->rd_options)->generate_deltas : false) + + /* * RelationIsValid * True iff relation descriptor is valid. */