Trigger tuple slotification (extracted from pluggable storage patch)

Started by Amit Khandekarabout 7 years ago1 messages
#1Amit Khandekar
amitdkhan.pg@gmail.com
2 attachment(s)

Hi,

In [1]https://commitfest.postgresql.org/14/1283/, there was a plan to do the trigger tuple slotification
changes as a separate patch, and not include them in the pluggable
storage changes. I have come up with such a patch by extracting the
trigger-specific changes from the pluggable storage changes proposed
in [1]https://commitfest.postgresql.org/14/1283/. I used this repository to extract the changes :
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.

Basically, the attached patch contains changes that slotify the
trigger tuple handling. Details below :

In the pluggable storage changes, there were some TODOs such as this
in trigger.c :
- /* TODO : */
- if (false && oldtuple != newtuple && oldtuple != slottuple)

I removed this condition. Andres, you had put this in the pluggable
storage patch, probably to check whether it is important. But after
giving a thought, I think it is not required.

Did all the slotification changes in ri_trigger.c also, because they
are all dependent on the slotification of Trigger tuples.

In AfterTriggerSaveEvent(), newslot->tts_tid value is copied over to
new_event.ate_ctid1. Since this function only accepts slot now,
there's no point in retrieving the tid from the slot tuple since later
on we are going to have tid in the slot, looking at the pluggable
storage changes. So in a separate patch
(0001-Populate-slot-tts_tid-wherever-tuple-t_self-changes.patch), I
have just added this field in the TupleTableSlot, and populated
slot->tts_tid wherever tuple->t_self changes.

[1]: https://commitfest.postgresql.org/14/1283/

--
Thanks,
-Amit Khandekar
EnterpriseDB Corporation
The Postgres Database Company

Attachments:

0001-Populate-slot-tts_tid-wherever-tuple-t_self-changes.patchapplication/octet-stream; name=0001-Populate-slot-tts_tid-wherever-tuple-t_self-changes.patchDownload
From c3f827a729f457c8b3bf760553b6c85d5b0a0db1 Mon Sep 17 00:00:00 2001
From: Amit Khandekar <amit.khandekar@enterprisedb.com>
Date: Mon, 19 Nov 2018 12:09:20 +0530
Subject: [PATCH 1/2] Populate &slot->tts_tid wherever tuple->t_self changes.

Author: Andres Freund.
---
 src/backend/commands/copy.c            | 4 +++-
 src/backend/commands/createas.c        | 1 +
 src/backend/commands/matview.c         | 1 +
 src/backend/commands/tablecmds.c       | 3 +++
 src/backend/executor/execReplication.c | 1 +
 src/backend/executor/execTuples.c      | 2 ++
 src/backend/executor/nodeModifyTable.c | 4 ++++
 src/include/executor/tuptable.h        | 1 +
 8 files changed, 16 insertions(+), 1 deletion(-)

diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index a61f3bc..93badf7 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2953,9 +2953,11 @@ CopyFrom(CopyState cstate)
 						tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
 					}
 					else
+					{
 						heap_insert(resultRelInfo->ri_RelationDesc, tuple,
 									mycid, hi_options, bistate);
-
+						ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
+					}
 					/* And create index entries for it */
 					if (resultRelInfo->ri_NumIndices > 0)
 						recheckIndexes = ExecInsertIndexTuples(slot,
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 7b60aa9..7df40c6 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -602,6 +602,7 @@ intorel_receive(TupleTableSlot *slot, DestReceiver *self)
 				myState->output_cid,
 				myState->hi_options,
 				myState->bistate);
+	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index fd12288..f0120e4 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -491,6 +491,7 @@ transientrel_receive(TupleTableSlot *slot, DestReceiver *self)
 				myState->output_cid,
 				myState->hi_options,
 				myState->bistate);
+	ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 	/* We know this is a newly created relation, so there are no indexes */
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index a15e604..dfa15a5 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4875,7 +4875,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 
 			/* Write the tuple out to the new relation */
 			if (newrel)
+			{
 				heap_insert(newrel, tuple, mycid, hi_options, bistate);
+				ItemPointerCopy(&tuple->t_self, &newslot->tts_tid);
+			}
 
 			ResetExprContext(econtext);
 
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 5bd3bbc..fc00b49 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -500,6 +500,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 
 		/* OK, update the tuple and index entries for it */
 		simple_heap_update(rel, &hsearchslot->tuple->t_self, hslot->tuple);
+		ItemPointerCopy(&hslot->tuple->t_self, &hslot->base.tts_tid);
 
 		if (resultRelInfo->ri_NumIndices > 0 &&
 			!HeapTupleIsHeapOnly(hslot->tuple))
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index c9523c5..47aed0a 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -432,6 +432,7 @@ tts_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, bool shouldFree)
 	hslot->tuple = tuple;
 	hslot->off = 0;
 	slot->tts_flags &= ~TTS_FLAG_EMPTY;
+	slot->tts_tid = tuple->t_self;
 
 	if (shouldFree)
 		slot->tts_flags |= TTS_FLAG_SHOULDFREE;
@@ -810,6 +811,7 @@ tts_buffer_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, Buffer buffer
 	slot->tts_nvalid = 0;
 	bslot->base.tuple = tuple;
 	bslot->base.off = 0;
+	slot->tts_tid = tuple->t_self;
 
 	/*
 	 * If tuple is on a disk page, keep the page pinned as long as we hold a
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 7e05c15..9786d95 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -477,6 +477,7 @@ ExecInsert(ModifyTableState *mtstate,
 								estate->es_output_cid,
 								HEAP_INSERT_SPECULATIVE,
 								NULL);
+			ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 			/* insert index entries for tuple */
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
@@ -522,6 +523,7 @@ ExecInsert(ModifyTableState *mtstate,
 			newId = heap_insert(resultRelationDesc, tuple,
 								estate->es_output_cid,
 								0, NULL);
+			ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
 
 			/* insert index entries for tuple */
 			if (resultRelInfo->ri_NumIndices > 0)
@@ -1204,6 +1206,8 @@ lreplace:;
 							 estate->es_crosscheck_snapshot,
 							 true /* wait for commit */ ,
 							 &hufd, &lockmode);
+		ItemPointerCopy(&tuple->t_self, &slot->tts_tid);
+
 		switch (result)
 		{
 			case HeapTupleSelfUpdated:
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index db5031f..6990f6e 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -125,6 +125,7 @@ typedef struct TupleTableSlot
 #define FIELDNO_TUPLETABLESLOT_ISNULL 6
 	bool	   *tts_isnull;		/* current per-attribute isnull flags */
 	MemoryContext tts_mcxt;		/* slot itself is in this context */
+	ItemPointerData tts_tid;	/* tid of the underlying tuple */
 } TupleTableSlot;
 
 /* routines for a TupleTableSlot implementation */
-- 
2.1.4

0002-Trigger-tuple-slotification.patchapplication/octet-stream; name=0002-Trigger-tuple-slotification.patchDownload
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