From 732b8e82496e1a45fdd7ccbd1f87105dc0b22b95 Mon Sep 17 00:00:00 2001
From: Amit Khandekar <amit.khandekar@enterprisedb.com>
Date: Mon, 19 Nov 2018 14:25:30 +0530
Subject: [PATCH 2/2] Trigger tuple slotification.

Extracted the trigger-related changes from the pluggable storage patch
present in this repository :
https://github.com/anarazel/postgres-pluggable-storage.git

Extracted changes from this commit :

commit 6066202a97c9a2d540fd2a2e5fd2cad22f307cd2
Author: Andres Freund <andres@anarazel.de>
Date:   Tue Oct 2 22:15:23 2018 -0700

    Pluggable Storage.
---
 contrib/postgres_fdw/postgres_fdw.c      |   2 +-
 src/backend/commands/copy.c              |  23 +-
 src/backend/commands/tablecmds.c         |   8 +-
 src/backend/commands/trigger.c           | 559 +++++++++++++++----------------
 src/backend/executor/execMain.c          |   2 +-
 src/backend/executor/execReplication.c   |  17 +-
 src/backend/executor/execUtils.c         |  70 +++-
 src/backend/executor/nodeModifyTable.c   |  60 +---
 src/backend/replication/logical/worker.c |   5 -
 src/backend/utils/adt/ri_triggers.c      | 149 ++++----
 src/include/commands/trigger.h           |  20 +-
 src/include/executor/executor.h          |   4 +
 src/include/nodes/execnodes.h            |   2 +-
 13 files changed, 468 insertions(+), 453 deletions(-)

diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 6f9c6e1..472f0a5 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -3904,7 +3904,7 @@ apply_returning_filter(PgFdwDirectModifyState *dmstate,
 	/*
 	 * Use the trigger tuple slot as a place to store the result tuple.
 	 */
-	resultSlot = estate->es_trig_tuple_slot;
+	resultSlot = ExecTriggerGetReturnSlot(estate, dmstate->resultRel);
 	if (resultSlot->tts_tupleDescriptor != resultTupType)
 		ExecSetSlotDescriptor(resultSlot, resultTupType);
 
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 93badf7..2ecd6e7 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2490,9 +2490,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
@@ -2862,12 +2859,15 @@ 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;
+			if (!ExecBRInsertTriggers(estate, resultRelInfo, slot))
+				skip_tuple = true;	/* "do nothing" */
 			else				/* trigger might have changed tuple */
-				tuple = ExecFetchSlotHeapTuple(slot, true, NULL);
+			{
+				/* Similar to above, get a tuple copy in per-tuple context */
+				oldcontext = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
+				tuple = ExecCopySlotHeapTuple(slot);
+				MemoryContextSwitchTo(oldcontext);
+			}
 		}
 
 		if (!skip_tuple)
@@ -2968,7 +2968,7 @@ CopyFrom(CopyState cstate)
 															   NIL);
 
 					/* AFTER ROW INSERT Triggers */
-					ExecARInsertTriggers(estate, resultRelInfo, tuple,
+					ExecARInsertTriggers(estate, resultRelInfo, slot,
 										 recheckIndexes, cstate->transition_capture);
 
 					list_free(recheckIndexes);
@@ -3106,7 +3106,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);
 		}
