]> granicus.if.org Git - postgresql/commitdiff
Fix transition tables for wCTEs.
authorAndrew Gierth <rhodiumtoad@postgresql.org>
Wed, 28 Jun 2017 17:59:01 +0000 (18:59 +0100)
committerAndrew Gierth <rhodiumtoad@postgresql.org>
Wed, 28 Jun 2017 17:59:01 +0000 (18:59 +0100)
The original coding didn't handle this case properly; each separate
DML substatement needs its own set of transitions.

Patch by Thomas Munro

Discussion: https://postgr.es/m/CAL9smLCDQ%3D2o024rBgtD4WihzX8B3C6u_oSQ2K3%2BR5grJrV0bg%40mail.gmail.com

src/backend/commands/copy.c
src/backend/commands/trigger.c
src/backend/executor/execReplication.c
src/backend/executor/nodeModifyTable.c
src/include/commands/trigger.h
src/test/regress/expected/triggers.out
src/test/regress/sql/triggers.sql

index a4c02e6b7c52d54cf484ac55eeb04fc607b16e61..f391828e74fe44ce020b3e50c3b3fbbf43139377 100644 (file)
@@ -1416,6 +1416,12 @@ BeginCopy(ParseState *pstate,
                                         errmsg("table \"%s\" does not have OIDs",
                                                        RelationGetRelationName(cstate->rel))));
 
+               /*
+                * If there are any triggers with transition tables on the named
+                * relation, we need to be prepared to capture transition tuples.
+                */
+               cstate->transition_capture = MakeTransitionCaptureState(rel->trigdesc);
+
                /* Initialize state for CopyFrom tuple routing. */
                if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
                {
@@ -1439,14 +1445,6 @@ BeginCopy(ParseState *pstate,
                        cstate->partition_tupconv_maps = partition_tupconv_maps;
                        cstate->partition_tuple_slot = partition_tuple_slot;
 
-                       /*
-                        * If there are any triggers with transition tables on the named
-                        * relation, we need to be prepared to capture transition tuples
-                        * from child relations too.
-                        */
-                       cstate->transition_capture =
-                               MakeTransitionCaptureState(rel->trigdesc);
-
                        /*
                         * If we are capturing transition tuples, they may need to be
                         * converted from partition format back to partitioned table
@@ -2807,7 +2805,7 @@ CopyFrom(CopyState cstate)
                pq_endmsgread();
 
        /* Execute AFTER STATEMENT insertion triggers */
-       ExecASInsertTriggers(estate, resultRelInfo);
+       ExecASInsertTriggers(estate, resultRelInfo, cstate->transition_capture);
 
        /* Handle queued AFTER triggers */
        AfterTriggerEndQuery(estate);
@@ -2935,7 +2933,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
                        cstate->cur_lineno = firstBufferedLineNo + i;
                        ExecARInsertTriggers(estate, resultRelInfo,
                                                                 bufferedTuples[i],
-                                                                NIL, NULL);
+                                                                NIL, cstate->transition_capture);
                }
        }
 
