diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c index 6b96e7de0a..2f387fac42 100644 --- a/contrib/postgres_fdw/postgres_fdw.c +++ b/contrib/postgres_fdw/postgres_fdw.c @@ -3507,8 +3507,13 @@ store_returning_result(PgFdwModifyState *fmstate, fmstate->retrieved_attrs, NULL, fmstate->temp_cxt); - /* tuple will be deleted when it is cleared from the slot */ - ExecStoreHeapTuple(newtup, slot, true); + /* + * The returning slot will not necessarily be suitable to store + * heaptuples directly, so allow for conversion. + */ + ExecForceStoreHeapTuple(newtup, slot); + ExecMaterializeSlot(slot); + pfree(newtup); } PG_CATCH(); { @@ -3886,6 +3891,7 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate, TupleTableSlot *slot, EState *estate) { + ResultRelInfo *relInfo = estate->es_result_relation_info; TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel); TupleTableSlot *resultSlot; Datum *values; @@ -3895,11 +3901,9 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate, int i; /* - * Use the trigger tuple slot as a place to store the result tuple. + * Use the return tuple slot as a place to store the result tuple. */ - resultSlot = estate->es_trig_tuple_slot; - if (resultSlot->tts_tupleDescriptor != resultTupType) - ExecSetSlotDescriptor(resultSlot, resultTupType); + resultSlot = ExecGetReturningSlot(estate, relInfo); /* * Extract all the values of the scan tuple. diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index 93aa163177..5dd6fe02c6 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -2519,9 +2519,6 @@ CopyFrom(CopyState cstate) /* Set up a tuple slot too */ myslot = ExecInitExtraTupleSlot(estate, tupDesc, &TTSOpsHeapTuple); - /* Triggers might need a slot as well */ - estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL, - &TTSOpsHeapTuple); /* * Set up a ModifyTableState so we can let FDW(s) init themselves for @@ -2870,7 +2867,7 @@ CopyFrom(CopyState cstate) * Otherwise, just remember the original unconverted * tuple, to avoid a needless round trip conversion. */ - cstate->transition_capture->tcs_original_insert_tuple = tuple; + cstate->transition_capture->tcs_original_insert_tuple = myslot; cstate->transition_capture->tcs_map = NULL; } } @@ -2907,12 +2904,8 @@ CopyFrom(CopyState cstate) /* BEFORE ROW INSERT Triggers */ if (has_before_insert_row_trig) { - slot = ExecBRInsertTriggers(estate, resultRelInfo, slot); - - if (slot == NULL) /* "do nothing" */ - skip_tuple = true; - else /* trigger might have changed tuple */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); + if (!ExecBRInsertTriggers(estate, resultRelInfo, slot)) + skip_tuple = true; /* "do nothing" */ } if (!skip_tuple) @@ -2990,9 +2983,6 @@ CopyFrom(CopyState cstate) if (slot == NULL) /* "do nothing" */ continue; /* next tuple please */ - /* FDW might have changed tuple */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); - /* * AFTER ROW Triggers might reference the tableoid * column, so (re-)initialize tts_tableOid before @@ -3002,6 +2992,7 @@ CopyFrom(CopyState cstate) } else { + tuple = ExecFetchSlotHeapTuple(slot, true, NULL); heap_insert(resultRelInfo->ri_RelationDesc, tuple, mycid, hi_options, bistate); ItemPointerCopy(&tuple->t_self, &slot->tts_tid); @@ -3018,7 +3009,7 @@ CopyFrom(CopyState cstate) NIL); /* AFTER ROW INSERT Triggers */ - ExecARInsertTriggers(estate, resultRelInfo, tuple, + ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes, cstate->transition_capture); list_free(recheckIndexes); @@ -3158,7 +3149,7 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid, ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self), estate, false, NULL, NIL); ExecARInsertTriggers(estate, resultRelInfo, - bufferedTuples[i], + myslot, recheckIndexes, cstate->transition_capture); list_free(recheckIndexes); } @@ -3175,8 +3166,9 @@ CopyFromInsertBatch(CopyState cstate, EState *estate, CommandId mycid, for (i = 0; i < nBufferedTuples; i++) { cstate->cur_lineno = firstBufferedLineNo + i; + ExecStoreHeapTuple(bufferedTuples[i], myslot, false); ExecARInsertTriggers(estate, resultRelInfo, - bufferedTuples[i], + myslot, NIL, cstate->transition_capture); } } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 0fb0b186bb..a93b13c2fe 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -8921,8 +8921,6 @@ validateForeignKeyConstraint(char *conname, trigdata.tg_trigtuple = tuple; trigdata.tg_newtuple = NULL; trigdata.tg_trigger = &trig; - trigdata.tg_trigtuplebuf = scan->rs_cbuf; - trigdata.tg_newtuplebuf = InvalidBuffer; fcinfo->context = (Node *) &trigdata; diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 7e5bf0d27f..c5e588e801 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -79,16 +79,17 @@ static int MyTriggerDepth = 0; /* Local function prototypes */ static void ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid); static void SetTriggerFlags(TriggerDesc *trigdesc, Trigger *trigger); -static HeapTuple GetTupleForTrigger(EState *estate, +static bool GetTupleForTrigger(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, ItemPointer tid, LockTupleMode lockmode, + TupleTableSlot *oldslot, TupleTableSlot **newSlot); static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo, Trigger *trigger, TriggerEvent event, Bitmapset *modifiedCols, - HeapTuple oldtup, HeapTuple newtup); + TupleTableSlot *oldslot, TupleTableSlot *newslot); static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata, int tgindx, FmgrInfo *finfo, @@ -96,7 +97,7 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata, MemoryContext per_tuple_context); static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, int event, bool row_trigger, - HeapTuple oldtup, HeapTuple newtup, + TupleTableSlot *oldtup, TupleTableSlot *newtup, List *recheckIndexes, Bitmapset *modifiedCols, TransitionCaptureState *transition_capture); static void AfterTriggerEnlargeQueryState(void); @@ -2467,10 +2468,10 @@ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo) LocTriggerData.tg_relation = relinfo->ri_RelationDesc; LocTriggerData.tg_trigtuple = NULL; LocTriggerData.tg_newtuple = NULL; + LocTriggerData.tg_trigslot = NULL; + LocTriggerData.tg_newslot = NULL; LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_newtable = NULL; - LocTriggerData.tg_trigtuplebuf = InvalidBuffer; - LocTriggerData.tg_newtuplebuf = InvalidBuffer; for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; @@ -2510,15 +2511,13 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo, false, NULL, NULL, NIL, NULL, transition_capture); } -TupleTableSlot * +bool ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TupleTableSlot *slot) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; + HeapTuple newtuple = false; bool should_free; - HeapTuple slottuple = ExecFetchSlotHeapTuple(slot, true, &should_free); - HeapTuple newtuple = slottuple; - HeapTuple oldtuple; TriggerData LocTriggerData; int i; @@ -2527,13 +2526,16 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.tg_trigtuple = NULL; LocTriggerData.tg_newtuple = NULL; + LocTriggerData.tg_trigslot = NULL; + LocTriggerData.tg_newslot = NULL; LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_newtable = NULL; - LocTriggerData.tg_newtuplebuf = InvalidBuffer; for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; + HeapTuple oldtuple; if (!TRIGGER_TYPE_MATCHES(trigger->tgtype, TRIGGER_TYPE_ROW, @@ -2541,52 +2543,44 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TRIGGER_TYPE_INSERT)) continue; if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, - NULL, NULL, newtuple)) + NULL, NULL, slot)) continue; + if (!newtuple) + newtuple = ExecFetchSlotHeapTuple(slot, true, &should_free); + + LocTriggerData.tg_trigslot = slot; LocTriggerData.tg_trigtuple = oldtuple = newtuple; - LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_trigger = trigger; newtuple = ExecCallTriggerFunc(&LocTriggerData, i, relinfo->ri_TrigFunctions, relinfo->ri_TrigInstrument, GetPerTupleMemoryContext(estate)); - if (oldtuple != newtuple && oldtuple != slottuple) - heap_freetuple(oldtuple); if (newtuple == NULL) { if (should_free) - heap_freetuple(slottuple); - return NULL; /* "do nothing" */ + heap_freetuple(oldtuple); + return false; /* "do nothing" */ + } + else if (newtuple != oldtuple) + { + ExecForceStoreHeapTuple(newtuple, slot); + + if (should_free) + heap_freetuple(oldtuple); + + /* signal tuple should be re-fetched if used */ + newtuple = NULL; } } - if (newtuple != slottuple) - { - /* - * Return the modified tuple using the es_trig_tuple_slot. We assume - * the tuple was allocated in per-tuple memory context, and therefore - * will go away by itself. The tuple table slot should not try to - * clear it. - */ - TupleTableSlot *newslot = estate->es_trig_tuple_slot; - TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc); - - if (newslot->tts_tupleDescriptor != tupdesc) - ExecSetSlotDescriptor(newslot, tupdesc); - ExecStoreHeapTuple(newtuple, newslot, false); - slot = newslot; - } - - if (should_free) - heap_freetuple(slottuple); - return slot; + return true; } void ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, - HeapTuple trigtuple, List *recheckIndexes, + TupleTableSlot *slot, List *recheckIndexes, TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; @@ -2594,20 +2588,18 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, if ((trigdesc && trigdesc->trig_insert_after_row) || (transition_capture && transition_capture->tcs_insert_new_table)) AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_INSERT, - true, NULL, trigtuple, + true, NULL, slot, recheckIndexes, NULL, transition_capture); } -TupleTableSlot * +bool ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TupleTableSlot *slot) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; + HeapTuple newtuple = NULL; bool should_free; - HeapTuple slottuple = ExecFetchSlotHeapTuple(slot, true, &should_free); - HeapTuple newtuple = slottuple; - HeapTuple oldtuple; TriggerData LocTriggerData; int i; @@ -2616,13 +2608,16 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TRIGGER_EVENT_ROW | TRIGGER_EVENT_INSTEAD; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.tg_trigtuple = NULL; LocTriggerData.tg_newtuple = NULL; + LocTriggerData.tg_trigslot = NULL; + LocTriggerData.tg_newslot = NULL; LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_newtable = NULL; - LocTriggerData.tg_newtuplebuf = InvalidBuffer; for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; + HeapTuple oldtuple; if (!TRIGGER_TYPE_MATCHES(trigger->tgtype, TRIGGER_TYPE_ROW, @@ -2630,47 +2625,39 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TRIGGER_TYPE_INSERT)) continue; if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, - NULL, NULL, newtuple)) + NULL, NULL, slot)) continue; + if (!newtuple) + newtuple = ExecFetchSlotHeapTuple(slot, true, &should_free); + + LocTriggerData.tg_trigslot = slot; LocTriggerData.tg_trigtuple = oldtuple = newtuple; - LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_trigger = trigger; newtuple = ExecCallTriggerFunc(&LocTriggerData, i, relinfo->ri_TrigFunctions, relinfo->ri_TrigInstrument, GetPerTupleMemoryContext(estate)); - if (oldtuple != newtuple && oldtuple != slottuple) - heap_freetuple(oldtuple); if (newtuple == NULL) { if (should_free) - heap_freetuple(slottuple); - return NULL; /* "do nothing" */ + heap_freetuple(oldtuple); + return false; /* "do nothing" */ + } + else if (newtuple != oldtuple) + { + ExecForceStoreHeapTuple(newtuple, slot); + + if (should_free) + heap_freetuple(oldtuple); + + /* signal tuple should be re-fetched if used */ + newtuple = NULL; } } - if (newtuple != slottuple) - { - /* - * Return the modified tuple using the es_trig_tuple_slot. We assume - * the tuple was allocated in per-tuple memory context, and therefore - * will go away by itself. The tuple table slot should not try to - * clear it. - */ - TupleTableSlot *newslot = estate->es_trig_tuple_slot; - TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc); - - if (newslot->tts_tupleDescriptor != tupdesc) - ExecSetSlotDescriptor(newslot, tupdesc); - ExecStoreHeapTuple(newtuple, newslot, false); - slot = newslot; - } - - if (should_free) - heap_freetuple(slottuple); - return slot; + return true; } void @@ -2698,10 +2685,10 @@ ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo) LocTriggerData.tg_relation = relinfo->ri_RelationDesc; LocTriggerData.tg_trigtuple = NULL; LocTriggerData.tg_newtuple = NULL; + LocTriggerData.tg_trigslot = NULL; + LocTriggerData.tg_newslot = NULL; LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_newtable = NULL; - LocTriggerData.tg_trigtuplebuf = InvalidBuffer; - LocTriggerData.tg_newtuplebuf = InvalidBuffer; for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; @@ -2755,20 +2742,21 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, HeapTuple fdw_trigtuple, TupleTableSlot **epqslot) { + TupleTableSlot *slot = ExecGetTriggerOldSlot(estate, relinfo); TriggerDesc *trigdesc = relinfo->ri_TrigDesc; bool result = true; TriggerData LocTriggerData; HeapTuple trigtuple; - HeapTuple newtuple; - TupleTableSlot *newSlot; + bool should_free = false; int i; Assert(HeapTupleIsValid(fdw_trigtuple) ^ ItemPointerIsValid(tupleid)); if (fdw_trigtuple == NULL) { - trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid, - LockTupleExclusive, &newSlot); - if (trigtuple == NULL) + TupleTableSlot *newSlot; + + if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid, + LockTupleExclusive, slot, &newSlot)) return false; /* @@ -2779,24 +2767,32 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, if (newSlot != NULL && epqslot != NULL) { *epqslot = newSlot; - heap_freetuple(trigtuple); return false; } + + trigtuple = ExecFetchSlotHeapTuple(slot, true, &should_free); + } else + { trigtuple = fdw_trigtuple; + ExecForceStoreHeapTuple(trigtuple, slot); + } LocTriggerData.type = T_TriggerData; LocTriggerData.tg_event = TRIGGER_EVENT_DELETE | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.tg_trigtuple = NULL; LocTriggerData.tg_newtuple = NULL; + LocTriggerData.tg_trigslot = NULL; + LocTriggerData.tg_newslot = NULL; LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_newtable = NULL; - LocTriggerData.tg_newtuplebuf = InvalidBuffer; for (i = 0; i < trigdesc->numtriggers; i++) { + HeapTuple newtuple; Trigger *trigger = &trigdesc->triggers[i]; if (!TRIGGER_TYPE_MATCHES(trigger->tgtype, @@ -2805,11 +2801,11 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, TRIGGER_TYPE_DELETE)) continue; if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, - NULL, trigtuple, NULL)) + NULL, slot, NULL)) continue; + LocTriggerData.tg_trigslot = slot; LocTriggerData.tg_trigtuple = trigtuple; - LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_trigger = trigger; newtuple = ExecCallTriggerFunc(&LocTriggerData, i, @@ -2824,7 +2820,7 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate, if (newtuple != trigtuple) heap_freetuple(newtuple); } - if (trigtuple != fdw_trigtuple) + if (should_free) heap_freetuple(trigtuple); return result; @@ -2837,28 +2833,26 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo, TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; + TupleTableSlot *slot = ExecGetTriggerOldSlot(estate, relinfo); if ((trigdesc && trigdesc->trig_delete_after_row) || (transition_capture && transition_capture->tcs_delete_old_table)) { - HeapTuple trigtuple; - Assert(HeapTupleIsValid(fdw_trigtuple) ^ ItemPointerIsValid(tupleid)); if (fdw_trigtuple == NULL) - trigtuple = GetTupleForTrigger(estate, - NULL, - relinfo, - tupleid, - LockTupleExclusive, - NULL); + GetTupleForTrigger(estate, + NULL, + relinfo, + tupleid, + LockTupleExclusive, + slot, + NULL); else - trigtuple = fdw_trigtuple; + ExecForceStoreHeapTuple(fdw_trigtuple, slot); AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_DELETE, - true, trigtuple, NULL, NIL, NULL, + true, slot, NULL, NIL, NULL, transition_capture); - if (trigtuple != fdw_trigtuple) - heap_freetuple(trigtuple); } } @@ -2867,8 +2861,8 @@ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo, HeapTuple trigtuple) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; + TupleTableSlot *slot = ExecGetTriggerOldSlot(estate, relinfo); TriggerData LocTriggerData; - HeapTuple rettuple; int i; LocTriggerData.type = T_TriggerData; @@ -2876,12 +2870,18 @@ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo, TRIGGER_EVENT_ROW | TRIGGER_EVENT_INSTEAD; LocTriggerData.tg_relation = relinfo->ri_RelationDesc; + LocTriggerData.tg_trigtuple = NULL; LocTriggerData.tg_newtuple = NULL; + LocTriggerData.tg_trigslot = NULL; + LocTriggerData.tg_newslot = NULL; LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_newtable = NULL; - LocTriggerData.tg_newtuplebuf = InvalidBuffer; + + ExecForceStoreHeapTuple(trigtuple, slot); + for (i = 0; i < trigdesc->numtriggers; i++) { + HeapTuple rettuple; Trigger *trigger = &trigdesc->triggers[i]; if (!TRIGGER_TYPE_MATCHES(trigger->tgtype, @@ -2890,11 +2890,11 @@ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo, TRIGGER_TYPE_DELETE)) continue; if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, - NULL, trigtuple, NULL)) + NULL, slot, NULL)) continue; + LocTriggerData.tg_trigslot = slot; LocTriggerData.tg_trigtuple = trigtuple; - LocTriggerData.tg_trigtuplebuf = InvalidBuffer; LocTriggerData.tg_trigger = trigger; rettuple = ExecCallTriggerFunc(&LocTriggerData, i, @@ -2937,10 +2937,10 @@ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo) LocTriggerData.tg_relation = relinfo->ri_RelationDesc; LocTriggerData.tg_trigtuple = NULL; LocTriggerData.tg_newtuple = NULL; + LocTriggerData.tg_trigslot = NULL; + LocTriggerData.tg_newslot = NULL; LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_newtable = NULL; - LocTriggerData.tg_trigtuplebuf = InvalidBuffer; - LocTriggerData.tg_newtuplebuf = InvalidBuffer; for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; @@ -2982,20 +2982,20 @@ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo, transition_capture); } -TupleTableSlot * +bool ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, - TupleTableSlot *slot) + TupleTableSlot *newslot) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; - HeapTuple slottuple = ExecFetchSlotHeapTuple(slot, true, NULL); - HeapTuple newtuple = slottuple; - TriggerData LocTriggerData; + TupleTableSlot *oldslot = ExecGetTriggerOldSlot(estate, relinfo); + HeapTuple newtuple = NULL; HeapTuple trigtuple; - HeapTuple oldtuple; - TupleTableSlot *newSlot; + bool should_free_trig = false; + bool should_free_new = false; + TriggerData LocTriggerData; int i; Bitmapset *updatedCols; LockTupleMode lockmode; @@ -3006,37 +3006,40 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, Assert(HeapTupleIsValid(fdw_trigtuple) ^ ItemPointerIsValid(tupleid)); if (fdw_trigtuple == NULL) { + TupleTableSlot *newSlot = NULL; + /* get a copy of the on-disk tuple we are planning to update */ - trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid, - lockmode, &newSlot); - if (trigtuple == NULL) - return NULL; /* cancel the update action */ + if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid, + lockmode, oldslot, &newSlot)) + return false; /* cancel the update action */ + + /* + * In READ COMMITTED isolation level it's possible that target tuple + * was changed due to concurrent update. In that case we have a raw + * subplan output tuple in newSlot, and need to run it through the + * junk filter to produce an insertable tuple. + * + * Caution: more than likely, the passed-in slot is the same as the + * junkfilter's output slot, so we are clobbering the original value + * of slottuple by doing the filtering. This is OK since neither we + * nor our caller have any more interest in the prior contents of that + * slot. + */ + if (newSlot != NULL) + { + TupleTableSlot *slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot); + + ExecCopySlot(newslot, slot); + } + + trigtuple = ExecFetchSlotHeapTuple(oldslot, true, &should_free_trig); } else { + ExecForceStoreHeapTuple(fdw_trigtuple, oldslot); trigtuple = fdw_trigtuple; - newSlot = NULL; } - /* - * In READ COMMITTED isolation level it's possible that target tuple was - * changed due to concurrent update. In that case we have a raw subplan - * output tuple in newSlot, and need to run it through the junk filter to - * produce an insertable tuple. - * - * Caution: more than likely, the passed-in slot is the same as the - * junkfilter's output slot, so we are clobbering the original value of - * slottuple by doing the filtering. This is OK since neither we nor our - * caller have any more interest in the prior contents of that slot. - */ - if (newSlot != NULL) - { - slot = ExecFilterJunk(relinfo->ri_junkFilter, newSlot); - slottuple = ExecFetchSlotHeapTuple(slot, true, NULL); - newtuple = slottuple; - } - - LocTriggerData.type = T_TriggerData; LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE | TRIGGER_EVENT_ROW | @@ -3048,6 +3051,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; + HeapTuple oldtuple; if (!TRIGGER_TYPE_MATCHES(trigger->tgtype, TRIGGER_TYPE_ROW, @@ -3055,67 +3059,66 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, TRIGGER_TYPE_UPDATE)) continue; if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, - updatedCols, trigtuple, newtuple)) + updatedCols, oldslot, newslot)) continue; + if (!newtuple) + newtuple = ExecFetchSlotHeapTuple(newslot, true, &should_free_new); + + LocTriggerData.tg_trigslot = oldslot; LocTriggerData.tg_trigtuple = trigtuple; LocTriggerData.tg_newtuple = oldtuple = newtuple; - LocTriggerData.tg_trigtuplebuf = InvalidBuffer; - LocTriggerData.tg_newtuplebuf = InvalidBuffer; + LocTriggerData.tg_newslot = newslot; LocTriggerData.tg_trigger = trigger; newtuple = ExecCallTriggerFunc(&LocTriggerData, i, relinfo->ri_TrigFunctions, relinfo->ri_TrigInstrument, GetPerTupleMemoryContext(estate)); - if (oldtuple != newtuple && oldtuple != slottuple) - heap_freetuple(oldtuple); + if (newtuple == NULL) { - if (trigtuple != fdw_trigtuple) + if (should_free_trig) heap_freetuple(trigtuple); - return NULL; /* "do nothing" */ + if (should_free_new) + heap_freetuple(oldtuple); + return false; /* "do nothing" */ + } + else if (newtuple != oldtuple) + { + ExecForceStoreHeapTuple(newtuple, newslot); + + if (should_free_new) + heap_freetuple(oldtuple); + + /* signal tuple should be re-fetched if used */ + newtuple = NULL; } } - if (trigtuple != fdw_trigtuple && trigtuple != newtuple) + if (should_free_trig) heap_freetuple(trigtuple); - if (newtuple != slottuple) - { - /* - * Return the modified tuple using the es_trig_tuple_slot. We assume - * the tuple was allocated in per-tuple memory context, and therefore - * will go away by itself. The tuple table slot should not try to - * clear it. - */ - TupleTableSlot *newslot = estate->es_trig_tuple_slot; - TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc); - - if (newslot->tts_tupleDescriptor != tupdesc) - ExecSetSlotDescriptor(newslot, tupdesc); - ExecStoreHeapTuple(newtuple, newslot, false); - slot = newslot; - } - return slot; + return true; } void ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, - HeapTuple newtuple, + TupleTableSlot *newslot, List *recheckIndexes, TransitionCaptureState *transition_capture) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; + TupleTableSlot *oldslot = ExecGetTriggerOldSlot(estate, relinfo); + + ExecClearTuple(oldslot); if ((trigdesc && trigdesc->trig_update_after_row) || (transition_capture && (transition_capture->tcs_update_old_table || transition_capture->tcs_update_new_table))) { - HeapTuple trigtuple; - /* * Note: if the UPDATE is converted into a DELETE+INSERT as part of * update-partition-key operation, then this function is also called @@ -3123,33 +3126,32 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, * In such case, either old tuple or new tuple can be NULL. */ if (fdw_trigtuple == NULL && ItemPointerIsValid(tupleid)) - trigtuple = GetTupleForTrigger(estate, - NULL, - relinfo, - tupleid, - LockTupleExclusive, - NULL); - else - trigtuple = fdw_trigtuple; + GetTupleForTrigger(estate, + NULL, + relinfo, + tupleid, + LockTupleExclusive, + oldslot, + NULL); + else if (fdw_trigtuple != NULL) + ExecForceStoreHeapTuple(fdw_trigtuple, oldslot); AfterTriggerSaveEvent(estate, relinfo, TRIGGER_EVENT_UPDATE, - true, trigtuple, newtuple, recheckIndexes, + true, oldslot, newslot, recheckIndexes, GetUpdatedColumns(relinfo, estate), transition_capture); - if (trigtuple != fdw_trigtuple) - heap_freetuple(trigtuple); } } -TupleTableSlot * +bool ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo, - HeapTuple trigtuple, TupleTableSlot *slot) + HeapTuple trigtuple, TupleTableSlot *newslot) { TriggerDesc *trigdesc = relinfo->ri_TrigDesc; - HeapTuple slottuple = ExecFetchSlotHeapTuple(slot, true, NULL); - HeapTuple newtuple = slottuple; + TupleTableSlot *oldslot = ExecGetTriggerOldSlot(estate, relinfo); + HeapTuple newtuple = false; + bool should_free; TriggerData LocTriggerData; - HeapTuple oldtuple; int i; LocTriggerData.type = T_TriggerData; @@ -3159,9 +3161,13 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo, LocTriggerData.tg_relation = relinfo->ri_RelationDesc; LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_newtable = NULL; + + ExecForceStoreHeapTuple(trigtuple, oldslot); + for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; + HeapTuple oldtuple; if (!TRIGGER_TYPE_MATCHES(trigger->tgtype, TRIGGER_TYPE_ROW, @@ -3169,42 +3175,40 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo, TRIGGER_TYPE_UPDATE)) continue; if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event, - NULL, trigtuple, newtuple)) + NULL, oldslot, newslot)) continue; + if (!newtuple) + newtuple = ExecFetchSlotHeapTuple(newslot, true, &should_free); + + LocTriggerData.tg_trigslot = oldslot; LocTriggerData.tg_trigtuple = trigtuple; + LocTriggerData.tg_newslot = newslot; LocTriggerData.tg_newtuple = oldtuple = newtuple; - LocTriggerData.tg_trigtuplebuf = InvalidBuffer; - LocTriggerData.tg_newtuplebuf = InvalidBuffer; + LocTriggerData.tg_trigger = trigger; newtuple = ExecCallTriggerFunc(&LocTriggerData, i, relinfo->ri_TrigFunctions, relinfo->ri_TrigInstrument, GetPerTupleMemoryContext(estate)); - if (oldtuple != newtuple && oldtuple != slottuple) - heap_freetuple(oldtuple); if (newtuple == NULL) - return NULL; /* "do nothing" */ + { + return false; /* "do nothing" */ + } + else if (newtuple != oldtuple) + { + ExecForceStoreHeapTuple(newtuple, newslot); + + if (should_free) + heap_freetuple(oldtuple); + + /* signal tuple should be re-fetched if used */ + newtuple = NULL; + } } - if (newtuple != slottuple) - { - /* - * Return the modified tuple using the es_trig_tuple_slot. We assume - * the tuple was allocated in per-tuple memory context, and therefore - * will go away by itself. The tuple table slot should not try to - * clear it. - */ - TupleTableSlot *newslot = estate->es_trig_tuple_slot; - TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc); - - if (newslot->tts_tupleDescriptor != tupdesc) - ExecSetSlotDescriptor(newslot, tupdesc); - ExecStoreHeapTuple(newtuple, newslot, false); - slot = newslot; - } - return slot; + return true; } void @@ -3227,10 +3231,11 @@ ExecBSTruncateTriggers(EState *estate, ResultRelInfo *relinfo) LocTriggerData.tg_relation = relinfo->ri_RelationDesc; LocTriggerData.tg_trigtuple = NULL; LocTriggerData.tg_newtuple = NULL; + LocTriggerData.tg_trigslot = NULL; + LocTriggerData.tg_newslot = NULL; LocTriggerData.tg_oldtable = NULL; LocTriggerData.tg_newtable = NULL; - LocTriggerData.tg_trigtuplebuf = InvalidBuffer; - LocTriggerData.tg_newtuplebuf = InvalidBuffer; + for (i = 0; i < trigdesc->numtriggers; i++) { Trigger *trigger = &trigdesc->triggers[i]; @@ -3270,18 +3275,24 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo) } -static HeapTuple +static bool GetTupleForTrigger(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, ItemPointer tid, LockTupleMode lockmode, + TupleTableSlot *oldslot, TupleTableSlot **newSlot) { Relation relation = relinfo->ri_RelationDesc; - HeapTupleData tuple; - HeapTuple result; + HeapTuple tuple; Buffer buffer; + BufferHeapTupleTableSlot *boldslot; + + Assert(TTS_IS_BUFFERTUPLE(oldslot)); + ExecClearTuple(oldslot); + boldslot = (BufferHeapTupleTableSlot *) oldslot; + tuple = &boldslot->base.tupdata; if (newSlot != NULL) { @@ -3297,8 +3308,8 @@ GetTupleForTrigger(EState *estate, * lock tuple for update */ ltrmark:; - tuple.t_self = *tid; - test = heap_lock_tuple(relation, &tuple, + tuple->t_self = *tid; + test = heap_lock_tuple(relation, tuple, estate->es_output_cid, lockmode, LockWaitBlock, false, &buffer, &hufd); @@ -3322,9 +3333,11 @@ ltrmark:; /* treat it as deleted; do not process */ ReleaseBuffer(buffer); - return NULL; + return false; case HeapTupleMayBeUpdated: + ExecStorePinnedBufferHeapTuple(tuple, oldslot, buffer); + break; case HeapTupleUpdated: @@ -3338,7 +3351,7 @@ ltrmark:; (errcode(ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("tuple to be locked was already moved to another partition due to concurrent update"))); - if (!ItemPointerEquals(&hufd.ctid, &tuple.t_self)) + if (!ItemPointerEquals(&hufd.ctid, &tuple->t_self)) { /* it was updated, so look at the updated version */ TupleTableSlot *epqslot; @@ -3353,6 +3366,7 @@ ltrmark:; if (!TupIsNull(epqslot)) { *tid = hufd.ctid; + *newSlot = epqslot; /* @@ -3369,7 +3383,7 @@ ltrmark:; * if tuple was deleted or PlanQual failed for updated tuple - * we must not process this tuple! */ - return NULL; + return false; case HeapTupleInvisible: elog(ERROR, "attempted to lock invisible tuple"); @@ -3378,7 +3392,7 @@ ltrmark:; default: ReleaseBuffer(buffer); elog(ERROR, "unrecognized heap_lock_tuple status: %u", test); - return NULL; /* keep compiler quiet */ + return false; /* keep compiler quiet */ } } else @@ -3403,18 +3417,17 @@ ltrmark:; Assert(ItemIdIsNormal(lp)); - tuple.t_data = (HeapTupleHeader) PageGetItem(page, lp); - tuple.t_len = ItemIdGetLength(lp); - tuple.t_self = *tid; - tuple.t_tableOid = RelationGetRelid(relation); + tuple->t_data = (HeapTupleHeader) PageGetItem(page, lp); + tuple->t_len = ItemIdGetLength(lp); + tuple->t_self = *tid; + tuple->t_tableOid = RelationGetRelid(relation); LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + + ExecStorePinnedBufferHeapTuple(tuple, oldslot, buffer); } - result = heap_copytuple(&tuple); - ReleaseBuffer(buffer); - - return result; + return true; } /* @@ -3424,7 +3437,7 @@ static bool TriggerEnabled(EState *estate, ResultRelInfo *relinfo, Trigger *trigger, TriggerEvent event, Bitmapset *modifiedCols, - HeapTuple oldtup, HeapTuple newtup) + TupleTableSlot *oldslot, TupleTableSlot *newslot) { /* Check replication-role-dependent enable state */ if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA) @@ -3466,11 +3479,8 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo, /* Check for WHEN clause */ if (trigger->tgqual) { - TupleDesc tupdesc = RelationGetDescr(relinfo->ri_RelationDesc); ExprState **predicate; ExprContext *econtext; - TupleTableSlot *oldslot = NULL; - TupleTableSlot *newslot = NULL; MemoryContext oldContext; int i; @@ -3509,40 +3519,6 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo, */ econtext = GetPerTupleExprContext(estate); - /* - * Put OLD and NEW tuples into tupleslots for expression evaluation. - * These slots can be shared across the whole estate, but be careful - * that they have the current resultrel's tupdesc. - */ - if (HeapTupleIsValid(oldtup)) - { - if (estate->es_trig_oldtup_slot == NULL) - { - oldContext = MemoryContextSwitchTo(estate->es_query_cxt); - estate->es_trig_oldtup_slot = - ExecInitExtraTupleSlot(estate, NULL, &TTSOpsHeapTuple); - MemoryContextSwitchTo(oldContext); - } - oldslot = estate->es_trig_oldtup_slot; - if (oldslot->tts_tupleDescriptor != tupdesc) - ExecSetSlotDescriptor(oldslot, tupdesc); - ExecStoreHeapTuple(oldtup, oldslot, false); - } - if (HeapTupleIsValid(newtup)) - { - if (estate->es_trig_newtup_slot == NULL) - { - oldContext = MemoryContextSwitchTo(estate->es_query_cxt); - estate->es_trig_newtup_slot = - ExecInitExtraTupleSlot(estate, NULL, &TTSOpsHeapTuple); - MemoryContextSwitchTo(oldContext); - } - newslot = estate->es_trig_newtup_slot; - if (newslot->tts_tupleDescriptor != tupdesc) - ExecSetSlotDescriptor(newslot, tupdesc); - ExecStoreHeapTuple(newtup, newslot, false); - } - /* * Finally evaluate the expression, making the old and/or new tuples * available as INNER_VAR/OUTER_VAR respectively. @@ -3872,12 +3848,15 @@ struct AfterTriggersTableData AfterTriggerEventList after_trig_events; /* if so, saved list pointer */ Tuplestorestate *old_tuplestore; /* "old" transition table, if any */ Tuplestorestate *new_tuplestore; /* "new" transition table, if any */ + TupleTableSlot *storeslot; /* for converting to tuplestore's format */ }; static AfterTriggersData afterTriggers; -static void AfterTriggerExecute(AfterTriggerEvent event, - Relation rel, TriggerDesc *trigdesc, +static void AfterTriggerExecute(EState *estate, + AfterTriggerEvent event, + ResultRelInfo *relInfo, + TriggerDesc *trigdesc, FmgrInfo *finfo, Instrumentation *instr, MemoryContext per_tuple_context, @@ -4211,27 +4190,33 @@ afterTriggerDeleteHeadEventChunk(AfterTriggersQueryData *qs) * ---------- */ static void -AfterTriggerExecute(AfterTriggerEvent event, - Relation rel, TriggerDesc *trigdesc, +AfterTriggerExecute(EState *estate, + AfterTriggerEvent event, + ResultRelInfo *relInfo, + TriggerDesc *trigdesc, FmgrInfo *finfo, Instrumentation *instr, MemoryContext per_tuple_context, TupleTableSlot *trig_tuple_slot1, TupleTableSlot *trig_tuple_slot2) { + Relation rel = relInfo->ri_RelationDesc; AfterTriggerShared evtshared = GetTriggerSharedData(event); Oid tgoid = evtshared->ats_tgoid; TriggerData LocTriggerData; HeapTupleData tuple1; HeapTupleData tuple2; HeapTuple rettuple; - Buffer buffer1 = InvalidBuffer; - Buffer buffer2 = InvalidBuffer; int tgindx; + bool should_free_trig = false; + bool should_free_new = false; /* * Locate trigger in trigdesc. */ LocTriggerData.tg_trigger = NULL; + LocTriggerData.tg_trigslot = NULL; + LocTriggerData.tg_newslot = NULL; + for (tgindx = 0; tgindx < trigdesc->numtriggers; tgindx++) { if (trigdesc->triggers[tgindx].tgoid == tgoid) @@ -4273,7 +4258,7 @@ AfterTriggerExecute(AfterTriggerEvent event, case AFTER_TRIGGER_FDW_REUSE: /* - * Materialize tuple in the slot so that tg_trigtuple does not + * Store tuple in the slot so that tg_trigtuple does not * reference tuplestore memory. (It is formally possible for the * trigger function to queue trigger events that add to the same * tuplestore, which can push other tuples out of memory.) The @@ -4281,31 +4266,38 @@ AfterTriggerExecute(AfterTriggerEvent event, * that is stored as a heap tuple, constructed in different memory * context, in the slot anyway. */ - LocTriggerData.tg_trigtuple = ExecFetchSlotHeapTuple(trig_tuple_slot1, - true, NULL); - LocTriggerData.tg_trigtuplebuf = InvalidBuffer; + LocTriggerData.tg_trigslot = trig_tuple_slot1; + LocTriggerData.tg_trigtuple = + ExecFetchSlotHeapTuple(trig_tuple_slot1, true, &should_free_trig); + LocTriggerData.tg_newslot = trig_tuple_slot2; LocTriggerData.tg_newtuple = ((evtshared->ats_event & TRIGGER_EVENT_OPMASK) == TRIGGER_EVENT_UPDATE) ? - ExecFetchSlotHeapTuple(trig_tuple_slot2, true, NULL) : NULL; - LocTriggerData.tg_newtuplebuf = InvalidBuffer; + ExecFetchSlotHeapTuple(trig_tuple_slot2, true, &should_free_new) : NULL; break; default: if (ItemPointerIsValid(&(event->ate_ctid1))) { + Buffer buffer; + + LocTriggerData.tg_trigslot = ExecGetTriggerOldSlot(estate, relInfo); + ItemPointerCopy(&(event->ate_ctid1), &(tuple1.t_self)); - if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer1, false, NULL)) + if (!heap_fetch(rel, SnapshotAny, &tuple1, &buffer, false, NULL)) elog(ERROR, "failed to fetch tuple1 for AFTER trigger"); - LocTriggerData.tg_trigtuple = &tuple1; - LocTriggerData.tg_trigtuplebuf = buffer1; + ExecStorePinnedBufferHeapTuple(&tuple1, + LocTriggerData.tg_trigslot, + buffer); + LocTriggerData.tg_trigtuple = + ExecFetchSlotHeapTuple(LocTriggerData.tg_trigslot, false, + &should_free_trig); } else { LocTriggerData.tg_trigtuple = NULL; - LocTriggerData.tg_trigtuplebuf = InvalidBuffer; } /* don't touch ctid2 if not there */ @@ -4313,16 +4305,23 @@ AfterTriggerExecute(AfterTriggerEvent event, AFTER_TRIGGER_2CTID && ItemPointerIsValid(&(event->ate_ctid2))) { + Buffer buffer; + + LocTriggerData.tg_newslot = ExecGetTriggerNewSlot(estate, relInfo); + ItemPointerCopy(&(event->ate_ctid2), &(tuple2.t_self)); - if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer2, false, NULL)) + if (!heap_fetch(rel, SnapshotAny, &tuple2, &buffer, false, NULL)) elog(ERROR, "failed to fetch tuple2 for AFTER trigger"); - LocTriggerData.tg_newtuple = &tuple2; - LocTriggerData.tg_newtuplebuf = buffer2; + ExecStorePinnedBufferHeapTuple(&tuple2, + LocTriggerData.tg_newslot, + buffer); + LocTriggerData.tg_newtuple = + ExecFetchSlotHeapTuple(LocTriggerData.tg_newslot, false, + &should_free_new); } else { LocTriggerData.tg_newtuple = NULL; - LocTriggerData.tg_newtuplebuf = InvalidBuffer; } } @@ -4374,12 +4373,17 @@ AfterTriggerExecute(AfterTriggerEvent event, heap_freetuple(rettuple); /* - * Release buffers + * Release resources */ - if (buffer1 != InvalidBuffer) - ReleaseBuffer(buffer1); - if (buffer2 != InvalidBuffer) - ReleaseBuffer(buffer2); + if (should_free_trig) + heap_freetuple(LocTriggerData.tg_trigtuple); + if (should_free_new) + heap_freetuple(LocTriggerData.tg_newtuple); + + if (LocTriggerData.tg_trigslot) + ExecClearTuple(LocTriggerData.tg_trigslot); + if (LocTriggerData.tg_newslot) + ExecClearTuple(LocTriggerData.tg_newslot); /* * If doing EXPLAIN ANALYZE, stop charging time to this trigger, and count @@ -4486,6 +4490,7 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, AfterTriggerEventChunk *chunk; MemoryContext per_tuple_context; bool local_estate = false; + ResultRelInfo *rInfo; Relation rel = NULL; TriggerDesc *trigdesc = NULL; FmgrInfo *finfo = NULL; @@ -4527,8 +4532,6 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, */ if (rel == NULL || RelationGetRelid(rel) != evtshared->ats_relid) { - ResultRelInfo *rInfo; - rInfo = ExecGetTriggerResultRel(estate, evtshared->ats_relid); rel = rInfo->ri_RelationDesc; trigdesc = rInfo->ri_TrigDesc; @@ -4556,7 +4559,7 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, * still set, so recursive examinations of the event list * won't try to re-fire it. */ - AfterTriggerExecute(event, rel, trigdesc, finfo, instr, + AfterTriggerExecute(estate, event, rInfo, trigdesc, finfo, instr, per_tuple_context, slot1, slot2); /* @@ -4600,6 +4603,7 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events, if (local_estate) { ExecCleanUpTriggerState(estate); + ExecResetTupleTable(estate->es_tupleTable, false); FreeExecutorState(estate); } @@ -5737,7 +5741,7 @@ AfterTriggerPendingOnRel(Oid relid) static void AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, int event, bool row_trigger, - HeapTuple oldtup, HeapTuple newtup, + TupleTableSlot *oldslot, TupleTableSlot *newslot, List *recheckIndexes, Bitmapset *modifiedCols, TransitionCaptureState *transition_capture) { @@ -5769,7 +5773,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, */ if (row_trigger && transition_capture != NULL) { - HeapTuple original_insert_tuple = transition_capture->tcs_original_insert_tuple; + TupleTableSlot *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; @@ -5777,20 +5781,19 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, bool insert_new_table = transition_capture->tcs_insert_new_table; /* - * For INSERT events newtup should be non-NULL, for DELETE events - * oldtup should be non-NULL, whereas for UPDATE events normally both - * oldtup and newtup are non-NULL. But for UPDATE events fired for - * capturing transition tuples during UPDATE partition-key row - * movement, oldtup is NULL when the event is for a row being - * inserted, whereas newtup is NULL when the event is for a row being - * deleted. + * For INSERT events NEW should be non-NULL, for DELETE events OLD + * should be non-NULL, whereas for UPDATE events normally both OLD and + * NEW are non-NULL. But for UPDATE events fired for capturing + * transition tuples during UPDATE partition-key row movement, OLD is + * NULL when the event is for a row being inserted, whereas NEW is + * NULL when the event is for a row being deleted. */ Assert(!(event == TRIGGER_EVENT_DELETE && delete_old_table && - oldtup == NULL)); + TupIsNull(oldslot))); Assert(!(event == TRIGGER_EVENT_INSERT && insert_new_table && - newtup == NULL)); + TupIsNull(newslot))); - if (oldtup != NULL && + if (!TupIsNull(oldslot) && ((event == TRIGGER_EVENT_DELETE && delete_old_table) || (event == TRIGGER_EVENT_UPDATE && update_old_table))) { @@ -5800,15 +5803,24 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, if (map != NULL) { - HeapTuple converted = execute_attr_map_tuple(oldtup, map); + TupleTableSlot *storeslot; - tuplestore_puttuple(old_tuplestore, converted); - pfree(converted); + storeslot = transition_capture->tcs_private->storeslot; + if (!storeslot) + { + storeslot = ExecAllocTableSlot(&estate->es_tupleTable, + map->outdesc, + &TTSOpsVirtual); + transition_capture->tcs_private->storeslot = storeslot; + } + + execute_attr_map_slot(map->attrMap, oldslot, storeslot); + tuplestore_puttupleslot(old_tuplestore, storeslot); } else - tuplestore_puttuple(old_tuplestore, oldtup); + tuplestore_puttupleslot(old_tuplestore, oldslot); } - if (newtup != NULL && + if (!TupIsNull(newslot) && ((event == TRIGGER_EVENT_INSERT && insert_new_table) || (event == TRIGGER_EVENT_UPDATE && update_new_table))) { @@ -5817,16 +5829,27 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, new_tuplestore = transition_capture->tcs_private->new_tuplestore; if (original_insert_tuple != NULL) - tuplestore_puttuple(new_tuplestore, original_insert_tuple); + tuplestore_puttupleslot(new_tuplestore, + original_insert_tuple); else if (map != NULL) { - HeapTuple converted = execute_attr_map_tuple(newtup, map); + TupleTableSlot *storeslot; - tuplestore_puttuple(new_tuplestore, converted); - pfree(converted); + storeslot = transition_capture->tcs_private->storeslot; + + if (!storeslot) + { + storeslot = ExecAllocTableSlot(&estate->es_tupleTable, + map->outdesc, + &TTSOpsVirtual); + transition_capture->tcs_private->storeslot = storeslot; + } + + execute_attr_map_slot(map->attrMap, newslot, storeslot); + tuplestore_puttupleslot(new_tuplestore, storeslot); } else - tuplestore_puttuple(new_tuplestore, newtup); + tuplestore_puttupleslot(new_tuplestore, newslot); } /* @@ -5840,7 +5863,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, (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) || - (event == TRIGGER_EVENT_UPDATE && ((oldtup == NULL) ^ (newtup == NULL)))) + (event == TRIGGER_EVENT_UPDATE && (TupIsNull(oldslot) ^ TupIsNull(newslot)))) return; } @@ -5862,15 +5885,15 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, tgtype_event = TRIGGER_TYPE_INSERT; if (row_trigger) { - Assert(oldtup == NULL); - Assert(newtup != NULL); - ItemPointerCopy(&(newtup->t_self), &(new_event.ate_ctid1)); + Assert(oldslot == NULL); + Assert(newslot != NULL); + ItemPointerCopy(&(newslot->tts_tid), &(new_event.ate_ctid1)); ItemPointerSetInvalid(&(new_event.ate_ctid2)); } else { - Assert(oldtup == NULL); - Assert(newtup == NULL); + Assert(oldslot == NULL); + Assert(newslot == NULL); ItemPointerSetInvalid(&(new_event.ate_ctid1)); ItemPointerSetInvalid(&(new_event.ate_ctid2)); cancel_prior_stmt_triggers(RelationGetRelid(rel), @@ -5881,15 +5904,15 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, tgtype_event = TRIGGER_TYPE_DELETE; if (row_trigger) { - Assert(oldtup != NULL); - Assert(newtup == NULL); - ItemPointerCopy(&(oldtup->t_self), &(new_event.ate_ctid1)); + Assert(oldslot != NULL); + Assert(newslot == NULL); + ItemPointerCopy(&(oldslot->tts_tid), &(new_event.ate_ctid1)); ItemPointerSetInvalid(&(new_event.ate_ctid2)); } else { - Assert(oldtup == NULL); - Assert(newtup == NULL); + Assert(oldslot == NULL); + Assert(newslot == NULL); ItemPointerSetInvalid(&(new_event.ate_ctid1)); ItemPointerSetInvalid(&(new_event.ate_ctid2)); cancel_prior_stmt_triggers(RelationGetRelid(rel), @@ -5900,15 +5923,15 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, tgtype_event = TRIGGER_TYPE_UPDATE; if (row_trigger) { - Assert(oldtup != NULL); - Assert(newtup != NULL); - ItemPointerCopy(&(oldtup->t_self), &(new_event.ate_ctid1)); - ItemPointerCopy(&(newtup->t_self), &(new_event.ate_ctid2)); + Assert(oldslot != NULL); + Assert(newslot != NULL); + ItemPointerCopy(&(oldslot->tts_tid), &(new_event.ate_ctid1)); + ItemPointerCopy(&(newslot->tts_tid), &(new_event.ate_ctid2)); } else { - Assert(oldtup == NULL); - Assert(newtup == NULL); + Assert(oldslot == NULL); + Assert(newslot == NULL); ItemPointerSetInvalid(&(new_event.ate_ctid1)); ItemPointerSetInvalid(&(new_event.ate_ctid2)); cancel_prior_stmt_triggers(RelationGetRelid(rel), @@ -5917,8 +5940,8 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, break; case TRIGGER_EVENT_TRUNCATE: tgtype_event = TRIGGER_TYPE_TRUNCATE; - Assert(oldtup == NULL); - Assert(newtup == NULL); + Assert(oldslot == NULL); + Assert(newslot == NULL); ItemPointerSetInvalid(&(new_event.ate_ctid1)); ItemPointerSetInvalid(&(new_event.ate_ctid2)); break; @@ -5945,7 +5968,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, tgtype_event)) continue; if (!TriggerEnabled(estate, relinfo, trigger, event, - modifiedCols, oldtup, newtup)) + modifiedCols, oldslot, newslot)) continue; if (relkind == RELKIND_FOREIGN_TABLE && row_trigger) @@ -5972,7 +5995,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, case RI_TRIGGER_PK: /* Update or delete on trigger's PK table */ if (!RI_FKey_pk_upd_check_required(trigger, rel, - oldtup, newtup)) + oldslot, newslot)) { /* skip queuing this event */ continue; @@ -5982,7 +6005,7 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, case RI_TRIGGER_FK: /* Update on trigger's FK table */ if (!RI_FKey_fk_upd_check_required(trigger, rel, - oldtup, newtup)) + oldslot, newslot)) { /* skip queuing this event */ continue; @@ -6036,10 +6059,10 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, */ if (fdw_tuplestore) { - if (oldtup != NULL) - tuplestore_puttuple(fdw_tuplestore, oldtup); - if (newtup != NULL) - tuplestore_puttuple(fdw_tuplestore, newtup); + if (oldslot != NULL) + tuplestore_puttupleslot(fdw_tuplestore, oldslot); + if (newslot != NULL) + tuplestore_puttupleslot(fdw_tuplestore, newslot); } } diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 9a20460e76..00d8e8fc58 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -975,9 +975,6 @@ InitPlan(QueryDesc *queryDesc, int eflags) * Initialize the executor's tuple table to empty. */ estate->es_tupleTable = NIL; - estate->es_trig_tuple_slot = NULL; - estate->es_trig_oldtup_slot = NULL; - estate->es_trig_newtup_slot = NULL; /* mark EvalPlanQual not active */ estate->es_epqTuple = NULL; @@ -1324,6 +1321,9 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo, resultRelInfo->ri_projectReturning = NULL; resultRelInfo->ri_onConflictArbiterIndexes = NIL; resultRelInfo->ri_onConflict = NULL; + resultRelInfo->ri_ReturningSlot = NULL; + resultRelInfo->ri_TrigOldSlot = NULL; + resultRelInfo->ri_TrigNewSlot = NULL; /* * Partition constraint, which also includes the partition constraint of diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index 663d6e3264..5c5aa96e7f 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -403,10 +403,8 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot) if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_before_row) { - slot = ExecBRInsertTriggers(estate, resultRelInfo, slot); - - if (slot == NULL) /* "do nothing" */ - skip_tuple = true; + if (!ExecBRInsertTriggers(estate, resultRelInfo, slot)) + skip_tuple = true; /* "do nothing" */ } if (!skip_tuple) @@ -432,7 +430,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot) NIL); /* AFTER ROW INSERT Triggers */ - ExecARInsertTriggers(estate, resultRelInfo, tuple, + ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes, NULL); /* @@ -475,11 +473,10 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_update_before_row) { - slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, - &hsearchslot->tuple->t_self, NULL, slot); - - if (slot == NULL) /* "do nothing" */ - skip_tuple = true; + if (!ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, + &hsearchslot->tuple->t_self, + NULL, slot)) + skip_tuple = true; /* "do nothing" */ } if (!skip_tuple) @@ -507,7 +504,8 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, /* AFTER ROW UPDATE Triggers */ ExecARUpdateTriggers(estate, resultRelInfo, - &hsearchslot->tuple->t_self, NULL, tuple, + &(tuple->t_self), + NULL, slot, recheckIndexes, NULL); list_free(recheckIndexes); @@ -540,8 +538,9 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, resultRelInfo->ri_TrigDesc->trig_delete_before_row) { skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo, - &hsearchslot->tuple->t_self, NULL, - NULL); + &hsearchslot->tuple->t_self, + NULL, NULL); + } if (!skip_tuple) diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index e04c048b54..3a14251821 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -119,6 +119,7 @@ tts_virtual_clear(TupleTableSlot *slot) slot->tts_nvalid = 0; slot->tts_flags |= TTS_FLAG_EMPTY; + ItemPointerSetInvalid(&slot->tts_tid); } /* @@ -314,6 +315,7 @@ tts_heap_clear(TupleTableSlot *slot) slot->tts_nvalid = 0; slot->tts_flags |= TTS_FLAG_EMPTY; + ItemPointerSetInvalid(&slot->tts_tid); hslot->off = 0; hslot->tuple = NULL; } @@ -477,6 +479,7 @@ tts_minimal_clear(TupleTableSlot *slot) slot->tts_nvalid = 0; slot->tts_flags |= TTS_FLAG_EMPTY; + ItemPointerSetInvalid(&slot->tts_tid); mslot->off = 0; mslot->mintuple = NULL; } @@ -658,6 +661,7 @@ tts_buffer_heap_clear(TupleTableSlot *slot) slot->tts_nvalid = 0; slot->tts_flags |= TTS_FLAG_EMPTY; + ItemPointerSetInvalid(&slot->tts_tid); bslot->base.tuple = NULL; bslot->base.off = 0; bslot->buffer = InvalidBuffer; diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 312a0dc805..5136269348 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -131,9 +131,6 @@ CreateExecutorState(void) estate->es_tuple_routing_result_relations = NIL; estate->es_trig_target_relations = NIL; - estate->es_trig_tuple_slot = NULL; - estate->es_trig_oldtup_slot = NULL; - estate->es_trig_newtup_slot = NULL; estate->es_param_list_info = NULL; estate->es_param_exec_vals = NULL; @@ -1102,3 +1099,69 @@ ExecCleanTargetListLength(List *targetlist) } return len; } + +/* + * Return a relInfo's tuple slot for a trigger's OLD tuples. + */ +TupleTableSlot * +ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relInfo) +{ + if (relInfo->ri_TrigOldSlot == NULL) + { + Relation rel = relInfo->ri_RelationDesc; + MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + + relInfo->ri_TrigOldSlot = + ExecInitExtraTupleSlot(estate, + RelationGetDescr(rel), + &TTSOpsBufferHeapTuple); + + MemoryContextSwitchTo(oldcontext); + } + + return relInfo->ri_TrigOldSlot; +} + +/* + * Return a relInfo's tuple slot for a trigger's NEW tuples. + */ +TupleTableSlot * +ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo) +{ + if (relInfo->ri_TrigNewSlot == NULL) + { + Relation rel = relInfo->ri_RelationDesc; + MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + + relInfo->ri_TrigNewSlot = + ExecInitExtraTupleSlot(estate, + RelationGetDescr(rel), + &TTSOpsBufferHeapTuple); + + MemoryContextSwitchTo(oldcontext); + } + + return relInfo->ri_TrigNewSlot; +} + +/* + * Return a relInfo's tuple slot for processing returning tuples. + */ +TupleTableSlot * +ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo) +{ + if (relInfo->ri_ReturningSlot == NULL) + { + Relation rel = relInfo->ri_RelationDesc; + MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt); + + relInfo->ri_ReturningSlot = + ExecInitExtraTupleSlot(estate, + RelationGetDescr(rel), + &TTSOpsBufferHeapTuple); + + MemoryContextSwitchTo(oldcontext); + } + + return relInfo->ri_ReturningSlot; +} diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index fe62da06ea..76175aaa6b 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -255,7 +255,6 @@ ExecInsert(ModifyTableState *mtstate, EState *estate, bool canSetTag) { - HeapTuple tuple; ResultRelInfo *resultRelInfo; Relation resultRelationDesc; List *recheckIndexes = NIL; @@ -264,11 +263,7 @@ ExecInsert(ModifyTableState *mtstate, ModifyTable *node = (ModifyTable *) mtstate->ps.plan; OnConflictAction onconflict = node->onConflictAction; - /* - * get the heap tuple out of the tuple table slot, making sure we have a - * writable copy - */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); + ExecMaterializeSlot(slot); /* * get information on the (current) result relation @@ -288,26 +283,16 @@ ExecInsert(ModifyTableState *mtstate, if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_before_row) { - slot = ExecBRInsertTriggers(estate, resultRelInfo, slot); - - if (slot == NULL) /* "do nothing" */ - return NULL; - - /* trigger might have changed tuple */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); + if (!ExecBRInsertTriggers(estate, resultRelInfo, slot)) + return NULL; /* "do nothing" */ } /* INSTEAD OF ROW INSERT Triggers */ if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_insert_instead_row) { - slot = ExecIRInsertTriggers(estate, resultRelInfo, slot); - - if (slot == NULL) /* "do nothing" */ - return NULL; - - /* trigger might have changed tuple */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); + if (!ExecIRInsertTriggers(estate, resultRelInfo, slot)) + return NULL; /* "do nothing" */ } else if (resultRelInfo->ri_FdwRoutine) { @@ -322,9 +307,6 @@ ExecInsert(ModifyTableState *mtstate, if (slot == NULL) /* "do nothing" */ return NULL; - /* FDW might have changed tuple */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); - /* * AFTER ROW Triggers or RETURNING expressions might reference the * tableoid column, so (re-)initialize tts_tableOid before evaluating @@ -336,6 +318,7 @@ ExecInsert(ModifyTableState *mtstate, else { WCOKind wco_kind; + HeapTuple inserttuple; /* * Constraints might reference the tableoid column, so (re-)initialize @@ -441,6 +424,8 @@ ExecInsert(ModifyTableState *mtstate, } } + inserttuple = ExecFetchSlotHeapTuple(slot, true, NULL); + /* * Before we start insertion proper, acquire our "speculative * insertion lock". Others can use that to wait for us to decide @@ -448,26 +433,26 @@ ExecInsert(ModifyTableState *mtstate, * waiting for the whole transaction to complete. */ specToken = SpeculativeInsertionLockAcquire(GetCurrentTransactionId()); - HeapTupleHeaderSetSpeculativeToken(tuple->t_data, specToken); + HeapTupleHeaderSetSpeculativeToken(inserttuple->t_data, specToken); /* insert the tuple, with the speculative token */ - heap_insert(resultRelationDesc, tuple, + heap_insert(resultRelationDesc, inserttuple, estate->es_output_cid, HEAP_INSERT_SPECULATIVE, NULL); slot->tts_tableOid = RelationGetRelid(resultRelationDesc); - ItemPointerCopy(&tuple->t_self, &slot->tts_tid); + ItemPointerCopy(&inserttuple->t_self, &slot->tts_tid); /* insert index entries for tuple */ - recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), + recheckIndexes = ExecInsertIndexTuples(slot, &(inserttuple->t_self), estate, true, &specConflict, arbiterIndexes); /* adjust the tuple's state accordingly */ if (!specConflict) - heap_finish_speculative(resultRelationDesc, tuple); + heap_finish_speculative(resultRelationDesc, inserttuple); else - heap_abort_speculative(resultRelationDesc, tuple); + heap_abort_speculative(resultRelationDesc, inserttuple); /* * Wake up anyone waiting for our decision. They will re-check @@ -499,15 +484,16 @@ ExecInsert(ModifyTableState *mtstate, * Note: heap_insert returns the tid (location) of the new tuple * in the t_self field. */ - heap_insert(resultRelationDesc, tuple, + inserttuple = ExecFetchSlotHeapTuple(slot, true, NULL); + heap_insert(resultRelationDesc, inserttuple, estate->es_output_cid, 0, NULL); slot->tts_tableOid = RelationGetRelid(resultRelationDesc); - ItemPointerCopy(&tuple->t_self, &slot->tts_tid); + ItemPointerCopy(&inserttuple->t_self, &slot->tts_tid); /* insert index entries for tuple */ if (resultRelInfo->ri_NumIndices > 0) - recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), + recheckIndexes = ExecInsertIndexTuples(slot, &(inserttuple->t_self), estate, false, NULL, NIL); } @@ -531,7 +517,7 @@ ExecInsert(ModifyTableState *mtstate, { ExecARUpdateTriggers(estate, resultRelInfo, NULL, NULL, - tuple, + slot, NULL, mtstate->mt_transition_capture); @@ -543,7 +529,7 @@ ExecInsert(ModifyTableState *mtstate, } /* AFTER ROW INSERT Triggers */ - ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes, + ExecARInsertTriggers(estate, resultRelInfo, slot, recheckIndexes, ar_insert_trig_tcs); list_free(recheckIndexes); @@ -603,7 +589,7 @@ ExecDelete(ModifyTableState *mtstate, bool canSetTag, bool changingPart, bool *tupleDeleted, - TupleTableSlot **epqslot) + TupleTableSlot **epqreturnslot) { ResultRelInfo *resultRelInfo; Relation resultRelationDesc; @@ -628,7 +614,7 @@ ExecDelete(ModifyTableState *mtstate, bool dodelete; dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo, - tupleid, oldtuple, epqslot); + tupleid, oldtuple, epqreturnslot); if (!dodelete) /* "do nothing" */ return NULL; @@ -651,14 +637,10 @@ ExecDelete(ModifyTableState *mtstate, /* * delete from foreign table: let the FDW do it * - * We offer the trigger tuple slot as a place to store RETURNING data, - * although the FDW can return some other slot if it wants. Set up - * the slot's tupdesc so the FDW doesn't need to do that for itself. + * We offer the returning slot as a place to store RETURNING data, + * although the FDW can return some other slot if it wants. */ - slot = estate->es_trig_tuple_slot; - if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc)) - ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc)); - + slot = ExecGetReturningSlot(estate, resultRelInfo); slot = resultRelInfo->ri_FdwRoutine->ExecForeignDelete(estate, resultRelInfo, slot, @@ -673,6 +655,8 @@ ExecDelete(ModifyTableState *mtstate, */ if (TTS_EMPTY(slot)) ExecStoreAllNullTuple(slot); + ExecMaterializeSlot(slot); + slot->tts_tableOid = RelationGetRelid(resultRelationDesc); } else @@ -762,9 +746,9 @@ ldelete:; * If requested, skip delete and pass back the updated * row. */ - if (epqslot) + if (epqreturnslot) { - *epqslot = my_epqslot; + *epqreturnslot = my_epqslot; return NULL; } else @@ -832,34 +816,37 @@ ldelete:; * gotta fetch it. We can use the trigger tuple slot. */ TupleTableSlot *rslot; - HeapTupleData deltuple; - Buffer delbuffer; if (resultRelInfo->ri_FdwRoutine) { /* FDW must have provided a slot containing the deleted row */ Assert(!TupIsNull(slot)); - delbuffer = InvalidBuffer; } else { - slot = estate->es_trig_tuple_slot; + slot = ExecGetReturningSlot(estate, resultRelInfo); if (oldtuple != NULL) { - deltuple = *oldtuple; - delbuffer = InvalidBuffer; + ExecForceStoreHeapTuple(oldtuple, slot); } else { - deltuple.t_self = *tupleid; - if (!heap_fetch(resultRelationDesc, SnapshotAny, - &deltuple, &delbuffer, false, NULL)) - elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); - } + BufferHeapTupleTableSlot *bslot; + HeapTuple deltuple; + Buffer buffer; - if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc)) - ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc)); - ExecStoreHeapTuple(&deltuple, slot, false); + Assert(TTS_IS_BUFFERTUPLE(slot)); + ExecClearTuple(slot); + bslot = (BufferHeapTupleTableSlot *) slot; + deltuple = &bslot->base.tupdata; + + deltuple->t_self = *tupleid; + if (!heap_fetch(resultRelationDesc, SnapshotAny, + deltuple, &buffer, false, NULL)) + elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); + + ExecStorePinnedBufferHeapTuple(deltuple, slot, buffer); + } } rslot = ExecProcessReturning(resultRelInfo, slot, planSlot); @@ -871,8 +858,6 @@ ldelete:; ExecMaterializeSlot(rslot); ExecClearTuple(slot); - if (BufferIsValid(delbuffer)) - ReleaseBuffer(delbuffer); return rslot; } @@ -912,7 +897,7 @@ ExecUpdate(ModifyTableState *mtstate, EState *estate, bool canSetTag) { - HeapTuple tuple; + HeapTuple updatetuple; ResultRelInfo *resultRelInfo; Relation resultRelationDesc; HTSU_Result result; @@ -926,11 +911,7 @@ ExecUpdate(ModifyTableState *mtstate, if (IsBootstrapProcessingMode()) elog(ERROR, "cannot UPDATE during bootstrap"); - /* - * get the heap tuple out of the tuple table slot, making sure we have a - * writable copy - */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); + ExecMaterializeSlot(slot); /* * get information on the (current) result relation @@ -942,28 +923,18 @@ ExecUpdate(ModifyTableState *mtstate, if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_update_before_row) { - slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, - tupleid, oldtuple, slot); - - if (slot == NULL) /* "do nothing" */ - return NULL; - - /* trigger might have changed tuple */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); + if (!ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, + tupleid, oldtuple, slot)) + return NULL; /* "do nothing" */ } /* INSTEAD OF ROW UPDATE Triggers */ if (resultRelInfo->ri_TrigDesc && resultRelInfo->ri_TrigDesc->trig_update_instead_row) { - slot = ExecIRUpdateTriggers(estate, resultRelInfo, - oldtuple, slot); - - if (slot == NULL) /* "do nothing" */ - return NULL; - - /* trigger might have changed tuple */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); + if (!ExecIRUpdateTriggers(estate, resultRelInfo, + oldtuple, slot)) + return NULL; /* "do nothing" */ } else if (resultRelInfo->ri_FdwRoutine) { @@ -978,9 +949,6 @@ ExecUpdate(ModifyTableState *mtstate, if (slot == NULL) /* "do nothing" */ return NULL; - /* FDW might have changed tuple */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); - /* * AFTER ROW Triggers or RETURNING expressions might reference the * tableoid column, so (re-)initialize tts_tableOid before evaluating @@ -1107,7 +1075,6 @@ lreplace:; else { slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); goto lreplace; } } @@ -1178,12 +1145,14 @@ lreplace:; * needed for referential integrity updates in transaction-snapshot * mode transactions. */ - result = heap_update(resultRelationDesc, tupleid, tuple, + updatetuple = ExecFetchSlotHeapTuple(slot, true, NULL); + result = heap_update(resultRelationDesc, tupleid, + updatetuple, estate->es_output_cid, estate->es_crosscheck_snapshot, true /* wait for commit */ , &hufd, &lockmode); - ItemPointerCopy(&tuple->t_self, &slot->tts_tid); + ItemPointerCopy(&updatetuple->t_self, &slot->tts_tid); switch (result) { @@ -1249,7 +1218,6 @@ lreplace:; { *tupleid = hufd.ctid; slot = ExecFilterJunk(resultRelInfo->ri_junkFilter, epqslot); - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); goto lreplace; } } @@ -1277,8 +1245,8 @@ lreplace:; * * If it's a HOT update, we mustn't insert new index entries. */ - if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(tuple)) - recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), + if (resultRelInfo->ri_NumIndices > 0 && !HeapTupleIsHeapOnly(updatetuple)) + recheckIndexes = ExecInsertIndexTuples(slot, &(updatetuple->t_self), estate, false, NULL, NIL); } @@ -1286,7 +1254,7 @@ lreplace:; (estate->es_processed)++; /* AFTER ROW UPDATE Triggers */ - ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, tuple, + ExecARUpdateTriggers(estate, resultRelInfo, tupleid, oldtuple, slot, recheckIndexes, mtstate->operation == CMD_INSERT ? mtstate->mt_oc_transition_capture : @@ -1669,7 +1637,6 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate, ModifyTable *node; ResultRelInfo *partrel; PartitionRoutingInfo *partrouteinfo; - HeapTuple tuple; TupleConversionMap *map; /* @@ -1688,9 +1655,6 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate, */ estate->es_result_relation_info = partrel; - /* Get the heap tuple out of the given slot. */ - tuple = ExecFetchSlotHeapTuple(slot, true, NULL); - /* * If we're capturing transition tuples, we might need to convert from the * partition rowtype to root partitioned table's rowtype. @@ -1714,7 +1678,7 @@ ExecPrepareTupleRouting(ModifyTableState *mtstate, * Otherwise, just remember the original unconverted tuple, to * avoid a needless round trip conversion. */ - mtstate->mt_transition_capture->tcs_original_insert_tuple = tuple; + mtstate->mt_transition_capture->tcs_original_insert_tuple = slot; mtstate->mt_transition_capture->tcs_map = NULL; } } @@ -2541,16 +2505,6 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) } } - /* - * Set up a tuple table slot for use for trigger output tuples. In a plan - * containing multiple ModifyTable nodes, all can share one such slot, so - * we keep it in the estate. The tuple being inserted doesn't come from a - * buffer. - */ - if (estate->es_trig_tuple_slot == NULL) - estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL, - &TTSOpsHeapTuple); - /* * Lastly, if this is not the primary (canSetTag) ModifyTable node, add it * to estate->es_auxmodifytables so that it will be run to completion by diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index f9516515bc..27934cb4a9 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -196,11 +196,6 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel) estate->es_output_cid = GetCurrentCommandId(true); - /* Triggers might need a slot */ - if (resultRelInfo->ri_TrigDesc) - estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL, - &TTSOpsVirtual); - /* Prepare to catch AFTER triggers. */ AfterTriggerBeginQuery(); diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index e1aa3d0044..5b4579fe83 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -192,7 +192,7 @@ static int ri_constraint_cache_valid_count = 0; * ---------- */ static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, - HeapTuple old_row, + TupleTableSlot *oldslot, const RI_ConstraintInfo *riinfo); static Datum ri_restrict(TriggerData *trigdata, bool is_no_action); static Datum ri_setnull(TriggerData *trigdata); @@ -205,12 +205,12 @@ static void ri_GenerateQual(StringInfo buf, Oid opoid, const char *rightop, Oid rightoptype); static void ri_GenerateQualCollation(StringInfo buf, Oid collation); -static int ri_NullCheck(TupleDesc tupdesc, HeapTuple tup, +static int ri_NullCheck(TupleDesc tupdesc, TupleTableSlot *slot, const RI_ConstraintInfo *riinfo, bool rel_is_pk); static void ri_BuildQueryKey(RI_QueryKey *key, const RI_ConstraintInfo *riinfo, int32 constr_queryno); -static bool ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, +static bool ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot, const RI_ConstraintInfo *riinfo, bool rel_is_pk); static bool ri_AttributesEqual(Oid eq_opr, Oid typeid, Datum oldvalue, Datum newvalue); @@ -232,14 +232,14 @@ static SPIPlanPtr ri_PlanCheck(const char *querystr, int nargs, Oid *argtypes, static bool ri_PerformCheck(const RI_ConstraintInfo *riinfo, RI_QueryKey *qkey, SPIPlanPtr qplan, Relation fk_rel, Relation pk_rel, - HeapTuple old_tuple, HeapTuple new_tuple, + TupleTableSlot *oldslot, TupleTableSlot *newslot, bool detectNewRows, int expect_OK); -static void ri_ExtractValues(Relation rel, HeapTuple tup, +static void ri_ExtractValues(Relation rel, TupleTableSlot *slot, const RI_ConstraintInfo *riinfo, bool rel_is_pk, Datum *vals, char *nulls); static void ri_ReportViolation(const RI_ConstraintInfo *riinfo, Relation pk_rel, Relation fk_rel, - HeapTuple violator, TupleDesc tupdesc, + TupleTableSlot *violator, TupleDesc tupdesc, int queryno) pg_attribute_noreturn(); @@ -255,8 +255,7 @@ RI_FKey_check(TriggerData *trigdata) const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; - HeapTuple new_row; - Buffer new_row_buf; + TupleTableSlot *newslot; RI_QueryKey qkey; SPIPlanPtr qplan; int i; @@ -268,15 +267,9 @@ RI_FKey_check(TriggerData *trigdata) trigdata->tg_relation, false); if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event)) - { - new_row = trigdata->tg_newtuple; - new_row_buf = trigdata->tg_newtuplebuf; - } + newslot = trigdata->tg_newslot; else - { - new_row = trigdata->tg_trigtuple; - new_row_buf = trigdata->tg_trigtuplebuf; - } + newslot = trigdata->tg_trigslot; /* * We should not even consider checking the row if it is no longer valid, @@ -285,14 +278,26 @@ RI_FKey_check(TriggerData *trigdata) * checked). Test its liveness according to SnapshotSelf. We need pin * and lock on the buffer to call HeapTupleSatisfiesVisibility. Caller * should be holding pin, but not lock. + * + * XXX: Note that the buffer-tuple specificity will be removed in the near + * future. */ - LockBuffer(new_row_buf, BUFFER_LOCK_SHARE); - if (!HeapTupleSatisfiesVisibility(new_row, SnapshotSelf, new_row_buf)) + if (TTS_IS_BUFFERTUPLE(newslot)) { - LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK); - return PointerGetDatum(NULL); + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) newslot; + + Assert(BufferIsValid(bslot->buffer)); + + LockBuffer(bslot->buffer, BUFFER_LOCK_SHARE); + if (!HeapTupleSatisfiesVisibility(bslot->base.tuple, SnapshotSelf, bslot->buffer)) + { + LockBuffer(bslot->buffer, BUFFER_LOCK_UNLOCK); + return PointerGetDatum(NULL); + } + LockBuffer(bslot->buffer, BUFFER_LOCK_UNLOCK); } - LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK); + else + elog(ERROR, "expected buffer tuple"); /* * Get the relation descriptors of the FK and PK tables. @@ -308,7 +313,7 @@ RI_FKey_check(TriggerData *trigdata) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); - switch (ri_NullCheck(RelationGetDescr(fk_rel), new_row, riinfo, false)) + switch (ri_NullCheck(RelationGetDescr(fk_rel), newslot, riinfo, false)) { case RI_KEYS_ALL_NULL: @@ -438,7 +443,7 @@ RI_FKey_check(TriggerData *trigdata) */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, - NULL, new_row, + NULL, newslot, false, SPI_OK_SELECT); @@ -506,7 +511,7 @@ RI_FKey_check_upd(PG_FUNCTION_ARGS) */ static bool ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, - HeapTuple old_row, + TupleTableSlot *oldslot, const RI_ConstraintInfo *riinfo) { SPIPlanPtr qplan; @@ -515,7 +520,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, bool result; /* Only called for non-null rows */ - Assert(ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true) == RI_KEYS_NONE_NULL); + Assert(ri_NullCheck(RelationGetDescr(pk_rel), oldslot, riinfo, true) == RI_KEYS_NONE_NULL); if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); @@ -573,7 +578,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, */ result = ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, - old_row, NULL, + oldslot, NULL, true, /* treat like update */ SPI_OK_SELECT); @@ -691,7 +696,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action) const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; - HeapTuple old_row; + TupleTableSlot *old_slot; RI_QueryKey qkey; SPIPlanPtr qplan; @@ -709,7 +714,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action) */ fk_rel = table_open(riinfo->fk_relid, RowShareLock); pk_rel = trigdata->tg_relation; - old_row = trigdata->tg_trigtuple; + old_slot = trigdata->tg_trigslot; switch (riinfo->confmatchtype) { @@ -733,7 +738,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action) * allow another row to be substituted. */ if (is_no_action && - ri_Check_Pk_Match(pk_rel, fk_rel, old_row, riinfo)) + ri_Check_Pk_Match(pk_rel, fk_rel, old_slot, riinfo)) { table_close(fk_rel, RowShareLock); return PointerGetDatum(NULL); @@ -801,7 +806,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action) */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, - old_row, NULL, + old_slot, NULL, true, /* must detect new rows */ SPI_OK_SELECT); @@ -845,7 +850,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; - HeapTuple old_row; + TupleTableSlot *old_slot; RI_QueryKey qkey; SPIPlanPtr qplan; int i; @@ -869,7 +874,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) */ fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; - old_row = trigdata->tg_trigtuple; + old_slot = trigdata->tg_trigslot; switch (riinfo->confmatchtype) { @@ -941,7 +946,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, - old_row, NULL, + old_slot, NULL, true, /* must detect new rows */ SPI_OK_DELETE); @@ -985,8 +990,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; - HeapTuple new_row; - HeapTuple old_row; + TupleTableSlot *new_slot; + TupleTableSlot *old_slot; RI_QueryKey qkey; SPIPlanPtr qplan; int i; @@ -1012,8 +1017,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) */ fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; - new_row = trigdata->tg_newtuple; - old_row = trigdata->tg_trigtuple; + new_slot = trigdata->tg_newslot; + old_slot = trigdata->tg_trigslot; switch (riinfo->confmatchtype) { @@ -1097,7 +1102,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, - old_row, new_row, + old_slot, new_slot, true, /* must detect new rows */ SPI_OK_UPDATE); @@ -1180,7 +1185,7 @@ ri_setnull(TriggerData *trigdata) const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; - HeapTuple old_row; + TupleTableSlot *old_slot; RI_QueryKey qkey; SPIPlanPtr qplan; int i; @@ -1199,7 +1204,7 @@ ri_setnull(TriggerData *trigdata) */ fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; - old_row = trigdata->tg_trigtuple; + old_slot = trigdata->tg_trigslot; switch (riinfo->confmatchtype) { @@ -1284,7 +1289,7 @@ ri_setnull(TriggerData *trigdata) */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, - old_row, NULL, + old_slot, NULL, true, /* must detect new rows */ SPI_OK_UPDATE); @@ -1367,7 +1372,7 @@ ri_setdefault(TriggerData *trigdata) const RI_ConstraintInfo *riinfo; Relation fk_rel; Relation pk_rel; - HeapTuple old_row; + TupleTableSlot *old_slot; RI_QueryKey qkey; SPIPlanPtr qplan; @@ -1385,7 +1390,7 @@ ri_setdefault(TriggerData *trigdata) */ fk_rel = table_open(riinfo->fk_relid, RowExclusiveLock); pk_rel = trigdata->tg_relation; - old_row = trigdata->tg_trigtuple; + old_slot = trigdata->tg_trigslot; switch (riinfo->confmatchtype) { @@ -1471,7 +1476,7 @@ ri_setdefault(TriggerData *trigdata) */ ri_PerformCheck(riinfo, &qkey, qplan, fk_rel, pk_rel, - old_row, NULL, + old_slot, NULL, true, /* must detect new rows */ SPI_OK_UPDATE); @@ -1530,7 +1535,7 @@ ri_setdefault(TriggerData *trigdata) */ bool RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel, - HeapTuple old_row, HeapTuple new_row) + TupleTableSlot *old_slot, TupleTableSlot *new_slot) { const RI_ConstraintInfo *riinfo; @@ -1548,11 +1553,11 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel, * If any old key value is NULL, the row could not have been * referenced by an FK row, so no check is needed. */ - if (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true) != RI_KEYS_NONE_NULL) + if (ri_NullCheck(RelationGetDescr(pk_rel), old_slot, riinfo, true) != RI_KEYS_NONE_NULL) return false; /* If all old and new key values are equal, no check is needed */ - if (new_row && ri_KeysEqual(pk_rel, old_row, new_row, riinfo, true)) + if (new_slot && ri_KeysEqual(pk_rel, old_slot, new_slot, riinfo, true)) return false; /* Else we need to fire the trigger. */ @@ -1587,9 +1592,12 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel, */ bool RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, - HeapTuple old_row, HeapTuple new_row) + TupleTableSlot *old_slot, TupleTableSlot *new_slot) { const RI_ConstraintInfo *riinfo; + Datum xminDatum; + TransactionId xmin; + bool isnull; /* * Get arguments. @@ -1604,7 +1612,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, * If any new key value is NULL, the row must satisfy the * constraint, so no check is needed. */ - if (ri_NullCheck(RelationGetDescr(fk_rel), new_row, riinfo, false) != RI_KEYS_NONE_NULL) + if (ri_NullCheck(RelationGetDescr(fk_rel), new_slot, riinfo, false) != RI_KEYS_NONE_NULL) return false; /* @@ -1615,11 +1623,14 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, * UPDATE check. (We could skip this if we knew the INSERT * trigger already fired, but there is no easy way to know that.) */ - if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(old_row->t_data))) + xminDatum = slot_getsysattr(old_slot, MinTransactionIdAttributeNumber, &isnull); + Assert(!isnull); + xmin = DatumGetTransactionId(xminDatum); + if (TransactionIdIsCurrentTransactionId(xmin)) return true; /* If all old and new key values are equal, no check is needed */ - if (ri_KeysEqual(fk_rel, old_row, new_row, riinfo, false)) + if (ri_KeysEqual(fk_rel, old_slot, new_slot, riinfo, false)) return false; /* Else we need to fire the trigger. */ @@ -1635,7 +1646,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, * invalidated before the constraint is to be checked, but we * should queue the event to apply the check later. */ - switch (ri_NullCheck(RelationGetDescr(fk_rel), new_row, riinfo, false)) + switch (ri_NullCheck(RelationGetDescr(fk_rel), new_slot, riinfo, false)) { case RI_KEYS_ALL_NULL: return false; @@ -1653,11 +1664,14 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, * UPDATE check. (We could skip this if we knew the INSERT * trigger already fired, but there is no easy way to know that.) */ - if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(old_row->t_data))) + xminDatum = slot_getsysattr(old_slot, MinTransactionIdAttributeNumber, &isnull); + Assert(!isnull); + xmin = DatumGetTransactionId(xminDatum); + if (TransactionIdIsCurrentTransactionId(xmin)) return true; /* If all old and new key values are equal, no check is needed */ - if (ri_KeysEqual(fk_rel, old_row, new_row, riinfo, false)) + if (ri_KeysEqual(fk_rel, old_slot, new_slot, riinfo, false)) return false; /* Else we need to fire the trigger. */ @@ -1911,10 +1925,17 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) /* Did we find a tuple violating the constraint? */ if (SPI_processed > 0) { + TupleTableSlot *slot; HeapTuple tuple = SPI_tuptable->vals[0]; TupleDesc tupdesc = SPI_tuptable->tupdesc; RI_ConstraintInfo fake_riinfo; + slot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsVirtual); + + heap_deform_tuple(tuple, tupdesc, + slot->tts_values, slot->tts_isnull); + ExecStoreVirtualTuple(slot); + /* * The columns to look at in the result tuple are 1..N, not whatever * they are in the fk_rel. Hack up riinfo so that the subroutines @@ -1934,7 +1955,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) * disallows partially-null FK rows. */ if (fake_riinfo.confmatchtype == FKCONSTR_MATCH_FULL && - ri_NullCheck(tupdesc, tuple, &fake_riinfo, false) != RI_KEYS_NONE_NULL) + ri_NullCheck(tupdesc, slot, &fake_riinfo, false) != RI_KEYS_NONE_NULL) ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", @@ -1951,8 +1972,10 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) */ ri_ReportViolation(&fake_riinfo, pk_rel, fk_rel, - tuple, tupdesc, + slot, tupdesc, RI_PLAN_CHECK_LOOKUPPK); + + ExecDropSingleTupleTableSlot(slot); } if (SPI_finish() != SPI_OK_FINISH) @@ -2355,7 +2378,7 @@ static bool ri_PerformCheck(const RI_ConstraintInfo *riinfo, RI_QueryKey *qkey, SPIPlanPtr qplan, Relation fk_rel, Relation pk_rel, - HeapTuple old_tuple, HeapTuple new_tuple, + TupleTableSlot *old_slot, TupleTableSlot *new_slot, bool detectNewRows, int expect_OK) { Relation query_rel, @@ -2398,17 +2421,17 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo, } /* Extract the parameters to be passed into the query */ - if (new_tuple) + if (new_slot) { - ri_ExtractValues(source_rel, new_tuple, riinfo, source_is_pk, + ri_ExtractValues(source_rel, new_slot, riinfo, source_is_pk, vals, nulls); - if (old_tuple) - ri_ExtractValues(source_rel, old_tuple, riinfo, source_is_pk, + if (old_slot) + ri_ExtractValues(source_rel, old_slot, riinfo, source_is_pk, vals + riinfo->nkeys, nulls + riinfo->nkeys); } else { - ri_ExtractValues(source_rel, old_tuple, riinfo, source_is_pk, + ri_ExtractValues(source_rel, old_slot, riinfo, source_is_pk, vals, nulls); } @@ -2478,7 +2501,7 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo, (SPI_processed == 0) == (qkey->constr_queryno == RI_PLAN_CHECK_LOOKUPPK)) ri_ReportViolation(riinfo, pk_rel, fk_rel, - new_tuple ? new_tuple : old_tuple, + new_slot ? new_slot : old_slot, NULL, qkey->constr_queryno); @@ -2489,11 +2512,10 @@ ri_PerformCheck(const RI_ConstraintInfo *riinfo, * Extract fields from a tuple into Datum/nulls arrays */ static void -ri_ExtractValues(Relation rel, HeapTuple tup, +ri_ExtractValues(Relation rel, TupleTableSlot *slot, const RI_ConstraintInfo *riinfo, bool rel_is_pk, Datum *vals, char *nulls) { - TupleDesc tupdesc = rel->rd_att; const int16 *attnums; int i; bool isnull; @@ -2505,8 +2527,7 @@ ri_ExtractValues(Relation rel, HeapTuple tup, for (i = 0; i < riinfo->nkeys; i++) { - vals[i] = heap_getattr(tup, attnums[i], tupdesc, - &isnull); + vals[i] = slot_getattr(slot, attnums[i], &isnull); nulls[i] = isnull ? 'n' : ' '; } } @@ -2523,7 +2544,7 @@ ri_ExtractValues(Relation rel, HeapTuple tup, static void ri_ReportViolation(const RI_ConstraintInfo *riinfo, Relation pk_rel, Relation fk_rel, - HeapTuple violator, TupleDesc tupdesc, + TupleTableSlot *violatorslot, TupleDesc tupdesc, int queryno) { StringInfoData key_names; @@ -2598,12 +2619,24 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo, for (idx = 0; idx < riinfo->nkeys; idx++) { int fnum = attnums[idx]; + Form_pg_attribute att = TupleDescAttr(tupdesc, fnum - 1); char *name, *val; + Datum datum; + bool isnull; - name = SPI_fname(tupdesc, fnum); - val = SPI_getvalue(violator, tupdesc, fnum); - if (!val) + name = NameStr(att->attname); + + datum = slot_getattr(violatorslot, fnum, &isnull); + if (!isnull) + { + Oid foutoid; + bool typisvarlena; + + getTypeOutputInfo(att->atttypid, &foutoid, &typisvarlena); + val = OidOutputFunctionCall(foutoid, datum); + } + else val = "null"; if (idx > 0) @@ -2656,7 +2689,7 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo, */ static int ri_NullCheck(TupleDesc tupDesc, - HeapTuple tup, + TupleTableSlot *slot, const RI_ConstraintInfo *riinfo, bool rel_is_pk) { const int16 *attnums; @@ -2671,7 +2704,7 @@ ri_NullCheck(TupleDesc tupDesc, for (i = 0; i < riinfo->nkeys; i++) { - if (heap_attisnull(tup, attnums[i], tupDesc)) + if (slot_attisnull(slot, attnums[i])) nonenull = false; else allnull = false; @@ -2822,10 +2855,9 @@ ri_HashPreparedPlan(RI_QueryKey *key, SPIPlanPtr plan) * ---------- */ static bool -ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, +ri_KeysEqual(Relation rel, TupleTableSlot *oldslot, TupleTableSlot *newslot, const RI_ConstraintInfo *riinfo, bool rel_is_pk) { - TupleDesc tupdesc = RelationGetDescr(rel); const int16 *attnums; const Oid *eq_oprs; int i; @@ -2841,6 +2873,7 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, eq_oprs = riinfo->ff_eq_oprs; } + /* XXX: could be worthwhile to fetch all necessary attrs at once */ for (i = 0; i < riinfo->nkeys; i++) { Datum oldvalue; @@ -2850,14 +2883,14 @@ ri_KeysEqual(Relation rel, HeapTuple oldtup, HeapTuple newtup, /* * Get one attribute's oldvalue. If it is NULL - they're not equal. */ - oldvalue = heap_getattr(oldtup, attnums[i], tupdesc, &isnull); + oldvalue = slot_getattr(oldslot, attnums[i], &isnull); if (isnull) return false; /* * Get one attribute's newvalue. If it is NULL - they're not equal. */ - newvalue = heap_getattr(newtup, attnums[i], tupdesc, &isnull); + newvalue = slot_getattr(newslot, attnums[i], &isnull); if (isnull) return false; diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 9f212ac24b..846679ecc1 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -35,8 +35,8 @@ typedef struct TriggerData HeapTuple tg_trigtuple; HeapTuple tg_newtuple; Trigger *tg_trigger; - Buffer tg_trigtuplebuf; - Buffer tg_newtuplebuf; + TupleTableSlot *tg_trigslot; + TupleTableSlot *tg_newslot; Tuplestorestate *tg_oldtable; Tuplestorestate *tg_newtable; } TriggerData; @@ -77,9 +77,9 @@ typedef struct TransitionCaptureState * format to parent format after they have already been converted in the * opposite direction during routing. In that case we bypass conversion * and allow the inserting code (copy.c and nodeModifyTable.c) to provide - * the original tuple directly. + * a slot containing the original tuple directly. */ - HeapTuple tcs_original_insert_tuple; + TupleTableSlot *tcs_original_insert_tuple; /* * Private data including the tuplestore(s) into which to insert tuples. @@ -186,15 +186,15 @@ extern void ExecBSInsertTriggers(EState *estate, extern void ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo, TransitionCaptureState *transition_capture); -extern TupleTableSlot *ExecBRInsertTriggers(EState *estate, +extern bool ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TupleTableSlot *slot); extern void ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo, - HeapTuple trigtuple, + TupleTableSlot *slot, List *recheckIndexes, TransitionCaptureState *transition_capture); -extern TupleTableSlot *ExecIRInsertTriggers(EState *estate, +extern bool ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo, TupleTableSlot *slot); extern void ExecBSDeleteTriggers(EState *estate, @@ -221,7 +221,7 @@ extern void ExecBSUpdateTriggers(EState *estate, extern void ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo, TransitionCaptureState *transition_capture); -extern TupleTableSlot *ExecBRUpdateTriggers(EState *estate, +extern bool ExecBRUpdateTriggers(EState *estate, EPQState *epqstate, ResultRelInfo *relinfo, ItemPointer tupleid, @@ -231,10 +231,10 @@ extern void ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo, ItemPointer tupleid, HeapTuple fdw_trigtuple, - HeapTuple newtuple, + TupleTableSlot *slot, List *recheckIndexes, TransitionCaptureState *transition_capture); -extern TupleTableSlot *ExecIRUpdateTriggers(EState *estate, +extern bool ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo, HeapTuple trigtuple, TupleTableSlot *slot); @@ -258,9 +258,9 @@ extern bool AfterTriggerPendingOnRel(Oid relid); * in utils/adt/ri_triggers.c */ extern bool RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel, - HeapTuple old_row, HeapTuple new_row); + TupleTableSlot *old_slot, TupleTableSlot *new_slot); extern bool RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, - HeapTuple old_row, HeapTuple new_row); + TupleTableSlot *old_slot, TupleTableSlot *new_slot); extern bool RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 7a63e9a7cc..25b72c59e2 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -560,6 +560,10 @@ extern Datum GetAttributeByNum(HeapTupleHeader tuple, AttrNumber attrno, extern int ExecTargetListLength(List *targetlist); extern int ExecCleanTargetListLength(List *targetlist); +extern TupleTableSlot *ExecGetTriggerOldSlot(EState *estate, ResultRelInfo *relInfo); +extern TupleTableSlot *ExecGetTriggerNewSlot(EState *estate, ResultRelInfo *relInfo); +extern TupleTableSlot *ExecGetReturningSlot(EState *estate, ResultRelInfo *relInfo); + /* * prototypes from functions in execIndexing.c */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 3b789ee7cf..09f8217c80 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -427,6 +427,11 @@ typedef struct ResultRelInfo /* optional runtime measurements for triggers */ Instrumentation *ri_TrigInstrument; + /* On-demand created slots for triggers / returning processing */ + TupleTableSlot *ri_ReturningSlot; /* for trigger output tuples */ + TupleTableSlot *ri_TrigOldSlot; /* for a trigger's old tuple */ + TupleTableSlot *ri_TrigNewSlot; /* for a trigger's new tuple */ + /* FDW callback functions, if foreign table */ struct FdwRoutine *ri_FdwRoutine; @@ -524,9 +529,6 @@ typedef struct EState /* Stuff used for firing triggers: */ List *es_trig_target_relations; /* trigger-only ResultRelInfos */ - TupleTableSlot *es_trig_tuple_slot; /* for trigger output tuples */ - TupleTableSlot *es_trig_oldtup_slot; /* for TriggerEnabled */ - TupleTableSlot *es_trig_newtup_slot; /* for TriggerEnabled */ /* Parameter info: */ ParamListInfo es_param_list_info; /* values of external params */