@@ -3123,8 +3123,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 dfa15a5..2ed69ea 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8578,6 +8578,7 @@ validateForeignKeyConstraint(char *conname,
 {
 	HeapScanDesc scan;
 	HeapTuple	tuple;
+	TupleTableSlot *slot;
 	Trigger		trig;
 	Snapshot	snapshot;
 
@@ -8613,12 +8614,15 @@ validateForeignKeyConstraint(char *conname,
 	 */
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
 	scan = heap_beginscan(rel, snapshot, 0, NULL);
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(rel), &TTSOpsBufferHeapTuple);
 
 	while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
 	{
 		FunctionCallInfoData fcinfo;
 		TriggerData trigdata;
 
+		ExecStoreBufferHeapTuple(tuple, slot, scan->rs_cbuf);
+
 		/*
 		 * Make a call to the trigger function
 		 *
@@ -8633,10 +8637,9 @@ validateForeignKeyConstraint(char *conname,
 		trigdata.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW;
 		trigdata.tg_relation = rel;
 		trigdata.tg_trigtuple = tuple;
+		trigdata.tg_trigslot = slot;
 		trigdata.tg_newtuple = NULL;
 		trigdata.tg_trigger = &trig;
-		trigdata.tg_trigtuplebuf = scan->rs_cbuf;
-		trigdata.tg_newtuplebuf = InvalidBuffer;
 
 		fcinfo.context = (Node *) &trigdata;
 
@@ -8645,6 +8648,7 @@ validateForeignKeyConstraint(char *conname,
 
 	heap_endscan(scan);
 	UnregisterSnapshot(snapshot);
+	ExecDropSingleTupleTableSlot(slot);
 }
 
 static void
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index b91ebdb..9febe99 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -80,16 +80,18 @@ 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 **newSlot);
+				   TupleTableSlot *oldslot,
+				   TupleTableSlot *newslot,
+				   bool *is_epq);
 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,
@@ -97,7 +99,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);
@@ -2469,10 +2471,11 @@ 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];
@@ -2512,7 +2515,7 @@ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 							  false, NULL, NULL, NIL, NULL, transition_capture);
 }
 
-TupleTableSlot *
+bool
 ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 					 TupleTableSlot *slot)
 {
@@ -2529,10 +2532,13 @@ 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];
@@ -2543,52 +2549,37 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 								  TRIGGER_TYPE_INSERT))
 			continue;
 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
-							NULL, NULL, newtuple))
+							NULL, NULL, slot))
 			continue;
 
+		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" */
+			return false;
 		}
-	}
-
-	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 (newtuple != oldtuple)
+			ExecForceStoreHeapTuple(newtuple, slot);
 	}
 
 	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;
@@ -2596,12 +2587,12 @@ 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)
 {
@@ -2618,10 +2609,13 @@ 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];
@@ -2632,47 +2626,33 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 								  TRIGGER_TYPE_INSERT))
 			continue;
 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
-							NULL, NULL, newtuple))
+							NULL, NULL, slot))
 			continue;
 
+		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" */
+			return false;		/* "do nothing" */
+		}
+		if (oldtuple != newtuple)
+		{
+			ExecForceStoreHeapTuple(newtuple, LocTriggerData.tg_trigslot);
+			newtuple = ExecFetchSlotHeapTuple(slot, false, 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
@@ -2700,10 +2680,11 @@ 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];
@@ -2757,20 +2738,20 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 					 HeapTuple fdw_trigtuple,
 					 TupleTableSlot **epqslot)
 {
+	TupleTableSlot *slot = ExecTriggerGetOldSlot(estate, relinfo->ri_RelationDesc);
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 	bool		result = true;
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
-	HeapTuple	newtuple;
-	TupleTableSlot *newSlot;
 	int			i;
 
 	Assert(HeapTupleIsValid(fdw_trigtuple) ^ ItemPointerIsValid(tupleid));
 	if (fdw_trigtuple == NULL)
 	{
-		trigtuple = GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
-									   LockTupleExclusive, &newSlot);
-		if (trigtuple == NULL)
+		bool is_epqtuple;
+
+		if (!GetTupleForTrigger(estate, epqstate, relinfo, tupleid,
+								LockTupleExclusive, slot, NULL, &is_epqtuple))
 			return false;
 
 		/*
@@ -2778,27 +2759,36 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 		 * function requested for the updated tuple, skip the trigger
 		 * execution.
 		 */
-		if (newSlot != NULL && epqslot != NULL)
+		if (is_epqtuple && epqslot != NULL)
 		{
-			*epqslot = newSlot;
-			heap_freetuple(trigtuple);
+			*epqslot = slot;
 			return false;
 		}
+
+		trigtuple = ExecFetchSlotHeapTuple(slot, false, NULL);
+
 	}
 	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,
@@ -2807,11 +2797,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,
@@ -2823,10 +2813,10 @@ ExecBRDeleteTriggers(EState *estate, EPQState *epqstate,
 			result = false;		/* tell caller to suppress delete */
 			break;
 		}
-		if (newtuple != trigtuple)
+		if (false && newtuple != trigtuple)
 			heap_freetuple(newtuple);
 	}
-	if (trigtuple != fdw_trigtuple)
+	if (false && trigtuple != fdw_trigtuple)
 		heap_freetuple(trigtuple);
 
 	return result;
@@ -2839,28 +2829,31 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 					 TransitionCaptureState *transition_capture)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	TupleTableSlot *slot = ExecTriggerGetOldSlot(estate, relinfo->ri_RelationDesc);
 
 	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,
+							   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);
 	}
 }
 
