From ff9b927e8c8e08d7fd2ad767505bd1f3537097f8 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Tue, 13 Nov 2018 12:57:56 -0800
Subject: [PATCH v14 2/7] Change tuple table slot creation routines to suite
 tuple table slot abstraction.

This change requires ExecInitResultTupleSlotTL, ExecInitScanTupleSlot,
ExecCreateScanSlotFromOuterPlan, ExecInitNullTupleSlot,
ExecInitExtraTupleSlot, MakeTupleTableSlot, ExecAllocTableSlot to
accept TupleTableSlotOps as a new parameter. Change all their calls.

For this commit, all the global variables corresponding to different
types of TupleTableSlots are empty. In the subsequent commit,
they will be assigned real structure fields.

Author: Ashutosh Bapat and Andres Freund, with changes by Amit Khandekar
Discussion: https://postgr.es/m/20181105210039.hh4vvi4vwoq5ba2q@alap3.anarazel.de
---
 src/backend/access/heap/heapam.c              |  3 +-
 src/backend/catalog/index.c                   |  9 ++-
 src/backend/catalog/indexing.c                |  3 +-
 src/backend/commands/analyze.c                |  3 +-
 src/backend/commands/constraint.c             |  3 +-
 src/backend/commands/copy.c                   |  6 +-
 src/backend/commands/explain.c                |  3 +-
 src/backend/commands/functioncmds.c           |  3 +-
 src/backend/commands/subscriptioncmds.c       |  2 +-
 src/backend/commands/tablecmds.c              |  6 +-
 src/backend/commands/trigger.c                | 10 +--
 src/backend/executor/execExpr.c               |  3 +-
 src/backend/executor/execGrouping.c           |  3 +-
 src/backend/executor/execIndexing.c           |  3 +-
 src/backend/executor/execJunk.c               |  4 +-
 src/backend/executor/execMain.c               | 10 +--
 src/backend/executor/execPartition.c          |  7 +-
 src/backend/executor/execSRF.c                |  3 +-
 src/backend/executor/execTuples.c             | 64 +++++++++++++------
 src/backend/executor/execUtils.c              | 44 ++++++++++++-
 src/backend/executor/functions.c              | 24 +++++--
 src/backend/executor/nodeAgg.c                | 17 +++--
 src/backend/executor/nodeAppend.c             |  6 +-
 src/backend/executor/nodeBitmapHeapscan.c     |  3 +-
 src/backend/executor/nodeCtescan.c            |  3 +-
 src/backend/executor/nodeCustom.c             | 12 ++--
 src/backend/executor/nodeForeignscan.c        | 10 ++-
 src/backend/executor/nodeFunctionscan.c       |  6 +-
 src/backend/executor/nodeGather.c             |  8 ++-
 src/backend/executor/nodeGatherMerge.c        | 10 ++-
 src/backend/executor/nodeGroup.c              |  5 +-
 src/backend/executor/nodeHash.c               |  2 +-
 src/backend/executor/nodeHashjoin.c           | 13 ++--
 src/backend/executor/nodeIndexonlyscan.c      |  2 +-
 src/backend/executor/nodeIndexscan.c          |  3 +-
 src/backend/executor/nodeLimit.c              |  4 ++
 src/backend/executor/nodeLockRows.c           |  5 ++
 src/backend/executor/nodeMaterial.c           | 10 ++-
 src/backend/executor/nodeMergeAppend.c        |  6 +-
 src/backend/executor/nodeMergejoin.c          | 15 +++--
 src/backend/executor/nodeModifyTable.c        | 14 ++--
 .../executor/nodeNamedtuplestorescan.c        |  3 +-
 src/backend/executor/nodeNestloop.c           |  5 +-
 src/backend/executor/nodeProjectSet.c         |  2 +-
 src/backend/executor/nodeResult.c             |  2 +-
 src/backend/executor/nodeSamplescan.c         |  3 +-
 src/backend/executor/nodeSeqscan.c            |  3 +-
 src/backend/executor/nodeSetOp.c              |  4 +-
 src/backend/executor/nodeSort.c               |  4 +-
 src/backend/executor/nodeSubplan.c            |  4 +-
 src/backend/executor/nodeSubqueryscan.c       | 13 +++-
 src/backend/executor/nodeTableFuncscan.c      |  3 +-
 src/backend/executor/nodeTidscan.c            |  3 +-
 src/backend/executor/nodeUnique.c             |  2 +-
 src/backend/executor/nodeValuesscan.c         |  2 +-
 src/backend/executor/nodeWindowAgg.c          | 27 +++++---
 src/backend/executor/nodeWorktablescan.c      |  7 +-
 src/backend/partitioning/partbounds.c         |  2 +-
 src/backend/replication/logical/tablesync.c   |  4 +-
 src/backend/replication/logical/worker.c      | 21 ++++--
 src/backend/replication/walsender.c           |  6 +-
 src/backend/tcop/pquery.c                     |  2 +-
 src/backend/utils/adt/orderedsetaggs.c        |  6 +-
 src/backend/utils/adt/selfuncs.c              |  3 +-
 src/backend/utils/misc/guc.c                  |  4 +-
 src/backend/utils/sort/tuplesort.c            |  2 +-
 src/include/executor/executor.h               | 25 +++++---
 src/include/executor/tuptable.h               | 35 ++++++++--
 src/include/nodes/execnodes.h                 | 36 +++++++++++
 69 files changed, 432 insertions(+), 171 deletions(-)

diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index fb63471a0e0..da2a8f34c20 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -4503,7 +4503,8 @@ ProjIndexIsUnchanged(Relation relation, HeapTuple oldtup, HeapTuple newtup)
 	List	   *indexoidlist = RelationGetIndexList(relation);
 	EState	   *estate = CreateExecutorState();
 	ExprContext *econtext = GetPerTupleExprContext(estate);
-	TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(relation));
+	TupleTableSlot *slot = MakeSingleTupleTableSlot(RelationGetDescr(relation),
+													&TTSOpsHeapTuple);
 	bool		equals = true;
 	Datum		old_values[INDEX_MAX_KEYS];
 	bool		old_isnull[INDEX_MAX_KEYS];
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 4088286151a..21bdf794da6 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -2510,7 +2510,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	 */
 	estate = CreateExecutorState();
 	econtext = GetPerTupleExprContext(estate);
-	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation));
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation),
+									&TTSOpsHeapTuple);
 
 	/* Arrange for econtext's scan tuple to be the tuple under test */
 	econtext->ecxt_scantuple = slot;
@@ -2997,7 +2998,8 @@ IndexCheckExclusion(Relation heapRelation,
 	 */
 	estate = CreateExecutorState();
 	econtext = GetPerTupleExprContext(estate);
-	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation));
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation),
+									&TTSOpsHeapTuple);
 
 	/* Arrange for econtext's scan tuple to be the tuple under test */
 	econtext->ecxt_scantuple = slot;
@@ -3315,7 +3317,8 @@ validate_index_heapscan(Relation heapRelation,
 	 */
 	estate = CreateExecutorState();
 	econtext = GetPerTupleExprContext(estate);
-	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation));
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation),
+									&TTSOpsHeapTuple);
 
 	/* Arrange for econtext's scan tuple to be the tuple under test */
 	econtext->ecxt_scantuple = slot;
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index daf7ae2eb2b..c5f6efba2b6 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -95,7 +95,8 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 	heapRelation = indstate->ri_RelationDesc;
 
 	/* Need a slot to hold the tuple being examined */
-	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation));
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation),
+									&TTSOpsHeapTuple);
 	ExecStoreHeapTuple(heapTuple, slot, false);
 
 	/*
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8ac868ad733..b8445dc3728 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -730,7 +730,8 @@ compute_index_stats(Relation onerel, double totalrows,
 		estate = CreateExecutorState();
 		econtext = GetPerTupleExprContext(estate);
 		/* Need a slot to hold the current heap tuple, too */
-		slot = MakeSingleTupleTableSlot(RelationGetDescr(onerel));
+		slot = MakeSingleTupleTableSlot(RelationGetDescr(onerel),
+										&TTSOpsHeapTuple);
 
 		/* Arrange for econtext's scan tuple to be the tuple under test */
 		econtext->ecxt_scantuple = slot;
diff --git a/src/backend/commands/constraint.c b/src/backend/commands/constraint.c
index f472355b48f..b0b2cb2a146 100644
--- a/src/backend/commands/constraint.c
+++ b/src/backend/commands/constraint.c
@@ -122,7 +122,8 @@ unique_key_recheck(PG_FUNCTION_ARGS)
 	/*
 	 * The heap tuple must be put into a slot for FormIndexDatum.
 	 */
-	slot = MakeSingleTupleTableSlot(RelationGetDescr(trigdata->tg_relation));
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(trigdata->tg_relation),
+									&TTSOpsHeapTuple);
 
 	ExecStoreHeapTuple(new_row, slot, false);
 
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index a9471c5ef6a..e62e3d8fba2 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2488,9 +2488,11 @@ CopyFrom(CopyState cstate)
 	ExecInitRangeTable(estate, cstate->range_table);
 
 	/* Set up a tuple slot too */
-	myslot = ExecInitExtraTupleSlot(estate, tupDesc);
+	myslot = ExecInitExtraTupleSlot(estate, tupDesc,
+									&TTSOpsHeapTuple);
 	/* Triggers might need a slot as well */
-	estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL);
+	estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL,
+														&TTSOpsHeapTuple);
 
 	/*
 	 * Set up a ModifyTableState so we can let FDW(s) init themselves for
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 799a22e9d55..ab2c84ff98a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -266,7 +266,8 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString,
 	Assert(es->indent == 0);
 
 	/* output tuples */
-	tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt));
+	tstate = begin_tup_output_tupdesc(dest, ExplainResultDesc(stmt),
+									  &TTSOpsVirtual);
 	if (es->format == EXPLAIN_FORMAT_TEXT)
 		do_text_output_multiline(tstate, es->str->data);
 	else
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 3925fb83a54..f6e12a33532 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -2347,7 +2347,8 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
 		tupTypmod = HeapTupleHeaderGetTypMod(td);
 		retdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
 
-		tstate = begin_tup_output_tupdesc(dest, retdesc);
+		tstate = begin_tup_output_tupdesc(dest, retdesc,
+										  &TTSOpsHeapTuple);
 
 		rettupdata.t_len = HeapTupleHeaderGetDatumLength(td);
 		ItemPointerSetInvalid(&(rettupdata.t_self));
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index f138e61a8d3..0efbfec4751 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1148,7 +1148,7 @@ fetch_table_list(WalReceiverConn *wrconn, List *publications)
 						res->err)));
 
 	/* Process tables. */