index f902e0cdf5f3689b03cb3df45e01cfcb00fa3558..54db16c909029a040f0c92c3c9e3145e20afe866 100644 (file)
@@ -2071,9 +2071,10 @@ FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc)
 /*
  * 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.  This allows us to use the flags
- * from a parent table to control the collection of transition tuples from
- * child tables.
+ * 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.
@@ -2091,17 +2092,68 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc)
                (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 || trigdesc->trig_update_new_table)
+                               state->tcs_new_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_new_tuplestore != NULL)
+               tuplestore_end(tcs->tcs_new_tuplestore);
+       if (tcs->tcs_old_tuplestore != NULL)
+               tuplestore_end(tcs->tcs_old_tuplestore);
+       pfree(tcs);
+}
+
 /*
  * Call a trigger function.
  *
@@ -2260,13 +2312,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, NULL);
+                                                         false, NULL, NULL, NIL, NULL, transition_capture);
 }
 
 TupleTableSlot *
@@ -2343,7 +2396,6 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
        TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
        if ((trigdesc && trigdesc->trig_insert_after_row) ||
-               (trigdesc && !transition_capture && trigdesc->trig_insert_new_table) ||
                (transition_capture && transition_capture->tcs_insert_new_table))
                AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
                                                          true, NULL, trigtuple,
@@ -2470,13 +2522,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, NULL);
+                                                         false, NULL, NULL, NIL, NULL, transition_capture);
 }
 
 bool
@@ -2557,7 +2610,6 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
        TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
        if ((trigdesc && trigdesc->trig_delete_after_row) ||
-               (trigdesc && !transition_capture && trigdesc->trig_delete_old_table) ||
                (transition_capture && transition_capture->tcs_delete_old_table))
        {
                HeapTuple       trigtuple;
@@ -2684,7 +2736,8 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
 }
 
 void
-ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
+ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
+                                        TransitionCaptureState *transition_capture)
 {
        TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
@@ -2692,7 +2745,7 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
                AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
                                                          false, NULL, NULL, NIL,
                                                          GetUpdatedColumns(relinfo, estate),
-                                                         NULL);
+                                                         transition_capture);
 }
 
 TupleTableSlot *
@@ -2823,9 +2876,6 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
        TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
        if ((trigdesc && trigdesc->trig_update_after_row) ||
-               (trigdesc && !transition_capture &&
-                (trigdesc->trig_update_old_table ||
-                 trigdesc->trig_update_new_table)) ||
                (transition_capture &&
                 (transition_capture->tcs_update_old_table ||
                  transition_capture->tcs_update_new_table)))
@@ -3362,6 +3412,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;
@@ -3467,9 +3518,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.
  *
@@ -3502,8 +3550,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 */
 
@@ -3524,7 +3570,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,
@@ -3533,8 +3580,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)
@@ -3714,6 +3759,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;
        }
@@ -3825,7 +3871,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;
@@ -3940,16 +3987,14 @@ 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)
+                       LocTriggerData.tg_newtable = transition_capture->tcs_new_tuplestore;
+       }
 
        /*
         * Setup the remaining trigger information
@@ -4157,7 +4202,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.
@@ -4231,8 +4277,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);
@@ -4277,8 +4321,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);
@@ -4337,18 +4379,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--;
@@ -4462,8 +4492,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;
 
@@ -4596,18 +4624,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]);
                        }
@@ -4687,12 +4703,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
@@ -4708,19 +4718,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;
        }
 
@@ -5205,51 +5205,17 @@ 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)
        {
-               HeapTuple original_insert_tuple = NULL;
-               TupleConversionMap *map = NULL;
-               bool delete_old_table = false;
-               bool update_old_table = false;
-               bool update_new_table = false;
-               bool insert_new_table = false;
-
-               if (transition_capture != NULL)
-               {
-                       /*
-                        * A TransitionCaptureState object was provided to tell us which
-                        * tuples to capture based on a parent table named in a DML
-                        * statement.  We may be dealing with a child table with an
-                        * incompatible TupleDescriptor, in which case we'll need a map to
-                        * convert them.  As a small optimization, we may receive the
-                        * original tuple from an insertion into a partitioned table to
-                        * avoid a wasteful parent->child->parent round trip.
-                        */
-                       delete_old_table = transition_capture->tcs_delete_old_table;
-                       update_old_table = transition_capture->tcs_update_old_table;
-                       update_new_table = transition_capture->tcs_update_new_table;
-                       insert_new_table = transition_capture->tcs_insert_new_table;
-                       map = transition_capture->tcs_map;
-                       original_insert_tuple =
-                               transition_capture->tcs_original_insert_tuple;
-               }
-               else if (trigdesc != NULL)
-               {
-                       /*
-                        * Check if we need to capture transition tuples for triggers
-                        * defined on this relation directly.  This case is useful for
-                        * cases like execReplication.c which don't set up a
-                        * TriggerCaptureState because they don't know how to work with
-                        * partitions.
-                        */
-                       delete_old_table = trigdesc->trig_delete_old_table;
-                       update_old_table = trigdesc->trig_update_old_table;
-                       update_new_table = trigdesc->trig_update_new_table;
-                       insert_new_table = trigdesc->trig_insert_new_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))
@@ -5257,9 +5223,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
                        Tuplestorestate *old_tuplestore;
 
                        Assert(oldtup != NULL);