@@ -2869,8 +2862,8 @@ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 					 HeapTuple trigtuple)
 {
 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+	TupleTableSlot *slot = ExecTriggerGetOldSlot(estate, relinfo->ri_RelationDesc);
 	TriggerData LocTriggerData;
-	HeapTuple	rettuple;
 	int			i;
 
 	LocTriggerData.type = T_TriggerData;
@@ -2878,12 +2871,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,
@@ -2892,11 +2891,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,
@@ -2905,7 +2904,7 @@ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 									   GetPerTupleMemoryContext(estate));
 		if (rettuple == NULL)
 			return false;		/* Delete was suppressed */
-		if (rettuple != trigtuple)
+		if (false && rettuple != trigtuple)
 			heap_freetuple(rettuple);
 	}
 	return true;
@@ -2939,10 +2938,11 @@ 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];
@@ -2984,20 +2984,19 @@ 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);
+	TupleTableSlot *oldslot = ExecTriggerGetOldSlot(estate, relinfo->ri_RelationDesc);
+	HeapTuple	slottuple = ExecFetchSlotHeapTuple(newslot, true, NULL);
 	HeapTuple	newtuple = slottuple;
 	TriggerData LocTriggerData;
 	HeapTuple	trigtuple;
-	HeapTuple	oldtuple;
-	TupleTableSlot *newSlot;
 	int			i;
 	Bitmapset  *updatedCols;
 	LockTupleMode lockmode;
@@ -3008,37 +3007,41 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 	Assert(HeapTupleIsValid(fdw_trigtuple) ^ ItemPointerIsValid(tupleid));
 	if (fdw_trigtuple == NULL)
 	{
+		bool		is_epqtuple = false;
+
 		/* 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, &is_epqtuple))
+			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 (is_epqtuple)
+		{
+			TupleTableSlot *slot = ExecFilterJunk(relinfo->ri_junkFilter, newslot);
+
+			ExecCopySlot(newslot, slot);
+			slottuple = ExecFetchSlotHeapTuple(newslot, false, NULL);
+			newtuple = slottuple;
+		}
+
+		trigtuple = ExecFetchSlotHeapTuple(oldslot, false, NULL);
 	}
 	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 |
@@ -3050,6 +3053,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,
@@ -3057,67 +3061,47 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 								  TRIGGER_TYPE_UPDATE))
 			continue;
 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
-							updatedCols, trigtuple, newtuple))
+							updatedCols, oldslot, newslot))
 			continue;
 
+		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)
-				heap_freetuple(trigtuple);
-			return NULL;		/* "do nothing" */
-		}
-	}
-	if (trigtuple != fdw_trigtuple && trigtuple != newtuple)
-		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);
+			return false;		/* "do nothing" */
 
-		if (newslot->tts_tupleDescriptor != tupdesc)
-			ExecSetSlotDescriptor(newslot, tupdesc);
-		ExecStoreHeapTuple(newtuple, newslot, false);
-		slot = newslot;
+		if (newtuple != oldtuple)
+			ExecForceStoreHeapTuple(newtuple, 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 = ExecTriggerGetOldSlot(estate, relinfo->ri_RelationDesc);
+
+	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
@@ -3125,30 +3109,31 @@ 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,
+							   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);
+	TupleTableSlot *oldslot = ExecTriggerGetOldSlot(estate, relinfo->ri_RelationDesc);
+	HeapTuple	slottuple = ExecFetchSlotHeapTuple(newslot, true, NULL);
 	HeapTuple	newtuple = slottuple;
 	TriggerData LocTriggerData;
 	HeapTuple	oldtuple;
@@ -3161,6 +3146,9 @@ 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];
@@ -3171,42 +3159,30 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 								  TRIGGER_TYPE_UPDATE))
 			continue;
 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
-							NULL, trigtuple, newtuple))
+							NULL, oldslot, newslot))
 			continue;
 
+		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)
+		if (false && oldtuple != newtuple && oldtuple != slottuple)
 			heap_freetuple(oldtuple);
 		if (newtuple == NULL)
-			return NULL;		/* "do nothing" */
-	}
+			return false;		/* "do nothing" */
 
-	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 (oldtuple != newtuple)
+			ExecForceStoreHeapTuple(newtuple, newslot);
 	}
-	return slot;
+
+	return true;
 }
 
 void
@@ -3229,10 +3205,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];
@@ -3272,25 +3249,27 @@ ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
 }
 
 