-	slot = MakeSingleTupleTableSlot(res->tupledesc);
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
 	while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
 	{
 		char	   *nspname;
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 946119fa860..18a2b54b8e9 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4729,8 +4729,8 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 		 * tuples are the same, the tupDescs might not be (consider ADD COLUMN
 		 * without a default).
 		 */
-		oldslot = MakeSingleTupleTableSlot(oldTupDesc);
-		newslot = MakeSingleTupleTableSlot(newTupDesc);
+		oldslot = MakeSingleTupleTableSlot(oldTupDesc, &TTSOpsHeapTuple);
+		newslot = MakeSingleTupleTableSlot(newTupDesc, &TTSOpsHeapTuple);
 
 		/* Preallocate values/isnull arrays */
 		i = Max(newTupDesc->natts, oldTupDesc->natts);
@@ -8520,7 +8520,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 
 	econtext = GetPerTupleExprContext(estate);
 	tupdesc = RelationGetDescr(rel);
-	slot = MakeSingleTupleTableSlot(tupdesc);
+	slot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsHeapTuple);
 	econtext->ecxt_scantuple = slot;
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index d6f33ecbd04..b91ebdb3d04 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -3525,7 +3525,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 			{
 				oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
 				estate->es_trig_oldtup_slot =
-					ExecInitExtraTupleSlot(estate, NULL);
+					ExecInitExtraTupleSlot(estate, NULL, &TTSOpsHeapTuple);
 				MemoryContextSwitchTo(oldContext);
 			}
 			oldslot = estate->es_trig_oldtup_slot;
@@ -3539,7 +3539,7 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 			{
 				oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
 				estate->es_trig_newtup_slot =
-					ExecInitExtraTupleSlot(estate, NULL);
+					ExecInitExtraTupleSlot(estate, NULL, &TTSOpsHeapTuple);
 				MemoryContextSwitchTo(oldContext);
 			}
 			newslot = estate->es_trig_newtup_slot;
@@ -4546,8 +4546,10 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 							ExecDropSingleTupleTableSlot(slot1);
 							ExecDropSingleTupleTableSlot(slot2);
 						}
-						slot1 = MakeSingleTupleTableSlot(rel->rd_att);
-						slot2 = MakeSingleTupleTableSlot(rel->rd_att);
+						slot1 = MakeSingleTupleTableSlot(rel->rd_att,
+														 &TTSOpsMinimalTuple);
+						slot2 = MakeSingleTupleTableSlot(rel->rd_att,
+														 &TTSOpsMinimalTuple);
 					}
 					if (trigdesc == NULL)	/* should not happen */
 						elog(ERROR, "relation %u has no triggers",
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 885da18306a..82b5a0b404a 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2423,7 +2423,8 @@ ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable, ExprState *state)
 				scratch->d.wholerow.junkFilter =
 					ExecInitJunkFilter(subplan->plan->targetlist,
 									   ExecGetResultType(subplan)->tdhasoid,
-									   ExecInitExtraTupleSlot(parent->state, NULL));
+									   ExecInitExtraTupleSlot(parent->state, NULL,
+															  &TTSOpsVirtual));
 			}
 		}
 	}
diff --git a/src/backend/executor/execGrouping.c b/src/backend/executor/execGrouping.c
index c4d0e040587..5bc200e4dc3 100644
--- a/src/backend/executor/execGrouping.c
+++ b/src/backend/executor/execGrouping.c
@@ -202,7 +202,8 @@ BuildTupleHashTable(PlanState *parent,
 	 * We copy the input tuple descriptor just for safety --- we assume all
 	 * input tuples will have equivalent descriptors.
 	 */
-	hashtable->tableslot = MakeSingleTupleTableSlot(CreateTupleDescCopy(inputDesc));
+	hashtable->tableslot = MakeSingleTupleTableSlot(CreateTupleDescCopy(inputDesc),
+													&TTSOpsMinimalTuple);
 
 	/* build comparator for all columns */
 	hashtable->tab_eq_func = ExecBuildGroupingEqual(inputDesc, inputDesc,
diff --git a/src/backend/executor/execIndexing.c b/src/backend/executor/execIndexing.c
index 9927ad70e66..8b35bb458de 100644
--- a/src/backend/executor/execIndexing.c
+++ b/src/backend/executor/execIndexing.c
@@ -706,7 +706,8 @@ check_exclusion_or_unique_constraint(Relation heap, Relation index,
 	 * to this slot.  Be sure to save and restore caller's value for
 	 * scantuple.
 	 */
-	existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap));
+	existing_slot = MakeSingleTupleTableSlot(RelationGetDescr(heap),
+											 &TTSOpsHeapTuple);
 
 	econtext = GetPerTupleExprContext(estate);
 	save_scantuple = econtext->ecxt_scantuple;
diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c
index 57d74e57c1a..26558282e9c 100644
--- a/src/backend/executor/execJunk.c
+++ b/src/backend/executor/execJunk.c
@@ -78,7 +78,7 @@ ExecInitJunkFilter(List *targetList, bool hasoid, TupleTableSlot *slot)
 	if (slot)
 		ExecSetSlotDescriptor(slot, cleanTupType);
 	else
-		slot = MakeSingleTupleTableSlot(cleanTupType);
+		slot = MakeSingleTupleTableSlot(cleanTupType, &TTSOpsVirtual);
 
 	/*
 	 * Now calculate the mapping between the original tuple's attributes and
@@ -149,7 +149,7 @@ ExecInitJunkFilterConversion(List *targetList,
 	if (slot)
 		ExecSetSlotDescriptor(slot, cleanTupType);
 	else
-		slot = MakeSingleTupleTableSlot(cleanTupType);
+		slot = MakeSingleTupleTableSlot(cleanTupType, &TTSOpsVirtual);
 
 	/*
 	 * Calculate the mapping between the original tuple's attributes and the
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index d10e533fd16..4843550a6f1 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1055,7 +1055,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 			j = ExecInitJunkFilter(planstate->plan->targetlist,
 								   tupType->tdhasoid,
-								   ExecInitExtraTupleSlot(estate, NULL));
+								   ExecInitExtraTupleSlot(estate, NULL, &TTSOpsVirtual));
 			estate->es_junkFilter = j;
 
 			/* Want to return the cleaned tuple type */
@@ -1928,7 +1928,7 @@ ExecPartitionCheckEmitError(ResultRelInfo *resultRelInfo,
 		 */
 		if (map != NULL)
 			slot = execute_attr_map_slot(map, slot,
-										 MakeTupleTableSlot(tupdesc));
+										 MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
 	}
 
 	insertedCols = GetInsertedColumns(resultRelInfo, estate);
@@ -2009,7 +2009,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 					 */
 					if (map != NULL)
 						slot = execute_attr_map_slot(map, slot,
-													 MakeTupleTableSlot(tupdesc));
+													 MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
 				}
 
 				insertedCols = GetInsertedColumns(resultRelInfo, estate);
@@ -2059,7 +2059,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				 */
 				if (map != NULL)
 					slot = execute_attr_map_slot(map, slot,
-												 MakeTupleTableSlot(tupdesc));
+												 MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
 			}
 
 			insertedCols = GetInsertedColumns(resultRelInfo, estate);
@@ -2167,7 +2167,7 @@ ExecWithCheckOptions(WCOKind kind, ResultRelInfo *resultRelInfo,
 						 */
 						if (map != NULL)
 							slot = execute_attr_map_slot(map, slot,
-														 MakeTupleTableSlot(tupdesc));
+														 MakeTupleTableSlot(tupdesc, &TTSOpsVirtual));
 					}
 
 					insertedCols = GetInsertedColumns(resultRelInfo, estate);
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 1e72e9fb3f5..53cbd34be86 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -144,7 +144,8 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel)
 		 * We need an additional tuple slot for storing transient tuples that
 		 * are converted to the root table descriptor.
 		 */
-		proute->root_tuple_slot = MakeTupleTableSlot(RelationGetDescr(rel));
+		proute->root_tuple_slot = MakeTupleTableSlot(RelationGetDescr(rel),
+													 &TTSOpsHeapTuple);
 	}
 
 	i = 0;
@@ -740,7 +741,7 @@ ExecInitRoutingInfo(ModifyTableState *mtstate,
 		 */
 		proute->partition_tuple_slots[partidx] =
 			ExecInitExtraTupleSlot(estate,
-								   RelationGetDescr(partrel));
+								   RelationGetDescr(partrel), &TTSOpsHeapTuple);
 	}
 
 	/*
@@ -974,7 +975,7 @@ get_partition_dispatch_recurse(Relation rel, Relation parent,
 		 * using the correct tuple descriptor when computing its partition key
 		 * for tuple routing.
 		 */
-		pd->tupslot = MakeSingleTupleTableSlot(tupdesc);
+		pd->tupslot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsHeapTuple);
 		pd->tupmap = convert_tuples_by_name_map_if_req(RelationGetDescr(parent),
 													   tupdesc,
 													   gettext_noop("could not convert row type"));
diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c
index 3bffb0ea71f..248283f2543 100644
--- a/src/backend/executor/execSRF.c
+++ b/src/backend/executor/execSRF.c
@@ -873,7 +873,8 @@ ExecPrepareTuplestoreResult(SetExprState *sexpr,
 			slotDesc = NULL;	/* keep compiler quiet */
 		}
 
-		sexpr->funcResultSlot = MakeSingleTupleTableSlot(slotDesc);
+		sexpr->funcResultSlot = MakeSingleTupleTableSlot(slotDesc,
+														 &TTSOpsMinimalTuple);
 		MemoryContextSwitchTo(oldcontext);
 	}
 
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index ca2ca7f2d63..bb618e94f5d 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -72,6 +72,10 @@
 static TupleDesc ExecTypeFromTLInternal(List *targetList,
 					   bool hasoid, bool skipjunk);
 
+const TupleTableSlotOps TTSOpsVirtual;
+const TupleTableSlotOps TTSOpsHeapTuple;
+const TupleTableSlotOps TTSOpsMinimalTuple;
+const TupleTableSlotOps TTSOpsBufferTuple;
 
 /* ----------------------------------------------------------------
  *				  tuple table create/delete functions
@@ -87,7 +91,8 @@ static TupleDesc ExecTypeFromTLInternal(List *targetList,
  * --------------------------------
  */
 TupleTableSlot *