-                       old_tuplestore =
-                               GetTriggerTransitionTuplestore
-                               (afterTriggers.old_tuplestores);
+                       old_tuplestore = transition_capture->tcs_old_tuplestore;
+
                        if (map != NULL)
                        {
                                HeapTuple       converted = do_convert_tuple(oldtup, map);
@@ -5276,9 +5241,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
                        Tuplestorestate *new_tuplestore;
 
                        Assert(newtup != NULL);
-                       new_tuplestore =
-                               GetTriggerTransitionTuplestore
-                               (afterTriggers.new_tuplestores);
+                       new_tuplestore = transition_capture->tcs_new_tuplestore;
+
                        if (original_insert_tuple != NULL)
                                tuplestore_puttuple(new_tuplestore, original_insert_tuple);
                        else if (map != NULL)
@@ -5464,6 +5428,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);
index 36960eaa7e8d01dc848da2d0841741f0cf9519a5..bc53d07c7db09dcedf3796d42aebb8b292275163 100644 (file)
@@ -419,6 +419,12 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
                ExecARInsertTriggers(estate, resultRelInfo, tuple,
                                                         recheckIndexes, NULL);
 
+               /*
+                * XXX we should in theory pass a TransitionCaptureState object to the
+                * above to capture transition tuples, but after statement triggers
+                * don't actually get fired by replication yet anyway
+                */
+
                list_free(recheckIndexes);
        }
 }
index f2534f2062297e86dc72419f10d8a07314e58221..8d17425abea2c3aeef6b919c16a3fb445e67e99b 100644 (file)
@@ -1442,14 +1442,18 @@ fireASTriggers(ModifyTableState *node)
                case CMD_INSERT:
                        if (node->mt_onconflict == ONCONFLICT_UPDATE)
                                ExecASUpdateTriggers(node->ps.state,
-                                                                        resultRelInfo);
-                       ExecASInsertTriggers(node->ps.state, resultRelInfo);
+                                                                        resultRelInfo,
+                                                                        node->mt_transition_capture);
+                       ExecASInsertTriggers(node->ps.state, resultRelInfo,
+                                                                node->mt_transition_capture);
                        break;
                case CMD_UPDATE:
-                       ExecASUpdateTriggers(node->ps.state, resultRelInfo);
+                       ExecASUpdateTriggers(node->ps.state, resultRelInfo,
+                                                                node->mt_transition_capture);
                        break;
                case CMD_DELETE:
-                       ExecASDeleteTriggers(node->ps.state, resultRelInfo);
+                       ExecASDeleteTriggers(node->ps.state, resultRelInfo,
+                                                                node->mt_transition_capture);
                        break;
                default:
                        elog(ERROR, "unknown operation");