-static HeapTuple
+static bool
 GetTupleForTrigger(EState *estate,
 				   EPQState *epqstate,
 				   ResultRelInfo *relinfo,
 				   ItemPointer tid,
 				   LockTupleMode lockmode,
-				   TupleTableSlot **newSlot)
+				   TupleTableSlot *oldslot,
+				   TupleTableSlot *newslot,
+				   bool *is_epqtuple)
 {
 	Relation	relation = relinfo->ri_RelationDesc;
 	HeapTupleData tuple;
-	HeapTuple	result;
+	HeapTuple	result = &tuple;
 	Buffer		buffer;
 
-	if (newSlot != NULL)
+	if (is_epqtuple)
 	{
 		HTSU_Result test;
 		HeapUpdateFailureData hufd;
 
-		*newSlot = NULL;
+		*is_epqtuple = false;
 
 		/* caller must pass an epqstate if EvalPlanQual is possible */
 		Assert(epqstate != NULL);
@@ -3324,7 +3303,7 @@ ltrmark:;
 
 				/* treat it as deleted; do not process */
 				ReleaseBuffer(buffer);
-				return NULL;
+				return false;
 
 			case HeapTupleMayBeUpdated:
 				break;
@@ -3355,7 +3334,12 @@ ltrmark:;
 					if (!TupIsNull(epqslot))
 					{
 						*tid = hufd.ctid;
-						*newSlot = epqslot;
+
+						if (newslot)
+							ExecCopySlot(newslot, epqslot);
+						/* oldslot is updated in the function end  */
+
+						*is_epqtuple = true;
 
 						/*
 						 * EvalPlanQual already locked the tuple, but we
@@ -3371,7 +3355,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");
@@ -3380,7 +3364,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
@@ -3415,11 +3399,13 @@ ltrmark:;
 
 	if (HeapTupleHeaderGetNatts(tuple.t_data) < relation->rd_att->natts)
 		result = heap_expand_tuple(&tuple, relation->rd_att);
-	else
-		result = heap_copytuple(&tuple);
+
+	ExecStoreBufferHeapTuple(result, oldslot, buffer);
+	ExecMaterializeSlot(oldslot);
+
 	ReleaseBuffer(buffer);
 
-	return result;
+	return true;
 }
 
 /*
@@ -3429,7 +3415,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)
@@ -3471,11 +3457,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;
 
@@ -3515,40 +3498,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.
 		 */
@@ -3881,7 +3830,8 @@ struct AfterTriggersTableData
 
 static AfterTriggersData afterTriggers;
 
-static void AfterTriggerExecute(AfterTriggerEvent event,
+static void AfterTriggerExecute(EState *estate,
+					AfterTriggerEvent event,
 					Relation rel, TriggerDesc *trigdesc,
 					FmgrInfo *finfo,
 					Instrumentation *instr,
@@ -4216,7 +4166,8 @@ afterTriggerDeleteHeadEventChunk(AfterTriggersQueryData *qs)
  * ----------
  */
 static void
-AfterTriggerExecute(AfterTriggerEvent event,
+AfterTriggerExecute(EState *estate,
+					AfterTriggerEvent event,
 					Relation rel, TriggerDesc *trigdesc,
 					FmgrInfo *finfo, Instrumentation *instr,
 					MemoryContext per_tuple_context,
@@ -4229,14 +4180,15 @@ AfterTriggerExecute(AfterTriggerEvent event,
 	HeapTupleData tuple1;
 	HeapTupleData tuple2;
 	HeapTuple	rettuple;
-	Buffer		buffer1 = InvalidBuffer;
-	Buffer		buffer2 = InvalidBuffer;
 	int			tgindx;
 
 	/*
 	 * 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)
@@ -4286,31 +4238,35 @@ 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, false, NULL);
 
+			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, false, NULL) : NULL;
 
 			break;
 
 		default:
 			if (ItemPointerIsValid(&(event->ate_ctid1)))
 			{
+				Buffer		buffer;
+
 				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_trigslot = ExecTriggerGetOldSlot(estate, rel);
+				ExecStoreBufferHeapTuple(&tuple1,
+										 LocTriggerData.tg_trigslot, buffer);
 				LocTriggerData.tg_trigtuple = &tuple1;
-				LocTriggerData.tg_trigtuplebuf = buffer1;
+				ReleaseBuffer(buffer);
 			}
 			else
 			{
 				LocTriggerData.tg_trigtuple = NULL;
-				LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
 			}
 
 			/* don't touch ctid2 if not there */
@@ -4318,16 +4274,20 @@ AfterTriggerExecute(AfterTriggerEvent event,
 				AFTER_TRIGGER_2CTID &&
 				ItemPointerIsValid(&(event->ate_ctid2)))
 			{
+				Buffer		buffer;
+
 				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_newslot = ExecTriggerGetNewSlot(estate, rel);
+				ExecStoreBufferHeapTuple(&tuple2,
+										 LocTriggerData.tg_newslot, buffer);
 				LocTriggerData.tg_newtuple = &tuple2;
-				LocTriggerData.tg_newtuplebuf = buffer2;
+				ReleaseBuffer(buffer);
 			}
 			else
 			{
 				LocTriggerData.tg_newtuple = NULL;
-				LocTriggerData.tg_newtuplebuf = InvalidBuffer;
 			}
 	}
 
@@ -4379,12 +4339,12 @@ AfterTriggerExecute(AfterTriggerEvent event,
 		heap_freetuple(rettuple);
 
 	/*
-	 * Release buffers
+	 * Release resources
 	 */
-	if (buffer1 != InvalidBuffer)
-		ReleaseBuffer(buffer1);
-	if (buffer2 != InvalidBuffer)
-		ReleaseBuffer(buffer2);
+	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
@@ -4561,7 +4521,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, rel, trigdesc, finfo, instr,
 									per_tuple_context, slot1, slot2);
 
 				/*
@@ -4605,6 +4565,7 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 	if (local_estate)
 	{
 		ExecCleanUpTriggerState(estate);
+		ExecResetTupleTable(estate->es_tupleTable, false);
 		FreeExecutorState(estate);
 	}
 
@@ -5740,7 +5701,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)
 {
@@ -5789,11 +5750,11 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 		 * 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)))
 		{
@@ -5803,15 +5764,17 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 
 			if (map != NULL)
 			{
-				HeapTuple	converted = execute_attr_map_tuple(oldtup, map);
+				HeapTuple	tuple = ExecFetchSlotHeapTuple(oldslot, false, NULL);
+				HeapTuple	converted;
 
+				converted = execute_attr_map_tuple(tuple, map);
 				tuplestore_puttuple(old_tuplestore, converted);
 				pfree(converted);
 			}
 			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)))
 		{
@@ -5823,13 +5786,15 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo,
 				tuplestore_puttuple(new_tuplestore, original_insert_tuple);
 			else if (map != NULL)
 			{
-				HeapTuple	converted = execute_attr_map_tuple(newtup, map);
+				HeapTuple	tuple = ExecFetchSlotHeapTuple(newslot, false, NULL);
+				HeapTuple	converted;
 
+				converted = execute_attr_map_tuple(tuple, map);
 				tuplestore_puttuple(new_tuplestore, converted);
 				pfree(converted);
 			}
 			else
-				tuplestore_puttuple(new_tuplestore, newtup);
+				tuplestore_puttupleslot(new_tuplestore, newslot);
 		}
 
 		/*
@@ -5843,7 +5808,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;
 	}
 
@@ -5865,15 +5830,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),
@@ -5884,15 +5849,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),
@@ -5903,15 +5868,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),
@@ -5920,8 +5885,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;
@@ -5948,7 +5913,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)
@@ -5975,7 +5940,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;
@@ -5985,7 +5950,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;
@@ -6039,10 +6004,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 757df07..2e4d8eb 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -977,7 +977,7 @@ 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_return_slot = NULL;
 	estate->es_trig_oldtup_slot = NULL;
 	estate->es_trig_newtup_slot = NULL;
 
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index fc00b49..8557d5a 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -407,10 +407,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)
@@ -435,7 +433,7 @@ ExecSimpleRelationInsert(EState *estate, TupleTableSlot *slot)
 												   NIL);
 
 		/* AFTER ROW INSERT Triggers */
-		ExecARInsertTriggers(estate, resultRelInfo, tuple,
+		ExecARInsertTriggers(estate, resultRelInfo, slot,
 							 recheckIndexes, NULL);
 
 		/*
@@ -478,11 +476,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 (!ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
+								  &hsearchslot->tuple->t_self, NULL, slot))
 
-		if (slot == NULL)		/* "do nothing" */
-			skip_tuple = true;
+			skip_tuple = true;		/* "do nothing" */
 	}
 
 	if (!skip_tuple)
@@ -510,7 +507,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
-							 &hsearchslot->tuple->t_self, NULL, tuple,
+							 &hsearchslot->tuple->t_self, NULL, slot,
 							 recheckIndexes, NULL);
 
 		list_free(recheckIndexes);
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index f39be12..7bf9bc4 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -130,7 +130,7 @@ CreateExecutorState(void)
 	estate->es_tuple_routing_result_relations = NIL;
 
 	estate->es_trig_target_relations = NIL;
-	estate->es_trig_tuple_slot = NULL;
+	estate->es_trig_return_slot = NULL;
 	estate->es_trig_oldtup_slot = NULL;
 	estate->es_trig_newtup_slot = NULL;
 
@@ -420,6 +420,74 @@ MakePerTupleExprContext(EState *estate)
 	return estate->es_per_tuple_exprcontext;
 }
 
+static TupleTableSlot *
+ExecTriggerGetSlot(EState *estate, Relation rel, TupleTableSlot *slot)
+{
+	TupleDesc reldesc = RelationGetDescr(rel);
+	MemoryContext oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+	if (slot != NULL)
+	{
+		/*
+		 * Foreign tables need a non-buffer tuple slot, whereas local tables
+		 * need buffer tuple slots. For any other combinations, need to
+		 * re-create a matching tuple slot type.
+		 */
+		if (!(TTS_IS_BUFFERTUPLE(slot) &&
+			  rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
+			&&
+			!(!TTS_IS_BUFFERTUPLE(slot) &&
+			  rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE))
+		{
+			/* tuple slot type does not match. */
+			ExecDropSingleTupleTableSlot(slot);
+			slot = NULL;
+		}
+	}
+
+	if (slot == NULL)
+	{
+		/* For foreign tables, there are no buffer tuples */
+		slot =
+			ExecInitExtraTupleSlot(estate, NULL,
+								   rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE ?
+								   &TTSOpsHeapTuple : &TTSOpsBufferHeapTuple);
+	}
+
+	if (slot->tts_tupleDescriptor != reldesc)
+		ExecSetSlotDescriptor(slot, reldesc);
+
+	MemoryContextSwitchTo(oldcontext);
+
+	return slot;
+}
+
+TupleTableSlot *
+ExecTriggerGetOldSlot(EState *estate, Relation rel)
+{
+	estate->es_trig_oldtup_slot =
+		ExecTriggerGetSlot(estate, rel, estate->es_trig_oldtup_slot);
+
+	return estate->es_trig_oldtup_slot;
+}
+
+TupleTableSlot *
+ExecTriggerGetNewSlot(EState *estate, Relation rel)
+{
+	estate->es_trig_newtup_slot =
+		ExecTriggerGetSlot(estate, rel, estate->es_trig_newtup_slot);
+
+	return estate->es_trig_newtup_slot;
+}
+
+TupleTableSlot *
+ExecTriggerGetReturnSlot(EState *estate, Relation rel)
+{
+	estate->es_trig_return_slot =
+		ExecTriggerGetSlot(estate, rel, estate->es_trig_return_slot);
+
+	return estate->es_trig_return_slot;
+}
 
 /* ----------------------------------------------------------------
  *				 miscellaneous node-init support functions
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 9786d95..73b2f83 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -308,10 +308,8 @@ 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;
+		if (!ExecBRInsertTriggers(estate, resultRelInfo, slot))
+			return NULL;		/* "do nothing" */
 
 		/* trigger might have changed tuple */
 		tuple = ExecFetchSlotHeapTuple(slot, true, NULL);
@@ -321,10 +319,8 @@ ExecInsert(ModifyTableState *mtstate,
 	if (resultRelInfo->ri_TrigDesc &&
 		resultRelInfo->ri_TrigDesc->trig_insert_instead_row)
 	{
-		slot = ExecIRInsertTriggers(estate, resultRelInfo, slot);
-
-		if (slot == NULL)		/* "do nothing" */
-			return NULL;
+		if (!ExecIRInsertTriggers(estate, resultRelInfo, slot))
+			return NULL;		/* "do nothing" */
 
 		/* trigger might have changed tuple */
 		tuple = ExecFetchSlotHeapTuple(slot, true, NULL);
@@ -552,7 +548,7 @@ ExecInsert(ModifyTableState *mtstate,
 	{
 		ExecARUpdateTriggers(estate, resultRelInfo, NULL,
 							 NULL,
-							 tuple,
+							 slot,
 							 NULL,
 							 mtstate->mt_transition_capture);
 
@@ -564,7 +560,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);
@@ -678,10 +674,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 * 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.
 		 */
-		slot = estate->es_trig_tuple_slot;
-		if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
-			ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
-
+		slot = ExecTriggerGetReturnSlot(estate, resultRelationDesc);
 		slot = resultRelInfo->ri_FdwRoutine->ExecForeignDelete(estate,
 															   resultRelInfo,
 															   slot,
@@ -857,7 +850,7 @@ ldelete:;
 		 */
 		TupleTableSlot *rslot;
 		HeapTupleData deltuple;
-		Buffer		delbuffer;
+		Buffer		delbuffer = InvalidBuffer;
 
 		if (resultRelInfo->ri_FdwRoutine)
 		{
@@ -867,11 +860,10 @@ ldelete:;
 		}
 		else
 		{
-			slot = estate->es_trig_tuple_slot;
+			slot = ExecTriggerGetReturnSlot(estate, resultRelationDesc);
 			if (oldtuple != NULL)
 			{
-				deltuple = *oldtuple;
-				delbuffer = InvalidBuffer;
+				ExecForceStoreHeapTuple(oldtuple, slot);
 			}
 			else
 			{
@@ -879,11 +871,8 @@ ldelete:;
 				if (!heap_fetch(resultRelationDesc, SnapshotAny,
 								&deltuple, &delbuffer, false, NULL))
 					elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
+				ExecStoreBufferHeapTuple(&deltuple, slot, delbuffer);
 			}
-
-			if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
-				ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
-			ExecStoreHeapTuple(&deltuple, slot, false);
 		}
 
 		rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
@@ -966,11 +955,9 @@ 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;
+		if (!ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
+								  tupleid, oldtuple, slot))
+			return NULL;		/* "do nothing" */
 
 		/* trigger might have changed tuple */
 		tuple = ExecFetchSlotHeapTuple(slot, true, NULL);
@@ -980,11 +967,9 @@ ExecUpdate(ModifyTableState *mtstate,
 	if (resultRelInfo->ri_TrigDesc &&
 		resultRelInfo->ri_TrigDesc->trig_update_instead_row)
 	{
-		slot = ExecIRUpdateTriggers(estate, resultRelInfo,
-									oldtuple, slot);
-
-		if (slot == NULL)		/* "do nothing" */
-			return NULL;
+		if (!ExecIRUpdateTriggers(estate, resultRelInfo,
+								  oldtuple, slot))
+			return NULL;		/* "do nothing" */
 
 		/* trigger might have changed tuple */
 		tuple = ExecFetchSlotHeapTuple(slot, true, NULL);
@@ -1309,7 +1294,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 :
@@ -2566,15 +2551,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
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 3cd1e0d..3906eed 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -211,11 +211,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 cdda860..a94751b 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -191,7 +191,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);
@@ -204,12 +204,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);
@@ -231,14 +231,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();
 
 
@@ -254,8 +254,8 @@ RI_FKey_check(TriggerData *trigdata)
 	const RI_ConstraintInfo *riinfo;
 	Relation	fk_rel;
 	Relation	pk_rel;
-	HeapTuple	new_row;
-	Buffer		new_row_buf;
+	TupleTableSlot *newslot;
+	BufferHeapTupleTableSlot *bslot;
 	RI_QueryKey qkey;
 	SPIPlanPtr	qplan;
 	int			i;
@@ -267,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,13 +279,14 @@ RI_FKey_check(TriggerData *trigdata)
 	 * and lock on the buffer to call HeapTupleSatisfiesVisibility.  Caller
 	 * should be holding pin, but not lock.
 	 */
-	LockBuffer(new_row_buf, BUFFER_LOCK_SHARE);
-	if (!HeapTupleSatisfiesVisibility(new_row, SnapshotSelf, new_row_buf))
+	bslot = (BufferHeapTupleTableSlot *) newslot;
+	LockBuffer(bslot->buffer, BUFFER_LOCK_SHARE);
+	if (!HeapTupleSatisfiesVisibility(bslot->base.tuple, SnapshotSelf, bslot->buffer))
 	{
-		LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
+		LockBuffer(bslot->buffer, BUFFER_LOCK_UNLOCK);
 		return PointerGetDatum(NULL);
 	}
-	LockBuffer(new_row_buf, BUFFER_LOCK_UNLOCK);
+	LockBuffer(bslot->buffer, BUFFER_LOCK_UNLOCK);
 
 	/*
 	 * Get the relation descriptors of the FK and PK tables.
@@ -307,7 +302,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:
 
@@ -437,7 +432,7 @@ RI_FKey_check(TriggerData *trigdata)
 	 */
 	ri_PerformCheck(riinfo, &qkey, qplan,
 					fk_rel, pk_rel,
-					NULL, new_row,
+					NULL, newslot,
 					false,
 					SPI_OK_SELECT);
 
@@ -505,7 +500,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;
@@ -514,7 +509,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");
@@ -572,7 +567,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);
 
@@ -690,7 +685,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;
 
@@ -708,7 +703,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action)
 	 */
 	fk_rel = heap_open(riinfo->fk_relid, RowShareLock);
 	pk_rel = trigdata->tg_relation;
-	old_row = trigdata->tg_trigtuple;
+	old_slot = trigdata->tg_trigslot;
 
 	switch (riinfo->confmatchtype)
 	{
@@ -732,7 +727,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))
 			{
 				heap_close(fk_rel, RowShareLock);
 				return PointerGetDatum(NULL);
@@ -800,7 +795,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);
 
@@ -844,7 +839,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;
@@ -868,7 +863,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 	 */
 	fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
-	old_row = trigdata->tg_trigtuple;
+	old_slot = trigdata->tg_trigslot;
 
 	switch (riinfo->confmatchtype)
 	{
@@ -940,7 +935,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);
 
@@ -984,8 +979,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;
@@ -1011,8 +1006,8 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 	 */
 	fk_rel = heap_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)
 	{
@@ -1096,7 +1091,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);
 
@@ -1179,7 +1174,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;
@@ -1198,7 +1193,7 @@ ri_setnull(TriggerData *trigdata)
 	 */
 	fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
-	old_row = trigdata->tg_trigtuple;
+	old_slot = trigdata->tg_trigslot;
 
 	switch (riinfo->confmatchtype)
 	{
@@ -1283,7 +1278,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);
 
@@ -1366,7 +1361,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;
 
@@ -1384,7 +1379,7 @@ ri_setdefault(TriggerData *trigdata)
 	 */
 	fk_rel = heap_open(riinfo->fk_relid, RowExclusiveLock);
 	pk_rel = trigdata->tg_relation;
-	old_row = trigdata->tg_trigtuple;
+	old_slot = trigdata->tg_trigslot;
 
 	switch (riinfo->confmatchtype)
 	{
@@ -1470,7 +1465,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);
 
@@ -1529,7 +1524,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;
 
@@ -1547,11 +1542,12 @@ 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 (!TupIsNull(new_slot) &&
+				ri_KeysEqual(pk_rel, old_slot, new_slot, riinfo, true))
 				return false;
 
 			/* Else we need to fire the trigger. */
@@ -1586,9 +1582,10 @@ 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;
+	HeapTuple		old_row = ((HeapTupleTableSlot *) old_slot)->tuple;
 
 	/*
 	 * Get arguments.
@@ -1603,7 +1600,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;
 
 			/*
@@ -1618,7 +1615,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 				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. */
@@ -1634,7 +1631,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;
@@ -1656,7 +1653,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 				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. */
@@ -1910,10 +1907,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
@@ -1933,7 +1937,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\"",
@@ -1950,8 +1954,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)
@@ -2429,7 +2435,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,
@@ -2472,17 +2478,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);
 	}
 
@@ -2552,7 +2558,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);
 
@@ -2563,11 +2569,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;
@@ -2579,8 +2584,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' : ' ';
 	}
 }