-MakeTupleTableSlot(TupleDesc tupleDesc)
+MakeTupleTableSlot(TupleDesc tupleDesc,
+				   const TupleTableSlotOps *tts_cb)
 {
 	Size		sz;
 	TupleTableSlot *slot;
@@ -104,6 +109,8 @@ MakeTupleTableSlot(TupleDesc tupleDesc)
 		sz = sizeof(TupleTableSlot);
 
 	slot = palloc0(sz);
+	/* const for optimization purposes, OK to modify at allocation time */
+	*((const TupleTableSlotOps **) &slot->tts_cb) = tts_cb;
 	slot->type = T_TupleTableSlot;
 	slot->tts_flags |= TTS_FLAG_EMPTY;
 	if (tupleDesc != NULL)
@@ -140,9 +147,10 @@ MakeTupleTableSlot(TupleDesc tupleDesc)
  * --------------------------------
  */
 TupleTableSlot *
-ExecAllocTableSlot(List **tupleTable, TupleDesc desc)
+ExecAllocTableSlot(List **tupleTable, TupleDesc desc,
+				   const TupleTableSlotOps *tts_cb)
 {
-	TupleTableSlot *slot = MakeTupleTableSlot(desc);
+	TupleTableSlot *slot = MakeTupleTableSlot(desc, tts_cb);
 
 	*tupleTable = lappend(*tupleTable, slot);
 
@@ -198,16 +206,17 @@ ExecResetTupleTable(List *tupleTable,	/* tuple table */
 /* --------------------------------
  *		MakeSingleTupleTableSlot
  *
- *		This is a convenience routine for operations that need a
- *		standalone TupleTableSlot not gotten from the main executor
- *		tuple table.  It makes a single slot and initializes it
- *		to use the given tuple descriptor.
+ *		This is a convenience routine for operations that need a standalone
+ *		TupleTableSlot not gotten from the main executor tuple table.  It makes
+ *		a single slot of given TupleTableSlotType and initializes it to use the
+ *		given tuple descriptor.
  * --------------------------------
  */
 TupleTableSlot *
-MakeSingleTupleTableSlot(TupleDesc tupdesc)
+MakeSingleTupleTableSlot(TupleDesc tupdesc,
+						 const TupleTableSlotOps *tts_cb)
 {
-	TupleTableSlot *slot = MakeTupleTableSlot(tupdesc);
+	TupleTableSlot *slot = MakeTupleTableSlot(tupdesc, tts_cb);
 
 	return slot;
 }
@@ -962,13 +971,17 @@ ExecInitResultTypeTL(PlanState *planstate)
  * ----------------
  */
 void
-ExecInitResultSlot(PlanState *planstate)
+ExecInitResultSlot(PlanState *planstate, const TupleTableSlotOps *tts_cb)
 {
 	TupleTableSlot *slot;
 
 	slot = ExecAllocTableSlot(&planstate->state->es_tupleTable,
-							  planstate->ps_ResultTupleDesc);
+							  planstate->ps_ResultTupleDesc, tts_cb);
 	planstate->ps_ResultTupleSlot = slot;
+
+	planstate->resultopsfixed = planstate->ps_ResultTupleDesc != NULL;
+	planstate->resultops = tts_cb;
+	planstate->resultopsset = true;
 }
 
 /* ----------------
@@ -978,10 +991,10 @@ ExecInitResultSlot(PlanState *planstate)
  * ----------------
  */
 void
-ExecInitResultTupleSlotTL(PlanState *planstate)
+ExecInitResultTupleSlotTL(PlanState *planstate, const TupleTableSlotOps *tts_cb)
 {
 	ExecInitResultTypeTL(planstate);
-	ExecInitResultSlot(planstate);
+	ExecInitResultSlot(planstate, tts_cb);
 }
 
 /* ----------------
@@ -989,11 +1002,15 @@ ExecInitResultTupleSlotTL(PlanState *planstate)
  * ----------------
  */
 void
-ExecInitScanTupleSlot(EState *estate, ScanState *scanstate, TupleDesc tupledesc)
+ExecInitScanTupleSlot(EState *estate, ScanState *scanstate,
+					  TupleDesc tupledesc, const TupleTableSlotOps *tts_cb)
 {
 	scanstate->ss_ScanTupleSlot = ExecAllocTableSlot(&estate->es_tupleTable,
-													 tupledesc);
+													 tupledesc, tts_cb);
 	scanstate->ps.scandesc = tupledesc;
+	scanstate->ps.scanopsfixed = tupledesc != NULL;
+	scanstate->ps.scanops = tts_cb;
+	scanstate->ps.scanopsset = true;
 }
 
 /* ----------------
@@ -1005,9 +1022,11 @@ ExecInitScanTupleSlot(EState *estate, ScanState *scanstate, TupleDesc tupledesc)
  * ----------------
  */
 TupleTableSlot *
-ExecInitExtraTupleSlot(EState *estate, TupleDesc tupledesc)
+ExecInitExtraTupleSlot(EState *estate,
+					   TupleDesc tupledesc,
+					   const TupleTableSlotOps *tts_cb)
 {
-	return ExecAllocTableSlot(&estate->es_tupleTable, tupledesc);
+	return ExecAllocTableSlot(&estate->es_tupleTable, tupledesc, tts_cb);
 }
 
 /* ----------------
@@ -1019,9 +1038,10 @@ ExecInitExtraTupleSlot(EState *estate, TupleDesc tupledesc)
  * ----------------
  */
 TupleTableSlot *
-ExecInitNullTupleSlot(EState *estate, TupleDesc tupType)
+ExecInitNullTupleSlot(EState *estate, TupleDesc tupType,
+					  const TupleTableSlotOps *tts_cb)
 {
-	TupleTableSlot *slot = ExecInitExtraTupleSlot(estate, tupType);
+	TupleTableSlot *slot = ExecInitExtraTupleSlot(estate, tupType, tts_cb);
 
 	return ExecStoreAllNullTuple(slot);
 }
@@ -1545,13 +1565,15 @@ HeapTupleHeaderGetDatum(HeapTupleHeader tuple)
  * table function capability. Currently used by EXPLAIN and SHOW ALL.
  */
 TupOutputState *
-begin_tup_output_tupdesc(DestReceiver *dest, TupleDesc tupdesc)
+begin_tup_output_tupdesc(DestReceiver *dest,
+						 TupleDesc tupdesc,
+						 const TupleTableSlotOps *tts_cb)
 {
 	TupOutputState *tstate;
 
 	tstate = (TupOutputState *) palloc(sizeof(TupOutputState));
 
-	tstate->slot = MakeSingleTupleTableSlot(tupdesc);
+	tstate->slot = MakeSingleTupleTableSlot(tupdesc, tts_cb);
 	tstate->dest = dest;
 
 	tstate->dest->rStartup(tstate->dest, (int) CMD_SELECT, tupdesc);
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index f9e7bb479f1..e66b8322069 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -454,6 +454,32 @@ ExecGetResultType(PlanState *planstate)
 	return planstate->ps_ResultTupleDesc;
 }
 
+const TupleTableSlotOps *
+ExecGetResultSlotOps(PlanState *planstate, bool *isfixed)
+{
+	if (planstate->resultopsset && planstate->resultops)
+	{
+		if (isfixed)
+			*isfixed = planstate->resultopsfixed;
+		return planstate->resultops;
+	}
+
+	if (isfixed)
+	{
+		if (planstate->resultopsset)
+			*isfixed = planstate->resultopsfixed;
+		else if (planstate->ps_ResultTupleSlot)
+			*isfixed = TTS_FIXED(planstate->ps_ResultTupleSlot);
+		else
+			*isfixed = false;
+	}
+
+	if (!planstate->ps_ResultTupleSlot)
+		return &TTSOpsVirtual;
+
+	return planstate->ps_ResultTupleSlot->tts_cb;
+}
+
 
 /* ----------------
  *		ExecAssignProjectionInfo
@@ -492,11 +518,21 @@ ExecConditionalAssignProjectionInfo(PlanState *planstate, TupleDesc inputDesc,
 							  planstate->plan->targetlist,
 							  varno,
 							  inputDesc))
+	{
 		planstate->ps_ProjInfo = NULL;
+		planstate->resultopsset = planstate->scanopsset;
+		planstate->resultopsfixed = planstate->scanopsfixed;
+		planstate->resultops = planstate->scanops;
+	}
 	else
 	{
 		if (!planstate->ps_ResultTupleSlot)
-			ExecInitResultSlot(planstate);
+		{
+			ExecInitResultSlot(planstate, &TTSOpsVirtual);
+			planstate->resultops = &TTSOpsVirtual;
+			planstate->resultopsfixed = true;
+			planstate->resultopsset = true;
+		}
 		ExecAssignProjectionInfo(planstate, inputDesc);
 	}
 }
@@ -611,7 +647,9 @@ ExecAssignScanType(ScanState *scanstate, TupleDesc tupDesc)
  * ----------------
  */
 void
-ExecCreateScanSlotFromOuterPlan(EState *estate, ScanState *scanstate)
+ExecCreateScanSlotFromOuterPlan(EState *estate,
+								ScanState *scanstate,
+								const TupleTableSlotOps *tts_cb)
 {
 	PlanState  *outerPlan;
 	TupleDesc	tupDesc;
@@ -619,7 +657,7 @@ ExecCreateScanSlotFromOuterPlan(EState *estate, ScanState *scanstate)
 	outerPlan = outerPlanState(scanstate);
 	tupDesc = ExecGetResultType(outerPlan);
 
-	ExecInitScanTupleSlot(estate, scanstate, tupDesc);
+	ExecInitScanTupleSlot(estate, scanstate, tupDesc, tts_cb);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index f4dd5732198..ae5c7c5490b 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1717,7 +1717,8 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 
 		/* Set up junk filter if needed */
 		if (junkFilter)
-			*junkFilter = ExecInitJunkFilter(tlist, false, NULL);
+			*junkFilter = ExecInitJunkFilter(tlist, false,
+											 MakeSingleTupleTableSlot(NULL, &TTSOpsMinimalTuple));
 	}
 	else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID)
 	{
@@ -1770,7 +1771,12 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 				}
 				/* Set up junk filter if needed */
 				if (junkFilter)
-					*junkFilter = ExecInitJunkFilter(tlist, false, NULL);
+				{
+					TupleTableSlot *slot =
+						MakeSingleTupleTableSlot(NULL, &TTSOpsMinimalTuple);
+
+					*junkFilter = ExecInitJunkFilter(tlist, false, slot);
+				}
 				return false;	/* NOT returning whole tuple */
 			}
 		}
@@ -1786,7 +1792,12 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 			 * what the caller expects will happen at runtime.
 			 */
 			if (junkFilter)
-				*junkFilter = ExecInitJunkFilter(tlist, false, NULL);
+			{
+				TupleTableSlot *slot;
+
+				slot = MakeSingleTupleTableSlot(NULL, &TTSOpsMinimalTuple);
+				*junkFilter = ExecInitJunkFilter(tlist, false, slot);
+			}
 			return true;
 		}
 		Assert(tupdesc);
@@ -1927,9 +1938,14 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 
 		/* Set up junk filter if needed */
 		if (junkFilter)