@@ -2304,6 +2308,10 @@ ExecEndModifyTable(ModifyTableState *node)
 {
        int                     i;
 
+       /* Free transition tables */
+       if (node->mt_transition_capture != NULL)
+               DestroyTransitionCaptureState(node->mt_transition_capture);
+
        /*
         * Allow any FDWs to shut down
         */
index 51a25c8ddc2086c3b2e51ce8b65eb039ebe75856..06199953fe9afda58068bc41c5b570d63874baa1 100644 (file)
@@ -42,8 +42,8 @@ typedef struct TriggerData
 } TriggerData;
 
 /*
- * Meta-data to control the capture of old and new tuples into transition
- * tables from child tables.
+ * The state for capturing old and new tuples into transition tables for a
+ * single ModifyTable node.
  */
 typedef struct TransitionCaptureState
 {
@@ -72,6 +72,10 @@ typedef struct TransitionCaptureState
         * the original tuple directly.
         */
        HeapTuple       tcs_original_insert_tuple;
+
+       /* The tuplestores backing the transition tables. */
+       Tuplestorestate *tcs_old_tuplestore;
+       Tuplestorestate *tcs_new_tuplestore;
 } TransitionCaptureState;
 
 /*
@@ -162,13 +166,15 @@ extern TriggerDesc *CopyTriggerDesc(TriggerDesc *trigdesc);
 
 extern const char *FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc);
 extern TransitionCaptureState *MakeTransitionCaptureState(TriggerDesc *trigdesc);
+extern void DestroyTransitionCaptureState(TransitionCaptureState *tcs);
 
 extern void FreeTriggerDesc(TriggerDesc *trigdesc);
 
 extern void ExecBSInsertTriggers(EState *estate,
                                         ResultRelInfo *relinfo);
 extern void ExecASInsertTriggers(EState *estate,
-                                        ResultRelInfo *relinfo);
+                                        ResultRelInfo *relinfo,
+                                        TransitionCaptureState *transition_capture);
 extern TupleTableSlot *ExecBRInsertTriggers(EState *estate,
                                         ResultRelInfo *relinfo,
                                         TupleTableSlot *slot);
@@ -183,7 +189,8 @@ extern TupleTableSlot *ExecIRInsertTriggers(EState *estate,
 extern void ExecBSDeleteTriggers(EState *estate,
                                         ResultRelInfo *relinfo);
 extern void ExecASDeleteTriggers(EState *estate,
-                                        ResultRelInfo *relinfo);
+                                        ResultRelInfo *relinfo,
+                                        TransitionCaptureState *transition_capture);
 extern bool ExecBRDeleteTriggers(EState *estate,
                                         EPQState *epqstate,
                                         ResultRelInfo *relinfo,
@@ -200,7 +207,8 @@ extern bool ExecIRDeleteTriggers(EState *estate,
 extern void ExecBSUpdateTriggers(EState *estate,
                                         ResultRelInfo *relinfo);
 extern void ExecASUpdateTriggers(EState *estate,
-                                        ResultRelInfo *relinfo);
+                                        ResultRelInfo *relinfo,
+                                        TransitionCaptureState *transition_capture);
 extern TupleTableSlot *ExecBRUpdateTriggers(EState *estate,
                                         EPQState *epqstate,
                                         ResultRelInfo *relinfo,
index 995410f1aaecb3704682580ed8fb890b4bb9d9ec..0261d66e5c593b91fb2aea80412e5fc64c10b8a9 100644 (file)
@@ -2194,6 +2194,26 @@ DETAIL:  ROW triggers with transition tables are not supported in inheritance hi
 drop trigger child_row_trig on child;
 alter table child inherit parent;
 drop table child, parent;
+--
+-- Verify behavior of queries with wCTEs, where multiple transition
+-- tuplestores can be active at the same time because there are
+-- multiple DML statements that might fire triggers with transition
+-- tables
+--
+create table table1 (a int);
+create table table2 (a text);
+create trigger table1_trig
+  after insert on table1 referencing new table as new_table
+  for each statement execute procedure dump_insert();
+create trigger table2_trig
+  after insert on table2 referencing new table as new_table
+  for each statement execute procedure dump_insert();
+with wcte as (insert into table1 values (42))
+  insert into table2 values ('hello world');
+NOTICE:  trigger = table2_trig, new table = ("hello world")
+NOTICE:  trigger = table1_trig, new table = (42)
+drop table table1;
+drop table table2;
 -- cleanup
 drop function dump_insert();
 drop function dump_update();
index 683a5f1e5c43b97fea501fcd83b2b004b7f511fd..128126a0f1a1bf11382c88bf4fb52326025e079c 100644 (file)
@@ -1704,6 +1704,27 @@ alter table child inherit parent;
 
 drop table child, parent;
 
+--
+-- Verify behavior of queries with wCTEs, where multiple transition
+-- tuplestores can be active at the same time because there are
+-- multiple DML statements that might fire triggers with transition
+-- tables
+--
+create table table1 (a int);
+create table table2 (a text);
+create trigger table1_trig
+  after insert on table1 referencing new table as new_table
+  for each statement execute procedure dump_insert();
+create trigger table2_trig
+  after insert on table2 referencing new table as new_table
+  for each statement execute procedure dump_insert();
+
+with wcte as (insert into table1 values (42))
+  insert into table2 values ('hello world');
+
+drop table table1;
+drop table table2;
+
 -- cleanup
 drop function dump_insert();
 drop function dump_update();