@@ -2597,7 +2601,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;
@@ -2676,7 +2680,8 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo,
 					   *val;
 
 			name = SPI_fname(tupdesc, fnum);
-			val = SPI_getvalue(violator, tupdesc, fnum);
+			val = SPI_getvalue(ExecFetchSlotHeapTuple(violatorslot, false, NULL),
+							   tupdesc, fnum);
 			if (!val)
 				val = "null";
 
@@ -2730,7 +2735,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;
@@ -2745,7 +2750,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;
@@ -2896,10 +2901,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;
@@ -2915,6 +2919,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;
@@ -2924,14 +2929,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 1031448..0f02bae 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;
@@ -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 14c4e3a..369d1e6 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -493,6 +493,10 @@ extern void ReScanExprContext(ExprContext *econtext);
 
 extern ExprContext *MakePerTupleExprContext(EState *estate);
 
+extern TupleTableSlot *ExecTriggerGetOldSlot(EState *estate, Relation rel);
+extern TupleTableSlot *ExecTriggerGetNewSlot(EState *estate, Relation rel);
+extern TupleTableSlot *ExecTriggerGetReturnSlot(EState *estate, Relation rel);
+
 /* Get an EState's per-output-tuple exprcontext, making it if first use */
 #define GetPerTupleExprContext(estate) \
 	((estate)->es_per_tuple_exprcontext ? \
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 569cc7c..c7e6e88 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -525,7 +525,7 @@ 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_return_slot;	/* for trigger output tuples */
 	TupleTableSlot *es_trig_oldtup_slot;	/* for TriggerEnabled */
 	TupleTableSlot *es_trig_newtup_slot;	/* for TriggerEnabled */
 
-- 
2.1.4