+		{
+			TupleTableSlot *slot =
+				MakeSingleTupleTableSlot(NULL, &TTSOpsMinimalTuple);
+
 			*junkFilter = ExecInitJunkFilterConversion(tlist,
 													   CreateTupleDescCopy(tupdesc),
-													   NULL);
+													   slot);
+		}
 
 		/* Report that we are returning entire tuple result */
 		return true;
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 85f1ec7140f..20d6d8e9cbb 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -1403,7 +1403,8 @@ find_hash_columns(AggState *aggstate)
 							  &perhash->eqfuncoids,
 							  &perhash->hashfunctions);
 		perhash->hashslot =
-			ExecAllocTableSlot(&estate->es_tupleTable, hashDesc);
+			ExecAllocTableSlot(&estate->es_tupleTable, hashDesc,
+							   &TTSOpsMinimalTuple);
 
 		list_free(hashTlist);
 		bms_free(colnos);
@@ -2211,15 +2212,17 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	/*
 	 * initialize source tuple type.
 	 */
-	ExecCreateScanSlotFromOuterPlan(estate, &aggstate->ss);
+	ExecCreateScanSlotFromOuterPlan(estate, &aggstate->ss,
+									ExecGetResultSlotOps(outerPlanState(&aggstate->ss), NULL));
 	scanDesc = aggstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
 	if (node->chain)
-		aggstate->sort_slot = ExecInitExtraTupleSlot(estate, scanDesc);
+		aggstate->sort_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+													 &TTSOpsMinimalTuple);
 
 	/*
 	 * Initialize result type, slot and projection.
 	 */
-	ExecInitResultTupleSlotTL(&aggstate->ss.ps);
+	ExecInitResultTupleSlotTL(&aggstate->ss.ps, &TTSOpsVirtual);
 	ExecAssignProjectionInfo(&aggstate->ss.ps, NULL);
 
 	/*
@@ -3062,7 +3065,8 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans,
 	{
 		pertrans->sortdesc = ExecTypeFromTL(aggref->args, false);
 		pertrans->sortslot =
-			ExecInitExtraTupleSlot(estate, pertrans->sortdesc);
+			ExecInitExtraTupleSlot(estate, pertrans->sortdesc,
+								   &TTSOpsMinimalTuple);
 	}
 
 	if (numSortCols > 0)
@@ -3084,7 +3088,8 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans,
 		{
 			/* we will need an extra slot to store prior values */
 			pertrans->uniqslot =
-				ExecInitExtraTupleSlot(estate, pertrans->sortdesc);
+				ExecInitExtraTupleSlot(estate, pertrans->sortdesc,
+									   &TTSOpsMinimalTuple);
 		}
 
 		/* Extract the sort information for use later */
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 94a17c7c67c..8c4abe12882 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -196,7 +196,11 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 	/*
 	 * Initialize result tuple type and slot.
 	 */
-	ExecInitResultTupleSlotTL(&appendstate->ps);
+	ExecInitResultTupleSlotTL(&appendstate->ps, &TTSOpsVirtual);
+
+	/* node returns slots from each of its subnodes, therefore not fixed */
+	appendstate->ps.resultopsset = true;
+	appendstate->ps.resultopsfixed = false;
 
 	appendplanstates = (PlanState **) palloc(nplans *
 											 sizeof(PlanState *));
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index c153d74f411..1c27bfc412c 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -913,7 +913,8 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 	 * get the scan type from the relation descriptor.
 	 */
 	ExecInitScanTupleSlot(estate, &scanstate->ss,
-						  RelationGetDescr(currentRelation));
+						  RelationGetDescr(currentRelation),
+						  &TTSOpsBufferTuple);
 
 
 	/*
diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c
index 017b8772775..133aaf2d3b7 100644
--- a/src/backend/executor/nodeCtescan.c
+++ b/src/backend/executor/nodeCtescan.c
@@ -260,7 +260,8 @@ ExecInitCteScan(CteScan *node, EState *estate, int eflags)
 	 * table) is the same as the result rowtype of the CTE query.
 	 */
 	ExecInitScanTupleSlot(estate, &scanstate->ss,
-						  ExecGetResultType(scanstate->cteplanstate));
+						  ExecGetResultType(scanstate->cteplanstate),
+						  &TTSOpsMinimalTuple);
 
 	/*
 	 * Initialize result type and projection.
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
index ab3e34790e8..a015d72bf92 100644
--- a/src/backend/executor/nodeCustom.c
+++ b/src/backend/executor/nodeCustom.c
@@ -66,20 +66,24 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
 	/*
 	 * Determine the scan tuple type.  If the custom scan provider provided a
 	 * targetlist describing the scan tuples, use that; else use base
-	 * relation's rowtype.
+	 * relation's rowtype. XXX: Probably we should let BeginCustomScan create
+	 * scan tuple slot with appropriate type of slot. But that means the
+	 * existing custom scan APIs need to be updated. Having a virtual tuple
+	 * table slot works since a custom scan can always decompose a tuple into
+	 * attributes and fill up the slot.
 	 */
 	if (cscan->custom_scan_tlist != NIL || scan_rel == NULL)
 	{
 		TupleDesc	scan_tupdesc;
 
 		scan_tupdesc = ExecTypeFromTL(cscan->custom_scan_tlist, false);
-		ExecInitScanTupleSlot(estate, &css->ss, scan_tupdesc);
+		ExecInitScanTupleSlot(estate, &css->ss, scan_tupdesc, &TTSOpsVirtual);
 		/* Node's targetlist will contain Vars with varno = INDEX_VAR */
 		tlistvarno = INDEX_VAR;
 	}
 	else
 	{
-		ExecInitScanTupleSlot(estate, &css->ss, RelationGetDescr(scan_rel));
+		ExecInitScanTupleSlot(estate, &css->ss, RelationGetDescr(scan_rel), &TTSOpsVirtual);
 		/* Node's targetlist will contain Vars with varno = scanrelid */
 		tlistvarno = scanrelid;
 	}
@@ -87,7 +91,7 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(&css->ss.ps);
+	ExecInitResultTupleSlotTL(&css->ss.ps, &TTSOpsVirtual);
 	ExecAssignScanProjectionInfoWithVarno(&css->ss, tlistvarno);
 
 	/* initialize child expressions */
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index f7eef32f6fe..a2ab2d265b3 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -180,7 +180,8 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 		TupleDesc	scan_tupdesc;
 
 		scan_tupdesc = ExecTypeFromTL(node->fdw_scan_tlist, false);
-		ExecInitScanTupleSlot(estate, &scanstate->ss, scan_tupdesc);
+		ExecInitScanTupleSlot(estate, &scanstate->ss, scan_tupdesc,
+							  &TTSOpsHeapTuple);
 		/* Node's targetlist will contain Vars with varno = INDEX_VAR */
 		tlistvarno = INDEX_VAR;
 	}
@@ -190,11 +191,16 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 
 		/* don't trust FDWs to return tuples fulfilling NOT NULL constraints */
 		scan_tupdesc = CreateTupleDescCopy(RelationGetDescr(currentRelation));
-		ExecInitScanTupleSlot(estate, &scanstate->ss, scan_tupdesc);
+		ExecInitScanTupleSlot(estate, &scanstate->ss, scan_tupdesc,
+							  &TTSOpsHeapTuple);
 		/* Node's targetlist will contain Vars with varno = scanrelid */
 		tlistvarno = scanrelid;
 	}
 
+	/* Don't know what an FDW might return */
+	scanstate->ss.ps.scanopsfixed = false;
+	scanstate->ss.ps.scanopsset = true;
+
 	/*
 	 * Initialize result slot, type and projection.
 	 */
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index 0596adbb2f1..b6a1fa14560 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -424,7 +424,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 		 */
 		if (!scanstate->simple)
 		{
-			fs->func_slot = ExecInitExtraTupleSlot(estate, fs->tupdesc);
+			fs->func_slot = ExecInitExtraTupleSlot(estate, fs->tupdesc,
+												   &TTSOpsMinimalTuple);
 		}
 		else
 			fs->func_slot = NULL;
@@ -482,7 +483,8 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	/*
 	 * Initialize scan slot and type.
 	 */
-	ExecInitScanTupleSlot(estate, &scanstate->ss, scan_tupdesc);
+	ExecInitScanTupleSlot(estate, &scanstate->ss, scan_tupdesc,
+						  &TTSOpsMinimalTuple);
 
 	/*
 	 * Initialize result slot, type and projection.
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index afddb0a0394..e45e07f0a1a 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -91,6 +91,11 @@ ExecInitGather(Gather *node, EState *estate, int eflags)
 	outerPlanState(gatherstate) = ExecInitNode(outerNode, estate, eflags);
 	tupDesc = ExecGetResultType(outerPlanState(gatherstate));
 
+	/* this node uses tuples from the tuple queue as scan slot */
+	gatherstate->ps.scanops = &TTSOpsHeapTuple;
+	gatherstate->ps.scanopsfixed = true;
+	gatherstate->ps.scanopsset = true;
+
 	/*
 	 * Initialize result type and projection.
 	 */
@@ -100,7 +105,8 @@ ExecInitGather(Gather *node, EState *estate, int eflags)
 	/*
 	 * Initialize funnel slot to same tuple descriptor as outer plan.
 	 */
-	gatherstate->funnel_slot = ExecInitExtraTupleSlot(estate, tupDesc);
+	gatherstate->funnel_slot = ExecInitExtraTupleSlot(estate, tupDesc,
+													  &TTSOpsHeapTuple);
 
 	/*
 	 * Gather doesn't support checking a qual (it's always more efficient to
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index 7ae067f9ebf..90aa7e42d50 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -122,6 +122,13 @@ ExecInitGatherMerge(GatherMerge *node, EState *estate, int eflags)
 	ExecInitResultTypeTL(&gm_state->ps);
 	ExecConditionalAssignProjectionInfo(&gm_state->ps, tupDesc, OUTER_VAR);
 
+	/* leader accesses ExecProcNode result directly, others go through tuple queue */
+	if (gm_state->ps.ps_ProjInfo == NULL)
+	{
+		gm_state->ps.resultopsset = true;
+		gm_state->ps.resultopsfixed = false;
+	}
+
 	/*
 	 * initialize sort-key information
 	 */
@@ -404,7 +411,8 @@ gather_merge_setup(GatherMergeState *gm_state)
 
 		/* Initialize tuple slot for worker */
 		gm_state->gm_slots[i + 1] =
-			ExecInitExtraTupleSlot(gm_state->ps.state, gm_state->tupDesc);
+			ExecInitExtraTupleSlot(gm_state->ps.state, gm_state->tupDesc,
+				&TTSOpsHeapTuple);
 	}
 
 	/* Allocate the resources for the merge */
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index 9c1e51bc954..124753618a6 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -188,12 +188,13 @@ ExecInitGroup(Group *node, EState *estate, int eflags)
 	/*
 	 * Initialize scan slot and type.
 	 */
