Commit c46c0e52 authored by Andrew Gierth's avatar Andrew Gierth

Fix transition tables for wCTEs.

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
parent 501ed02c
...@@ -1416,6 +1416,12 @@ BeginCopy(ParseState *pstate, ...@@ -1416,6 +1416,12 @@ BeginCopy(ParseState *pstate,
errmsg("table \"%s\" does not have OIDs", errmsg("table \"%s\" does not have OIDs",
RelationGetRelationName(cstate->rel)))); 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. */ /* Initialize state for CopyFrom tuple routing. */
if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) if (is_from && rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
{ {
...@@ -1439,14 +1445,6 @@ BeginCopy(ParseState *pstate, ...@@ -1439,14 +1445,6 @@ BeginCopy(ParseState *pstate,
cstate->partition_tupconv_maps = partition_tupconv_maps; cstate->partition_tupconv_maps = partition_tupconv_maps;
cstate->partition_tuple_slot = partition_tuple_slot; 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 * If we are capturing transition tuples, they may need to be
* converted from partition format back to partitioned table * converted from partition format back to partitioned table
...@@ -2807,7 +2805,7 @@ CopyFrom(CopyState cstate) ...@@ -2807,7 +2805,7 @@ CopyFrom(CopyState cstate)
pq_endmsgread(); pq_endmsgread();
/* Execute AFTER STATEMENT insertion triggers */ /* Execute AFTER STATEMENT insertion triggers */
ExecASInsertTriggers(estate, resultRelInfo); ExecASInsertTriggers(estate, resultRelInfo, cstate->transition_capture);
/* Handle queued AFTER triggers */ /* Handle queued AFTER triggers */
AfterTriggerEndQuery(estate); AfterTriggerEndQuery(estate);
...@@ -2935,7 +2933,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid, ...@@ -2935,7 +2933,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid,
cstate->cur_lineno = firstBufferedLineNo + i; cstate->cur_lineno = firstBufferedLineNo + i;
ExecARInsertTriggers(estate, resultRelInfo, ExecARInsertTriggers(estate, resultRelInfo,
bufferedTuples[i], bufferedTuples[i],
NIL, NULL); NIL, cstate->transition_capture);
} }
} }
......
...@@ -2071,9 +2071,10 @@ FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc) ...@@ -2071,9 +2071,10 @@ FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc)
/* /*
* Make a TransitionCaptureState object from a given TriggerDesc. The * Make a TransitionCaptureState object from a given TriggerDesc. The
* resulting object holds the flags which control whether transition tuples * resulting object holds the flags which control whether transition tuples
* are collected when tables are modified. This allows us to use the flags * are collected when tables are modified, and the tuplestores themselves.
* from a parent table to control the collection of transition tuples from * Note that we copy the flags from a parent table into this struct (rather
* child tables. * 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', * If there are no triggers with transition tables configured for 'trigdesc',
* then return NULL. * then return NULL.
...@@ -2091,17 +2092,68 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc) ...@@ -2091,17 +2092,68 @@ MakeTransitionCaptureState(TriggerDesc *trigdesc)
(trigdesc->trig_delete_old_table || trigdesc->trig_update_old_table || (trigdesc->trig_delete_old_table || trigdesc->trig_update_old_table ||
trigdesc->trig_update_new_table || trigdesc->trig_insert_new_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 *) state = (TransitionCaptureState *)
palloc0(sizeof(TransitionCaptureState)); palloc0(sizeof(TransitionCaptureState));
state->tcs_delete_old_table = trigdesc->trig_delete_old_table; state->tcs_delete_old_table = trigdesc->trig_delete_old_table;
state->tcs_update_old_table = trigdesc->trig_update_old_table; state->tcs_update_old_table = trigdesc->trig_update_old_table;
state->tcs_update_new_table = trigdesc->trig_update_new_table; state->tcs_update_new_table = trigdesc->trig_update_new_table;
state->tcs_insert_new_table = trigdesc->trig_insert_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; 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. * Call a trigger function.
* *
...@@ -2260,13 +2312,14 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo) ...@@ -2260,13 +2312,14 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
} }
void void
ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo) ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo,
TransitionCaptureState *transition_capture)
{ {
TriggerDesc *trigdesc = relinfo->ri_TrigDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->trig_insert_after_statement) if (trigdesc && trigdesc->trig_insert_after_statement)
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT, AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
false, NULL, NULL, NIL, NULL, NULL); false, NULL, NULL, NIL, NULL, transition_capture);
} }
TupleTableSlot * TupleTableSlot *
...@@ -2343,7 +2396,6 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, ...@@ -2343,7 +2396,6 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
TriggerDesc *trigdesc = relinfo->ri_TrigDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if ((trigdesc && trigdesc->trig_insert_after_row) || if ((trigdesc && trigdesc->trig_insert_after_row) ||
(trigdesc && !transition_capture && trigdesc->trig_insert_new_table) ||
(transition_capture && transition_capture->tcs_insert_new_table)) (transition_capture && transition_capture->tcs_insert_new_table))
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT, AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT,
true, NULL, trigtuple, true, NULL, trigtuple,
...@@ -2470,13 +2522,14 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo) ...@@ -2470,13 +2522,14 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
} }
void void
ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo) ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
TransitionCaptureState *transition_capture)
{ {
TriggerDesc *trigdesc = relinfo->ri_TrigDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if (trigdesc && trigdesc->trig_delete_after_statement) if (trigdesc && trigdesc->trig_delete_after_statement)
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE, AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE,
false, NULL, NULL, NIL, NULL, NULL); false, NULL, NULL, NIL, NULL, transition_capture);
} }
bool bool
...@@ -2557,7 +2610,6 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo, ...@@ -2557,7 +2610,6 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
TriggerDesc *trigdesc = relinfo->ri_TrigDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if ((trigdesc && trigdesc->trig_delete_after_row) || if ((trigdesc && trigdesc->trig_delete_after_row) ||
(trigdesc && !transition_capture && trigdesc->trig_delete_old_table) ||
(transition_capture && transition_capture->tcs_delete_old_table)) (transition_capture && transition_capture->tcs_delete_old_table))
{ {
HeapTuple trigtuple; HeapTuple trigtuple;
...@@ -2684,7 +2736,8 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo) ...@@ -2684,7 +2736,8 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
} }
void void
ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo) ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
TransitionCaptureState *transition_capture)
{ {
TriggerDesc *trigdesc = relinfo->ri_TrigDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
...@@ -2692,7 +2745,7 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo) ...@@ -2692,7 +2745,7 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE,
false, NULL, NULL, NIL, false, NULL, NULL, NIL,
GetUpdatedColumns(relinfo, estate), GetUpdatedColumns(relinfo, estate),
NULL); transition_capture);
} }
TupleTableSlot * TupleTableSlot *
...@@ -2823,9 +2876,6 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, ...@@ -2823,9 +2876,6 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
TriggerDesc *trigdesc = relinfo->ri_TrigDesc; TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
if ((trigdesc && trigdesc->trig_update_after_row) || if ((trigdesc && trigdesc->trig_update_after_row) ||
(trigdesc && !transition_capture &&
(trigdesc->trig_update_old_table ||
trigdesc->trig_update_new_table)) ||
(transition_capture && (transition_capture &&
(transition_capture->tcs_update_old_table || (transition_capture->tcs_update_old_table ||
transition_capture->tcs_update_new_table))) transition_capture->tcs_update_new_table)))
...@@ -3362,6 +3412,7 @@ typedef struct AfterTriggerSharedData ...@@ -3362,6 +3412,7 @@ typedef struct AfterTriggerSharedData
Oid ats_tgoid; /* the trigger's ID */ Oid ats_tgoid; /* the trigger's ID */
Oid ats_relid; /* the relation it's on */ Oid ats_relid; /* the relation it's on */
CommandId ats_firing_id; /* ID for firing cycle */ CommandId ats_firing_id; /* ID for firing cycle */
TransitionCaptureState *ats_transition_capture;
} AfterTriggerSharedData; } AfterTriggerSharedData;
typedef struct AfterTriggerEventData *AfterTriggerEvent; typedef struct AfterTriggerEventData *AfterTriggerEvent;
...@@ -3467,9 +3518,6 @@ typedef struct AfterTriggerEventList ...@@ -3467,9 +3518,6 @@ typedef struct AfterTriggerEventList
* fdw_tuplestores[query_depth] is a tuplestore containing the foreign tuples * fdw_tuplestores[query_depth] is a tuplestore containing the foreign tuples
* needed for the current query. * 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 * maxquerydepth is just the allocated length of query_stack and the
* tuplestores. * tuplestores.
* *
...@@ -3502,8 +3550,6 @@ typedef struct AfterTriggersData ...@@ -3502,8 +3550,6 @@ typedef struct AfterTriggersData
AfterTriggerEventList *query_stack; /* events pending from each query */ AfterTriggerEventList *query_stack; /* events pending from each query */
Tuplestorestate **fdw_tuplestores; /* foreign tuples for one row from Tuplestorestate **fdw_tuplestores; /* foreign tuples for one row from
* each query */ * 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 */ int maxquerydepth; /* allocated len of above array */
MemoryContext event_cxt; /* memory context for events, if any */ MemoryContext event_cxt; /* memory context for events, if any */
...@@ -3524,7 +3570,8 @@ static void AfterTriggerExecute(AfterTriggerEvent event, ...@@ -3524,7 +3570,8 @@ static void AfterTriggerExecute(AfterTriggerEvent event,
Instrumentation *instr, Instrumentation *instr,
MemoryContext per_tuple_context, MemoryContext per_tuple_context,
TupleTableSlot *trig_tuple_slot1, TupleTableSlot *trig_tuple_slot1,
TupleTableSlot *trig_tuple_slot2); TupleTableSlot *trig_tuple_slot2,
TransitionCaptureState *transition_capture);
static SetConstraintState SetConstraintStateCreate(int numalloc); static SetConstraintState SetConstraintStateCreate(int numalloc);
static SetConstraintState SetConstraintStateCopy(SetConstraintState state); static SetConstraintState SetConstraintStateCopy(SetConstraintState state);
static SetConstraintState SetConstraintStateAddItem(SetConstraintState state, static SetConstraintState SetConstraintStateAddItem(SetConstraintState state,
...@@ -3533,8 +3580,6 @@ 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. * 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 * static Tuplestorestate *
GetTriggerTransitionTuplestore(Tuplestorestate **tss) GetTriggerTransitionTuplestore(Tuplestorestate **tss)
...@@ -3714,6 +3759,7 @@ afterTriggerAddEvent(AfterTriggerEventList *events, ...@@ -3714,6 +3759,7 @@ afterTriggerAddEvent(AfterTriggerEventList *events,
if (newshared->ats_tgoid == evtshared->ats_tgoid && if (newshared->ats_tgoid == evtshared->ats_tgoid &&
newshared->ats_relid == evtshared->ats_relid && newshared->ats_relid == evtshared->ats_relid &&
newshared->ats_event == evtshared->ats_event && newshared->ats_event == evtshared->ats_event &&
newshared->ats_transition_capture == evtshared->ats_transition_capture &&
newshared->ats_firing_id == 0) newshared->ats_firing_id == 0)
break; break;
} }
...@@ -3825,7 +3871,8 @@ AfterTriggerExecute(AfterTriggerEvent event, ...@@ -3825,7 +3871,8 @@ AfterTriggerExecute(AfterTriggerEvent event,
FmgrInfo *finfo, Instrumentation *instr, FmgrInfo *finfo, Instrumentation *instr,
MemoryContext per_tuple_context, MemoryContext per_tuple_context,
TupleTableSlot *trig_tuple_slot1, TupleTableSlot *trig_tuple_slot1,
TupleTableSlot *trig_tuple_slot2) TupleTableSlot *trig_tuple_slot2,
TransitionCaptureState *transition_capture)
{ {
AfterTriggerShared evtshared = GetTriggerSharedData(event); AfterTriggerShared evtshared = GetTriggerSharedData(event);
Oid tgoid = evtshared->ats_tgoid; Oid tgoid = evtshared->ats_tgoid;
...@@ -3940,16 +3987,14 @@ AfterTriggerExecute(AfterTriggerEvent event, ...@@ -3940,16 +3987,14 @@ AfterTriggerExecute(AfterTriggerEvent event,
/* /*
* Set up the tuplestore information. * Set up the tuplestore information.
*/ */
if (LocTriggerData.tg_trigger->tgoldtable) LocTriggerData.tg_oldtable = LocTriggerData.tg_newtable = NULL;
LocTriggerData.tg_oldtable = if (transition_capture != NULL)
GetTriggerTransitionTuplestore(afterTriggers.old_tuplestores); {
else if (LocTriggerData.tg_trigger->tgoldtable)
LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_oldtable = transition_capture->tcs_old_tuplestore;
if (LocTriggerData.tg_trigger->tgnewtable) if (LocTriggerData.tg_trigger->tgnewtable)
LocTriggerData.tg_newtable = LocTriggerData.tg_newtable = transition_capture->tcs_new_tuplestore;
GetTriggerTransitionTuplestore(afterTriggers.new_tuplestores); }
else
LocTriggerData.tg_newtable = NULL;
/* /*
* Setup the remaining trigger information * Setup the remaining trigger information
...@@ -4157,7 +4202,8 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, ...@@ -4157,7 +4202,8 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
* won't try to 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, slot1, slot2); per_tuple_context, slot1, slot2,
evtshared->ats_transition_capture);
/* /*
* Mark the event as done. * Mark the event as done.
...@@ -4231,8 +4277,6 @@ AfterTriggerBeginXact(void) ...@@ -4231,8 +4277,6 @@ AfterTriggerBeginXact(void)
Assert(afterTriggers.state == NULL); Assert(afterTriggers.state == NULL);
Assert(afterTriggers.query_stack == NULL); Assert(afterTriggers.query_stack == NULL);
Assert(afterTriggers.fdw_tuplestores == NULL); Assert(afterTriggers.fdw_tuplestores == NULL);
Assert(afterTriggers.old_tuplestores == NULL);
Assert(afterTriggers.new_tuplestores == NULL);
Assert(afterTriggers.maxquerydepth == 0); Assert(afterTriggers.maxquerydepth == 0);
Assert(afterTriggers.event_cxt == NULL); Assert(afterTriggers.event_cxt == NULL);
Assert(afterTriggers.events.head == NULL); Assert(afterTriggers.events.head == NULL);
...@@ -4277,8 +4321,6 @@ AfterTriggerEndQuery(EState *estate) ...@@ -4277,8 +4321,6 @@ AfterTriggerEndQuery(EState *estate)
{ {
AfterTriggerEventList *events; AfterTriggerEventList *events;
Tuplestorestate *fdw_tuplestore; Tuplestorestate *fdw_tuplestore;
Tuplestorestate *old_tuplestore;
Tuplestorestate *new_tuplestore;
/* Must be inside a query, too */ /* Must be inside a query, too */
Assert(afterTriggers.query_depth >= 0); Assert(afterTriggers.query_depth >= 0);
...@@ -4337,18 +4379,6 @@ AfterTriggerEndQuery(EState *estate) ...@@ -4337,18 +4379,6 @@ AfterTriggerEndQuery(EState *estate)
tuplestore_end(fdw_tuplestore); tuplestore_end(fdw_tuplestore);
afterTriggers.fdw_tuplestores[afterTriggers.query_depth] = NULL; 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]); afterTriggerFreeEventList(&afterTriggers.query_stack[afterTriggers.query_depth]);
afterTriggers.query_depth--; afterTriggers.query_depth--;
...@@ -4462,8 +4492,6 @@ AfterTriggerEndXact(bool isCommit) ...@@ -4462,8 +4492,6 @@ AfterTriggerEndXact(bool isCommit)
*/ */
afterTriggers.query_stack = NULL; afterTriggers.query_stack = NULL;
afterTriggers.fdw_tuplestores = NULL; afterTriggers.fdw_tuplestores = NULL;
afterTriggers.old_tuplestores = NULL;
afterTriggers.new_tuplestores = NULL;
afterTriggers.maxquerydepth = 0; afterTriggers.maxquerydepth = 0;
afterTriggers.state = NULL; afterTriggers.state = NULL;
...@@ -4596,18 +4624,6 @@ AfterTriggerEndSubXact(bool isCommit) ...@@ -4596,18 +4624,6 @@ AfterTriggerEndSubXact(bool isCommit)
tuplestore_end(ts); tuplestore_end(ts);
afterTriggers.fdw_tuplestores[afterTriggers.query_depth] = NULL; 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]); afterTriggerFreeEventList(&afterTriggers.query_stack[afterTriggers.query_depth]);
} }
...@@ -4687,12 +4703,6 @@ AfterTriggerEnlargeQueryState(void) ...@@ -4687,12 +4703,6 @@ AfterTriggerEnlargeQueryState(void)
afterTriggers.fdw_tuplestores = (Tuplestorestate **) afterTriggers.fdw_tuplestores = (Tuplestorestate **)
MemoryContextAllocZero(TopTransactionContext, MemoryContextAllocZero(TopTransactionContext,
new_alloc * sizeof(Tuplestorestate *)); 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; afterTriggers.maxquerydepth = new_alloc;
} }
else else
...@@ -4708,19 +4718,9 @@ AfterTriggerEnlargeQueryState(void) ...@@ -4708,19 +4718,9 @@ AfterTriggerEnlargeQueryState(void)
afterTriggers.fdw_tuplestores = (Tuplestorestate **) afterTriggers.fdw_tuplestores = (Tuplestorestate **)
repalloc(afterTriggers.fdw_tuplestores, repalloc(afterTriggers.fdw_tuplestores,
new_alloc * sizeof(Tuplestorestate *)); 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. */ /* Clear newly-allocated slots for subsequent lazy initialization. */
memset(afterTriggers.fdw_tuplestores + old_alloc, memset(afterTriggers.fdw_tuplestores + old_alloc,
0, (new_alloc - old_alloc) * sizeof(Tuplestorestate *)); 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; afterTriggers.maxquerydepth = new_alloc;
} }
...@@ -5205,51 +5205,17 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, ...@@ -5205,51 +5205,17 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
AfterTriggerEnlargeQueryState(); AfterTriggerEnlargeQueryState();
/* /*
* If the relation has AFTER ... FOR EACH ROW triggers, capture rows into * If the directly named relation has any triggers with transition tables,
* transition tuplestores for this depth. * then we need to capture transition tuples.
*/ */
if (row_trigger) if (row_trigger && transition_capture != NULL)
{ {
HeapTuple original_insert_tuple = NULL; HeapTuple original_insert_tuple = transition_capture->tcs_original_insert_tuple;
TupleConversionMap *map = NULL; TupleConversionMap *map = transition_capture->tcs_map;
bool delete_old_table = false; bool delete_old_table = transition_capture->tcs_delete_old_table;
bool update_old_table = false; bool update_old_table = transition_capture->tcs_update_old_table;
bool update_new_table = false; bool update_new_table = transition_capture->tcs_update_new_table;
bool insert_new_table = false; bool insert_new_table = transition_capture->tcs_insert_new_table;;
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;
}
if ((event == TRIGGER_EVENT_DELETE && delete_old_table) || if ((event == TRIGGER_EVENT_DELETE && delete_old_table) ||
(event == TRIGGER_EVENT_UPDATE && update_old_table)) (event == TRIGGER_EVENT_UPDATE && update_old_table))
...@@ -5257,9 +5223,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, ...@@ -5257,9 +5223,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
Tuplestorestate *old_tuplestore; Tuplestorestate *old_tuplestore;
Assert(oldtup != NULL); Assert(oldtup != NULL);
old_tuplestore = old_tuplestore = transition_capture->tcs_old_tuplestore;
GetTriggerTransitionTuplestore
(afterTriggers.old_tuplestores);
if (map != NULL) if (map != NULL)
{ {
HeapTuple converted = do_convert_tuple(oldtup, map); HeapTuple converted = do_convert_tuple(oldtup, map);
...@@ -5276,9 +5241,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, ...@@ -5276,9 +5241,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
Tuplestorestate *new_tuplestore; Tuplestorestate *new_tuplestore;
Assert(newtup != NULL); Assert(newtup != NULL);
new_tuplestore = new_tuplestore = transition_capture->tcs_new_tuplestore;
GetTriggerTransitionTuplestore
(afterTriggers.new_tuplestores);
if (original_insert_tuple != NULL) if (original_insert_tuple != NULL)
tuplestore_puttuple(new_tuplestore, original_insert_tuple); tuplestore_puttuple(new_tuplestore, original_insert_tuple);
else if (map != NULL) else if (map != NULL)
...@@ -5464,6 +5428,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, ...@@ -5464,6 +5428,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
new_shared.ats_tgoid = trigger->tgoid; new_shared.ats_tgoid = trigger->tgoid;
new_shared.ats_relid = RelationGetRelid(rel); new_shared.ats_relid = RelationGetRelid(rel);
new_shared.ats_firing_id = 0; new_shared.ats_firing_id = 0;
new_shared.ats_transition_capture = transition_capture;
afterTriggerAddEvent(&afterTriggers.query_stack[afterTriggers.query_depth], afterTriggerAddEvent(&afterTriggers.query_stack[afterTriggers.query_depth],
&new_event, &new_shared); &new_event, &new_shared);
......
...@@ -419,6 +419,12 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot) ...@@ -419,6 +419,12 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
ExecARInsertTriggers(estate, resultRelInfo, tuple, ExecARInsertTriggers(estate, resultRelInfo, tuple,
recheckIndexes, NULL); 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); list_free(recheckIndexes);
} }
} }
......
...@@ -1442,14 +1442,18 @@ fireASTriggers(ModifyTableState *node) ...@@ -1442,14 +1442,18 @@ fireASTriggers(ModifyTableState *node)
case CMD_INSERT: case CMD_INSERT:
if (node->mt_onconflict == ONCONFLICT_UPDATE) if (node->mt_onconflict == ONCONFLICT_UPDATE)
ExecASUpdateTriggers(node->ps.state, ExecASUpdateTriggers(node->ps.state,
resultRelInfo); resultRelInfo,
ExecASInsertTriggers(node->ps.state, resultRelInfo); node->mt_transition_capture);
ExecASInsertTriggers(node->ps.state, resultRelInfo,
node->mt_transition_capture);
break; break;
case CMD_UPDATE: case CMD_UPDATE:
ExecASUpdateTriggers(node->ps.state, resultRelInfo); ExecASUpdateTriggers(node->ps.state, resultRelInfo,
node->mt_transition_capture);
break; break;
case CMD_DELETE: case CMD_DELETE:
ExecASDeleteTriggers(node->ps.state, resultRelInfo); ExecASDeleteTriggers(node->ps.state, resultRelInfo,
node->mt_transition_capture);
break; break;
default: default:
elog(ERROR, "unknown operation"); elog(ERROR, "unknown operation");
...@@ -2304,6 +2308,10 @@ ExecEndModifyTable(ModifyTableState *node) ...@@ -2304,6 +2308,10 @@ ExecEndModifyTable(ModifyTableState *node)
{ {
int i; int i;
/* Free transition tables */
if (node->mt_transition_capture != NULL)
DestroyTransitionCaptureState(node->mt_transition_capture);
/* /*
* Allow any FDWs to shut down * Allow any FDWs to shut down
*/ */
......
...@@ -42,8 +42,8 @@ typedef struct TriggerData ...@@ -42,8 +42,8 @@ typedef struct TriggerData
} TriggerData; } TriggerData;
/* /*
* Meta-data to control the capture of old and new tuples into transition * The state for capturing old and new tuples into transition tables for a
* tables from child tables. * single ModifyTable node.
*/ */
typedef struct TransitionCaptureState typedef struct TransitionCaptureState
{ {
...@@ -72,6 +72,10 @@ typedef struct TransitionCaptureState ...@@ -72,6 +72,10 @@ typedef struct TransitionCaptureState
* the original tuple directly. * the original tuple directly.
*/ */
HeapTuple tcs_original_insert_tuple; HeapTuple tcs_original_insert_tuple;
/* The tuplestores backing the transition tables. */
Tuplestorestate *tcs_old_tuplestore;
Tuplestorestate *tcs_new_tuplestore;
} TransitionCaptureState; } TransitionCaptureState;
/* /*
...@@ -162,13 +166,15 @@ extern TriggerDesc *CopyTriggerDesc(TriggerDesc *trigdesc); ...@@ -162,13 +166,15 @@ extern TriggerDesc *CopyTriggerDesc(TriggerDesc *trigdesc);
extern const char *FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc); extern const char *FindTriggerIncompatibleWithInheritance(TriggerDesc *trigdesc);
extern TransitionCaptureState *MakeTransitionCaptureState(TriggerDesc *trigdesc); extern TransitionCaptureState *MakeTransitionCaptureState(TriggerDesc *trigdesc);
extern void DestroyTransitionCaptureState(TransitionCaptureState *tcs);
extern void FreeTriggerDesc(TriggerDesc *trigdesc); extern void FreeTriggerDesc(TriggerDesc *trigdesc);
extern void ExecBSInsertTriggers(EState *estate, extern void ExecBSInsertTriggers(EState *estate,
ResultRelInfo *relinfo); ResultRelInfo *relinfo);
extern void ExecASInsertTriggers(EState *estate, extern void ExecASInsertTriggers(EState *estate,
ResultRelInfo *relinfo); ResultRelInfo *relinfo,
TransitionCaptureState *transition_capture);
extern TupleTableSlot *ExecBRInsertTriggers(EState *estate, extern TupleTableSlot *ExecBRInsertTriggers(EState *estate,
ResultRelInfo *relinfo, ResultRelInfo *relinfo,
TupleTableSlot *slot); TupleTableSlot *slot);
...@@ -183,7 +189,8 @@ extern TupleTableSlot *ExecIRInsertTriggers(EState *estate, ...@@ -183,7 +189,8 @@ extern TupleTableSlot *ExecIRInsertTriggers(EState *estate,
extern void ExecBSDeleteTriggers(EState *estate, extern void ExecBSDeleteTriggers(EState *estate,
ResultRelInfo *relinfo); ResultRelInfo *relinfo);
extern void ExecASDeleteTriggers(EState *estate, extern void ExecASDeleteTriggers(EState *estate,
ResultRelInfo *relinfo); ResultRelInfo *relinfo,
TransitionCaptureState *transition_capture);
extern bool ExecBRDeleteTriggers(EState *estate, extern bool ExecBRDeleteTriggers(EState *estate,
EPQState *epqstate, EPQState *epqstate,
ResultRelInfo *relinfo, ResultRelInfo *relinfo,
...@@ -200,7 +207,8 @@ extern bool ExecIRDeleteTriggers(EState *estate, ...@@ -200,7 +207,8 @@ extern bool ExecIRDeleteTriggers(EState *estate,
extern void ExecBSUpdateTriggers(EState *estate, extern void ExecBSUpdateTriggers(EState *estate,
ResultRelInfo *relinfo); ResultRelInfo *relinfo);
extern void ExecASUpdateTriggers(EState *estate, extern void ExecASUpdateTriggers(EState *estate,
ResultRelInfo *relinfo); ResultRelInfo *relinfo,
TransitionCaptureState *transition_capture);
extern TupleTableSlot *ExecBRUpdateTriggers(EState *estate, extern TupleTableSlot *ExecBRUpdateTriggers(EState *estate,
EPQState *epqstate, EPQState *epqstate,
ResultRelInfo *relinfo, ResultRelInfo *relinfo,
......
...@@ -2194,6 +2194,26 @@ DETAIL: ROW triggers with transition tables are not supported in inheritance hi ...@@ -2194,6 +2194,26 @@ DETAIL: ROW triggers with transition tables are not supported in inheritance hi
drop trigger child_row_trig on child; drop trigger child_row_trig on child;
alter table child inherit parent; alter table child inherit parent;
drop table child, 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 -- cleanup
drop function dump_insert(); drop function dump_insert();
drop function dump_update(); drop function dump_update();
......
...@@ -1704,6 +1704,27 @@ alter table child inherit parent; ...@@ -1704,6 +1704,27 @@ alter table child inherit parent;
drop table child, 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 -- cleanup
drop function dump_insert(); drop function dump_insert();
drop function dump_update(); drop function dump_update();
......
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