-	ExecCreateScanSlotFromOuterPlan(estate, &grpstate->ss);
+	ExecCreateScanSlotFromOuterPlan(estate, &grpstate->ss,
+									ExecGetResultSlotOps(outerPlanState(&grpstate->ss), NULL));
 
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(&grpstate->ss.ps);
+	ExecInitResultTupleSlotTL(&grpstate->ss.ps, &TTSOpsVirtual);
 	ExecAssignProjectionInfo(&grpstate->ss.ps, NULL);
 
 	/*
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 5a9f1ea3c55..ba2f6686cf6 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -382,7 +382,7 @@ ExecInitHash(Hash *node, EState *estate, int eflags)
 	 * initialize our result slot and type. No need to build projection
 	 * because this node doesn't do projections.
 	 */
-	ExecInitResultTupleSlotTL(&hashstate->ps);
+	ExecInitResultTupleSlotTL(&hashstate->ps, &TTSOpsMinimalTuple);
 	hashstate->ps.ps_ProjInfo = NULL;
 
 	/*
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index d6a6ef770dd..d9afd0bdded 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -650,13 +650,14 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(&hjstate->js.ps);
+	ExecInitResultTupleSlotTL(&hjstate->js.ps, &TTSOpsVirtual);
 	ExecAssignProjectionInfo(&hjstate->js.ps, NULL);
 
 	/*
 	 * tuple table initialization
 	 */
-	hjstate->hj_OuterTupleSlot = ExecInitExtraTupleSlot(estate, outerDesc);
+	hjstate->hj_OuterTupleSlot = ExecInitExtraTupleSlot(estate, outerDesc,
+														ExecGetResultSlotOps(outerPlanState(hjstate), NULL));
 
 	/*
 	 * detect whether we need only consider the first matching inner tuple
@@ -673,17 +674,17 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 		case JOIN_LEFT:
 		case JOIN_ANTI:
 			hjstate->hj_NullInnerTupleSlot =
-				ExecInitNullTupleSlot(estate, innerDesc);
+				ExecInitNullTupleSlot(estate, innerDesc, &TTSOpsVirtual);
 			break;
 		case JOIN_RIGHT:
 			hjstate->hj_NullOuterTupleSlot =
-				ExecInitNullTupleSlot(estate, outerDesc);
+				ExecInitNullTupleSlot(estate, outerDesc, &TTSOpsVirtual);
 			break;
 		case JOIN_FULL:
 			hjstate->hj_NullOuterTupleSlot =
-				ExecInitNullTupleSlot(estate, outerDesc);
+				ExecInitNullTupleSlot(estate, outerDesc, &TTSOpsVirtual);
 			hjstate->hj_NullInnerTupleSlot =
-				ExecInitNullTupleSlot(estate, innerDesc);
+				ExecInitNullTupleSlot(estate, innerDesc, &TTSOpsVirtual);
 			break;
 		default:
 			elog(ERROR, "unrecognized join type: %d",
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index 865a056c027..4e5e52cec3b 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -527,7 +527,7 @@ ExecInitIndexOnlyScan(IndexOnlyScan *node, EState *estate, int eflags)
 	 * suitable data anyway.)
 	 */
 	tupDesc = ExecTypeFromTL(node->indextlist, false);
-	ExecInitScanTupleSlot(estate, &indexstate->ss, tupDesc);
+	ExecInitScanTupleSlot(estate, &indexstate->ss, tupDesc, &TTSOpsHeapTuple);
 
 	/*
 	 * Initialize result type and projection info.  The node's targetlist will
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 8593c0e3050..83038e31f66 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -945,7 +945,8 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
 	 * get the scan type from the relation descriptor.
 	 */
 	ExecInitScanTupleSlot(estate, &indexstate->ss,
-						  RelationGetDescr(currentRelation));
+						  RelationGetDescr(currentRelation),
+						  &TTSOpsBufferTuple); /* FIXME: wrong for reorder case */
 
 	/*
 	 * Initialize result type and projection.
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index f0b68191400..6792f9e86c7 100644
--- a/src/backend/executor/nodeLimit.c
+++ b/src/backend/executor/nodeLimit.c
@@ -380,6 +380,10 @@ ExecInitLimit(Limit *node, EState *estate, int eflags)
 	 */
 	ExecInitResultTypeTL(&limitstate->ps);
 
+	limitstate->ps.resultopsset = true;
+	limitstate->ps.resultops = ExecGetResultSlotOps(outerPlanState(limitstate),
+													&limitstate->ps.resultopsfixed);
+
 	/*
 	 * limit nodes do no projections, so initialize projection info for this
 	 * node appropriately
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 961798cecb3..7887388b9e9 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -390,6 +390,11 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
 	 */
 	outerPlanState(lrstate) = ExecInitNode(outerPlan, estate, eflags);
 
+	/* node returns unmodified slots from the outer plan */
+	lrstate->ps.resultopsset = true;
+	lrstate->ps.resultops = ExecGetResultSlotOps(outerPlanState(lrstate),
+													&lrstate->ps.resultopsfixed);
+
 	/*
 	 * LockRows nodes do no projections, so initialize projection info for
 	 * this node appropriately
diff --git a/src/backend/executor/nodeMaterial.c b/src/backend/executor/nodeMaterial.c
index 4ede428f908..657814cd0db 100644
--- a/src/backend/executor/nodeMaterial.c
+++ b/src/backend/executor/nodeMaterial.c
@@ -146,10 +146,8 @@ ExecMaterial(PlanState *pstate)
 		if (tuplestorestate)
 			tuplestore_puttupleslot(tuplestorestate, outerslot);
 
-		/*
-		 * We can just return the subplan's returned tuple, without copying.
-		 */
-		return outerslot;
+		ExecCopySlot(slot, outerslot);
+		return slot;
 	}
 
 	/*
@@ -223,13 +221,13 @@ ExecInitMaterial(Material *node, EState *estate, int eflags)
 	 *
 	 * material nodes only return tuples from their materialized relation.
 	 */
-	ExecInitResultTupleSlotTL(&matstate->ss.ps);
+	ExecInitResultTupleSlotTL(&matstate->ss.ps, &TTSOpsMinimalTuple);
 	matstate->ss.ps.ps_ProjInfo = NULL;
 
 	/*
 	 * initialize tuple type.
 	 */
-	ExecCreateScanSlotFromOuterPlan(estate, &matstate->ss);
+	ExecCreateScanSlotFromOuterPlan(estate, &matstate->ss, &TTSOpsMinimalTuple);
 
 	return matstate;
 }
diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c
index dbed667d164..188e76b60cd 100644
--- a/src/backend/executor/nodeMergeAppend.c
+++ b/src/backend/executor/nodeMergeAppend.c
@@ -167,7 +167,11 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 	 * MergeAppend nodes do have Result slots, which hold pointers to tuples,
 	 * so we have to initialize them.  FIXME
 	 */
-	ExecInitResultTupleSlotTL(&mergestate->ps);
+	ExecInitResultTupleSlotTL(&mergestate->ps, &TTSOpsVirtual);
+
+	/* node returns slots from each of its subnodes, therefore not fixed */
+	mergestate->ps.resultopsset = true;
+	mergestate->ps.resultopsfixed = false;
 
 	/*
 	 * call ExecInitNode on each of the valid plans to be executed and save
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index 9c978313318..1c90291d127 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -1438,6 +1438,7 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 	MergeJoinState *mergestate;
 	TupleDesc	outerDesc,
 				innerDesc;
+	const TupleTableSlotOps *innerOps;
 
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
@@ -1512,13 +1513,15 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(&mergestate->js.ps);
+	ExecInitResultTupleSlotTL(&mergestate->js.ps, &TTSOpsVirtual);
 	ExecAssignProjectionInfo(&mergestate->js.ps, NULL);
 
 	/*
 	 * tuple table initialization
 	 */
-	mergestate->mj_MarkedTupleSlot = ExecInitExtraTupleSlot(estate, innerDesc);
+	innerOps = ExecGetResultSlotOps(innerPlanState(mergestate), NULL);
+	mergestate->mj_MarkedTupleSlot = ExecInitExtraTupleSlot(estate, innerDesc,
+															innerOps);
 
 	/*
 	 * initialize child expressions
@@ -1548,13 +1551,13 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 			mergestate->mj_FillOuter = true;
 			mergestate->mj_FillInner = false;
 			mergestate->mj_NullInnerTupleSlot =
-				ExecInitNullTupleSlot(estate, innerDesc);
+				ExecInitNullTupleSlot(estate, innerDesc, &TTSOpsVirtual);
 			break;
 		case JOIN_RIGHT:
 			mergestate->mj_FillOuter = false;
 			mergestate->mj_FillInner = true;
 			mergestate->mj_NullOuterTupleSlot =
-				ExecInitNullTupleSlot(estate, outerDesc);
+				ExecInitNullTupleSlot(estate, outerDesc, &TTSOpsVirtual);
 
 			/*
 			 * Can't handle right or full join with non-constant extra
@@ -1570,9 +1573,9 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 			mergestate->mj_FillOuter = true;
 			mergestate->mj_FillInner = true;
 			mergestate->mj_NullOuterTupleSlot =
-				ExecInitNullTupleSlot(estate, outerDesc);
+				ExecInitNullTupleSlot(estate, outerDesc, &TTSOpsVirtual);
 			mergestate->mj_NullInnerTupleSlot =
-				ExecInitNullTupleSlot(estate, innerDesc);
+				ExecInitNullTupleSlot(estate, innerDesc, &TTSOpsVirtual);
 
 			/*
 			 * Can't handle right or full join with non-constant extra
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index ca7ece3235f..9cc499d6533 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -2407,7 +2407,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		mtstate->ps.plan->targetlist = (List *) linitial(node->returningLists);
 
 		/* Set up a slot for the output of the RETURNING projection(s) */
-		ExecInitResultTupleSlotTL(&mtstate->ps);
+		ExecInitResultTupleSlotTL(&mtstate->ps, &TTSOpsVirtual);
 		slot = mtstate->ps.ps_ResultTupleSlot;
 
 		/* Need an econtext too */
@@ -2476,7 +2476,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		mtstate->mt_existing =
 			ExecInitExtraTupleSlot(mtstate->ps.state,
 								   mtstate->mt_partition_tuple_routing ?
-								   NULL : relationDesc);
+								   NULL : relationDesc, &TTSOpsBufferTuple);
 
 		/* carried forward solely for the benefit of explain */
 		mtstate->mt_excludedtlist = node->exclRelTlist;
@@ -2498,7 +2498,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		mtstate->mt_conflproj =
 			ExecInitExtraTupleSlot(mtstate->ps.state,
 								   mtstate->mt_partition_tuple_routing ?
-								   NULL : tupDesc);
+								   NULL : tupDesc, &TTSOpsHeapTuple);
 		resultRelInfo->ri_onConflict->oc_ProjTupdesc = tupDesc;
 
 		/* build UPDATE SET projection state */
@@ -2609,7 +2609,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 				j = ExecInitJunkFilter(subplan->targetlist,
 									   resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
-									   ExecInitExtraTupleSlot(estate, NULL));
+									   ExecInitExtraTupleSlot(estate, NULL, &TTSOpsHeapTuple));
 
 				if (operation == CMD_UPDATE || operation == CMD_DELETE)
 				{
@@ -2656,10 +2656,12 @@ 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.
+	 * 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);
+		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/executor/nodeNamedtuplestorescan.c b/src/backend/executor/nodeNamedtuplestorescan.c
index cf1b7b4f872..cd8a1fceac5 100644
--- a/src/backend/executor/nodeNamedtuplestorescan.c
+++ b/src/backend/executor/nodeNamedtuplestorescan.c
@@ -137,7 +137,8 @@ ExecInitNamedTuplestoreScan(NamedTuplestoreScan *node, EState *estate, int eflag
 	/*
 	 * The scan tuple type is specified for the tuplestore.
 	 */
-	ExecInitScanTupleSlot(estate, &scanstate->ss, scanstate->tupdesc);
+	ExecInitScanTupleSlot(estate, &scanstate->ss, scanstate->tupdesc,
+						  &TTSOpsMinimalTuple);
 
 	/*
 	 * Initialize result type and projection.
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 8dbec685eb1..0cb6f9dd4cd 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -304,7 +304,7 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(&nlstate->js.ps);
+	ExecInitResultTupleSlotTL(&nlstate->js.ps, &TTSOpsVirtual);
 	ExecAssignProjectionInfo(&nlstate->js.ps, NULL);
 
 	/*
@@ -332,7 +332,8 @@ ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
 		case JOIN_ANTI:
 			nlstate->nl_NullInnerTupleSlot =
 				ExecInitNullTupleSlot(estate,
-									  ExecGetResultType(innerPlanState(nlstate)));
+									  ExecGetResultType(innerPlanState(nlstate)),
+									  &TTSOpsVirtual);
 			break;
 		default:
 			elog(ERROR, "unrecognized join type: %d",
diff --git a/src/backend/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c
index e4dd4142177..06a7da8f77e 100644
--- a/src/backend/executor/nodeProjectSet.c
+++ b/src/backend/executor/nodeProjectSet.c
@@ -256,7 +256,7 @@ ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags)
 	/*
 	 * tuple table and result type initialization
 	 */
-	ExecInitResultTupleSlotTL(&state->ps);
+	ExecInitResultTupleSlotTL(&state->ps, &TTSOpsVirtual);
 
 	/* Create workspace for per-tlist-entry expr state & SRF-is-done state */
 	state->nelems = list_length(node->plan.targetlist);
diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c
index 2bbb2e78848..63428c7ffe0 100644
--- a/src/backend/executor/nodeResult.c
+++ b/src/backend/executor/nodeResult.c
@@ -217,7 +217,7 @@ ExecInitResult(Result *node, EState *estate, int eflags)
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(&resstate->ps);
+	ExecInitResultTupleSlotTL(&resstate->ps, &TTSOpsVirtual);
 	ExecAssignProjectionInfo(&resstate->ps, NULL);
 
 	/*
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index cfa26535d7b..55e7bd2f6cf 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -146,7 +146,8 @@ ExecInitSampleScan(SampleScan *node, EState *estate, int eflags)
 
 	/* and create slot with appropriate rowtype */
 	ExecInitScanTupleSlot(estate, &scanstate->ss,
-						  RelationGetDescr(scanstate->ss.ss_currentRelation));
+						  RelationGetDescr(scanstate->ss.ss_currentRelation),
+						  &TTSOpsBufferTuple);
 
 	/*
 	 * Initialize result type and projection.
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index b4bea67610f..307ad9ccd53 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -172,7 +172,8 @@ ExecInitSeqScan(SeqScan *node, EState *estate, int eflags)
 
 	/* and create slot with the appropriate rowtype */
 	ExecInitScanTupleSlot(estate, &scanstate->ss,
-						  RelationGetDescr(scanstate->ss.ss_currentRelation));
+						  RelationGetDescr(scanstate->ss.ss_currentRelation),
+						  &TTSOpsBufferTuple);
 
 	/*
 	 * Initialize result type and projection.
diff --git a/src/backend/executor/nodeSetOp.c b/src/backend/executor/nodeSetOp.c
index 46bf77775c4..44c43e99a02 100644
--- a/src/backend/executor/nodeSetOp.c
+++ b/src/backend/executor/nodeSetOp.c
@@ -532,7 +532,9 @@ ExecInitSetOp(SetOp *node, EState *estate, int eflags)
 	 * Initialize result slot and type. Setop nodes do no projections, so
 	 * initialize projection info for this node appropriately.
 	 */
-	ExecInitResultTupleSlotTL(&setopstate->ps);
+	ExecInitResultTupleSlotTL(&setopstate->ps,
+							  node->strategy == SETOP_HASHED ?
+							  &TTSOpsMinimalTuple : &TTSOpsHeapTuple);
 	setopstate->ps.ps_ProjInfo = NULL;
 
 	/*
diff --git a/src/backend/executor/nodeSort.c b/src/backend/executor/nodeSort.c
index 5492cd45579..750292b1014 100644
--- a/src/backend/executor/nodeSort.c
+++ b/src/backend/executor/nodeSort.c
@@ -211,13 +211,13 @@ ExecInitSort(Sort *node, EState *estate, int eflags)
 	/*
 	 * Initialize scan slot and type.
 	 */
-	ExecCreateScanSlotFromOuterPlan(estate, &sortstate->ss);
+	ExecCreateScanSlotFromOuterPlan(estate, &sortstate->ss, &TTSOpsVirtual);
 
 	/*
 	 * Initialize return slot and type. No need to initialize projection info
 	 * because this node doesn't do projections.
 	 */
-	ExecInitResultTupleSlotTL(&sortstate->ss.ps);
+	ExecInitResultTupleSlotTL(&sortstate->ss.ps, &TTSOpsMinimalTuple);
 	sortstate->ss.ps.ps_ProjInfo = NULL;
 
 	SO1_printf("ExecInitSort: %s\n",
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 63de981034d..62811ed0302 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -968,7 +968,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 		 * own innerecontext.
 		 */
 		tupDescLeft = ExecTypeFromTL(lefttlist, false);
-		slot = ExecInitExtraTupleSlot(estate, tupDescLeft);
+		slot = ExecInitExtraTupleSlot(estate, tupDescLeft, &TTSOpsVirtual);
 		sstate->projLeft = ExecBuildProjectionInfo(lefttlist,
 												   NULL,
 												   slot,
@@ -976,7 +976,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 												   NULL);
 
 		sstate->descRight = tupDescRight = ExecTypeFromTL(righttlist, false);
-		slot = ExecInitExtraTupleSlot(estate, tupDescRight);
+		slot = ExecInitExtraTupleSlot(estate, tupDescRight, &TTSOpsVirtual);
 		sstate->projRight = ExecBuildProjectionInfo(righttlist,
 													sstate->innerecontext,
 													slot,
diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c
index b84c6892d50..71a5711362b 100644
--- a/src/backend/executor/nodeSubqueryscan.c
+++ b/src/backend/executor/nodeSubqueryscan.c
@@ -129,7 +129,18 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, int eflags)
 	 * Initialize scan slot and type (needed by ExecAssignScanProjectionInfo)
 	 */
 	ExecInitScanTupleSlot(estate, &subquerystate->ss,
-						  ExecGetResultType(subquerystate->subplan));
+						  ExecGetResultType(subquerystate->subplan),
+						  ExecGetResultSlotOps(subquerystate->subplan, NULL));
+	/*
+	 * The slot used as the scantuple isn't the slot above (outside of EPQ),
+	 * therefore only mark it as fixed if node below is.
+	 */
+	subquerystate->ss.ps.scanopsfixed = subquerystate->subplan->resultopsfixed;
+	subquerystate->ss.ps.scanopsset = subquerystate->subplan->resultopsset;
+
+	subquerystate->ss.ps.resultops = subquerystate->subplan->resultops;
+	subquerystate->ss.ps.resultopsfixed = subquerystate->subplan->resultopsfixed;
+	subquerystate->ss.ps.resultopsset = subquerystate->subplan->resultopsset;
 
 	/*
 	 * Initialize result type and projection.
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c
index b0c94d7e063..a79e4cb2cf5 100644
--- a/src/backend/executor/nodeTableFuncscan.c
+++ b/src/backend/executor/nodeTableFuncscan.c
@@ -147,7 +147,8 @@ ExecInitTableFuncScan(TableFuncScan *node, EState *estate, int eflags)
 								 tf->coltypmods,
 								 tf->colcollations);
 	/* and the corresponding scan slot */
-	ExecInitScanTupleSlot(estate, &scanstate->ss, tupdesc);
+	ExecInitScanTupleSlot(estate, &scanstate->ss, tupdesc,
+						  &TTSOpsMinimalTuple);
 
 	/*
 	 * Initialize result type and projection.
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index bc859e3d516..939ece2faa1 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -543,7 +543,8 @@ ExecInitTidScan(TidScan *node, EState *estate, int eflags)
 	 * get the scan type from the relation descriptor.
 	 */
 	ExecInitScanTupleSlot(estate, &tidstate->ss,
-						  RelationGetDescr(currentRelation));
+						  RelationGetDescr(currentRelation),
+						  &TTSOpsBufferTuple);
 
 	/*
 	 * Initialize result type and projection.
diff --git a/src/backend/executor/nodeUnique.c b/src/backend/executor/nodeUnique.c
index c791f89b48c..c5e4232e68c 100644
--- a/src/backend/executor/nodeUnique.c
+++ b/src/backend/executor/nodeUnique.c
@@ -141,7 +141,7 @@ ExecInitUnique(Unique *node, EState *estate, int eflags)
 	 * Initialize result slot and type. Unique nodes do no projections, so
 	 * initialize projection info for this node appropriately.
 	 */
-	ExecInitResultTupleSlotTL(&uniquestate->ps);
+	ExecInitResultTupleSlotTL(&uniquestate->ps, &TTSOpsMinimalTuple);
 	uniquestate->ps.ps_ProjInfo = NULL;
 
 	/*
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
index fa49d0470fc..6351cdac27a 100644
--- a/src/backend/executor/nodeValuesscan.c
+++ b/src/backend/executor/nodeValuesscan.c
@@ -261,7 +261,7 @@ ExecInitValuesScan(ValuesScan *node, EState *estate, int eflags)
 	 * Get info about values list, initialize scan slot with it.
 	 */
 	tupdesc = ExecTypeFromExprList((List *) linitial(node->values_lists));
-	ExecInitScanTupleSlot(estate, &scanstate->ss, tupdesc);
+	ExecInitScanTupleSlot(estate, &scanstate->ss, tupdesc, &TTSOpsVirtual);
 
 	/*
 	 * Initialize result type and projection.
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 6e597e82856..9cb99cab72c 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2316,16 +2316,25 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	 * initialize source tuple type (which is also the tuple type that we'll
 	 * store in the tuplestore and use in all our working slots).
 	 */
-	ExecCreateScanSlotFromOuterPlan(estate, &winstate->ss);
+	ExecCreateScanSlotFromOuterPlan(estate, &winstate->ss, &TTSOpsMinimalTuple);
 	scanDesc = winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
 
+	/* the outer tuple isn't the child's tuple, but always a minimal tuple */
+	winstate->ss.ps.leftopsset = true;
+	winstate->ss.ps.leftops = &TTSOpsMinimalTuple;
+	winstate->ss.ps.leftopsfixed = true;
+
 	/*
 	 * tuple table initialization
 	 */
-	winstate->first_part_slot = ExecInitExtraTupleSlot(estate, scanDesc);
-	winstate->agg_row_slot = ExecInitExtraTupleSlot(estate, scanDesc);
-	winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate, scanDesc);
-	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc);
+	winstate->first_part_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+													   &TTSOpsMinimalTuple);
+	winstate->agg_row_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+													&TTSOpsMinimalTuple);
+	winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate, scanDesc,
+												   &TTSOpsMinimalTuple);
+	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
+												   &TTSOpsMinimalTuple);
 
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
@@ -2339,17 +2348,19 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 		if (((frameOptions & FRAMEOPTION_START_CURRENT_ROW) &&
 			 node->ordNumCols != 0) ||
 			(frameOptions & FRAMEOPTION_START_OFFSET))
-			winstate->framehead_slot = ExecInitExtraTupleSlot(estate, scanDesc);
+			winstate->framehead_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+															  &TTSOpsMinimalTuple);
 		if (((frameOptions & FRAMEOPTION_END_CURRENT_ROW) &&
 			 node->ordNumCols != 0) ||
 			(frameOptions & FRAMEOPTION_END_OFFSET))
-			winstate->frametail_slot = ExecInitExtraTupleSlot(estate, scanDesc);
+			winstate->frametail_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+															  &TTSOpsMinimalTuple);
 	}
 
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(&winstate->ss.ps);
+	ExecInitResultTupleSlotTL(&winstate->ss.ps, &TTSOpsVirtual);
 	ExecAssignProjectionInfo(&winstate->ss.ps, NULL);
 
 	/* Set up data for comparing tuples */
diff --git a/src/backend/executor/nodeWorktablescan.c b/src/backend/executor/nodeWorktablescan.c
index 1ce8ae9f026..a432926fe15 100644
--- a/src/backend/executor/nodeWorktablescan.c
+++ b/src/backend/executor/nodeWorktablescan.c
@@ -160,7 +160,12 @@ ExecInitWorkTableScan(WorkTableScan *node, EState *estate, int eflags)
 	 * tuple table initialization
 	 */
 	ExecInitResultTypeTL(&scanstate->ss.ps);
-	ExecInitScanTupleSlot(estate, &scanstate->ss, NULL);
+
+	/* signal that return type is not yet known */
+	scanstate->ss.ps.resultopsset = true;
+	scanstate->ss.ps.resultopsfixed = false;
+
+	ExecInitScanTupleSlot(estate, &scanstate->ss, NULL, &TTSOpsMinimalTuple);
 
 	/*
 	 * initialize child expressions
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index c94f73aadc1..1d6d1b31a2d 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -705,7 +705,7 @@ check_default_partition_contents(Relation parent, Relation default_rel,
 		econtext = GetPerTupleExprContext(estate);
 		snapshot = RegisterSnapshot(GetLatestSnapshot());
 		scan = heap_beginscan(part_rel, snapshot, 0, NULL);
-		tupslot = MakeSingleTupleTableSlot(tupdesc);
+		tupslot = MakeSingleTupleTableSlot(tupdesc, &TTSOpsHeapTuple);
 
 		/*
 		 * Switch to per-tuple memory context and reset it for each tuple
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 6e420d893cf..9e682331d2f 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -685,7 +685,7 @@ fetch_remote_table_info(char *nspname, char *relname,
 				(errmsg("could not fetch table info for table \"%s.%s\" from publisher: %s",
 						nspname, relname, res->err)));
 
-	slot = MakeSingleTupleTableSlot(res->tupledesc);
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
 	if (!tuplestore_gettupleslot(res->tuplestore, true, false, slot))
 		ereport(ERROR,
 				(errmsg("table \"%s.%s\" not found on publisher",
@@ -727,7 +727,7 @@ fetch_remote_table_info(char *nspname, char *relname,
 	lrel->attkeys = NULL;
 
 	natt = 0;
-	slot = MakeSingleTupleTableSlot(res->tupledesc);
+	slot = MakeSingleTupleTableSlot(res->tupledesc, &TTSOpsMinimalTuple);
 	while (tuplestore_gettupleslot(res->tuplestore, true, false, slot))
 	{
 		lrel->attnames[natt] =
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 277da69fa6c..fa17e1b3bb1 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -213,7 +213,8 @@ create_estate_for_relation(LogicalRepRelMapEntry *rel)
 
 	/* Triggers might need a slot */
 	if (resultRelInfo->ri_TrigDesc)
-		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL);
+		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL,
+															&TTSOpsVirtual);
 
 	/* Prepare to catch AFTER triggers. */
 	AfterTriggerBeginQuery();
@@ -609,7 +610,8 @@ apply_handle_insert(StringInfo s)
 	/* Initialize the executor state. */
 	estate = create_estate_for_relation(rel);
 	remoteslot = ExecInitExtraTupleSlot(estate,
-										RelationGetDescr(rel->localrel));
+										RelationGetDescr(rel->localrel),
+										&TTSOpsHeapTuple);
 
 	/* Input functions may need an active snapshot, so get one */
 	PushActiveSnapshot(GetTransactionSnapshot());
@@ -715,9 +717,11 @@ apply_handle_update(StringInfo s)
 	/* Initialize the executor state. */
 	estate = create_estate_for_relation(rel);
 	remoteslot = ExecInitExtraTupleSlot(estate,
-										RelationGetDescr(rel->localrel));
+										RelationGetDescr(rel->localrel),
+										&TTSOpsHeapTuple);
 	localslot = ExecInitExtraTupleSlot(estate,
-									   RelationGetDescr(rel->localrel));
+									   RelationGetDescr(rel->localrel),
+									   &TTSOpsHeapTuple);
 	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1);
 
 	PushActiveSnapshot(GetTransactionSnapshot());
@@ -756,7 +760,8 @@ apply_handle_update(StringInfo s)
 	{
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-		ExecStoreHeapTuple(localslot->tts_tuple, remoteslot, false);
+		ExecStoreHeapTuple(ExecFetchSlotHeapTuple(localslot, false, NULL), remoteslot,
+						   false);
 		slot_modify_cstrings(remoteslot, rel, newtup.values, newtup.changed);
 		MemoryContextSwitchTo(oldctx);
 
@@ -833,9 +838,11 @@ apply_handle_delete(StringInfo s)
 	/* Initialize the executor state. */
 	estate = create_estate_for_relation(rel);
 	remoteslot = ExecInitExtraTupleSlot(estate,
-										RelationGetDescr(rel->localrel));
+										RelationGetDescr(rel->localrel),
+										&TTSOpsVirtual);
 	localslot = ExecInitExtraTupleSlot(estate,
-									   RelationGetDescr(rel->localrel));
+									   RelationGetDescr(rel->localrel),
+									   &TTSOpsHeapTuple);
 	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1);
 
 	PushActiveSnapshot(GetTransactionSnapshot());
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 2683385ca6e..39337d2f1f8 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -403,7 +403,7 @@ IdentifySystem(void)
 							  TEXTOID, -1, 0);
 
 	/* prepare for projection of tuples */
-	tstate = begin_tup_output_tupdesc(dest, tupdesc);
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
 
 	/* column 1: system identifier */
 	values[0] = CStringGetTextDatum(sysid);
@@ -735,7 +735,7 @@ StartReplication(StartReplicationCmd *cmd)
 								  TEXTOID, -1, 0);
 
 		/* prepare for projection of tuple */
-		tstate = begin_tup_output_tupdesc(dest, tupdesc);
+		tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
 
 		values[0] = Int64GetDatum((int64) sendTimeLineNextTLI);
 		values[1] = CStringGetTextDatum(startpos_str);
@@ -1007,7 +1007,7 @@ CreateReplicationSlot(CreateReplicationSlotCmd *cmd)
 							  TEXTOID, -1, 0);
 
 	/* prepare for projection of tuples */
-	tstate = begin_tup_output_tupdesc(dest, tupdesc);
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
 
 	/* slot_name */
 	slot_name = NameStr(MyReplicationSlot->data.name);
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 66cc5c35c68..b44438bf088 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -1071,7 +1071,7 @@ RunFromStore(Portal portal, ScanDirection direction, uint64 count,
 	uint64		current_tuple_count = 0;
 	TupleTableSlot *slot;
 
-	slot = MakeSingleTupleTableSlot(portal->tupDesc);
+	slot = MakeSingleTupleTableSlot(portal->tupDesc, &TTSOpsMinimalTuple);
 
 	dest->rStartup(dest, CMD_SELECT, portal->tupDesc);
 
diff --git a/src/backend/utils/adt/orderedsetaggs.c b/src/backend/utils/adt/orderedsetaggs.c
index be9422dcfb6..8871aed9045 100644
--- a/src/backend/utils/adt/orderedsetaggs.c
+++ b/src/backend/utils/adt/orderedsetaggs.c
@@ -239,7 +239,8 @@ ordered_set_startup(FunctionCallInfo fcinfo, bool use_tuples)
 			}
 
 			/* Create slot we'll use to store/retrieve rows */
-			qstate->tupslot = MakeSingleTupleTableSlot(qstate->tupdesc);
+			qstate->tupslot = MakeSingleTupleTableSlot(qstate->tupdesc,
+													   &TTSOpsMinimalTuple);
 		}
 		else
 		{
@@ -1375,7 +1376,8 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS)
 	 * previous row available for comparisons.  This is accomplished by
 	 * swapping the slot pointer variables after each row.
 	 */
-	extraslot = MakeSingleTupleTableSlot(osastate->qstate->tupdesc);
+	extraslot = MakeSingleTupleTableSlot(osastate->qstate->tupdesc,
+										 &TTSOpsMinimalTuple);
 	slot2 = extraslot;
 
 	/* iterate till we find the hypothetical row */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index e0ece74bb92..dbbbcc979b4 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5534,7 +5534,8 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
 			indexInfo = BuildIndexInfo(indexRel);
 
 			/* some other stuff */
-			slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRel));
+			slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRel),
+											&TTSOpsHeapTuple);
 			econtext->ecxt_scantuple = slot;
 			get_typlenbyval(vardata->atttype, &typLen, &typByVal);
 			InitNonVacuumableSnapshot(SnapshotNonVacuumable, RecentGlobalXmin);
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 0327b295da8..f9074215a2d 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -8262,7 +8262,7 @@ ShowGUCConfigOption(const char *name, DestReceiver *dest)
 							  TEXTOID, -1, 0);
 
 	/* prepare for projection of tuples */
-	tstate = begin_tup_output_tupdesc(dest, tupdesc);
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
 
 	/* Send it */
 	do_text_output_oneline(tstate, value);
@@ -8292,7 +8292,7 @@ ShowAllGUCConfig(DestReceiver *dest)
 							  TEXTOID, -1, 0);
 
 	/* prepare for projection of tuples */
-	tstate = begin_tup_output_tupdesc(dest, tupdesc);
+	tstate = begin_tup_output_tupdesc(dest, tupdesc, &TTSOpsVirtual);
 
 	for (i = 0; i < num_guc_variables; i++)
 	{
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 5909404e1e5..ee7fd83c02c 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -933,7 +933,7 @@ tuplesort_begin_cluster(TupleDesc tupDesc,
 		 * scantuple has to point to that slot, too.
 		 */
 		state->estate = CreateExecutorState();
-		slot = MakeSingleTupleTableSlot(tupDesc);
+		slot = MakeSingleTupleTableSlot(tupDesc, &TTSOpsVirtual);
 		econtext = GetPerTupleExprContext(state->estate);
 		econtext->ecxt_scantuple = slot;
 	}
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 84412657845..4f156f4a5e7 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -430,13 +430,17 @@ extern void ExecScanReScan(ScanState *node);
  * prototypes from functions in execTuples.c
  */
 extern void ExecInitResultTypeTL(PlanState *planstate);
-extern void ExecInitResultSlot(PlanState *planstate);
-extern void ExecInitResultTupleSlotTL(PlanState *planstate);
-extern void ExecInitScanTupleSlot(EState *estate, ScanState *scanstate, TupleDesc tupleDesc);
+extern void ExecInitResultSlot(PlanState *planstate,
+							   const TupleTableSlotOps *tts_cb);
+extern void ExecInitResultTupleSlotTL(PlanState *planstate,
+									  const TupleTableSlotOps *tts_cb);
+extern void ExecInitScanTupleSlot(EState *estate, ScanState *scanstate, TupleDesc tupleDesc,
+								  const TupleTableSlotOps *tts_cb);
 extern TupleTableSlot *ExecInitExtraTupleSlot(EState *estate,
-					   TupleDesc tupleDesc);
-extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
-					  TupleDesc tupType);
+					   TupleDesc tupledesc,
+					   const TupleTableSlotOps *tts_cb);
+extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate, TupleDesc tupType,
+					  const TupleTableSlotOps *tts_cb);
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecTypeFromExprList(List *exprList);
@@ -450,7 +454,8 @@ typedef struct TupOutputState
 } TupOutputState;
 
 extern TupOutputState *begin_tup_output_tupdesc(DestReceiver *dest,
-						 TupleDesc tupdesc);
+						 TupleDesc tupdesc,
+						 const TupleTableSlotOps *tts_cb);
 extern void do_tup_output(TupOutputState *tstate, Datum *values, bool *isnull);
 extern void do_text_output_multiline(TupOutputState *tstate, const char *txt);
 extern void end_tup_output(TupOutputState *tstate);
@@ -504,13 +509,17 @@ extern ExprContext *MakePerTupleExprContext(EState *estate);
 
 extern void ExecAssignExprContext(EState *estate, PlanState *planstate);
 extern TupleDesc ExecGetResultType(PlanState *planstate);
+extern TupleTableSlot ExecGetResultSlot(PlanState *planstate);
+extern const TupleTableSlotOps *ExecGetResultSlotOps(PlanState *planstate, bool *isfixed);
 extern void ExecAssignProjectionInfo(PlanState *planstate,
 						 TupleDesc inputDesc);
 extern void ExecConditionalAssignProjectionInfo(PlanState *planstate,
 									TupleDesc inputDesc, Index varno);
 extern void ExecFreeExprContext(PlanState *planstate);
 extern void ExecAssignScanType(ScanState *scanstate, TupleDesc tupDesc);
-extern void ExecCreateScanSlotFromOuterPlan(EState *estate, ScanState *scanstate);
+extern void ExecCreateScanSlotFromOuterPlan(EState *estate,
+								ScanState *scanstate,
+								const TupleTableSlotOps *tts_cb);
 
 extern bool ExecRelationIsTargetRelation(EState *estate, Index scanrelid);
 
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 8bfa73c30ea..a6f6084f0a9 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -132,6 +132,9 @@
 #define			TTS_FLAG_FIXED		(1 << 5)
 #define TTS_FIXED(slot) (((slot)->tts_flags & TTS_FLAG_FIXED) != 0)
 
+struct TupleTableSlotOps;
+typedef struct TupleTableSlotOps TupleTableSlotOps;
+
 typedef struct TupleTableSlot
 {
 	NodeTag		type;
@@ -141,20 +144,35 @@ typedef struct TupleTableSlot
 	AttrNumber	tts_nvalid;		/* # of valid values in tts_values */
 #define FIELDNO_TUPLETABLESLOT_TUPLE 3
 	HeapTuple	tts_tuple;		/* physical tuple, or NULL if virtual */
-#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 4
+	const TupleTableSlotOps *const tts_cb; /* implementation of slot */
+#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 5
 	TupleDesc	tts_tupleDescriptor;	/* slot's tuple descriptor */
 	MemoryContext tts_mcxt;		/* slot itself is in this context */
 	Buffer		tts_buffer;		/* tuple's buffer, or InvalidBuffer */
-#define FIELDNO_TUPLETABLESLOT_OFF 7
+#define FIELDNO_TUPLETABLESLOT_OFF 8
 	uint32		tts_off;		/* saved state for slot_deform_tuple */
-#define FIELDNO_TUPLETABLESLOT_VALUES 8
+#define FIELDNO_TUPLETABLESLOT_VALUES 9
 	Datum	   *tts_values;		/* current per-attribute values */
-#define FIELDNO_TUPLETABLESLOT_ISNULL 9
+#define FIELDNO_TUPLETABLESLOT_ISNULL 10
 	bool	   *tts_isnull;		/* current per-attribute isnull flags */
 	MinimalTuple tts_mintuple;	/* minimal tuple, or NULL if none */
 	HeapTupleData tts_minhdr;	/* workspace for minimal-tuple-only case */
 } TupleTableSlot;
 
+/* routines for a TupleTableSlot implementation */
+struct TupleTableSlotOps
+{
+};
+
+/*
+ * Predefined TupleTableSlotOps for various types of TupleTableSlotOps. The
+ * same are used to identify the type of a given slot.
+ */
+extern PGDLLIMPORT const TupleTableSlotOps TTSOpsVirtual;
+extern PGDLLIMPORT const TupleTableSlotOps TTSOpsHeapTuple;
+extern PGDLLIMPORT const TupleTableSlotOps TTSOpsMinimalTuple;
+extern PGDLLIMPORT const TupleTableSlotOps TTSOpsBufferTuple;
+
 #define TTS_HAS_PHYSICAL_TUPLE(slot)  \
 	((slot)->tts_tuple != NULL && (slot)->tts_tuple != &((slot)->tts_minhdr))
 
@@ -165,10 +183,13 @@ typedef struct TupleTableSlot
 	((slot) == NULL || TTS_EMPTY(slot))
 
 /* in executor/execTuples.c */
-extern TupleTableSlot *MakeTupleTableSlot(TupleDesc desc);
-extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable, TupleDesc desc);
+extern TupleTableSlot *MakeTupleTableSlot(TupleDesc tupleDesc,
+				   const TupleTableSlotOps *tts_cb);
+extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable, TupleDesc desc,
+				   const TupleTableSlotOps *tts_cb);
 extern void ExecResetTupleTable(List *tupleTable, bool shouldFree);
-extern TupleTableSlot *MakeSingleTupleTableSlot(TupleDesc tupdesc);
+extern TupleTableSlot *MakeSingleTupleTableSlot(TupleDesc tupdesc,
+						 const TupleTableSlotOps *tts_cb);
 extern void ExecDropSingleTupleTableSlot(TupleTableSlot *slot);
 extern void ExecSetSlotDescriptor(TupleTableSlot *slot, TupleDesc tupdesc);
 extern TupleTableSlot *ExecStoreHeapTuple(HeapTuple tuple,
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 18544566f70..d0e8cbddac4 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -977,6 +977,42 @@ typedef struct PlanState
 	 * descriptor, without encoding knowledge about all executor nodes.
 	 */
 	TupleDesc	scandesc;
+
+	/*
+	 * Define the slot types for right (inner), left (outer) and scanslots for
+	 * expression contexts with this state as a parent.  If *opsset is set,
+	 * then *opsfixed indicates whether *ops is guaranteed to be the type of
+	 * slot used. That means that every slot in the corresponding
+	 * ExprContext.ecxt_*tuple will point to a slot of that type, while
+	 * evaluating the expression.  If *opsfixed is false, but *ops is set,
+	 * that indicates the most likely type of slot.
+	 *
+	 * The scan* fields are set by ExecInitScanTupleSlot(). If that's not
+	 * called, nodes can initialize the fields themselves.
+	 *
+	 * If left/rightopsset is false, the information is inferred on-demand
+	 * using ExecGetResultSlotOps() on ->righttree/lefttree, using the
+	 * corresponding node's resultops* fields.
+	 *
+	 * The result* fields are automatically set when ExecInitResultSlot is
+	 * used (be it directly or when the slot is created by
+	 * ExecAssignScanProjectionInfo() /
+	 * ExecConditionalAssignProjectionInfo()).  If no projection is necessary
+	 * ExecConditionalAssignProjectionInfo() defaults those fields to the scan
+	 * operations.
+	 */
+	const TupleTableSlotOps *scanops;
+	const TupleTableSlotOps *leftops;
+	const TupleTableSlotOps *rightops;
+	const TupleTableSlotOps *resultops;
+	bool scanopsfixed;
+	bool leftopsfixed;
+	bool rightopsfixed;
+	bool resultopsfixed;
+	bool scanopsset;
+	bool leftopsset;
+	bool rightopsset;
+	bool resultopsset;
 } PlanState;
 
 /* ----------------
-- 
2.18.0.rc2.dirty

