From 42e39640f24ee15b5596e7cbb6831785883ca627 Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Tue, 20 Feb 2018 14:40:23 -0800
Subject: [PATCH] tupleslot-rewrite

---
 src/backend/access/common/heaptuple.c          |  268 +-----
 src/backend/access/common/printsimple.c        |    6 +-
 src/backend/access/common/printtup.c           |   24 +-
 src/backend/catalog/index.c                    |   18 +-
 src/backend/catalog/indexing.c                 |    3 +-
 src/backend/catalog/partition.c                |    2 +-
 src/backend/commands/analyze.c                 |    3 +-
 src/backend/commands/constraint.c              |    3 +-
 src/backend/commands/copy.c                    |    8 +-
 src/backend/commands/createas.c                |    2 +-
 src/backend/commands/subscriptioncmds.c        |    2 +-
 src/backend/commands/tablecmds.c               |    6 +-
 src/backend/commands/trigger.c                 |   22 +-
 src/backend/executor/execCurrent.c             |   19 +-
 src/backend/executor/execExpr.c                |    7 +-
 src/backend/executor/execExprInterp.c          |  102 +--
 src/backend/executor/execGrouping.c            |    3 +-
 src/backend/executor/execIndexing.c            |    3 +-
 src/backend/executor/execJunk.c                |   12 +-
 src/backend/executor/execMain.c                |    6 +-
 src/backend/executor/execPartition.c           |    7 +-
 src/backend/executor/execReplication.c         |   30 +-
 src/backend/executor/execSRF.c                 |    4 +-
 src/backend/executor/execScan.c                |    4 +-
 src/backend/executor/execTuples.c              | 1048 ++++++++++++++++--------
 src/backend/executor/execUtils.c               |    6 +-
 src/backend/executor/functions.c               |    5 +-
 src/backend/executor/nodeAgg.c                 |   39 +-
 src/backend/executor/nodeAppend.c              |    2 +-
 src/backend/executor/nodeBitmapHeapscan.c      |    6 +-
 src/backend/executor/nodeCtescan.c             |    5 +-
 src/backend/executor/nodeCustom.c              |    6 +-
 src/backend/executor/nodeForeignscan.c         |    9 +-
 src/backend/executor/nodeFunctionscan.c        |   21 +-
 src/backend/executor/nodeGather.c              |    5 +-
 src/backend/executor/nodeGatherMerge.c         |    5 +-
 src/backend/executor/nodeGroup.c               |    5 +-
 src/backend/executor/nodeHash.c                |    2 +-
 src/backend/executor/nodeHashjoin.c            |   13 +-
 src/backend/executor/nodeIndexonlyscan.c       |   12 +-
 src/backend/executor/nodeIndexscan.c           |    5 +-
 src/backend/executor/nodeLimit.c               |    3 +-
 src/backend/executor/nodeLockRows.c            |    2 +-
 src/backend/executor/nodeMaterial.c            |    4 +-
 src/backend/executor/nodeMergeAppend.c         |    3 +-
 src/backend/executor/nodeMergejoin.c           |   13 +-
 src/backend/executor/nodeModifyTable.c         |   27 +-
 src/backend/executor/nodeNamedtuplestorescan.c |    5 +-
 src/backend/executor/nodeNestloop.c            |    5 +-
 src/backend/executor/nodeProjectSet.c          |    6 +-
 src/backend/executor/nodeRecursiveunion.c      |    2 +-
 src/backend/executor/nodeResult.c              |    2 +-
 src/backend/executor/nodeSamplescan.c          |    6 +-
 src/backend/executor/nodeSeqscan.c             |    5 +-
 src/backend/executor/nodeSetOp.c               |    2 +-
 src/backend/executor/nodeSort.c                |    4 +-
 src/backend/executor/nodeSubplan.c             |   12 +-
 src/backend/executor/nodeSubqueryscan.c        |    6 +-
 src/backend/executor/nodeTableFuncscan.c       |   16 +-
 src/backend/executor/nodeTidscan.c             |    5 +-
 src/backend/executor/nodeUnique.c              |    2 +-
 src/backend/executor/nodeValuesscan.c          |   12 +-
 src/backend/executor/nodeWindowAgg.c           |   24 +-
 src/backend/executor/nodeWorktablescan.c       |    4 +-
 src/backend/executor/tstoreReceiver.c          |    8 +-
 src/backend/nodes/print.c                      |    2 +-
 src/backend/replication/logical/tablesync.c    |    4 +-
 src/backend/replication/logical/worker.c       |   60 +-
 src/backend/tcop/pquery.c                      |    2 +-
 src/backend/utils/adt/orderedsetaggs.c         |   34 +-
 src/backend/utils/adt/selfuncs.c               |    3 +-
 src/backend/utils/sort/tuplesort.c             |    2 +-
 src/include/access/htup_details.h              |    5 +
 src/include/executor/executor.h                |   16 +-
 src/include/executor/tuptable.h                |  204 ++++-
 src/include/nodes/execnodes.h                  |    3 +
 76 files changed, 1306 insertions(+), 965 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 0a13251067f..5ef9ea5c483 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -1032,18 +1032,17 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
  *		re-computing information about previously extracted attributes.
  *		slot->tts_nvalid is the number of attributes already extracted.
  */
-static void
-slot_deform_tuple(TupleTableSlot *slot, int natts)
+void
+slot_deform_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, int natts)
 {
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-	Datum	   *values = slot->tts_values;
-	bool	   *isnull = slot->tts_isnull;
+	TupleDesc	tupleDesc = slot->tupleDescriptor;
+	Datum	   *values = slot->values;
+	bool	   *isnull = slot->nulls;
 	HeapTupleHeader tup = tuple->t_data;
 	bool		hasnulls = HeapTupleHasNulls(tuple);
 	int			attnum;
 	char	   *tp;				/* ptr to tuple data */
-	long		off;			/* offset in tuple data */
+	uint32		off;			/* offset in tuple data */
 	bits8	   *bp = tup->t_bits;	/* ptr to null bitmap in tuple */
 	bool		slow;			/* can we use/set attcacheoff? */
 
@@ -1051,7 +1050,7 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 	 * Check whether the first call for this tuple, and initialize or restore
 	 * loop state.
 	 */
-	attnum = slot->tts_nvalid;
+	attnum = slot->nvalid;
 	if (attnum == 0)
 	{
 		/* Start from the first attribute */
@@ -1061,8 +1060,8 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 	else
 	{
 		/* Restore state from previous execution */
-		off = slot->tts_off;
-		slow = slot->tts_slow;
+		off = *offp;
+		slow = (slot->flags & TTS_SLOW) != 0;
 	}
 
 	tp = (char *) tup + tup->t_hoff;
@@ -1121,249 +1120,12 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 	/*
 	 * Save state for next execution
 	 */
-	slot->tts_nvalid = attnum;
-	slot->tts_off = off;
-	slot->tts_slow = slow;
-}
-
-/*
- * slot_getattr
- *		This function fetches an attribute of the slot's current tuple.
- *		It is functionally equivalent to heap_getattr, but fetches of
- *		multiple attributes of the same tuple will be optimized better,
- *		because we avoid O(N^2) behavior from multiple calls of
- *		nocachegetattr(), even when attcacheoff isn't usable.
- *
- *		A difference from raw heap_getattr is that attnums beyond the
- *		slot's tupdesc's last attribute will be considered NULL even
- *		when the physical tuple is longer than the tupdesc.
- */
-Datum
-slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
-{
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-	HeapTupleHeader tup;
-
-	/*
-	 * system attributes are handled by heap_getsysattr
-	 */
-	if (attnum <= 0)
-	{
-		if (tuple == NULL)		/* internal error */
-			elog(ERROR, "cannot extract system attribute from virtual tuple");
-		if (tuple == &(slot->tts_minhdr))	/* internal error */
-			elog(ERROR, "cannot extract system attribute from minimal tuple");
-		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
-	}
-
-	/*
-	 * fast path if desired attribute already cached
-	 */
-	if (attnum <= slot->tts_nvalid)
-	{
-		*isnull = slot->tts_isnull[attnum - 1];
-		return slot->tts_values[attnum - 1];
-	}
-
-	/*
-	 * return NULL if attnum is out of range according to the tupdesc
-	 */
-	if (attnum > tupleDesc->natts)
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * return NULL if attnum is out of range according to the tuple
-	 *
-	 * (We have to check this separately because of various inheritance and
-	 * table-alteration scenarios: the tuple could be either longer or shorter
-	 * than the tupdesc.)
-	 */
-	tup = tuple->t_data;
-	if (attnum > HeapTupleHeaderGetNatts(tup))
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * check if target attribute is null: no point in groveling through tuple
-	 */
-	if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits))
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * If the attribute's column has been dropped, we force a NULL result.
-	 * This case should not happen in normal use, but it could happen if we
-	 * are executing a plan cached before the column was dropped.
-	 */
-	if (TupleDescAttr(tupleDesc, attnum - 1)->attisdropped)
-	{
-		*isnull = true;
-		return (Datum) 0;
-	}
-
-	/*
-	 * Extract the attribute, along with any preceding attributes.
-	 */
-	slot_deform_tuple(slot, attnum);
-
-	/*
-	 * The result is acquired from tts_values array.
-	 */
-	*isnull = slot->tts_isnull[attnum - 1];
-	return slot->tts_values[attnum - 1];
-}
-
-/*
- * slot_getallattrs
- *		This function forces all the entries of the slot's Datum/isnull
- *		arrays to be valid.  The caller may then extract data directly
- *		from those arrays instead of using slot_getattr.
- */
-void
-slot_getallattrs(TupleTableSlot *slot)
-{
-	int			tdesc_natts = slot->tts_tupleDescriptor->natts;
-	int			attnum;
-	HeapTuple	tuple;
-
-	/* Quick out if we have 'em all already */
-	if (slot->tts_nvalid == tdesc_natts)
-		return;
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	tuple = slot->tts_tuple;
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * load up any slots available from physical tuple
-	 */
-	attnum = HeapTupleHeaderGetNatts(tuple->t_data);
-	attnum = Min(attnum, tdesc_natts);
-
-	slot_deform_tuple(slot, attnum);
-
-	/*
-	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as null
-	 */
-	for (; attnum < tdesc_natts; attnum++)
-	{
-		slot->tts_values[attnum] = (Datum) 0;
-		slot->tts_isnull[attnum] = true;
-	}
-	slot->tts_nvalid = tdesc_natts;
-}
-
-/*
- * slot_getsomeattrs
- *		This function forces the entries of the slot's Datum/isnull
- *		arrays to be valid at least up through the attnum'th entry.
- */
-void
-slot_getsomeattrs(TupleTableSlot *slot, int attnum)
-{
-	HeapTuple	tuple;
-	int			attno;
-
-	/* Quick out if we have 'em all already */
-	if (slot->tts_nvalid >= attnum)
-		return;
-
-	/* Check for caller error */
-	if (attnum <= 0 || attnum > slot->tts_tupleDescriptor->natts)
-		elog(ERROR, "invalid attribute number %d", attnum);
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	tuple = slot->tts_tuple;
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/*
-	 * load up any slots available from physical tuple
-	 */
-	attno = HeapTupleHeaderGetNatts(tuple->t_data);
-	attno = Min(attno, attnum);
-
-	slot_deform_tuple(slot, attno);
-
-	/*
-	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as null
-	 */
-	for (; attno < attnum; attno++)
-	{
-		slot->tts_values[attno] = (Datum) 0;
-		slot->tts_isnull[attno] = true;
-	}
-	slot->tts_nvalid = attnum;
-}
-
-/*
- * slot_attisnull
- *		Detect whether an attribute of the slot is null, without
- *		actually fetching it.
- */
-bool
-slot_attisnull(TupleTableSlot *slot, int attnum)
-{
-	HeapTuple	tuple = slot->tts_tuple;
-	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
-
-	/*
-	 * system attributes are handled by heap_attisnull
-	 */
-	if (attnum <= 0)
-	{
-		if (tuple == NULL)		/* internal error */
-			elog(ERROR, "cannot extract system attribute from virtual tuple");
-		if (tuple == &(slot->tts_minhdr))	/* internal error */
-			elog(ERROR, "cannot extract system attribute from minimal tuple");
-		return heap_attisnull(tuple, attnum);
-	}
-
-	/*
-	 * fast path if desired attribute already cached
-	 */
-	if (attnum <= slot->tts_nvalid)
-		return slot->tts_isnull[attnum - 1];
-
-	/*
-	 * return NULL if attnum is out of range according to the tupdesc
-	 */
-	if (attnum > tupleDesc->natts)
-		return true;
-
-	/*
-	 * otherwise we had better have a physical tuple (tts_nvalid should equal
-	 * natts in all virtual-tuple cases)
-	 */
-	if (tuple == NULL)			/* internal error */
-		elog(ERROR, "cannot extract attribute from empty tuple slot");
-
-	/* and let the tuple tell it */
-	return heap_attisnull(tuple, attnum);
+	slot->nvalid = attnum;
+	*offp = off;
+	if (slow)
+		slot->flags |= TTS_SLOW;
+	else
+		slot->flags &= ~TTS_SLOW;
 }
 
 /*
diff --git a/src/backend/access/common/printsimple.c b/src/backend/access/common/printsimple.c
index 3c4d2277125..b3eb4f96757 100644
--- a/src/backend/access/common/printsimple.c
+++ b/src/backend/access/common/printsimple.c
@@ -58,7 +58,7 @@ printsimple_startup(DestReceiver *self, int operation, TupleDesc tupdesc)
 bool
 printsimple(TupleTableSlot *slot, DestReceiver *self)
 {
-	TupleDesc	tupdesc = slot->tts_tupleDescriptor;
+	TupleDesc	tupdesc = slot->tupleDescriptor;
 	StringInfoData buf;
 	int			i;
 
@@ -74,13 +74,13 @@ printsimple(TupleTableSlot *slot, DestReceiver *self)
 		Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
 		Datum		value;
 
-		if (slot->tts_isnull[i])
+		if (slot->nulls[i])
 		{
 			pq_sendint32(&buf, -1);
 			continue;
 		}
 
-		value = slot->tts_values[i];
+		value = slot->values[i];
 
 		/*
 		 * We can't call the regular type output functions here because we
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index a1d44157044..795b3f19a07 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -373,7 +373,7 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 static bool
 printtup(TupleTableSlot *slot, DestReceiver *self)
 {
-	TupleDesc	typeinfo = slot->tts_tupleDescriptor;
+	TupleDesc	typeinfo = slot->tupleDescriptor;
 	DR_printtup *myState = (DR_printtup *) self;
 	MemoryContext oldcontext;
 	StringInfo	buf = &myState->buf;
@@ -403,9 +403,9 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
 	for (i = 0; i < natts; ++i)
 	{
 		PrinttupAttrInfo *thisState = myState->myinfo + i;
-		Datum		attr = slot->tts_values[i];
+		Datum		attr = slot->values[i];
 
-		if (slot->tts_isnull[i])
+		if (slot->nulls[i])
 		{
 			pq_sendint32(buf, -1);
 			continue;
@@ -458,7 +458,7 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
 static bool
 printtup_20(TupleTableSlot *slot, DestReceiver *self)
 {
-	TupleDesc	typeinfo = slot->tts_tupleDescriptor;
+	TupleDesc	typeinfo = slot->tupleDescriptor;
 	DR_printtup *myState = (DR_printtup *) self;
 	MemoryContext oldcontext;
 	StringInfo	buf = &myState->buf;
@@ -489,7 +489,7 @@ printtup_20(TupleTableSlot *slot, DestReceiver *self)
 	k = 1 << 7;
 	for (i = 0; i < natts; ++i)
 	{
-		if (!slot->tts_isnull[i])
+		if (!slot->nulls[i])
 			j |= k;				/* set bit if not null */
 		k >>= 1;
 		if (k == 0)				/* end of byte? */
@@ -508,10 +508,10 @@ printtup_20(TupleTableSlot *slot, DestReceiver *self)
 	for (i = 0; i < natts; ++i)
 	{
 		PrinttupAttrInfo *thisState = myState->myinfo + i;
-		Datum		attr = slot->tts_values[i];
+		Datum		attr = slot->values[i];
 		char	   *outputstr;
 
-		if (slot->tts_isnull[i])
+		if (slot->nulls[i])
 			continue;
 
 		Assert(thisState->format == 0);
@@ -605,7 +605,7 @@ debugStartup(DestReceiver *self, int operation, TupleDesc typeinfo)
 bool
 debugtup(TupleTableSlot *slot, DestReceiver *self)
 {
-	TupleDesc	typeinfo = slot->tts_tupleDescriptor;
+	TupleDesc	typeinfo = slot->tupleDescriptor;
 	int			natts = typeinfo->natts;
 	int			i;
 	Datum		attr;
@@ -643,7 +643,7 @@ debugtup(TupleTableSlot *slot, DestReceiver *self)
 static bool
 printtup_internal_20(TupleTableSlot *slot, DestReceiver *self)
 {
-	TupleDesc	typeinfo = slot->tts_tupleDescriptor;
+	TupleDesc	typeinfo = slot->tupleDescriptor;
 	DR_printtup *myState = (DR_printtup *) self;
 	MemoryContext oldcontext;
 	StringInfo	buf = &myState->buf;
@@ -674,7 +674,7 @@ printtup_internal_20(TupleTableSlot *slot, DestReceiver *self)
 	k = 1 << 7;
 	for (i = 0; i < natts; ++i)
 	{
-		if (!slot->tts_isnull[i])
+		if (!slot->nulls[i])
 			j |= k;				/* set bit if not null */
 		k >>= 1;
 		if (k == 0)				/* end of byte? */
@@ -693,10 +693,10 @@ printtup_internal_20(TupleTableSlot *slot, DestReceiver *self)
 	for (i = 0; i < natts; ++i)
 	{
 		PrinttupAttrInfo *thisState = myState->myinfo + i;
-		Datum		attr = slot->tts_values[i];
+		Datum		attr = slot->values[i];
 		bytea	   *outputbytes;
 
-		if (slot->tts_isnull[i])
+		if (slot->nulls[i])
 			continue;
 
 		Assert(thisState->format == 1);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index f2cb6d7fb81..8fa19526214 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1964,7 +1964,14 @@ FormIndexDatum(IndexInfo *indexInfo,
 		Datum		iDatum;
 		bool		isNull;
 
-		if (keycol != 0)
+		if (keycol < 0)
+		{
+			iDatum = heap_getsysattr(((HeapTupleTableSlot *) slot)->tuple,
+									 keycol,
+									 slot->tupleDescriptor,
+									 &isNull);
+		}
+		else if (keycol != 0)
 		{
 			/*
 			 * Plain index column; get the value we need directly from the
@@ -2445,7 +2452,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	 */
 	estate = CreateExecutorState();
 	econtext = GetPerTupleExprContext(estate);
-	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation));
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation),
+									TTS_TYPE_HEAPTUPLE);
 
 	/* Arrange for econtext's scan tuple to be the tuple under test */
 	econtext->ecxt_scantuple = slot;
@@ -2900,7 +2908,8 @@ IndexCheckExclusion(Relation heapRelation,
 	 */
 	estate = CreateExecutorState();
 	econtext = GetPerTupleExprContext(estate);
-	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation));
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation),
+									TTS_TYPE_HEAPTUPLE);
 
 	/* Arrange for econtext's scan tuple to be the tuple under test */
 	econtext->ecxt_scantuple = slot;
@@ -3218,7 +3227,8 @@ validate_index_heapscan(Relation heapRelation,
 	 */
 	estate = CreateExecutorState();
 	econtext = GetPerTupleExprContext(estate);
-	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation));
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation),
+									TTS_TYPE_HEAPTUPLE);
 
 	/* 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 a84b7da114a..748f0a98cdf 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),
+									TTS_TYPE_HEAPTUPLE);
 	ExecStoreTuple(heapTuple, slot, InvalidBuffer, false);
 
 	/*
diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c
index 31c80c7f1ad..be31dcc57db 100644
--- a/src/backend/catalog/partition.c
+++ b/src/backend/catalog/partition.c
@@ -1338,7 +1338,7 @@ check_default_allows_bound(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, TTS_TYPE_HEAPTUPLE);
 
 		/*
 		 * Switch to per-tuple memory context and reset it for each tuple
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 5f21fcb5f40..d49068f0966 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -783,7 +783,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),
+										TTS_TYPE_HEAPTUPLE);
 
 		/* 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 90f19ad3dd9..2d77f475319 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),
+									TTS_TYPE_HEAPTUPLE);
 
 	ExecStoreTuple(new_row, slot, InvalidBuffer, false);
 
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index d5883c98d15..27aae408ac6 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -2444,9 +2444,11 @@ CopyFrom(CopyState cstate)
 	estate->es_range_table = cstate->range_table;
 
 	/* Set up a tuple slot too */
-	myslot = ExecInitExtraTupleSlot(estate, tupDesc);
+	myslot = ExecInitExtraTupleSlot(estate, tupDesc,
+									TTS_TYPE_HEAPTUPLE);
 	/* Triggers might need a slot as well */
-	estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL);
+	estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL,
+														TTS_TYPE_HEAPTUPLE);
 
 	/* Prepare to catch AFTER triggers. */
 	AfterTriggerBeginQuery();
@@ -4747,7 +4749,7 @@ copy_dest_receive(TupleTableSlot *slot, DestReceiver *self)
 	slot_getallattrs(slot);
 
 	/* And send the data */
-	CopyOneRowTo(cstate, InvalidOid, slot->tts_values, slot->tts_isnull);
+	CopyOneRowTo(cstate, InvalidOid, slot->values, slot->nulls);
 	myState->processed++;
 
 	return true;
diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c
index 3d82edbf581..fbb84382280 100644
--- a/src/backend/commands/createas.c
+++ b/src/backend/commands/createas.c
@@ -588,7 +588,7 @@ intorel_receive(TupleTableSlot *slot, DestReceiver *self)
 	 * get the heap tuple out of the tuple table slot, making sure we have a
 	 * writable copy
 	 */
-	tuple = ExecMaterializeSlot(slot);
+	tuple = ExecCopySlotTuple(slot);
 
 	/*
 	 * force assignment of new OID (see comments in ExecInsert)
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 9de59693022..2c2cc4f33d5 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, TTS_TYPE_MINIMALTUPLE);
 	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 89454d8e80f..6cca6a4b65e 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -4588,8 +4588,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, TTS_TYPE_HEAPTUPLE);
+		newslot = MakeSingleTupleTableSlot(newTupDesc, TTS_TYPE_HEAPTUPLE);
 
 		/* Preallocate values/isnull arrays */
 		i = Max(newTupDesc->natts, oldTupDesc->natts);
@@ -8280,7 +8280,7 @@ validateCheckConstraint(Relation rel, HeapTuple constrtup)
 
 	econtext = GetPerTupleExprContext(estate);
 	tupdesc = RelationGetDescr(rel);
-	slot = MakeSingleTupleTableSlot(tupdesc);
+	slot = MakeSingleTupleTableSlot(tupdesc, TTS_TYPE_HEAPTUPLE);
 	econtext->ecxt_scantuple = slot;
 
 	snapshot = RegisterSnapshot(GetLatestSnapshot());
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index fffc0095a79..aeeae171115 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -2342,7 +2342,7 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 		TupleTableSlot *newslot = estate->es_trig_tuple_slot;
 		TupleDesc	tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
 
-		if (newslot->tts_tupleDescriptor != tupdesc)
+		if (newslot->tupleDescriptor != tupdesc)
 			ExecSetSlotDescriptor(newslot, tupdesc);
 		ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
 		slot = newslot;
@@ -2423,7 +2423,7 @@ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 		TupleTableSlot *newslot = estate->es_trig_tuple_slot;
 		TupleDesc	tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
 
-		if (newslot->tts_tupleDescriptor != tupdesc)
+		if (newslot->tupleDescriptor != tupdesc)
 			ExecSetSlotDescriptor(newslot, tupdesc);
 		ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
 		slot = newslot;
@@ -2829,7 +2829,7 @@ ExecBRUpdateTriggers(EState *estate, EPQState *epqstate,
 		TupleTableSlot *newslot = estate->es_trig_tuple_slot;
 		TupleDesc	tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
 
-		if (newslot->tts_tupleDescriptor != tupdesc)
+		if (newslot->tupleDescriptor != tupdesc)
 			ExecSetSlotDescriptor(newslot, tupdesc);
 		ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
 		slot = newslot;
@@ -2937,7 +2937,7 @@ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 		TupleTableSlot *newslot = estate->es_trig_tuple_slot;
 		TupleDesc	tupdesc = RelationGetDescr(relinfo->ri_RelationDesc);
 
-		if (newslot->tts_tupleDescriptor != tupdesc)
+		if (newslot->tupleDescriptor != tupdesc)
 			ExecSetSlotDescriptor(newslot, tupdesc);
 		ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
 		slot = newslot;
@@ -3252,11 +3252,11 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 			{
 				oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
 				estate->es_trig_oldtup_slot =
-					ExecInitExtraTupleSlot(estate, NULL);
+					ExecInitExtraTupleSlot(estate, NULL, TTS_TYPE_HEAPTUPLE);
 				MemoryContextSwitchTo(oldContext);
 			}
 			oldslot = estate->es_trig_oldtup_slot;
-			if (oldslot->tts_tupleDescriptor != tupdesc)
+			if (oldslot->tupleDescriptor != tupdesc)
 				ExecSetSlotDescriptor(oldslot, tupdesc);
 			ExecStoreTuple(oldtup, oldslot, InvalidBuffer, false);
 		}
@@ -3266,11 +3266,11 @@ TriggerEnabled(EState *estate, ResultRelInfo *relinfo,
 			{
 				oldContext = MemoryContextSwitchTo(estate->es_query_cxt);
 				estate->es_trig_newtup_slot =
-					ExecInitExtraTupleSlot(estate, NULL);
+					ExecInitExtraTupleSlot(estate, NULL, TTS_TYPE_HEAPTUPLE);
 				MemoryContextSwitchTo(oldContext);
 			}
 			newslot = estate->es_trig_newtup_slot;
-			if (newslot->tts_tupleDescriptor != tupdesc)
+			if (newslot->tupleDescriptor != tupdesc)
 				ExecSetSlotDescriptor(newslot, tupdesc);
 			ExecStoreTuple(newtup, newslot, InvalidBuffer, false);
 		}
@@ -4273,8 +4273,10 @@ afterTriggerInvokeEvents(AfterTriggerEventList *events,
 							ExecDropSingleTupleTableSlot(slot1);
 							ExecDropSingleTupleTableSlot(slot2);
 						}
-						slot1 = MakeSingleTupleTableSlot(rel->rd_att);
-						slot2 = MakeSingleTupleTableSlot(rel->rd_att);
+						slot1 = MakeSingleTupleTableSlot(rel->rd_att,
+														 TTS_TYPE_HEAPTUPLE);
+						slot2 = MakeSingleTupleTableSlot(rel->rd_att,
+														 TTS_TYPE_HEAPTUPLE);
 					}
 					if (trigdesc == NULL)	/* should not happen */
 						elog(ERROR, "relation %u has no triggers",
diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c
index ce7d4ac592a..2574639ecef 100644
--- a/src/backend/executor/execCurrent.c
+++ b/src/backend/executor/execCurrent.c
@@ -13,6 +13,7 @@
 #include "postgres.h"
 
 #include "access/sysattr.h"
+#include "access/htup_details.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "utils/builtins.h"
@@ -183,16 +184,22 @@ execCurrentOf(CurrentOfExpr *cexpr,
 		if (TupIsNull(scanstate->ss_ScanTupleSlot))
 			return false;
 
+#if 0
 		/* Use slot_getattr to catch any possible mistakes */
 		tuple_tableoid =
-			DatumGetObjectId(slot_getattr(scanstate->ss_ScanTupleSlot,
-										  TableOidAttributeNumber,
-										  &lisnull));
+			DatumGetObjectId(heap_getsysattr(scanstate->ss_ScanTupleSlot->tts_tuple,
+											 TableOidAttributeNumber,
+											 scanstate->ss_ScanTupleSlot->tts_tupleDescriptor,
+											 &lisnull));
 		Assert(!lisnull);
 		tuple_tid = (ItemPointer)
-			DatumGetPointer(slot_getattr(scanstate->ss_ScanTupleSlot,
-										 SelfItemPointerAttributeNumber,
-										 &lisnull));
+			DatumGetPointer(heap_getsysattr(scanstate->ss_ScanTupleSlot->tts_tuple,
+											 SelfItemPointerAttributeNumber,
+											 scanstate->ss_ScanTupleSlot->tts_tupleDescriptor,
+											 &lisnull));
+#else
+		elog(ERROR, "not yet");
+#endif
 		Assert(!lisnull);
 
 		Assert(tuple_tableoid == table_oid);
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index db5fcafbfe4..c6e478c3650 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -2415,7 +2415,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,
+															  TTS_TYPE_VIRTUAL));
 			}
 		}
 	}
@@ -2994,8 +2995,8 @@ ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
 			/*
 			 * DISTINCT and/or ORDER BY case, with multiple columns sorted on.
 			 */
-			Datum	   *values = pertrans->sortslot->tts_values;
-			bool	   *nulls = pertrans->sortslot->tts_isnull;
+			Datum	   *values = pertrans->sortslot->values;
+			bool	   *nulls = pertrans->sortslot->nulls;
 
 			strictnulls = nulls;
 
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 771b7e3945c..e7c2b9fb43c 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -455,9 +455,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			 * directly out of the slot's decomposed-data arrays.  But let's
 			 * have an Assert to check that that did happen.
 			 */
-			Assert(attnum >= 0 && attnum < innerslot->tts_nvalid);
-			*op->resvalue = innerslot->tts_values[attnum];
-			*op->resnull = innerslot->tts_isnull[attnum];
+			Assert(attnum >= 0 && attnum < innerslot->nvalid);
+			*op->resvalue = innerslot->values[attnum];
+			*op->resnull = innerslot->nulls[attnum];
 
 			EEO_NEXT();
 		}
@@ -468,9 +468,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 
 			/* See EEOP_INNER_VAR comments */
 
-			Assert(attnum >= 0 && attnum < outerslot->tts_nvalid);
-			*op->resvalue = outerslot->tts_values[attnum];
-			*op->resnull = outerslot->tts_isnull[attnum];
+			Assert(attnum >= 0 && attnum < outerslot->nvalid);
+			*op->resvalue = outerslot->values[attnum];
+			*op->resnull = outerslot->nulls[attnum];
 
 			EEO_NEXT();
 		}
@@ -481,9 +481,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 
 			/* See EEOP_INNER_VAR comments */
 
-			Assert(attnum >= 0 && attnum < scanslot->tts_nvalid);
-			*op->resvalue = scanslot->tts_values[attnum];
-			*op->resnull = scanslot->tts_isnull[attnum];
+			Assert(attnum >= 0 && attnum < scanslot->nvalid);
+			*op->resvalue = scanslot->values[attnum];
+			*op->resnull = scanslot->nulls[attnum];
 
 			EEO_NEXT();
 		}
@@ -494,12 +494,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(innerslot->tts_tuple != NULL);
-			Assert(innerslot->tts_tuple != &(innerslot->tts_minhdr));
+			//Assert(innerslot->tts_tuple != NULL);
+			//Assert(innerslot->tts_tuple != &(innerslot->tts_minhdr));
 
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(innerslot->tts_tuple, attnum,
-								innerslot->tts_tupleDescriptor,
+			d = heap_getsysattr(((HeapTupleTableSlot *)innerslot)->tuple, attnum,
+								innerslot->tupleDescriptor,
 								op->resnull);
 			*op->resvalue = d;
 
@@ -512,12 +512,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(outerslot->tts_tuple != NULL);
-			Assert(outerslot->tts_tuple != &(outerslot->tts_minhdr));
+			//Assert(outerslot->tts_tuple != NULL);
+			//Assert(outerslot->tts_tuple != &(outerslot->tts_minhdr));
 
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(outerslot->tts_tuple, attnum,
-								outerslot->tts_tupleDescriptor,
+			d = heap_getsysattr(((HeapTupleTableSlot *)outerslot)->tuple, attnum,
+								outerslot->tupleDescriptor,
 								op->resnull);
 			*op->resvalue = d;
 
@@ -530,12 +530,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			Datum		d;
 
 			/* these asserts must match defenses in slot_getattr */
-			Assert(scanslot->tts_tuple != NULL);
-			Assert(scanslot->tts_tuple != &(scanslot->tts_minhdr));
+			//Assert(scanslot->tts_tuple != NULL);
+			//Assert(scanslot->tts_tuple != &(scanslot->tts_minhdr));
 
 			/* heap_getsysattr has sufficient defenses against bad attnums */
-			d = heap_getsysattr(scanslot->tts_tuple, attnum,
-								scanslot->tts_tupleDescriptor,
+			d = heap_getsysattr(((HeapTupleTableSlot *)scanslot)->tuple, attnum,
+								scanslot->tupleDescriptor,
 								op->resnull);
 			*op->resvalue = d;
 
@@ -559,9 +559,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			 * We do not need CheckVarSlotCompatibility here; that was taken
 			 * care of at compilation time.  But see EEOP_INNER_VAR comments.
 			 */
-			Assert(attnum >= 0 && attnum < innerslot->tts_nvalid);
-			resultslot->tts_values[resultnum] = innerslot->tts_values[attnum];
-			resultslot->tts_isnull[resultnum] = innerslot->tts_isnull[attnum];
+			Assert(attnum >= 0 && attnum < innerslot->nvalid);
+			resultslot->values[resultnum] = innerslot->values[attnum];
+			resultslot->nulls[resultnum] = innerslot->nulls[attnum];
 
 			EEO_NEXT();
 		}
@@ -575,9 +575,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			 * We do not need CheckVarSlotCompatibility here; that was taken
 			 * care of at compilation time.  But see EEOP_INNER_VAR comments.
 			 */
-			Assert(attnum >= 0 && attnum < outerslot->tts_nvalid);
-			resultslot->tts_values[resultnum] = outerslot->tts_values[attnum];
-			resultslot->tts_isnull[resultnum] = outerslot->tts_isnull[attnum];
+			Assert(attnum >= 0 && attnum < outerslot->nvalid);
+			resultslot->values[resultnum] = outerslot->values[attnum];
+			resultslot->nulls[resultnum] = outerslot->nulls[attnum];
 
 			EEO_NEXT();
 		}
@@ -591,9 +591,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			 * We do not need CheckVarSlotCompatibility here; that was taken
 			 * care of at compilation time.  But see EEOP_INNER_VAR comments.
 			 */
-			Assert(attnum >= 0 && attnum < scanslot->tts_nvalid);
-			resultslot->tts_values[resultnum] = scanslot->tts_values[attnum];
-			resultslot->tts_isnull[resultnum] = scanslot->tts_isnull[attnum];
+			Assert(attnum >= 0 && attnum < scanslot->nvalid);
+			resultslot->values[resultnum] = scanslot->values[attnum];
+			resultslot->nulls[resultnum] = scanslot->nulls[attnum];
 
 			EEO_NEXT();
 		}
@@ -602,8 +602,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		{
 			int			resultnum = op->d.assign_tmp.resultnum;
 
-			resultslot->tts_values[resultnum] = state->resvalue;
-			resultslot->tts_isnull[resultnum] = state->resnull;
+			resultslot->values[resultnum] = state->resvalue;
+			resultslot->nulls[resultnum] = state->resnull;
 
 			EEO_NEXT();
 		}
@@ -612,12 +612,12 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		{
 			int			resultnum = op->d.assign_tmp.resultnum;
 
-			resultslot->tts_isnull[resultnum] = state->resnull;
-			if (!resultslot->tts_isnull[resultnum])
-				resultslot->tts_values[resultnum] =
+			resultslot->nulls[resultnum] = state->resnull;
+			if (!resultslot->nulls[resultnum])
+				resultslot->values[resultnum] =
 					MakeExpandedObjectReadOnlyInternal(state->resvalue);
 			else
-				resultslot->tts_values[resultnum] = state->resvalue;
+				resultslot->values[resultnum] = state->resvalue;
 
 			EEO_NEXT();
 		}
@@ -1897,7 +1897,7 @@ CheckVarSlotCompatibility(TupleTableSlot *slot, int attnum, Oid vartype)
 	 */
 	if (attnum > 0)
 	{
-		TupleDesc	slot_tupdesc = slot->tts_tupleDescriptor;
+		TupleDesc	slot_tupdesc = slot->tupleDescriptor;
 		Form_pg_attribute attr;
 
 		if (attnum > slot_tupdesc->natts)	/* should never happen */
@@ -2050,8 +2050,8 @@ ExecJustAssignInnerVar(ExprState *state, ExprContext *econtext, bool *isnull)
 	 * step explicitly, and we also needn't Assert that the attnum is in range
 	 * --- slot_getattr() will take care of any problems.
 	 */
-	outslot->tts_values[resultnum] =
-		slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
+	outslot->values[resultnum] =
+		slot_getattr(inslot, attnum, &outslot->nulls[resultnum]);
 	return 0;
 }
 
@@ -2066,8 +2066,8 @@ ExecJustAssignOuterVar(ExprState *state, ExprContext *econtext, bool *isnull)
 	TupleTableSlot *outslot = state->resultslot;
 
 	/* See comments in ExecJustAssignInnerVar */
-	outslot->tts_values[resultnum] =
-		slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
+	outslot->values[resultnum] =
+		slot_getattr(inslot, attnum, &outslot->nulls[resultnum]);
 	return 0;
 }
 
@@ -2082,8 +2082,8 @@ ExecJustAssignScanVar(ExprState *state, ExprContext *econtext, bool *isnull)
 	TupleTableSlot *outslot = state->resultslot;
 
 	/* See comments in ExecJustAssignInnerVar */
-	outslot->tts_values[resultnum] =
-		slot_getattr(inslot, attnum, &outslot->tts_isnull[resultnum]);
+	outslot->values[resultnum] =
+		slot_getattr(inslot, attnum, &outslot->nulls[resultnum]);
 	return 0;
 }
 
@@ -3858,7 +3858,7 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 			var_tupdesc = lookup_rowtype_tupdesc_domain(variable->vartype,
 														-1, false);
 
-			slot_tupdesc = slot->tts_tupleDescriptor;
+			slot_tupdesc = slot->tupleDescriptor;
 
 			if (var_tupdesc->natts != slot_tupdesc->natts)
 				ereport(ERROR,
@@ -3911,7 +3911,7 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 			 * column names below.
 			 */
 			oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-			output_tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
+			output_tupdesc = CreateTupleDescCopy(slot->tupleDescriptor);
 			MemoryContextSwitchTo(oldcontext);
 		}
 
@@ -3963,7 +3963,7 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 	if (op->d.wholerow.slow)
 	{
 		/* Check to see if any dropped attributes are non-null */
-		TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
+		TupleDesc	tupleDesc = slot->tupleDescriptor;
 		TupleDesc	var_tupdesc = op->d.wholerow.tupdesc;
 		int			i;
 
@@ -3976,7 +3976,7 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 
 			if (!vattr->attisdropped)
 				continue;		/* already checked non-dropped cols */
-			if (slot->tts_isnull[i])
+			if (slot->nulls[i])
 				continue;		/* null is always okay */
 			if (vattr->attlen != sattr->attlen ||
 				vattr->attalign != sattr->attalign)
@@ -3993,9 +3993,9 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 	 *
 	 * (Note: it is critical that we not change the slot's state here.)
 	 */
-	tuple = toast_build_flattened_tuple(slot->tts_tupleDescriptor,
-										slot->tts_values,
-										slot->tts_isnull);
+	tuple = toast_build_flattened_tuple(slot->tupleDescriptor,
+										slot->values,
+										slot->nulls);
 	dtuple = tuple->t_data;
 
 	/*
@@ -4099,7 +4099,7 @@ ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op,
 	int			setno = op->d.agg_trans.setno;
 
 	ExecClearTuple(pertrans->sortslot);
-	pertrans->sortslot->tts_nvalid = pertrans->numInputs;
+	pertrans->sortslot->nvalid = pertrans->numInputs;
 	ExecStoreVirtualTuple(pertrans->sortslot);
 	tuplesort_puttupleslot(pertrans->sortstates[setno], pertrans->sortslot);
 }
diff --git a/src/backend/executor/execGrouping.c b/src/backend/executor/execGrouping.c
index c4d0e040587..9be9da4b161 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),
+													TTS_TYPE_MINIMALTUPLE);
 
 	/* 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 62e51f1ef3b..4403fd7fa3e 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),
+											 TTS_TYPE_HEAPTUPLE);
 
 	econtext = GetPerTupleExprContext(estate);
 	save_scantuple = econtext->ecxt_scantuple;
diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c
index 57d74e57c1a..1abc2630b94 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, TTS_TYPE_VIRTUAL);
 
 	/*
 	 * 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, TTS_TYPE_VIRTUAL);
 
 	/*
 	 * Calculate the mapping between the original tuple's attributes and the
@@ -275,8 +275,8 @@ ExecFilterJunk(JunkFilter *junkfilter, TupleTableSlot *slot)
 	 * Extract all the values of the old tuple.
 	 */
 	slot_getallattrs(slot);
-	old_values = slot->tts_values;
-	old_isnull = slot->tts_isnull;
+	old_values = slot->values;
+	old_isnull = slot->nulls;
 
 	/*
 	 * get info from the junk filter
@@ -290,8 +290,8 @@ ExecFilterJunk(JunkFilter *junkfilter, TupleTableSlot *slot)
 	 * Prepare to build a virtual result tuple.
 	 */
 	ExecClearTuple(resultSlot);
-	values = resultSlot->tts_values;
-	isnull = resultSlot->tts_isnull;
+	values = resultSlot->values;
+	isnull = resultSlot->nulls;
 
 	/*
 	 * Transpose data into proper fields of the new tuple.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 91ba939bdca..a93cfe963cb 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -1073,7 +1073,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
 			j = ExecInitJunkFilter(planstate->plan->targetlist,
 								   tupType->tdhasoid,
-								   ExecInitExtraTupleSlot(estate, NULL));
+								   ExecInitExtraTupleSlot(estate, NULL, TTS_TYPE_VIRTUAL));
 			estate->es_junkFilter = j;
 
 			/* Want to return the cleaned tuple type */
@@ -2319,7 +2319,7 @@ ExecBuildSlotValueDescription(Oid reloid,
 
 		if (table_perm || column_perm)
 		{
-			if (slot->tts_isnull[i])
+			if (slot->nulls[i])
 				val = "null";
 			else
 			{
@@ -2328,7 +2328,7 @@ ExecBuildSlotValueDescription(Oid reloid,
 
 				getTypeOutputInfo(att->atttypid,
 								  &foutoid, &typisvarlena);
-				val = OidOutputFunctionCall(foutoid, slot->tts_values[i]);
+				val = OidOutputFunctionCall(foutoid, slot->values[i]);
 			}
 
 			if (write_comma)
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 00523ce250d..04979eb7e75 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -93,7 +93,7 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate,
 		 * We need an additional tuple slot for storing transient tuples that
 		 * are converted to the root table descriptor.
 		 */
-		proute->root_tuple_slot = MakeTupleTableSlot(NULL);
+		proute->root_tuple_slot = MakeTupleTableSlot(NULL, TTS_TYPE_HEAPTUPLE);
 	}
 	else
 	{
@@ -112,7 +112,8 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate,
 	 * (such as ModifyTableState) and released when the node finishes
 	 * processing.
 	 */
-	proute->partition_tuple_slot = MakeTupleTableSlot(NULL);
+	proute->partition_tuple_slot = MakeTupleTableSlot(NULL,
+													  TTS_TYPE_HEAPTUPLE);
 
 	i = 0;
 	foreach(cell, leaf_parts)
@@ -590,7 +591,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, TTS_TYPE_HEAPTUPLE);
 		pd->tupmap = convert_tuples_by_name(RelationGetDescr(parent),
 											tupdesc,
 											gettext_noop("could not convert row type"));
diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c
index 32891abbdf5..daf20589f4f 100644
--- a/src/backend/executor/execReplication.c
+++ b/src/backend/executor/execReplication.c
@@ -92,10 +92,10 @@ build_replindex_scan_key(ScanKey skey, Relation rel, Relation idxrel,
 					pkattno,
 					BTEqualStrategyNumber,
 					regop,
-					searchslot->tts_values[mainattno - 1]);
+					searchslot->values[mainattno - 1]);
 
 		/* Check for null value. */
-		if (searchslot->tts_isnull[mainattno - 1])
+		if (searchslot->nulls[mainattno - 1])
 		{
 			hasnulls = true;
 			skey[attoff].sk_flags |= SK_ISNULL;
@@ -171,7 +171,7 @@ retry:
 		HTSU_Result res;
 		HeapTupleData locktup;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		ItemPointerCopy(&((HeapTupleTableSlot *)outslot)->tuple->t_self, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
@@ -238,7 +238,7 @@ tuple_equals_slot(TupleDesc desc, HeapTuple tup, TupleTableSlot *slot)
 		 * If one value is NULL and other is not, then they are certainly not
 		 * equal
 		 */
-		if (isnull[attrnum] != slot->tts_isnull[attrnum])
+		if (isnull[attrnum] != slot->nulls[attrnum])
 			return false;
 
 		/*
@@ -258,7 +258,7 @@ tuple_equals_slot(TupleDesc desc, HeapTuple tup, TupleTableSlot *slot)
 
 		if (!DatumGetBool(FunctionCall2(&typentry->eq_opr_finfo,
 										values[attrnum],
-										slot->tts_values[attrnum])))
+										slot->values[attrnum])))
 			return false;
 	}
 
@@ -286,7 +286,7 @@ RelationFindReplTupleSeq(Relation rel, LockTupleMode lockmode,
 	bool		found;
 	TupleDesc	desc = RelationGetDescr(rel);
 
-	Assert(equalTupleDescs(desc, outslot->tts_tupleDescriptor));
+	Assert(equalTupleDescs(desc, outslot->tupleDescriptor));
 
 	/* Start a heap scan. */
 	InitDirtySnapshot(snap);
@@ -329,7 +329,7 @@ retry:
 		HTSU_Result res;
 		HeapTupleData locktup;
 
-		ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self);
+		ItemPointerCopy(&((HeapTupleTableSlot *)outslot)->tuple->t_self, &locktup.t_self);
 
 		PushActiveSnapshot(GetLatestSnapshot());
 
@@ -453,7 +453,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		resultRelInfo->ri_TrigDesc->trig_update_before_row)
 	{
 		slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
-									&searchslot->tts_tuple->t_self,
+									&((HeapTupleTableSlot *) searchslot)->tuple->t_self,
 									NULL, slot);
 
 		if (slot == NULL)		/* "do nothing" */
@@ -472,18 +472,18 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate,
 		tuple = ExecMaterializeSlot(slot);
 
 		/* OK, update the tuple and index entries for it */
-		simple_heap_update(rel, &searchslot->tts_tuple->t_self,
-						   slot->tts_tuple);
+		simple_heap_update(rel, &((HeapTupleTableSlot *) searchslot)->tuple->t_self,
+						   ((HeapTupleTableSlot *)slot)->tuple);
 
 		if (resultRelInfo->ri_NumIndices > 0 &&
-			!HeapTupleIsHeapOnly(slot->tts_tuple))
+			!HeapTupleIsHeapOnly(((HeapTupleTableSlot *)slot)->tuple))
 			recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
 												   estate, false, NULL,
 												   NIL);
 
 		/* AFTER ROW UPDATE Triggers */
 		ExecARUpdateTriggers(estate, resultRelInfo,
-							 &searchslot->tts_tuple->t_self,
+							 &((HeapTupleTableSlot *)searchslot)->tuple->t_self,
 							 NULL, tuple, recheckIndexes, NULL);
 
 		list_free(recheckIndexes);
@@ -514,7 +514,7 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 		resultRelInfo->ri_TrigDesc->trig_delete_before_row)
 	{
 		skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
-										   &searchslot->tts_tuple->t_self,
+										   &((HeapTupleTableSlot *)searchslot)->tuple->t_self,
 										   NULL);
 	}
 
@@ -523,11 +523,11 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate,
 		List	   *recheckIndexes = NIL;
 
 		/* OK, delete the tuple */
-		simple_heap_delete(rel, &searchslot->tts_tuple->t_self);
+		simple_heap_delete(rel, &((HeapTupleTableSlot *)searchslot)->tuple->t_self);
 
 		/* AFTER ROW DELETE Triggers */
 		ExecARDeleteTriggers(estate, resultRelInfo,
-							 &searchslot->tts_tuple->t_self, NULL, NULL);
+							 &((HeapTupleTableSlot *)searchslot)->tuple->t_self, NULL, NULL);
 
 		list_free(recheckIndexes);
 	}
diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c
index b97b8d797ec..163d8bb1183 100644
--- a/src/backend/executor/execSRF.c
+++ b/src/backend/executor/execSRF.c
@@ -509,7 +509,7 @@ restart:
 		 * clearing the slot could end up trying to free something already
 		 * freed.
 		 */
-		oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
+		oldContext = MemoryContextSwitchTo(slot->mcxt);
 		foundTup = tuplestore_gettupleslot(fcache->funcResultStore, true, false,
 										   fcache->funcResultSlot);
 		MemoryContextSwitchTo(oldContext);
@@ -873,7 +873,7 @@ ExecPrepareTuplestoreResult(SetExprState *sexpr,
 			slotDesc = NULL;	/* keep compiler quiet */
 		}
 
-		sexpr->funcResultSlot = MakeSingleTupleTableSlot(slotDesc);
+		sexpr->funcResultSlot = MakeSingleTupleTableSlot(slotDesc, TTS_TYPE_MINIMALTUPLE);
 		MemoryContextSwitchTo(oldcontext);
 	}
 
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c
index caf91730ce1..0cc264f2c41 100644
--- a/src/backend/executor/execScan.c
+++ b/src/backend/executor/execScan.c
@@ -235,7 +235,7 @@ void
 ExecAssignScanProjectionInfo(ScanState *node)
 {
 	Scan	   *scan = (Scan *) node->ps.plan;
-	TupleDesc	tupdesc = node->ss_ScanTupleSlot->tts_tupleDescriptor;
+	TupleDesc	tupdesc = node->ss_ScanTupleSlot->tupleDescriptor;
 
 	ExecConditionalAssignProjectionInfo(&node->ps, tupdesc, scan->scanrelid);
 }
@@ -247,7 +247,7 @@ ExecAssignScanProjectionInfo(ScanState *node)
 void
 ExecAssignScanProjectionInfoWithVarno(ScanState *node, Index varno)
 {
-	TupleDesc	tupdesc = node->ss_ScanTupleSlot->tts_tupleDescriptor;
+	TupleDesc	tupdesc = node->ss_ScanTupleSlot->tupleDescriptor;
 
 	ExecConditionalAssignProjectionInfo(&node->ps, tupdesc, varno);
 }
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index c46d65cf938..32bee3acd7e 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -101,6 +101,560 @@ static TupleDesc ExecTypeFromTLInternal(List *targetList,
  * ----------------------------------------------------------------
  */
 
+static void
+tts_virtual_init(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_virtual_release(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_virtual_clear(TupleTableSlot *slot)
+{
+	slot->nvalid = 0;
+	slot->flags |= TTS_ISEMPTY;
+}
+
+static void
+tts_virtual_getsomeattrs(TupleTableSlot *slot, int natts)
+{
+	elog(ERROR, "should not be called");
+}
+
+static void
+tts_virtual_materialize(TupleTableSlot *slot)
+{
+	elog(ERROR, "materializing a virtual tuple");
+}
+
+static HeapTuple
+tts_virtual_get_heap_tuple(TupleTableSlot *slot)
+{
+	Assert(!(slot->flags & TTS_ISEMPTY));
+
+	return heap_form_tuple(slot->tupleDescriptor,
+						   slot->values,
+						   slot->nulls);
+
+}
+
+static MinimalTuple
+tts_virtual_get_minimal_tuple(TupleTableSlot *slot)
+{
+	Assert(!(slot->flags & TTS_ISEMPTY));
+
+	return heap_form_minimal_tuple(slot->tupleDescriptor,
+								   slot->values,
+								   slot->nulls);
+}
+
+static HeapTuple
+tts_virtual_copy_heap_tuple(TupleTableSlot *slot)
+{
+	Assert(!(slot->flags & TTS_ISEMPTY));
+
+	return heap_form_tuple(slot->tupleDescriptor,
+						   slot->values,
+						   slot->nulls);
+
+}
+
+static MinimalTuple
+tts_virtual_copy_minimal_tuple(TupleTableSlot *slot)
+{
+	Assert(!(slot->flags & TTS_ISEMPTY));
+
+	return heap_form_minimal_tuple(slot->tupleDescriptor,
+								   slot->values,
+								   slot->nulls);
+}
+
+
+static void
+tts_heap_init(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_heap_release(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_heap_clear(TupleTableSlot *slot)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	if (slot->flags & TTS_SHOULDFREE)
+	{
+		heap_freetuple(hslot->tuple);
+		slot->flags &= ~TTS_SHOULDFREE;
+	}
+
+	slot->nvalid = 0;
+	slot->flags |= TTS_ISEMPTY;
+	hslot->off = 0;
+	hslot->tuple = NULL;
+}
+
+static void
+tts_heap_getsomeattrs(TupleTableSlot *slot, int natts)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+
+	slot_deform_tuple(slot, hslot->tuple, &hslot->off, natts);
+}
+
+static void
+tts_heap_materialize(TupleTableSlot *slot)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+	MemoryContext oldContext;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+
+	if (slot->flags & TTS_SHOULDFREE)
+		return;
+
+	slot->flags |= TTS_SHOULDFREE;
+	oldContext = MemoryContextSwitchTo(slot->mcxt);
+
+	if (!hslot->tuple)
+		hslot->tuple = heap_form_tuple(slot->tupleDescriptor,
+									   slot->values,
+									   slot->nulls);
+	else
+		hslot->tuple = heap_copytuple(hslot->tuple);
+
+	slot->nvalid = 0;
+	hslot->off = 0;
+
+	MemoryContextSwitchTo(oldContext);
+}
+
+static HeapTuple
+tts_heap_get_heap_tuple(TupleTableSlot *slot)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+	if (!hslot->tuple)
+		tts_heap_materialize(slot);
+
+	return hslot->tuple;
+}
+
+static MinimalTuple
+tts_heap_get_minimal_tuple(TupleTableSlot *slot)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+	if (!hslot->tuple)
+		tts_heap_materialize(slot);
+
+	return minimal_tuple_from_heap_tuple(hslot->tuple);
+}
+
+static HeapTuple
+tts_heap_copy_heap_tuple(TupleTableSlot *slot)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+	if (!hslot->tuple)
+		tts_heap_materialize(slot);
+
+	return heap_copytuple(hslot->tuple);
+}
+
+static MinimalTuple
+tts_heap_copy_minimal_tuple(TupleTableSlot *slot)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	if (!hslot->tuple)
+		tts_heap_materialize(slot);
+
+	return minimal_tuple_from_heap_tuple(hslot->tuple);
+}
+
+static void
+tts_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, bool shouldFree)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	tts_heap_clear(slot);
+
+	slot->nvalid = 0;
+	hslot->tuple = tuple;
+	hslot->off = 0;
+	slot->flags &= ~TTS_ISEMPTY;
+
+	if (shouldFree)
+		slot->flags |= TTS_SHOULDFREE;
+}
+
+
+static void
+tts_minimal_init(TupleTableSlot *slot)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	mslot->tuple = &mslot->minhdr;
+}
+
+static void
+tts_minimal_release(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_minimal_clear(TupleTableSlot *slot)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	/* FIXME */
+	if (slot->flags & TTS_SHOULDFREE)
+	{
+		heap_free_minimal_tuple(mslot->mintuple);
+		slot->flags &= ~TTS_SHOULDFREE;
+	}
+
+	slot->nvalid = 0;
+	slot->flags |= TTS_ISEMPTY;
+	mslot->off = 0;
+	mslot->mintuple = NULL;
+}
+
+static void
+tts_minimal_getsomeattrs(TupleTableSlot *slot, int natts)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+
+	slot_deform_tuple(slot, mslot->tuple, &mslot->off, natts);
+}
+
+static void
+tts_minimal_materialize(TupleTableSlot *slot)
+{
+	elog(ERROR, "materializing a minimal tuple");
+}
+
+static void
+tts_minimal_localize(TupleTableSlot *slot)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+	MemoryContext oldContext;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+
+	if (slot->flags & TTS_SHOULDFREE)
+		return;
+
+	slot->flags |= TTS_SHOULDFREE;
+	oldContext = MemoryContextSwitchTo(slot->mcxt);
+
+	if (!mslot->mintuple)
+		mslot->mintuple = heap_form_minimal_tuple(slot->tupleDescriptor,
+												  slot->values,
+												  slot->nulls);
+	else
+		mslot->mintuple = heap_copy_minimal_tuple(mslot->mintuple);
+	mslot->tuple = &mslot->minhdr;
+	mslot->minhdr.t_len = mslot->mintuple->t_len + MINIMAL_TUPLE_OFFSET;
+	mslot->minhdr.t_data = (HeapTupleHeader) ((char *) mslot->mintuple - MINIMAL_TUPLE_OFFSET);
+
+	MemoryContextSwitchTo(oldContext);
+
+	slot->nvalid = 0;
+	mslot->off = 0;
+}
+
+static HeapTuple
+tts_minimal_get_heap_tuple(TupleTableSlot *slot)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+	if (!mslot->mintuple)
+		tts_minimal_localize(slot);
+
+	return mslot->tuple;
+}
+
+static MinimalTuple
+tts_minimal_get_minimal_tuple(TupleTableSlot *slot)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+	if (!mslot->mintuple)
+		tts_minimal_localize(slot);
+
+	return mslot->mintuple;
+}
+
+static HeapTuple
+tts_minimal_copy_heap_tuple(TupleTableSlot *slot)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	if (!mslot->mintuple)
+		tts_minimal_localize(slot);
+
+	return heap_tuple_from_minimal_tuple(mslot->mintuple);
+}
+
+static MinimalTuple
+tts_minimal_copy_minimal_tuple(TupleTableSlot *slot)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	if (!mslot->mintuple)
+		tts_minimal_localize(slot);
+
+	return heap_copy_minimal_tuple(mslot->mintuple);
+}
+
+static void
+tts_minimal_store_tuple(TupleTableSlot *slot, MinimalTuple mtup, bool shouldFree)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	tts_minimal_clear(slot);
+
+	slot->flags &= ~TTS_ISEMPTY;
+	slot->nvalid = 0;
+	mslot->off = 0;
+
+	mslot->mintuple = mtup;
+	mslot->tuple = &mslot->minhdr;
+	mslot->minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET;
+	mslot->minhdr.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET);
+	/* no need to set t_self or t_tableOid since we won't allow access */
+
+	if (shouldFree)
+		slot->flags |= TTS_SHOULDFREE;
+	else
+		Assert(!(slot->flags & TTS_SHOULDFREE));
+}
+
+
+static void
+tts_buffer_init(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_buffer_release(TupleTableSlot *slot)
+{
+}
+
+static void
+tts_buffer_clear(TupleTableSlot *slot)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	if (slot->flags & TTS_SHOULDFREE)
+	{
+		heap_freetuple(bslot->base.tuple);
+		slot->flags &= ~TTS_SHOULDFREE;
+	}
+
+	if (BufferIsValid(bslot->buffer))
+		ReleaseBuffer(bslot->buffer);
+
+	slot->nvalid = 0;
+	slot->flags |= TTS_ISEMPTY;
+	bslot->base.tuple = NULL;
+	bslot->buffer = InvalidBuffer;
+
+}
+
+static void
+tts_buffer_getsomeattrs(TupleTableSlot *slot, int natts)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+
+	slot_deform_tuple(slot, bslot->base.tuple, &bslot->base.off, natts);
+}
+
+static void
+tts_buffer_materialize(TupleTableSlot *slot)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+	MemoryContext oldContext;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+
+	if (slot->flags & TTS_SHOULDFREE)
+		return;
+
+	slot->flags |= TTS_SHOULDFREE;
+	oldContext = MemoryContextSwitchTo(slot->mcxt);
+
+	if (!bslot->base.tuple)
+		bslot->base.tuple = heap_form_tuple(slot->tupleDescriptor,
+											slot->values,
+											slot->nulls);
+	else
+		bslot->base.tuple = heap_copytuple(bslot->base.tuple);
+	MemoryContextSwitchTo(oldContext);
+
+
+	if (BufferIsValid(bslot->buffer))
+	{
+		ReleaseBuffer(bslot->buffer);
+		bslot->buffer = InvalidBuffer;
+	}
+
+	bslot->base.off = 0;
+	slot->nvalid = 0;
+
+}
+
+static HeapTuple
+tts_buffer_get_heap_tuple(TupleTableSlot *slot)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+	if (!bslot->base.tuple)
+		tts_buffer_materialize(slot);
+
+	return bslot->base.tuple;
+}
+
+static MinimalTuple
+tts_buffer_get_minimal_tuple(TupleTableSlot *slot)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+	if (!bslot->base.tuple)
+		tts_buffer_materialize(slot);
+
+	return minimal_tuple_from_heap_tuple(bslot->base.tuple);
+}
+
+static HeapTuple
+tts_buffer_copy_heap_tuple(TupleTableSlot *slot)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	Assert(!(slot->flags & TTS_ISEMPTY));
+	if (!bslot->base.tuple)
+		tts_buffer_materialize(slot);
+
+	return heap_copytuple(bslot->base.tuple);
+}
+
+static MinimalTuple
+tts_buffer_copy_minimal_tuple(TupleTableSlot *slot)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	if (!bslot->base.tuple)
+		tts_buffer_materialize(slot);
+
+	return minimal_tuple_from_heap_tuple(bslot->base.tuple);
+}
+
+static void
+tts_buffer_store_tuple(TupleTableSlot *slot, HeapTuple tuple, Buffer buffer)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	if (slot->flags & TTS_SHOULDFREE)
+	{
+		heap_freetuple(bslot->base.tuple);
+		slot->flags &= ~TTS_SHOULDFREE;
+	}
+
+	slot->flags &= ~TTS_ISEMPTY;
+	slot->nvalid = 0;
+	bslot->base.tuple = tuple;
+	bslot->base.off = 0;
+
+	/*
+	 * If tuple is on a disk page, keep the page pinned as long as we hold a
+	 * pointer into it.  We assume the caller already has such a pin.
+	 *
+	 * This is coded to optimize the case where the slot previously held a
+	 * tuple on the same disk page: in that case releasing and re-acquiring
+	 * the pin is a waste of cycles.  This is a common situation during
+	 * seqscans, so it's worth troubling over.
+	 */
+	if (bslot->buffer != buffer)
+	{
+		if (BufferIsValid(bslot->buffer))
+			ReleaseBuffer(bslot->buffer);
+		bslot->buffer = buffer;
+		IncrBufferRefCount(buffer);
+	}
+
+}
+
+
+static const TupleTableSlotOps TTSOpsVirtual = {
+	tts_virtual_init,
+	tts_virtual_release,
+	tts_virtual_clear,
+	tts_virtual_getsomeattrs,
+	tts_virtual_materialize,
+	tts_virtual_get_heap_tuple,
+	tts_virtual_get_minimal_tuple,
+	tts_virtual_copy_heap_tuple,
+	tts_virtual_copy_minimal_tuple,
+};
+
+static const TupleTableSlotOps TTSOpsHeapTuple = {
+	tts_heap_init,
+	tts_heap_release,
+	tts_heap_clear,
+	tts_heap_getsomeattrs,
+	tts_heap_materialize,
+	tts_heap_get_heap_tuple,
+	tts_heap_get_minimal_tuple,
+	tts_heap_copy_heap_tuple,
+	tts_heap_copy_minimal_tuple,
+};
+
+static const TupleTableSlotOps TTSOpsMinimalTuple = {
+	tts_minimal_init,
+	tts_minimal_release,
+	tts_minimal_clear,
+	tts_minimal_getsomeattrs,
+	tts_minimal_materialize,
+	tts_minimal_get_heap_tuple,
+	tts_minimal_get_minimal_tuple,
+	tts_minimal_copy_heap_tuple,
+	tts_minimal_copy_minimal_tuple,
+};
+
+static const TupleTableSlotOps TTSOpsBufferTuple = {
+	tts_buffer_init,
+	tts_buffer_release,
+	tts_buffer_clear,
+	tts_buffer_getsomeattrs,
+	tts_buffer_materialize,
+	tts_buffer_get_heap_tuple,
+	tts_buffer_get_minimal_tuple,
+	tts_buffer_copy_heap_tuple,
+	tts_buffer_copy_minimal_tuple,
+};
+
+
 /* --------------------------------
  *		MakeTupleTableSlot
  *
@@ -110,50 +664,75 @@ static TupleDesc ExecTypeFromTLInternal(List *targetList,
  * --------------------------------
  */
 TupleTableSlot *
-MakeTupleTableSlot(TupleDesc tupleDesc)
+MakeTupleTableSlot(TupleDesc tupleDesc, TupleTableSlotType st)
 {
-	Size		sz;
+	Size		basesz, allocsz;
 	TupleTableSlot *slot;
+	const TupleTableSlotOps *ops;
+
+	switch (st)
+	{
+		case TTS_TYPE_VIRTUAL:
+			ops = &TTSOpsVirtual;
+			basesz = sizeof(TupleTableSlot);
+			break;
+		case TTS_TYPE_HEAPTUPLE:
+			ops = &TTSOpsHeapTuple;
+			basesz = sizeof(HeapTupleTableSlot);
+			break;
+		case TTS_TYPE_MINIMALTUPLE:
+			ops = &TTSOpsMinimalTuple;
+			basesz = sizeof(MinimalTupleTableSlot);
+			break;
+		case TTS_TYPE_BUFFER:
+			ops = &TTSOpsBufferTuple;
+			basesz = sizeof(BufferHeapTupleTableSlot);
+			break;
+	}
+
+	/*
+	 * XXX: Main body of this should be in separate initialization
+	 * function so external types of slots can reuse.
+	 */
 
 	/*
 	 * When a fixed descriptor is specified, we can reduce overhead by
 	 * allocating the entire slot in one go.
 	 */
 	if (tupleDesc)
-		sz = MAXALIGN(sizeof(TupleTableSlot)) +
+		allocsz = MAXALIGN(basesz) +
 			MAXALIGN(tupleDesc->natts * sizeof(Datum)) +
 			MAXALIGN(tupleDesc->natts * sizeof(bool));
 	else
-		sz = sizeof(TupleTableSlot);
+		allocsz = basesz;
 
-	slot = palloc0(sz);
+	slot = palloc0(allocsz);
+	*((const TupleTableSlotOps **) &slot->cb) = ops;
 	slot->type = T_TupleTableSlot;
-	slot->tts_isempty = true;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = NULL;
-	slot->tts_fixedTupleDescriptor = tupleDesc != NULL;
-	slot->tts_tupleDescriptor = tupleDesc;
-	slot->tts_mcxt = CurrentMemoryContext;
-	slot->tts_buffer = InvalidBuffer;
-	slot->tts_nvalid = 0;
-	slot->tts_values = NULL;
-	slot->tts_isnull = NULL;
-	slot->tts_mintuple = NULL;
+	slot->flags = TTS_ISEMPTY;
+	slot->flags |= tupleDesc != NULL ? TTS_FIXED : 0;
+	slot->tupleDescriptor = tupleDesc;
+	slot->mcxt = CurrentMemoryContext;
+	slot->nvalid = 0;
 
 	if (tupleDesc != NULL)
 	{
-		slot->tts_values = (Datum *)
+		slot->values = (Datum *)
 			(((char *) slot)
-			 + MAXALIGN(sizeof(TupleTableSlot)));
-		slot->tts_isnull = (bool *)
+			 + MAXALIGN(basesz));
+		slot->nulls = (bool *)
 			(((char *) slot)
-			 + MAXALIGN(sizeof(TupleTableSlot))
+			 + MAXALIGN(basesz)
 			 + MAXALIGN(tupleDesc->natts * sizeof(Datum)));
 
 		PinTupleDesc(tupleDesc);
 	}
 
+	/*
+	 * And allow slot type specific initialization.
+	 */
+	slot->cb->init(slot);
+
 	return slot;
 }
 
@@ -164,9 +743,9 @@ MakeTupleTableSlot(TupleDesc tupleDesc)
  * --------------------------------
  */
 TupleTableSlot *
-ExecAllocTableSlot(List **tupleTable, TupleDesc desc)
+ExecAllocTableSlot(List **tupleTable, TupleDesc desc, TupleTableSlotType st)
 {
-	TupleTableSlot *slot = MakeTupleTableSlot(desc);
+	TupleTableSlot *slot = MakeTupleTableSlot(desc, st);
 
 	*tupleTable = lappend(*tupleTable, slot);
 
@@ -194,21 +773,21 @@ ExecResetTupleTable(List *tupleTable,	/* tuple table */
 
 		/* Always release resources and reset the slot to empty */
 		ExecClearTuple(slot);
-		if (slot->tts_tupleDescriptor)
+		if (slot->tupleDescriptor)
 		{
-			ReleaseTupleDesc(slot->tts_tupleDescriptor);
-			slot->tts_tupleDescriptor = NULL;
+			ReleaseTupleDesc(slot->tupleDescriptor);
+			slot->tupleDescriptor = NULL;
 		}
 
 		/* If shouldFree, release memory occupied by the slot itself */
 		if (shouldFree)
 		{
-			if (!slot->tts_fixedTupleDescriptor)
+			if (!(slot->flags & TTS_FIXED))
 			{
-				if (slot->tts_values)
-					pfree(slot->tts_values);
-				if (slot->tts_isnull)
-					pfree(slot->tts_isnull);
+				if (slot->values)
+					pfree(slot->values);
+				if (slot->nulls)
+					pfree(slot->nulls);
 			}
 			pfree(slot);
 		}
@@ -229,9 +808,9 @@ ExecResetTupleTable(List *tupleTable,	/* tuple table */
  * --------------------------------
  */
 TupleTableSlot *
-MakeSingleTupleTableSlot(TupleDesc tupdesc)
+MakeSingleTupleTableSlot(TupleDesc tupdesc, TupleTableSlotType st)
 {
-	TupleTableSlot *slot = MakeTupleTableSlot(tupdesc);
+	TupleTableSlot *slot = MakeTupleTableSlot(tupdesc, st);
 
 	return slot;
 }
@@ -246,17 +825,18 @@ MakeSingleTupleTableSlot(TupleDesc tupdesc)
 void
 ExecDropSingleTupleTableSlot(TupleTableSlot *slot)
 {
+	slot->cb->release(slot);
 	/* This should match ExecResetTupleTable's processing of one slot */
 	Assert(IsA(slot, TupleTableSlot));
 	ExecClearTuple(slot);
-	if (slot->tts_tupleDescriptor)
-		ReleaseTupleDesc(slot->tts_tupleDescriptor);
-	if (!slot->tts_fixedTupleDescriptor)
+	if (slot->tupleDescriptor)
+		ReleaseTupleDesc(slot->tupleDescriptor);
+	if (!(slot->flags & TTS_FIXED))
 	{
-		if (slot->tts_values)
-			pfree(slot->tts_values);
-		if (slot->tts_isnull)
-			pfree(slot->tts_isnull);
+		if (slot->values)
+			pfree(slot->values);
+		if (slot->nulls)
+			pfree(slot->nulls);
 	}
 	pfree(slot);
 }
@@ -281,7 +861,7 @@ void
 ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */
 					  TupleDesc tupdesc)	/* new tuple descriptor */
 {
-	Assert(!slot->tts_fixedTupleDescriptor);
+	Assert(!(slot->flags & TTS_FIXED));
 
 	/* For safety, make sure slot is empty before changing it */
 	ExecClearTuple(slot);
@@ -290,28 +870,28 @@ ExecSetSlotDescriptor(TupleTableSlot *slot, /* slot to change */
 	 * Release any old descriptor.  Also release old Datum/isnull arrays if
 	 * present (we don't bother to check if they could be re-used).
 	 */
-	if (slot->tts_tupleDescriptor)
-		ReleaseTupleDesc(slot->tts_tupleDescriptor);
+	if (slot->tupleDescriptor)
+		ReleaseTupleDesc(slot->tupleDescriptor);
 
-	if (slot->tts_values)
-		pfree(slot->tts_values);
-	if (slot->tts_isnull)
-		pfree(slot->tts_isnull);
+	if (slot->values)
+		pfree(slot->values);
+	if (slot->nulls)
+		pfree(slot->nulls);
 
 	/*
 	 * Install the new descriptor; if it's refcounted, bump its refcount.
 	 */
-	slot->tts_tupleDescriptor = tupdesc;
+	slot->tupleDescriptor = tupdesc;
 	PinTupleDesc(tupdesc);
 
 	/*
 	 * Allocate Datum/isnull arrays of the appropriate size.  These must have
 	 * the same lifetime as the slot, so allocate in the slot's own context.
 	 */
-	slot->tts_values = (Datum *)
-		MemoryContextAlloc(slot->tts_mcxt, tupdesc->natts * sizeof(Datum));
-	slot->tts_isnull = (bool *)
-		MemoryContextAlloc(slot->tts_mcxt, tupdesc->natts * sizeof(bool));
+	slot->values = (Datum *)
+		MemoryContextAlloc(slot->mcxt, tupdesc->natts * sizeof(Datum));
+	slot->nulls = (bool *)
+		MemoryContextAlloc(slot->mcxt, tupdesc->natts * sizeof(bool));
 }
 
 /* --------------------------------
@@ -363,51 +943,28 @@ ExecStoreTuple(HeapTuple tuple,
 	 */
 	Assert(tuple != NULL);
 	Assert(slot != NULL);
-	Assert(slot->tts_tupleDescriptor != NULL);
+	Assert(slot->tupleDescriptor != NULL);
 	/* passing shouldFree=true for a tuple on a disk page is not sane */
 	Assert(BufferIsValid(buffer) ? (!shouldFree) : true);
 
-	/*
-	 * Free any old physical tuple belonging to the slot.
-	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
-
-	/*
-	 * Store the new tuple into the specified slot.
-	 */
-	slot->tts_isempty = false;
-	slot->tts_shouldFree = shouldFree;
-	slot->tts_shouldFreeMin = false;
-	slot->tts_tuple = tuple;
-	slot->tts_mintuple = NULL;
-
-	/* Mark extracted state invalid */
-	slot->tts_nvalid = 0;
-
-	/*
-	 * If tuple is on a disk page, keep the page pinned as long as we hold a
-	 * pointer into it.  We assume the caller already has such a pin.
-	 *
-	 * This is coded to optimize the case where the slot previously held a
-	 * tuple on the same disk page: in that case releasing and re-acquiring
-	 * the pin is a waste of cycles.  This is a common situation during
-	 * seqscans, so it's worth troubling over.
-	 */
-	if (slot->tts_buffer != buffer)
+	if (BufferIsValid(buffer))
 	{
-		if (BufferIsValid(slot->tts_buffer))
-			ReleaseBuffer(slot->tts_buffer);
-		slot->tts_buffer = buffer;
-		if (BufferIsValid(buffer))
-			IncrBufferRefCount(buffer);
+		if (slot->cb != &TTSOpsBufferTuple)
+			elog(ERROR, "trying to store buffer tuple into wrong type of slot");
+		tts_buffer_store_tuple(slot, tuple, buffer);
+	}
+	else
+	{
+		if (slot->cb != &TTSOpsHeapTuple)
+			elog(ERROR, "trying to store heap tuple into wrong type of slot");
+		tts_heap_store_tuple(slot, tuple, shouldFree);
+
 	}
 
 	return slot;
 }
 
+
 /* --------------------------------
  *		ExecStoreMinimalTuple
  *
@@ -426,88 +983,15 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
 	 */
 	Assert(mtup != NULL);
 	Assert(slot != NULL);
-	Assert(slot->tts_tupleDescriptor != NULL);
+	Assert(slot->tupleDescriptor != NULL);
 
-	/*
-	 * Free any old physical tuple belonging to the slot.
-	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
-
-	/*
-	 * Store the new tuple into the specified slot.
-	 */
-	slot->tts_isempty = false;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = shouldFree;
-	slot->tts_tuple = &slot->tts_minhdr;
-	slot->tts_mintuple = mtup;
-
-	slot->tts_minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET;
-	slot->tts_minhdr.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET);
-	/* no need to set t_self or t_tableOid since we won't allow access */
-
-	/* Mark extracted state invalid */
-	slot->tts_nvalid = 0;
+	if (slot->cb != &TTSOpsMinimalTuple)
+		elog(ERROR, "trying to store minimal tuple into wrong type of slot");
+	tts_minimal_store_tuple(slot, mtup, shouldFree);
 
 	return slot;
 }
 
-/* --------------------------------
- *		ExecClearTuple
- *
- *		This function is used to clear out a slot in the tuple table.
- *
- *		NB: only the tuple is cleared, not the tuple descriptor (if any).
- * --------------------------------
- */
-TupleTableSlot *				/* return: slot passed */
-ExecClearTuple(TupleTableSlot *slot)	/* slot in which to store tuple */
-{
-	/*
-	 * sanity checks
-	 */
-	Assert(slot != NULL);
-
-	/*
-	 * Free the old physical tuple if necessary.
-	 */
-	if (slot->tts_shouldFree)
-		heap_freetuple(slot->tts_tuple);
-	if (slot->tts_shouldFreeMin)
-		heap_free_minimal_tuple(slot->tts_mintuple);
-
-	slot->tts_tuple = NULL;
-	slot->tts_mintuple = NULL;
-	slot->tts_shouldFree = false;
-	slot->tts_shouldFreeMin = false;
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
-
-	/*
-	 * Mark it empty.
-	 */
-	slot->tts_isempty = true;
-	slot->tts_nvalid = 0;
-
-	return slot;
-}
 
 /* --------------------------------
  *		ExecStoreVirtualTuple
@@ -527,11 +1011,11 @@ ExecStoreVirtualTuple(TupleTableSlot *slot)
 	 * sanity checks
 	 */
 	Assert(slot != NULL);
-	Assert(slot->tts_tupleDescriptor != NULL);
-	Assert(slot->tts_isempty);
+	Assert(slot->tupleDescriptor != NULL);
+	Assert(slot->flags & TTS_ISEMPTY);
 
-	slot->tts_isempty = false;
-	slot->tts_nvalid = slot->tts_tupleDescriptor->natts;
+	slot->flags &= ~TTS_ISEMPTY;
+	slot->nvalid = slot->tupleDescriptor->natts;
 
 	return slot;
 }
@@ -551,7 +1035,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
 	 * sanity checks
 	 */
 	Assert(slot != NULL);
-	Assert(slot->tts_tupleDescriptor != NULL);
+	Assert(slot->tupleDescriptor != NULL);
 
 	/* Clear any old contents */
 	ExecClearTuple(slot);
@@ -559,82 +1043,14 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
 	/*
 	 * Fill all the columns of the virtual tuple with nulls
 	 */
-	MemSet(slot->tts_values, 0,
-		   slot->tts_tupleDescriptor->natts * sizeof(Datum));
-	memset(slot->tts_isnull, true,
-		   slot->tts_tupleDescriptor->natts * sizeof(bool));
+	MemSet(slot->values, 0,
+		   slot->tupleDescriptor->natts * sizeof(Datum));
+	memset(slot->nulls, true,
+		   slot->tupleDescriptor->natts * sizeof(bool));
 
 	return ExecStoreVirtualTuple(slot);
 }
 
-/* --------------------------------
- *		ExecCopySlotTuple
- *			Obtain a copy of a slot's regular physical tuple.  The copy is
- *			palloc'd in the current memory context.
- *			The slot itself is undisturbed.
- *
- *		This works even if the slot contains a virtual or minimal tuple;
- *		however the "system columns" of the result will not be meaningful.
- * --------------------------------
- */
-HeapTuple
-ExecCopySlotTuple(TupleTableSlot *slot)
-{
-	/*
-	 * sanity checks
-	 */
-	Assert(slot != NULL);
-	Assert(!slot->tts_isempty);
-
-	/*
-	 * If we have a physical tuple (either format) then just copy it.
-	 */
-	if (TTS_HAS_PHYSICAL_TUPLE(slot))
-		return heap_copytuple(slot->tts_tuple);
-	if (slot->tts_mintuple)
-		return heap_tuple_from_minimal_tuple(slot->tts_mintuple);
-
-	/*
-	 * Otherwise we need to build a tuple from the Datum array.
-	 */
-	return heap_form_tuple(slot->tts_tupleDescriptor,
-						   slot->tts_values,
-						   slot->tts_isnull);
-}
-
-/* --------------------------------
- *		ExecCopySlotMinimalTuple
- *			Obtain a copy of a slot's minimal physical tuple.  The copy is
- *			palloc'd in the current memory context.
- *			The slot itself is undisturbed.
- * --------------------------------
- */
-MinimalTuple
-ExecCopySlotMinimalTuple(TupleTableSlot *slot)
-{
-	/*
-	 * sanity checks
-	 */
-	Assert(slot != NULL);
-	Assert(!slot->tts_isempty);
-
-	/*
-	 * If we have a physical tuple then just copy it.  Prefer to copy
-	 * tts_mintuple since that's a tad cheaper.
-	 */
-	if (slot->tts_mintuple)
-		return heap_copy_minimal_tuple(slot->tts_mintuple);
-	if (slot->tts_tuple)
-		return minimal_tuple_from_heap_tuple(slot->tts_tuple);
-
-	/*
-	 * Otherwise we need to build a tuple from the Datum array.
-	 */
-	return heap_form_minimal_tuple(slot->tts_tupleDescriptor,
-								   slot->tts_values,
-								   slot->tts_isnull);
-}
-
 /* --------------------------------
  *		ExecFetchSlotTuple
  *			Fetch the slot's regular physical tuple.
@@ -657,18 +1073,9 @@ ExecFetchSlotTuple(TupleTableSlot *slot)
 	 * sanity checks
 	 */
 	Assert(slot != NULL);
-	Assert(!slot->tts_isempty);
+	Assert(!(slot->flags & TTS_ISEMPTY));
 
-	/*
-	 * If we have a regular physical tuple then just return it.
-	 */
-	if (TTS_HAS_PHYSICAL_TUPLE(slot))
-		return slot->tts_tuple;
-
-	/*
-	 * Otherwise materialize the slot...
-	 */
-	return ExecMaterializeSlot(slot);
+	return slot->cb->get_heap_tuple(slot);
 }
 
 /* --------------------------------
@@ -687,14 +1094,23 @@ ExecFetchSlotTuple(TupleTableSlot *slot)
 MinimalTuple
 ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 {
+#ifdef NOT_ANYMORE
 	MemoryContext oldContext;
+#endif
 
 	/*
 	 * sanity checks
 	 */
 	Assert(slot != NULL);
-	Assert(!slot->tts_isempty);
+	Assert(!(slot->flags & TTS_ISEMPTY));
 
+	if (slot->cb == &TTSOpsMinimalTuple)
+		return tts_minimal_get_minimal_tuple(slot);
+	else
+		/* XXX: this'll copy! */
+		return slot->cb->get_minimal_tuple(slot);
+
+#ifdef NOT_ANYMORE
 	/*
 	 * If we have a minimal physical tuple (local or not) then just return it.
 	 */
@@ -721,6 +1137,7 @@ ExecFetchSlotMinimalTuple(TupleTableSlot *slot)
 	 */
 
 	return slot->tts_mintuple;
+#endif
 }
 
 /* --------------------------------
@@ -738,85 +1155,12 @@ ExecFetchSlotTupleDatum(TupleTableSlot *slot)
 
 	/* Fetch slot's contents in regular-physical-tuple form */
 	tup = ExecFetchSlotTuple(slot);
-	tupdesc = slot->tts_tupleDescriptor;
+	tupdesc = slot->tupleDescriptor;
 
 	/* Convert to Datum form */
 	return heap_copy_tuple_as_datum(tup, tupdesc);
 }
 
-/* --------------------------------
- *		ExecMaterializeSlot
- *			Force a slot into the "materialized" state.
- *
- *		This causes the slot's tuple to be a local copy not dependent on
- *		any external storage.  A pointer to the contained tuple is returned.
- *
- *		A typical use for this operation is to prepare a computed tuple
- *		for being stored on disk.  The original data may or may not be
- *		virtual, but in any case we need a private copy for heap_insert
- *		to scribble on.
- * --------------------------------
- */
-HeapTuple
-ExecMaterializeSlot(TupleTableSlot *slot)
-{
-	MemoryContext oldContext;
-
-	/*
-	 * sanity checks
-	 */
-	Assert(slot != NULL);
-	Assert(!slot->tts_isempty);
-
-	/*
-	 * If we have a regular physical tuple, and it's locally palloc'd, we have
-	 * nothing to do.
-	 */
-	if (slot->tts_tuple && slot->tts_shouldFree)
-		return slot->tts_tuple;
-
-	/*
-	 * Otherwise, copy or build a physical tuple, and store it into the slot.
-	 *
-	 * We may be called in a context that is shorter-lived than the tuple
-	 * slot, but we have to ensure that the materialized tuple will survive
-	 * anyway.
-	 */
-	oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
-	slot->tts_tuple = ExecCopySlotTuple(slot);
-	slot->tts_shouldFree = true;
-	MemoryContextSwitchTo(oldContext);
-
-	/*
-	 * Drop the pin on the referenced buffer, if there is one.
-	 */
-	if (BufferIsValid(slot->tts_buffer))
-		ReleaseBuffer(slot->tts_buffer);
-
-	slot->tts_buffer = InvalidBuffer;
-
-	/*
-	 * Mark extracted state invalid.  This is important because the slot is
-	 * not supposed to depend any more on the previous external data; we
-	 * mustn't leave any dangling pass-by-reference datums in tts_values.
-	 * However, we have not actually invalidated any such datums, if there
-	 * happen to be any previously fetched from the slot.  (Note in particular
-	 * that we have not pfree'd tts_mintuple, if there is one.)
-	 */
-	slot->tts_nvalid = 0;
-
-	/*
-	 * On the same principle of not depending on previous remote storage,
-	 * forget the mintuple if it's not local storage.  (If it is local
-	 * storage, we must not pfree it now, since callers might have already
-	 * fetched datum pointers referencing it.)
-	 */
-	if (!slot->tts_shouldFreeMin)
-		slot->tts_mintuple = NULL;
-
-	return slot->tts_tuple;
-}
-
 /* --------------------------------
  *		ExecCopySlot
  *			Copy the source slot's contents into the destination slot.
@@ -830,19 +1174,16 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 TupleTableSlot *
 ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
-	HeapTuple	newTuple;
-	MemoryContext oldContext;
+	int natts = srcslot->tupleDescriptor->natts;
 
-	/*
-	 * There might be ways to optimize this when the source is virtual, but
-	 * for now just always build a physical copy.  Make sure it is in the
-	 * right context.
-	 */
-	oldContext = MemoryContextSwitchTo(dstslot->tts_mcxt);
-	newTuple = ExecCopySlotTuple(srcslot);
-	MemoryContextSwitchTo(oldContext);
+	ExecClearTuple(dstslot);
+	/* XXX: optimize when tuple based? */
+	slot_getallattrs(srcslot);
+	memcpy(dstslot->values, srcslot->values, sizeof(Datum) * natts);
+	memcpy(dstslot->nulls, srcslot->nulls, sizeof(bool) * natts);
+	ExecStoreVirtualTuple(dstslot);
 
-	return ExecStoreTuple(newTuple, dstslot, InvalidBuffer, true);
+	return dstslot;
 }
 
 
@@ -867,7 +1208,7 @@ ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
  * ----------------
  */
 void
-ExecInitResultTupleSlotTL(EState *estate, PlanState *planstate)
+ExecInitResultTupleSlotTL(EState *estate, PlanState *planstate, TupleTableSlotType st)
 {
 	bool		hasoid;
 	TupleDesc	tupDesc;
@@ -884,7 +1225,8 @@ ExecInitResultTupleSlotTL(EState *estate, PlanState *planstate)
 
 	tupDesc = ExecTypeFromTL(planstate->plan->targetlist, hasoid);
 
-	planstate->ps_ResultTupleSlot = ExecAllocTableSlot(&estate->es_tupleTable, tupDesc);
+	planstate->ps_ResultTupleSlot =
+		ExecAllocTableSlot(&estate->es_tupleTable, tupDesc, st);
 }
 
 /* ----------------
@@ -892,10 +1234,10 @@ ExecInitResultTupleSlotTL(EState *estate, PlanState *planstate)
  * ----------------
  */
 void
-ExecInitScanTupleSlot(EState *estate, ScanState *scanstate, TupleDesc tupledesc)
+ExecInitScanTupleSlot(EState *estate, ScanState *scanstate, TupleDesc tupledesc, TupleTableSlotType st)
 {
-	scanstate->ss_ScanTupleSlot = ExecAllocTableSlot(&estate->es_tupleTable,
-													 tupledesc);
+	scanstate->ss_ScanTupleSlot =
+		ExecAllocTableSlot(&estate->es_tupleTable, tupledesc, st);
 }
 
 /* ----------------
@@ -907,9 +1249,9 @@ ExecInitScanTupleSlot(EState *estate, ScanState *scanstate, TupleDesc tupledesc)
  * ----------------
  */
 TupleTableSlot *
-ExecInitExtraTupleSlot(EState *estate, TupleDesc tupledesc)
+ExecInitExtraTupleSlot(EState *estate, TupleDesc tupledesc, TupleTableSlotType st)
 {
-	return ExecAllocTableSlot(&estate->es_tupleTable, tupledesc);
+	return ExecAllocTableSlot(&estate->es_tupleTable, tupledesc, st);
 }
 
 /* ----------------
@@ -921,9 +1263,9 @@ ExecInitExtraTupleSlot(EState *estate, TupleDesc tupledesc)
  * ----------------
  */
 TupleTableSlot *
-ExecInitNullTupleSlot(EState *estate, TupleDesc tupType)
+ExecInitNullTupleSlot(EState *estate, TupleDesc tupType, TupleTableSlotType st)
 {
-	TupleTableSlot *slot = ExecInitExtraTupleSlot(estate, tupType);
+	TupleTableSlot *slot = ExecInitExtraTupleSlot(estate, tupType, st);
 
 	return ExecStoreAllNullTuple(slot);
 }
@@ -1094,6 +1436,7 @@ BlessTupleDesc(TupleDesc tupdesc)
 	return tupdesc;				/* just for notational convenience */
 }
 
+#ifdef NOT_ANYMORE
 /*
  * TupleDescGetSlot - Initialize a slot based on the supplied tupledesc
  *
@@ -1115,6 +1458,7 @@ TupleDescGetSlot(TupleDesc tupdesc)
 	/* Return the slot */
 	return slot;
 }
+#endif
 
 /*
  * TupleDescGetAttInMetadata - Build an AttInMetadata structure based on the
@@ -1294,7 +1638,7 @@ begin_tup_output_tupdesc(DestReceiver *dest, TupleDesc tupdesc)
 
 	tstate = (TupOutputState *) palloc(sizeof(TupOutputState));
 
-	tstate->slot = MakeSingleTupleTableSlot(tupdesc);
+	tstate->slot = MakeSingleTupleTableSlot(tupdesc, TTS_TYPE_VIRTUAL);
 	tstate->dest = dest;
 
 	tstate->dest->rStartup(tstate->dest, (int) CMD_SELECT, tupdesc);
@@ -1309,14 +1653,14 @@ void
 do_tup_output(TupOutputState *tstate, Datum *values, bool *isnull)
 {
 	TupleTableSlot *slot = tstate->slot;
-	int			natts = slot->tts_tupleDescriptor->natts;
+	int			natts = slot->tupleDescriptor->natts;
 
 	/* make sure the slot is clear */
 	ExecClearTuple(slot);
 
 	/* insert data */
-	memcpy(slot->tts_values, values, natts * sizeof(Datum));
-	memcpy(slot->tts_isnull, isnull, natts * sizeof(bool));
+	memcpy(slot->values, values, natts * sizeof(Datum));
+	memcpy(slot->nulls, isnull, natts * sizeof(bool));
 
 	/* mark slot as containing a virtual tuple */
 	ExecStoreVirtualTuple(slot);
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index a8ae37ebc80..94e9fbf6766 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -436,7 +436,7 @@ ExecGetResultType(PlanState *planstate)
 {
 	TupleTableSlot *slot = planstate->ps_ResultTupleSlot;
 
-	return slot->tts_tupleDescriptor;
+	return slot->tupleDescriptor;
 }
 
 
@@ -590,7 +590,7 @@ ExecAssignScanType(ScanState *scanstate, TupleDesc tupDesc)
  * ----------------
  */
 void
-ExecCreateScanSlotFromOuterPlan(EState *estate, ScanState *scanstate)
+ExecCreateScanSlotFromOuterPlan(EState *estate, ScanState *scanstate, TupleTableSlotType tp)
 {
 	PlanState  *outerPlan;
 	TupleDesc	tupDesc;
@@ -598,7 +598,7 @@ ExecCreateScanSlotFromOuterPlan(EState *estate, ScanState *scanstate)
 	outerPlan = outerPlanState(scanstate);
 	tupDesc = ExecGetResultType(outerPlan);
 
-	ExecInitScanTupleSlot(estate, scanstate, tupDesc);
+	ExecInitScanTupleSlot(estate, scanstate, tupDesc, tp);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 7e249f575f3..7f2763d9b21 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -751,7 +751,7 @@ init_sql_fcache(FmgrInfo *finfo, Oid collation, bool lazyEvalOK)
 	if (fcache->returnsTuple)
 	{
 		/* Make sure output rowtype is properly blessed */
-		BlessTupleDesc(fcache->junkFilter->jf_resultSlot->tts_tupleDescriptor);
+		BlessTupleDesc(fcache->junkFilter->jf_resultSlot->tupleDescriptor);
 	}
 	else if (fcache->returnsSet && type_is_rowtype(fcache->rettype))
 	{
@@ -1667,7 +1667,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, TTS_TYPE_MINIMALTUPLE));
 	}
 	else if (fn_typtype == TYPTYPE_COMPOSITE || rettype == RECORDOID)
 	{
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 1b1334006fa..05958fb9ffa 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -843,8 +843,8 @@ process_ordered_aggregate_multi(AggState *aggstate,
 			/* Start from 1, since the 0th arg will be the transition value */
 			for (i = 0; i < numTransInputs; i++)
 			{
-				fcinfo->arg[i + 1] = slot1->tts_values[i];
-				fcinfo->argnull[i + 1] = slot1->tts_isnull[i];
+				fcinfo->arg[i + 1] = slot1->values[i];
+				fcinfo->argnull[i + 1] = slot1->nulls[i];
 			}
 
 			advance_transition_function(aggstate, pertrans, pergroupstate);
@@ -1080,7 +1080,7 @@ prepare_projection_slot(AggState *aggstate, TupleTableSlot *slot, int currentSet
 
 		aggstate->grouped_cols = grouped_cols;
 
-		if (slot->tts_isempty)
+		if (slot->flags & TTS_ISEMPTY)
 		{
 			/*
 			 * Force all values to be NULL if working on an empty input tuple
@@ -1101,7 +1101,7 @@ prepare_projection_slot(AggState *aggstate, TupleTableSlot *slot, int currentSet
 				int			attnum = lfirst_int(lc);
 
 				if (!bms_is_member(attnum, grouped_cols))
-					slot->tts_isnull[attnum - 1] = true;
+					slot->nulls[attnum - 1] = true;
 			}
 		}
 	}
@@ -1278,7 +1278,7 @@ build_hash_table(AggState *aggstate)
 		Assert(perhash->aggnode->numGroups > 0);
 
 		perhash->hashtable = BuildTupleHashTable(&aggstate->ss.ps,
-												 perhash->hashslot->tts_tupleDescriptor,
+												 perhash->hashslot->tupleDescriptor,
 												 perhash->numCols,
 												 perhash->hashGrpColIdxHash,
 												 perhash->eqfuncoids,
@@ -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,
+							   TTS_TYPE_MINIMALTUPLE);
 
 		list_free(hashTlist);
 		bms_free(colnos);
@@ -1459,8 +1460,8 @@ lookup_hash_entry(AggState *aggstate)
 	{
 		int			varNumber = perhash->hashGrpColIdxInput[i] - 1;
 
-		hashslot->tts_values[i] = inputslot->tts_values[varNumber];
-		hashslot->tts_isnull[i] = inputslot->tts_isnull[varNumber];
+		hashslot->values[i] = inputslot->values[varNumber];
+		hashslot->nulls[i] = inputslot->nulls[varNumber];
 	}
 	ExecStoreVirtualTuple(hashslot);
 
@@ -2030,15 +2031,15 @@ agg_retrieve_hash_table(AggState *aggstate)
 		slot_getallattrs(hashslot);
 
 		ExecClearTuple(firstSlot);
-		memset(firstSlot->tts_isnull, true,
-			   firstSlot->tts_tupleDescriptor->natts * sizeof(bool));
+		memset(firstSlot->nulls, true,
+			   firstSlot->tupleDescriptor->natts * sizeof(bool));
 
 		for (i = 0; i < perhash->numhashGrpCols; i++)
 		{
 			int			varNumber = perhash->hashGrpColIdxInput[i] - 1;
 
-			firstSlot->tts_values[varNumber] = hashslot->tts_values[i];
-			firstSlot->tts_isnull[varNumber] = hashslot->tts_isnull[i];
+			firstSlot->values[varNumber] = hashslot->values[i];
+			firstSlot->nulls[varNumber] = hashslot->nulls[i];
 		}
 		ExecStoreVirtualTuple(firstSlot);
 
@@ -2212,15 +2213,15 @@ ExecInitAgg(Agg *node, EState *estate, int eflags)
 	/*
 	 * initialize source tuple type.
 	 */
-	ExecCreateScanSlotFromOuterPlan(estate, &aggstate->ss);
-	scanDesc = aggstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
+	ExecCreateScanSlotFromOuterPlan(estate, &aggstate->ss, TTS_TYPE_HEAPTUPLE);
+	scanDesc = aggstate->ss.ss_ScanTupleSlot->tupleDescriptor;
 	if (node->chain)
-		aggstate->sort_slot = ExecInitExtraTupleSlot(estate, scanDesc);
+		aggstate->sort_slot = ExecInitExtraTupleSlot(estate, scanDesc, TTS_TYPE_MINIMALTUPLE);
 
 	/*
 	 * Initialize result type, slot and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &aggstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &aggstate->ss.ps, TTS_TYPE_VIRTUAL);
 	ExecAssignProjectionInfo(&aggstate->ss.ps, NULL);
 
 	/*
@@ -3063,7 +3064,8 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans,
 	{
 		pertrans->sortdesc = ExecTypeFromTL(aggref->args, false);
 		pertrans->sortslot =
-			ExecInitExtraTupleSlot(estate, pertrans->sortdesc);
+			ExecInitExtraTupleSlot(estate, pertrans->sortdesc,
+								   TTS_TYPE_MINIMALTUPLE);
 	}
 
 	if (numSortCols > 0)
@@ -3085,7 +3087,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,
+									   TTS_TYPE_MINIMALTUPLE);
 		}
 
 		/* Extract the sort information for use later */
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 7a3dd2ee2d0..8e10edbb63c 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -131,7 +131,7 @@ ExecInitAppend(Append *node, EState *estate, int eflags)
 	/*
 	 * Initialize result tuple type and slot.
 	 */
-	ExecInitResultTupleSlotTL(estate, &appendstate->ps);
+	ExecInitResultTupleSlotTL(estate, &appendstate->ps, TTS_TYPE_VIRTUAL);
 
 	/*
 	 * call ExecInitNode on each of the plans to be executed and save the
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 3e1c9e07145..399d67126cb 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -924,13 +924,15 @@ ExecInitBitmapHeapScan(BitmapHeapScan *node, EState *estate, int eflags)
 	 * get the scan type from the relation descriptor.
 	 */
 	ExecInitScanTupleSlot(estate, &scanstate->ss,
-						  RelationGetDescr(currentRelation));
+						  RelationGetDescr(currentRelation),
+						  TTS_TYPE_BUFFER);
 
 
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps,
+							  TTS_TYPE_VIRTUAL);
 	ExecAssignScanProjectionInfo(&scanstate->ss);
 
 	/*
diff --git a/src/backend/executor/nodeCtescan.c b/src/backend/executor/nodeCtescan.c
index 218619c760d..35b98a93b1b 100644
--- a/src/backend/executor/nodeCtescan.c
+++ b/src/backend/executor/nodeCtescan.c
@@ -247,12 +247,13 @@ 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),
+						  TTS_TYPE_MINIMALTUPLE);
 
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps, TTS_TYPE_VIRTUAL);
 	ExecAssignScanProjectionInfo(&scanstate->ss);
 
 	/*
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
index b816e0b31db..569a050d8f8 100644
--- a/src/backend/executor/nodeCustom.c
+++ b/src/backend/executor/nodeCustom.c
@@ -73,13 +73,13 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
 		TupleDesc	scan_tupdesc;
 
 		scan_tupdesc = ExecTypeFromTL(cscan->custom_scan_tlist, false);
-		ExecInitScanTupleSlot(estate, &css->ss, scan_tupdesc);
+		ExecInitScanTupleSlot(estate, &css->ss, scan_tupdesc, TTS_TYPE_VIRTUAL); /* FIXME */
 		/* 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), TTS_TYPE_VIRTUAL);
 		/* Node's targetlist will contain Vars with varno = scanrelid */
 		tlistvarno = scanrelid;
 	}
@@ -87,7 +87,7 @@ ExecInitCustomScan(CustomScan *cscan, EState *estate, int eflags)
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &css->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &css->ss.ps, TTS_TYPE_VIRTUAL);
 	ExecAssignScanProjectionInfoWithVarno(&css->ss, tlistvarno);
 
 	/* initialize child expressions */
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 0084234b350..7cd8657bfa8 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -180,13 +180,16 @@ 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,
+							  TTS_TYPE_HEAPTUPLE);
 		/* Node's targetlist will contain Vars with varno = INDEX_VAR */
 		tlistvarno = INDEX_VAR;
 	}
 	else
 	{
-		ExecInitScanTupleSlot(estate, &scanstate->ss, RelationGetDescr(currentRelation));
+		ExecInitScanTupleSlot(estate, &scanstate->ss,
+							  RelationGetDescr(currentRelation),
+							  TTS_TYPE_HEAPTUPLE);
 		/* Node's targetlist will contain Vars with varno = scanrelid */
 		tlistvarno = scanrelid;
 	}
@@ -194,7 +197,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps, TTS_TYPE_VIRTUAL);
 	ExecAssignScanProjectionInfoWithVarno(&scanstate->ss, tlistvarno);
 
 	/*
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index fb7c9f67875..be3ed221ac9 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -197,8 +197,8 @@ FunctionNext(FunctionScanState *node)
 			 */
 			for (i = 0; i < fs->colcount; i++)
 			{
-				scanslot->tts_values[att] = (Datum) 0;
-				scanslot->tts_isnull[att] = true;
+				scanslot->values[att] = (Datum) 0;
+				scanslot->nulls[att] = true;
 				att++;
 			}
 		}
@@ -211,8 +211,8 @@ FunctionNext(FunctionScanState *node)
 
 			for (i = 0; i < fs->colcount; i++)
 			{
-				scanslot->tts_values[att] = fs->func_slot->tts_values[i];
-				scanslot->tts_isnull[att] = fs->func_slot->tts_isnull[i];
+				scanslot->values[att] = fs->func_slot->values[i];
+				scanslot->nulls[att] = fs->func_slot->nulls[i];
 				att++;
 			}
 
@@ -229,8 +229,8 @@ FunctionNext(FunctionScanState *node)
 	 */
 	if (node->ordinality)
 	{
-		scanslot->tts_values[att] = Int64GetDatumFast(node->ordinal);
-		scanslot->tts_isnull[att] = false;
+		scanslot->values[att] = Int64GetDatumFast(node->ordinal);
+		scanslot->nulls[att] = false;
 	}
 
 	/*
@@ -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,
+												   TTS_TYPE_VIRTUAL);
 		}
 		else
 			fs->func_slot = NULL;
@@ -482,12 +483,14 @@ ExecInitFunctionScan(FunctionScan *node, EState *estate, int eflags)
 	/*
 	 * Initialize scan slot and type.
 	 */
-	ExecInitScanTupleSlot(estate, &scanstate->ss, scan_tupdesc);
+	ExecInitScanTupleSlot(estate, &scanstate->ss, scan_tupdesc,
+						  TTS_TYPE_MINIMALTUPLE);
 
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps,
+							  TTS_TYPE_VIRTUAL);
 	ExecAssignScanProjectionInfo(&scanstate->ss);
 
 	/*
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index eaf7d2d5632..4a19c589a2d 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -94,13 +94,14 @@ ExecInitGather(Gather *node, EState *estate, int eflags)
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &gatherstate->ps);
+	ExecInitResultTupleSlotTL(estate, &gatherstate->ps, TTS_TYPE_VIRTUAL);
 	ExecConditionalAssignProjectionInfo(&gatherstate->ps, tupDesc, OUTER_VAR);
 
 	/*
 	 * Initialize funnel slot to same tuple descriptor as outer plan.
 	 */
-	gatherstate->funnel_slot = ExecInitExtraTupleSlot(estate, tupDesc);
+	gatherstate->funnel_slot = ExecInitExtraTupleSlot(estate, tupDesc,
+													  TTS_TYPE_HEAPTUPLE);
 
 	/*
 	 * 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 83221cdbaee..5d71b00cfd9 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -119,7 +119,7 @@ ExecInitGatherMerge(GatherMerge *node, EState *estate, int eflags)
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &gm_state->ps);
+	ExecInitResultTupleSlotTL(estate, &gm_state->ps, TTS_TYPE_VIRTUAL);
 	ExecConditionalAssignProjectionInfo(&gm_state->ps, tupDesc, OUTER_VAR);
 
 	/*
@@ -403,7 +403,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,
+				TTS_TYPE_HEAPTUPLE);
 	}
 
 	/* Allocate the resources for the merge */
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index c6efd64d00d..34b2483a3a2 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -189,12 +189,13 @@ ExecInitGroup(Group *node, EState *estate, int eflags)
 	/*
 	 * Initialize scan slot and type.
 	 */
-	ExecCreateScanSlotFromOuterPlan(estate, &grpstate->ss);
+	ExecCreateScanSlotFromOuterPlan(estate, &grpstate->ss,
+									TTS_TYPE_VIRTUAL);
 
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &grpstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &grpstate->ss.ps, TTS_TYPE_VIRTUAL);
 	ExecAssignProjectionInfo(&grpstate->ss.ps, NULL);
 
 	/*
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 06bb44b1631..fa3490be726 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(estate, &hashstate->ps);
+	ExecInitResultTupleSlotTL(estate, &hashstate->ps, TTS_TYPE_MINIMALTUPLE);
 	hashstate->ps.ps_ProjInfo = NULL;
 
 	/*
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index ab91eb25273..97f76fc0d29 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -642,13 +642,14 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &hjstate->js.ps);
+	ExecInitResultTupleSlotTL(estate, &hjstate->js.ps, TTS_TYPE_VIRTUAL);
 	ExecAssignProjectionInfo(&hjstate->js.ps, NULL);
 
 	/*
 	 * tuple table initialization
 	 */
-	hjstate->hj_OuterTupleSlot = ExecInitExtraTupleSlot(estate, outerDesc);
+	hjstate->hj_OuterTupleSlot = ExecInitExtraTupleSlot(estate, outerDesc,
+														TTS_TYPE_MINIMALTUPLE);
 
 	/*
 	 * detect whether we need only consider the first matching inner tuple
@@ -665,17 +666,17 @@ ExecInitHashJoin(HashJoin *node, EState *estate, int eflags)
 		case JOIN_LEFT:
 		case JOIN_ANTI:
 			hjstate->hj_NullInnerTupleSlot =
-				ExecInitNullTupleSlot(estate, innerDesc);
+				ExecInitNullTupleSlot(estate, innerDesc, TTS_TYPE_VIRTUAL);
 			break;
 		case JOIN_RIGHT:
 			hjstate->hj_NullOuterTupleSlot =
-				ExecInitNullTupleSlot(estate, outerDesc);
+				ExecInitNullTupleSlot(estate, outerDesc, TTS_TYPE_VIRTUAL);
 			break;
 		case JOIN_FULL:
 			hjstate->hj_NullOuterTupleSlot =
-				ExecInitNullTupleSlot(estate, outerDesc);
+				ExecInitNullTupleSlot(estate, outerDesc, TTS_TYPE_VIRTUAL);
 			hjstate->hj_NullInnerTupleSlot =
-				ExecInitNullTupleSlot(estate, innerDesc);
+				ExecInitNullTupleSlot(estate, innerDesc, TTS_TYPE_VIRTUAL);
 			break;
 		default:
 			elog(ERROR, "unrecognized join type: %d",
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index ddc0ae90615..523ff79989f 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -197,7 +197,7 @@ IndexOnlyNext(IndexOnlyScanState *node)
 			 * exactly the slot's format, but it seems worth doing a quick
 			 * check on the number of fields.
 			 */
-			Assert(slot->tts_tupleDescriptor->natts ==
+			Assert(slot->tupleDescriptor->natts ==
 				   scandesc->xs_hitupdesc->natts);
 			ExecStoreTuple(scandesc->xs_hitup, slot, InvalidBuffer, false);
 		}
@@ -268,8 +268,8 @@ static void
 StoreIndexTuple(TupleTableSlot *slot, IndexTuple itup, TupleDesc itupdesc)
 {
 	int			nindexatts = itupdesc->natts;
-	Datum	   *values = slot->tts_values;
-	bool	   *isnull = slot->tts_isnull;
+	Datum	   *values = slot->values;
+	bool	   *isnull = slot->nulls;
 	int			i;
 
 	/*
@@ -279,7 +279,7 @@ StoreIndexTuple(TupleTableSlot *slot, IndexTuple itup, TupleDesc itupdesc)
 	 * number of columns though, as well as being datatype-compatible which is
 	 * something we can't so easily check.
 	 */
-	Assert(slot->tts_tupleDescriptor->natts == nindexatts);
+	Assert(slot->tupleDescriptor->natts == nindexatts);
 
 	ExecClearTuple(slot);
 	for (i = 0; i < nindexatts; i++)
@@ -534,14 +534,14 @@ 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, TTS_TYPE_HEAPTUPLE);
 
 	/*
 	 * Initialize result slot, type and projection info.  The node's
 	 * targetlist will contain Vars with varno = INDEX_VAR, referencing the
 	 * scan tuple.
 	 */
-	ExecInitResultTupleSlotTL(estate, &indexstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &indexstate->ss.ps, TTS_TYPE_VIRTUAL);
 	ExecAssignScanProjectionInfoWithVarno(&indexstate->ss, INDEX_VAR);
 
 	/*
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 01c9de88f4d..6ddec9be18a 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -952,12 +952,13 @@ ExecInitIndexScan(IndexScan *node, EState *estate, int eflags)
 	 * get the scan type from the relation descriptor.
 	 */
 	ExecInitScanTupleSlot(estate, &indexstate->ss,
-						  RelationGetDescr(currentRelation));
+						  RelationGetDescr(currentRelation),
+						  TTS_TYPE_BUFFER); /* FIXME: wrong for reorder case */
 
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &indexstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &indexstate->ss.ps, TTS_TYPE_VIRTUAL);
 	ExecAssignScanProjectionInfo(&indexstate->ss);
 
 	/*
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index 56d98b4490b..356ccc64746 100644
--- a/src/backend/executor/nodeLimit.c
+++ b/src/backend/executor/nodeLimit.c
@@ -371,7 +371,8 @@ ExecInitLimit(Limit *node, EState *estate, int eflags)
 	 * Initialize result slot and type. (XXX not actually used, but upper
 	 * nodes access it to get this node's result tupledesc...)
 	 */
-	ExecInitResultTupleSlotTL(estate, &limitstate->ps);
+	ExecInitResultTupleSlotTL(estate, &limitstate->ps,
+							  TTS_TYPE_VIRTUAL);
 
 	/*
 	 * limit nodes do no projections, so initialize projection info for this
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index b39ccf7dc13..30d8422e45e 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -378,7 +378,7 @@ ExecInitLockRows(LockRows *node, EState *estate, int eflags)
 	 * Tuple table initialization (XXX not actually used, but upper nodes
 	 * access it to get this node's result tupledesc...)
 	 */
-	ExecInitResultTupleSlotTL(estate, &lrstate->ps);
+	ExecInitResultTupleSlotTL(estate, &lrstate->ps, TTS_TYPE_VIRTUAL);
 
 	/*
 	 * then initialize outer plan
diff --git a/src/backend/executor/nodeMaterial.c b/src/backend/executor/nodeMaterial.c
index 8c2e57dbd07..3a106916097 100644
--- a/src/backend/executor/nodeMaterial.c
+++ b/src/backend/executor/nodeMaterial.c
@@ -223,13 +223,13 @@ ExecInitMaterial(Material *node, EState *estate, int eflags)
 	 *
 	 * material nodes only return tuples from their materialized relation.
 	 */
-	ExecInitResultTupleSlotTL(estate, &matstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &matstate->ss.ps, TTS_TYPE_MINIMALTUPLE);
 	matstate->ss.ps.ps_ProjInfo = NULL;
 
 	/*
 	 * initialize tuple type.
 	 */
-	ExecCreateScanSlotFromOuterPlan(estate, &matstate->ss);
+	ExecCreateScanSlotFromOuterPlan(estate, &matstate->ss, TTS_TYPE_MINIMALTUPLE);
 
 	return matstate;
 }
diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c
index 118f4ef07df..7a708901bba 100644
--- a/src/backend/executor/nodeMergeAppend.c
+++ b/src/backend/executor/nodeMergeAppend.c
@@ -109,7 +109,8 @@ ExecInitMergeAppend(MergeAppend *node, EState *estate, int eflags)
 	 * MergeAppend nodes do have Result slots, which hold pointers to tuples,
 	 * so we have to initialize them.
 	 */
-	ExecInitResultTupleSlotTL(estate, &mergestate->ps);
+	/* FIXME: unused? */
+	ExecInitResultTupleSlotTL(estate, &mergestate->ps, TTS_TYPE_VIRTUAL);
 
 	/*
 	 * call ExecInitNode on each of the plans to be executed and save the
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index f3cbe2f889b..fd236e6402b 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -1511,13 +1511,14 @@ ExecInitMergeJoin(MergeJoin *node, EState *estate, int eflags)
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &mergestate->js.ps);
+	ExecInitResultTupleSlotTL(estate, &mergestate->js.ps, TTS_TYPE_VIRTUAL);
 	ExecAssignProjectionInfo(&mergestate->js.ps, NULL);
 
 	/*
 	 * tuple table initialization
 	 */
-	mergestate->mj_MarkedTupleSlot = ExecInitExtraTupleSlot(estate, innerDesc);
+	mergestate->mj_MarkedTupleSlot = ExecInitExtraTupleSlot(estate, innerDesc,
+															TTS_TYPE_VIRTUAL);
 
 	/*
 	 * initialize child expressions
@@ -1547,13 +1548,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, TTS_TYPE_VIRTUAL);
 			break;
 		case JOIN_RIGHT:
 			mergestate->mj_FillOuter = false;
 			mergestate->mj_FillInner = true;
 			mergestate->mj_NullOuterTupleSlot =
-				ExecInitNullTupleSlot(estate, outerDesc);
+				ExecInitNullTupleSlot(estate, outerDesc, TTS_TYPE_VIRTUAL);
 
 			/*
 			 * Can't handle right or full join with non-constant extra
@@ -1569,9 +1570,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, TTS_TYPE_VIRTUAL);
 			mergestate->mj_NullInnerTupleSlot =
-				ExecInitNullTupleSlot(estate, innerDesc);
+				ExecInitNullTupleSlot(estate, innerDesc, TTS_TYPE_VIRTUAL);
 
 			/*
 			 * 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 93c03cfb071..c1316e80a49 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -766,7 +766,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 * 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))
+		if (slot->tupleDescriptor != RelationGetDescr(resultRelationDesc))
 			ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
 
 		slot = resultRelInfo->ri_FdwRoutine->ExecForeignDelete(estate,
@@ -781,7 +781,7 @@ ExecDelete(ModifyTableState *mtstate,
 		 * RETURNING expressions might reference the tableoid column, so
 		 * initialize t_tableOid before evaluating them.
 		 */
-		if (slot->tts_isempty)
+		if (slot->flags & TTS_ISEMPTY)
 			ExecStoreAllNullTuple(slot);
 		tuple = ExecMaterializeSlot(slot);
 		tuple->t_tableOid = RelationGetRelid(resultRelationDesc);
@@ -951,7 +951,7 @@ ldelete:;
 					elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
 			}
 
-			if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
+			if (slot->tupleDescriptor != RelationGetDescr(resultRelationDesc))
 				ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
 			ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
 		}
@@ -1944,6 +1944,13 @@ ExecModifyTable(PlanState *pstate)
 				break;
 		}
 
+		if (node->mt_scan->tupleDescriptor != planSlot->tupleDescriptor)
+			ExecSetSlotDescriptor(node->mt_scan, planSlot->tupleDescriptor);
+
+		ExecStoreTuple(ExecCopySlotTuple(planSlot), node->mt_scan,
+					   InvalidBuffer, true);
+		planSlot = node->mt_scan;
+
 		/*
 		 * If resultRelInfo->ri_usesFdwDirectModify is true, all we need to do
 		 * here is compute the RETURNING expressions.
@@ -2139,6 +2146,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	EvalPlanQualInit(&mtstate->mt_epqstate, estate, NULL, NIL, node->epqParam);
 	mtstate->fireBSTriggers = true;
 
+	mtstate->mt_scan = ExecInitExtraTupleSlot(mtstate->ps.state, NULL, TTS_TYPE_HEAPTUPLE);
+
 	/*
 	 * call ExecInitNode on each of the plans to be executed and save the
 	 * results into the array "mt_plans".  This is also a convenient place to
@@ -2367,7 +2376,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(estate, &mtstate->ps);
+		ExecInitResultTupleSlotTL(estate, &mtstate->ps, TTS_TYPE_VIRTUAL);
 		slot = mtstate->ps.ps_ResultTupleSlot;
 
 		/* Need an econtext too */
@@ -2434,7 +2443,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		 * expects one (maybe should change that?).
 		 */
 		mtstate->ps.plan->targetlist = NIL;
-		ExecInitResultTupleSlotTL(estate, &mtstate->ps);
+		ExecInitResultTupleSlotTL(estate, &mtstate->ps, TTS_TYPE_VIRTUAL);
 
 		mtstate->ps.ps_ExprContext = NULL;
 	}
@@ -2462,7 +2471,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 		/* initialize slot for the existing tuple */
 		mtstate->mt_existing =
-			ExecInitExtraTupleSlot(mtstate->ps.state, relationDesc);
+			ExecInitExtraTupleSlot(mtstate->ps.state, relationDesc, TTS_TYPE_BUFFER);
 
 		/* carried forward solely for the benefit of explain */
 		mtstate->mt_excludedtlist = node->exclRelTlist;
@@ -2471,7 +2480,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 		tupDesc = ExecTypeFromTL((List *) node->onConflictSet,
 								 relationDesc->tdhasoid);
 		mtstate->mt_conflproj =
-			ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc);
+			ExecInitExtraTupleSlot(mtstate->ps.state, tupDesc, TTS_TYPE_VIRTUAL);
 
 		/* build UPDATE SET projection state */
 		resultRelInfo->ri_onConflictSetProj =
@@ -2582,7 +2591,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 
 				j = ExecInitJunkFilter(subplan->targetlist,
 									   resultRelInfo->ri_RelationDesc->rd_att->tdhasoid,
-									   ExecInitExtraTupleSlot(estate, NULL));
+									   ExecInitExtraTupleSlot(estate, NULL, TTS_TYPE_HEAPTUPLE));
 
 				if (operation == CMD_UPDATE || operation == CMD_DELETE)
 				{
@@ -2632,7 +2641,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
 	 * we keep it in the estate.
 	 */
 	if (estate->es_trig_tuple_slot == NULL)
-		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL);
+		estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL, TTS_TYPE_BUFFER);
 
 	/*
 	 * 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 4d898b1f83e..9ba4da2f078 100644
--- a/src/backend/executor/nodeNamedtuplestorescan.c
+++ b/src/backend/executor/nodeNamedtuplestorescan.c
@@ -136,8 +136,9 @@ ExecInitNamedTuplestoreScan(NamedTuplestoreScan *node, EState *estate, int eflag
 	 * Tuple table and result type initialization. The scan tuple type is
 	 * specified for the tuplestore.
 	 */
-	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps);
-	ExecInitScanTupleSlot(estate, &scanstate->ss, scanstate->tupdesc);
+	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps, TTS_TYPE_VIRTUAL);
+	ExecInitScanTupleSlot(estate, &scanstate->ss, scanstate->tupdesc,
+						  TTS_TYPE_MINIMALTUPLE);
 
 	/*
 	 * initialize child expressions
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 9ae9863226c..9864473693b 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(estate, &nlstate->js.ps);
+	ExecInitResultTupleSlotTL(estate, &nlstate->js.ps, TTS_TYPE_VIRTUAL);
 	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)),
+									  TTS_TYPE_VIRTUAL);
 			break;
 		default:
 			elog(ERROR, "unrecognized join type: %d",
diff --git a/src/backend/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c
index 6d6ed38ceeb..2afe5abedc9 100644
--- a/src/backend/executor/nodeProjectSet.c
+++ b/src/backend/executor/nodeProjectSet.c
@@ -154,8 +154,8 @@ ExecProjectSRF(ProjectSetState *node, bool continuing)
 	{
 		Node	   *elem = node->elems[argno];
 		ExprDoneCond *isdone = &node->elemdone[argno];
-		Datum	   *result = &resultSlot->tts_values[argno];
-		bool	   *isnull = &resultSlot->tts_isnull[argno];
+		Datum	   *result = &resultSlot->values[argno];
+		bool	   *isnull = &resultSlot->nulls[argno];
 
 		if (continuing && *isdone == ExprEndResult)
 		{
@@ -256,7 +256,7 @@ ExecInitProjectSet(ProjectSet *node, EState *estate, int eflags)
 	/*
 	 * tuple table and result type initialization
 	 */
-	ExecInitResultTupleSlotTL(estate, &state->ps);
+	ExecInitResultTupleSlotTL(estate, &state->ps, TTS_TYPE_VIRTUAL);
 
 	/* 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/nodeRecursiveunion.c b/src/backend/executor/nodeRecursiveunion.c
index 6b3ea5afb31..3e4273eab5c 100644
--- a/src/backend/executor/nodeRecursiveunion.c
+++ b/src/backend/executor/nodeRecursiveunion.c
@@ -229,7 +229,7 @@ ExecInitRecursiveUnion(RecursiveUnion *node, EState *estate, int eflags)
 	 * RecursiveUnion nodes still have Result slots, which hold pointers to
 	 * tuples, so we have to initialize them.
 	 */
-	ExecInitResultTupleSlotTL(estate, &rustate->ps);
+	ExecInitResultTupleSlotTL(estate, &rustate->ps, TTS_TYPE_VIRTUAL);
 
 	/*
 	 * Initialize result tuple type.  (Note: we have to set up the result type
diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c
index e4418a29bba..b936b4876b8 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(estate, &resstate->ps);
+	ExecInitResultTupleSlotTL(estate, &resstate->ps, TTS_TYPE_VIRTUAL);
 	ExecAssignProjectionInfo(&resstate->ps, NULL);
 
 	/*
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 872d6e5735e..de7e976d5af 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -150,13 +150,15 @@ 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),
+						  TTS_TYPE_BUFFER);
 
 	/*
 	 * Initialize result slot, type and projection.
 	 * tuple table and result tuple initialization
 	 */
-	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps,
+							  TTS_TYPE_VIRTUAL);
 	ExecAssignScanProjectionInfo(&scanstate->ss);
 
 	/*
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 9db368922a3..34d356d4463 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -176,12 +176,13 @@ 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),
+						  TTS_TYPE_BUFFER);
 
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps, TTS_TYPE_VIRTUAL);
 	ExecAssignScanProjectionInfo(&scanstate->ss);
 
 	/*
diff --git a/src/backend/executor/nodeSetOp.c b/src/backend/executor/nodeSetOp.c
index 3fa4a5fcc65..20129f0fa5b 100644
--- a/src/backend/executor/nodeSetOp.c
+++ b/src/backend/executor/nodeSetOp.c
@@ -533,7 +533,7 @@ 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(estate, &setopstate->ps);
+	ExecInitResultTupleSlotTL(estate, &setopstate->ps, TTS_TYPE_VIRTUAL);
 	setopstate->ps.ps_ProjInfo = NULL;
 
 	/*
diff --git a/src/backend/executor/nodeSort.c b/src/backend/executor/nodeSort.c
index 73f16c9abaa..8b1950d4987 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, TTS_TYPE_VIRTUAL);
 
 	/*
 	 * Initialize return slot and type. No need to initialize projection info because
 	 * this node doesn't do projections.
 	 */
-	ExecInitResultTupleSlotTL(estate, &sortstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &sortstate->ss.ps, TTS_TYPE_MINIMALTUPLE);
 	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 d5411500a2b..fef59dbe1a7 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -314,7 +314,7 @@ ExecScanSubPlan(SubPlanState *node,
 		 !TupIsNull(slot);
 		 slot = ExecProcNode(planstate))
 	{
-		TupleDesc	tdesc = slot->tts_tupleDescriptor;
+		TupleDesc	tdesc = slot->tupleDescriptor;
 		Datum		rowresult;
 		bool		rownull;
 		int			col;
@@ -721,7 +721,7 @@ findPartialMatch(TupleHashTable hashtable, TupleTableSlot *slot,
 static bool
 slotAllNulls(TupleTableSlot *slot)
 {
-	int			ncols = slot->tts_tupleDescriptor->natts;
+	int			ncols = slot->tupleDescriptor->natts;
 	int			i;
 
 	for (i = 1; i <= ncols; i++)
@@ -741,7 +741,7 @@ slotAllNulls(TupleTableSlot *slot)
 static bool
 slotNoNulls(TupleTableSlot *slot)
 {
-	int			ncols = slot->tts_tupleDescriptor->natts;
+	int			ncols = slot->tupleDescriptor->natts;
 	int			i;
 
 	for (i = 1; i <= ncols; i++)
@@ -957,7 +957,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 		 * own innerecontext.
 		 */
 		tupDescLeft = ExecTypeFromTL(lefttlist, false);
-		slot = ExecInitExtraTupleSlot(estate, tupDescLeft);
+		slot = ExecInitExtraTupleSlot(estate, tupDescLeft, TTS_TYPE_VIRTUAL);
 		sstate->projLeft = ExecBuildProjectionInfo(lefttlist,
 												   NULL,
 												   slot,
@@ -965,7 +965,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent)
 												   NULL);
 
 		sstate->descRight = tupDescRight = ExecTypeFromTL(righttlist, false);
-		slot = ExecInitExtraTupleSlot(estate, tupDescRight);
+		slot = ExecInitExtraTupleSlot(estate, tupDescRight, TTS_TYPE_VIRTUAL);
 		sstate->projRight = ExecBuildProjectionInfo(righttlist,
 													sstate->innerecontext,
 													slot,
@@ -1057,7 +1057,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 		 !TupIsNull(slot);
 		 slot = ExecProcNode(planstate))
 	{
-		TupleDesc	tdesc = slot->tts_tupleDescriptor;
+		TupleDesc	tdesc = slot->tupleDescriptor;
 		int			i = 1;
 
 		if (subLinkType == EXISTS_SUBLINK)
diff --git a/src/backend/executor/nodeSubqueryscan.c b/src/backend/executor/nodeSubqueryscan.c
index fa618847851..a8e5a8c347c 100644
--- a/src/backend/executor/nodeSubqueryscan.c
+++ b/src/backend/executor/nodeSubqueryscan.c
@@ -129,12 +129,14 @@ ExecInitSubqueryScan(SubqueryScan *node, EState *estate, int eflags)
 	 * Initialize scan slot and type (needed by ExecInitResultTupleSlotTL)
 	 */
 	ExecInitScanTupleSlot(estate, &subquerystate->ss,
-						  ExecGetResultType(subquerystate->subplan));
+						  ExecGetResultType(subquerystate->subplan),
+						  TTS_TYPE_HEAPTUPLE); /* FIXME */
 
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &subquerystate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &subquerystate->ss.ps,
+							  TTS_TYPE_VIRTUAL);
 	ExecAssignScanProjectionInfo(&subquerystate->ss);
 
 	/*
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c
index fed6f2b3a53..9f11732b271 100644
--- a/src/backend/executor/nodeTableFuncscan.c
+++ b/src/backend/executor/nodeTableFuncscan.c
@@ -147,12 +147,14 @@ 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,
+						  TTS_TYPE_MINIMALTUPLE);
 
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps,
+							  TTS_TYPE_VIRTUAL);
 	ExecAssignScanProjectionInfo(&scanstate->ss);
 
 	/*
@@ -285,7 +287,7 @@ tfuncFetchRows(TableFuncScanState *tstate, ExprContext *econtext)
 	PG_TRY();
 	{
 		routine->InitOpaque(tstate,
-							tstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor->natts);
+							tstate->ss.ss_ScanTupleSlot->tupleDescriptor->natts);
 
 		/*
 		 * If evaluating the document expression returns NULL, the table
@@ -380,7 +382,7 @@ tfuncInitialize(TableFuncScanState *tstate, ExprContext *econtext, Datum doc)
 	 * is the column filter.
 	 */
 	colno = 0;
-	tupdesc = tstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
+	tupdesc = tstate->ss.ss_ScanTupleSlot->tupleDescriptor;
 	foreach(lc1, tstate->colexprs)
 	{
 		char	   *colfilter;
@@ -419,9 +421,9 @@ tfuncLoadRows(TableFuncScanState *tstate, ExprContext *econtext)
 {
 	const TableFuncRoutine *routine = tstate->routine;
 	TupleTableSlot *slot = tstate->ss.ss_ScanTupleSlot;
-	TupleDesc	tupdesc = slot->tts_tupleDescriptor;
-	Datum	   *values = slot->tts_values;
-	bool	   *nulls = slot->tts_isnull;
+	TupleDesc	tupdesc = slot->tupleDescriptor;
+	Datum	   *values = slot->values;
+	bool	   *nulls = slot->nulls;
 	int			natts = tupdesc->natts;
 	MemoryContext oldcxt;
 	int			ordinalitycol;
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index e207b1ffb51..d125611571d 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -549,12 +549,13 @@ ExecInitTidScan(TidScan *node, EState *estate, int eflags)
 	 * get the scan type from the relation descriptor.
 	 */
 	ExecInitScanTupleSlot(estate, &tidstate->ss,
-						  RelationGetDescr(currentRelation));
+						  RelationGetDescr(currentRelation),
+						  TTS_TYPE_BUFFER);
 
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &tidstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &tidstate->ss.ps, TTS_TYPE_VIRTUAL);
 	ExecAssignScanProjectionInfo(&tidstate->ss);
 
 	/*
diff --git a/src/backend/executor/nodeUnique.c b/src/backend/executor/nodeUnique.c
index 05d65330a0e..6cff86f9091 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(estate, &uniquestate->ps);
+	ExecInitResultTupleSlotTL(estate, &uniquestate->ps, TTS_TYPE_VIRTUAL);
 	uniquestate->ps.ps_ProjInfo = NULL;
 
 	/*
diff --git a/src/backend/executor/nodeValuesscan.c b/src/backend/executor/nodeValuesscan.c
index 63b7e7ef5b8..cb0d9643a79 100644
--- a/src/backend/executor/nodeValuesscan.c
+++ b/src/backend/executor/nodeValuesscan.c
@@ -133,20 +133,20 @@ ValuesNext(ValuesScanState *node)
 		node->ss.ps.subPlan = oldsubplans;
 
 		/* parser should have checked all sublists are the same length */
-		Assert(list_length(exprstatelist) == slot->tts_tupleDescriptor->natts);
+		Assert(list_length(exprstatelist) == slot->tupleDescriptor->natts);
 
 		/*
 		 * Compute the expressions and build a virtual result tuple. We
 		 * already did ExecClearTuple(slot).
 		 */
-		values = slot->tts_values;
-		isnull = slot->tts_isnull;
+		values = slot->values;
+		isnull = slot->nulls;
 
 		resind = 0;
 		foreach(lc, exprstatelist)
 		{
 			ExprState  *estate = (ExprState *) lfirst(lc);
-			Form_pg_attribute attr = TupleDescAttr(slot->tts_tupleDescriptor,
+			Form_pg_attribute attr = TupleDescAttr(slot->tupleDescriptor,
 												   resind);
 
 			values[resind] = ExecEvalExpr(estate,
@@ -251,12 +251,12 @@ 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, TTS_TYPE_VIRTUAL);
 
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps, TTS_TYPE_VIRTUAL);
 	ExecAssignScanProjectionInfo(&scanstate->ss);
 
 	/*
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index a56c3e89fd5..8768d8c6955 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2304,16 +2304,20 @@ 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);
-	scanDesc = winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
+	ExecCreateScanSlotFromOuterPlan(estate, &winstate->ss, TTS_TYPE_MINIMALTUPLE);
+	scanDesc = winstate->ss.ss_ScanTupleSlot->tupleDescriptor;
 
 	/*
 	 * 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,
+													   TTS_TYPE_VIRTUAL);
+	winstate->agg_row_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+													TTS_TYPE_VIRTUAL);
+	winstate->temp_slot_1 = ExecInitExtraTupleSlot(estate, scanDesc,
+												   TTS_TYPE_VIRTUAL);
+	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
+												   TTS_TYPE_VIRTUAL);
 
 	/*
 	 * create frame head and tail slots only if needed (must match logic in
@@ -2324,15 +2328,17 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
 	{
 		if (!(frameOptions & FRAMEOPTION_START_UNBOUNDED_PRECEDING))
-			winstate->framehead_slot = ExecInitExtraTupleSlot(estate, scanDesc);
+			winstate->framehead_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+															  TTS_TYPE_VIRTUAL);
 		if (!(frameOptions & FRAMEOPTION_END_UNBOUNDED_FOLLOWING))
-			winstate->frametail_slot = ExecInitExtraTupleSlot(estate, scanDesc);
+			winstate->frametail_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+															  TTS_TYPE_VIRTUAL);
 	}
 
 	/*
 	 * Initialize result slot, type and projection.
 	 */
-	ExecInitResultTupleSlotTL(estate, &winstate->ss.ps);
+	ExecInitResultTupleSlotTL(estate, &winstate->ss.ps, TTS_TYPE_VIRTUAL);
 	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 2ff9a215b12..76a1aad4aa7 100644
--- a/src/backend/executor/nodeWorktablescan.c
+++ b/src/backend/executor/nodeWorktablescan.c
@@ -159,8 +159,8 @@ ExecInitWorkTableScan(WorkTableScan *node, EState *estate, int eflags)
 	/*
 	 * tuple table initialization
 	 */
-	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps);
-	ExecInitScanTupleSlot(estate, &scanstate->ss, NULL);
+	ExecInitResultTupleSlotTL(estate, &scanstate->ss.ps, TTS_TYPE_VIRTUAL);
+	ExecInitScanTupleSlot(estate, &scanstate->ss, NULL, TTS_TYPE_MINIMALTUPLE);
 
 	/*
 	 * initialize child expressions
diff --git a/src/backend/executor/tstoreReceiver.c b/src/backend/executor/tstoreReceiver.c
index d02ca3afd12..a0ec3dcb404 100644
--- a/src/backend/executor/tstoreReceiver.c
+++ b/src/backend/executor/tstoreReceiver.c
@@ -109,7 +109,7 @@ static bool
 tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self)
 {
 	TStoreState *myState = (TStoreState *) self;
-	TupleDesc	typeinfo = slot->tts_tupleDescriptor;
+	TupleDesc	typeinfo = slot->tupleDescriptor;
 	int			natts = typeinfo->natts;
 	int			nfree;
 	int			i;
@@ -126,10 +126,10 @@ tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self)
 	nfree = 0;
 	for (i = 0; i < natts; i++)
 	{
-		Datum		val = slot->tts_values[i];
+		Datum		val = slot->values[i];
 		Form_pg_attribute attr = TupleDescAttr(typeinfo, i);
 
-		if (!attr->attisdropped && attr->attlen == -1 && !slot->tts_isnull[i])
+		if (!attr->attisdropped && attr->attlen == -1 && !slot->nulls[i])
 		{
 			if (VARATT_IS_EXTERNAL(DatumGetPointer(val)))
 			{
@@ -147,7 +147,7 @@ tstoreReceiveSlot_detoast(TupleTableSlot *slot, DestReceiver *self)
 	 */
 	oldcxt = MemoryContextSwitchTo(myState->cxt);
 	tuplestore_putvalues(myState->tstore, typeinfo,
-						 myState->outvalues, slot->tts_isnull);
+						 myState->outvalues, slot->nulls);
 	MemoryContextSwitchTo(oldcxt);
 
 	/* And release any temporary detoasted values */
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index b9bad5eacca..6b69ea8b5f3 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -492,7 +492,7 @@ print_slot(TupleTableSlot *slot)
 		printf("tuple is null.\n");
 		return;
 	}
-	if (!slot->tts_tupleDescriptor)
+	if (!slot->tupleDescriptor)
 	{
 		printf("no tuple descriptor.\n");
 		return;
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index e50b9f79056..495d91d555d 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, TTS_TYPE_MINIMALTUPLE);
 	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, TTS_TYPE_MINIMALTUPLE);
 	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 04985c9f91d..ee9adf74c31 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -208,7 +208,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,
+															TTS_TYPE_VIRTUAL);
 
 	/* Prepare to catch AFTER triggers. */
 	AfterTriggerBeginQuery();
@@ -271,8 +272,8 @@ slot_fill_defaults(LogicalRepRelMapEntry *rel, EState *estate,
 	}
 
 	for (i = 0; i < num_defaults; i++)
-		slot->tts_values[defmap[i]] =
-			ExecEvalExpr(defexprs[i], econtext, &slot->tts_isnull[defmap[i]]);
+		slot->values[defmap[i]] =
+			ExecEvalExpr(defexprs[i], econtext, &slot->nulls[defmap[i]]);
 }
 
 /*
@@ -307,7 +308,7 @@ static void
 slot_store_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
 					char **values)
 {
-	int			natts = slot->tts_tupleDescriptor->natts;
+	int			natts = slot->tupleDescriptor->natts;
 	int			i;
 	SlotErrCallbackArg errarg;
 	ErrorContextCallback errcallback;
@@ -325,7 +326,7 @@ slot_store_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
 	/* Call the "in" function for each non-dropped attribute */
 	for (i = 0; i < natts; i++)
 	{
-		Form_pg_attribute att = TupleDescAttr(slot->tts_tupleDescriptor, i);
+		Form_pg_attribute att = TupleDescAttr(slot->tupleDescriptor, i);
 		int			remoteattnum = rel->attrmap[i];
 
 		if (!att->attisdropped && remoteattnum >= 0 &&
@@ -337,11 +338,11 @@ slot_store_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
 			errarg.attnum = remoteattnum;
 
 			getTypeInputInfo(att->atttypid, &typinput, &typioparam);
-			slot->tts_values[i] = OidInputFunctionCall(typinput,
-													   values[remoteattnum],
-													   typioparam,
-													   att->atttypmod);
-			slot->tts_isnull[i] = false;
+			slot->values[i] = OidInputFunctionCall(typinput,
+												   values[remoteattnum],
+												   typioparam,
+												   att->atttypmod);
+			slot->nulls[i] = false;
 		}
 		else
 		{
@@ -350,8 +351,8 @@ slot_store_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
 			 * values (missing values should be later filled using
 			 * slot_fill_defaults).
 			 */
-			slot->tts_values[i] = (Datum) 0;
-			slot->tts_isnull[i] = true;
+			slot->values[i] = (Datum) 0;
+			slot->nulls[i] = true;
 		}
 	}
 
@@ -371,7 +372,7 @@ static void
 slot_modify_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
 					 char **values, bool *replaces)
 {
-	int			natts = slot->tts_tupleDescriptor->natts;
+	int			natts = slot->tupleDescriptor->natts;
 	int			i;
 	SlotErrCallbackArg errarg;
 	ErrorContextCallback errcallback;
@@ -390,7 +391,7 @@ slot_modify_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
 	/* Call the "in" function for each replaced attribute */
 	for (i = 0; i < natts; i++)
 	{
-		Form_pg_attribute att = TupleDescAttr(slot->tts_tupleDescriptor, i);
+		Form_pg_attribute att = TupleDescAttr(slot->tupleDescriptor, i);
 		int			remoteattnum = rel->attrmap[i];
 
 		if (remoteattnum < 0)
@@ -407,16 +408,16 @@ slot_modify_cstrings(TupleTableSlot *slot, LogicalRepRelMapEntry *rel,
 			errarg.attnum = remoteattnum;
 
 			getTypeInputInfo(att->atttypid, &typinput, &typioparam);
-			slot->tts_values[i] = OidInputFunctionCall(typinput,
-													   values[remoteattnum],
-													   typioparam,
-													   att->atttypmod);
-			slot->tts_isnull[i] = false;
+			slot->values[i] = OidInputFunctionCall(typinput,
+												   values[remoteattnum],
+												   typioparam,
+												   att->atttypmod);
+			slot->nulls[i] = false;
 		}
 		else
 		{
-			slot->tts_values[i] = (Datum) 0;
-			slot->tts_isnull[i] = true;
+			slot->values[i] = (Datum) 0;
+			slot->nulls[i] = true;
 		}
 	}
 
@@ -586,7 +587,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),
+										TTS_TYPE_VIRTUAL);
 
 	/* Process and store remote tuple in the slot */
 	oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
@@ -690,9 +692,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),
+										TTS_TYPE_VIRTUAL);
 	localslot = ExecInitExtraTupleSlot(estate,
-									   RelationGetDescr(rel->localrel));
+									   RelationGetDescr(rel->localrel),
+									   TTS_TYPE_HEAPTUPLE);
 	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1);
 
 	PushActiveSnapshot(GetTransactionSnapshot());
@@ -731,7 +735,7 @@ apply_handle_update(StringInfo s)
 	{
 		/* Process and store remote tuple in the slot */
 		oldctx = MemoryContextSwitchTo(GetPerTupleMemoryContext(estate));
-		ExecStoreTuple(localslot->tts_tuple, remoteslot, InvalidBuffer, false);
+		ExecCopySlot(localslot, remoteslot);
 		slot_modify_cstrings(remoteslot, rel, newtup.values, newtup.changed);
 		MemoryContextSwitchTo(oldctx);
 
@@ -808,9 +812,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),
+										TTS_TYPE_VIRTUAL);
 	localslot = ExecInitExtraTupleSlot(estate,
-									   RelationGetDescr(rel->localrel));
+									   RelationGetDescr(rel->localrel),
+									   TTS_TYPE_HEAPTUPLE);
 	EvalPlanQualInit(&epqstate, estate, NULL, NIL, -1);
 
 	PushActiveSnapshot(GetTransactionSnapshot());
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 66cc5c35c68..8b803c77d5b 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, TTS_TYPE_MINIMALTUPLE);
 
 	dest->rStartup(dest, CMD_SELECT, portal->tupDesc);
 
diff --git a/src/backend/utils/adt/orderedsetaggs.c b/src/backend/utils/adt/orderedsetaggs.c
index 50b34fcbc68..e833ed1f852 100644
--- a/src/backend/utils/adt/orderedsetaggs.c
+++ b/src/backend/utils/adt/orderedsetaggs.c
@@ -240,7 +240,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,
+													   TTS_TYPE_VIRTUAL);
 		}
 		else
 		{
@@ -398,17 +399,17 @@ ordered_set_transition_multi(PG_FUNCTION_ARGS)
 	nargs = PG_NARGS() - 1;
 	for (i = 0; i < nargs; i++)
 	{
-		slot->tts_values[i] = PG_GETARG_DATUM(i + 1);
-		slot->tts_isnull[i] = PG_ARGISNULL(i + 1);
+		slot->values[i] = PG_GETARG_DATUM(i + 1);
+		slot->nulls[i] = PG_ARGISNULL(i + 1);
 	}
 	if (osastate->qstate->aggref->aggkind == AGGKIND_HYPOTHETICAL)
 	{
 		/* Add a zero flag value to mark this row as a normal input row */
-		slot->tts_values[i] = Int32GetDatum(0);
-		slot->tts_isnull[i] = false;
+		slot->values[i] = Int32GetDatum(0);
+		slot->nulls[i] = false;
 		i++;
 	}
-	Assert(i == slot->tts_tupleDescriptor->natts);
+	Assert(i == slot->tupleDescriptor->natts);
 	ExecStoreVirtualTuple(slot);
 
 	/* Load the row into the tuplesort object */
@@ -1200,11 +1201,11 @@ hypothetical_rank_common(FunctionCallInfo fcinfo, int flag,
 	ExecClearTuple(slot);
 	for (i = 0; i < nargs; i++)
 	{
-		slot->tts_values[i] = PG_GETARG_DATUM(i + 1);
-		slot->tts_isnull[i] = PG_ARGISNULL(i + 1);
+		slot->values[i] = PG_GETARG_DATUM(i + 1);
+		slot->nulls[i] = PG_ARGISNULL(i + 1);
 	}
-	slot->tts_values[i] = Int32GetDatum(flag);
-	slot->tts_isnull[i] = false;
+	slot->values[i] = Int32GetDatum(flag);
+	slot->nulls[i] = false;
 	ExecStoreVirtualTuple(slot);
 
 	tuplesort_puttupleslot(osastate->sortstate, slot);
@@ -1353,11 +1354,11 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS)
 	ExecClearTuple(slot);
 	for (i = 0; i < nargs; i++)
 	{
-		slot->tts_values[i] = PG_GETARG_DATUM(i + 1);
-		slot->tts_isnull[i] = PG_ARGISNULL(i + 1);
+		slot->values[i] = PG_GETARG_DATUM(i + 1);
+		slot->nulls[i] = PG_ARGISNULL(i + 1);
 	}
-	slot->tts_values[i] = Int32GetDatum(-1);
-	slot->tts_isnull[i] = false;
+	slot->values[i] = Int32GetDatum(-1);
+	slot->nulls[i] = false;
 	ExecStoreVirtualTuple(slot);
 
 	tuplesort_puttupleslot(osastate->sortstate, slot);
@@ -1371,9 +1372,10 @@ 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,
+										 TTS_TYPE_MINIMALTUPLE);
 	slot2 = extraslot;
-
+	/* FIXME: will store minimal into virtual slot! */
 	/* iterate till we find the hypothetical row */
 	while (tuplesort_gettupleslot(osastate->sortstate, true, true, slot,
 								  &abbrevVal))
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fcc8323f626..69d5622ed07 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -5504,7 +5504,8 @@ get_actual_variable_range(PlannerInfo *root, VariableStatData *vardata,
 			indexInfo = BuildIndexInfo(indexRel);
 
 			/* some other stuff */
-			slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRel));
+			slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRel),
+											TTS_TYPE_HEAPTUPLE);
 			econtext->ecxt_scantuple = slot;
 			get_typlenbyval(vardata->atttype, &typLen, &typByVal);
 			InitNonVacuumableSnapshot(SnapshotNonVacuumable, RecentGlobalXmin);
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index 041bdc2fa7e..ef2669c603b 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, TTS_TYPE_VIRTUAL);
 		econtext = GetPerTupleExprContext(state->estate);
 		econtext->ecxt_scantuple = slot;
 	}
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 2ab1815390c..5bcdca99601 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -826,4 +826,9 @@ extern MinimalTuple heap_copy_minimal_tuple(MinimalTuple mtup);
 extern HeapTuple heap_tuple_from_minimal_tuple(MinimalTuple mtup);
 extern MinimalTuple minimal_tuple_from_heap_tuple(HeapTuple htup);
 
+struct TupleTableSlot;
+extern void
+slot_deform_tuple(struct TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, int natts);
+
+
 #endif							/* HTUP_DETAILS_H */
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 45a077a949c..430e3ec6b27 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -342,8 +342,9 @@ ExecProject(ProjectionInfo *projInfo)
 	 * Successfully formed a result row.  Mark the result slot as containing a
 	 * valid virtual tuple (inlined version of ExecStoreVirtualTuple()).
 	 */
-	slot->tts_isempty = false;
-	slot->tts_nvalid = slot->tts_tupleDescriptor->natts;
+	//FIXME: slot->isempty = false;
+	slot->flags &= ~TTS_ISEMPTY;
+	slot->nvalid = slot->tupleDescriptor->natts;
 
 	return slot;
 }
@@ -431,12 +432,12 @@ extern void ExecScanReScan(ScanState *node);
 /*
  * prototypes from functions in execTuples.c
  */
-extern void ExecInitResultTupleSlotTL(EState *estate, PlanState *planstate);
-extern void ExecInitScanTupleSlot(EState *estate, ScanState *scanstate, TupleDesc tupleDesc);
+extern void ExecInitResultTupleSlotTL(EState *estate, PlanState *planstate, TupleTableSlotType st);
+extern void ExecInitScanTupleSlot(EState *estate, ScanState *scanstate, TupleDesc tupleDesc, TupleTableSlotType st);
 extern TupleTableSlot *ExecInitExtraTupleSlot(EState *estate,
-					  TupleDesc tupleDesc);
+					  TupleDesc tupleDesc, TupleTableSlotType st);
 extern TupleTableSlot *ExecInitNullTupleSlot(EState *estate,
-					  TupleDesc tupType);
+					  TupleDesc tupType, TupleTableSlotType st);
 extern TupleDesc ExecTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecCleanTypeFromTL(List *targetList, bool hasoid);
 extern TupleDesc ExecTypeFromExprList(List *exprList);
@@ -510,7 +511,8 @@ 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,
+											TupleTableSlotType tp);
 
 extern bool ExecRelationIsTargetRelation(EState *estate, Index scanrelid);
 
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 8be0d5edc29..beeb630a4c1 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -110,42 +110,111 @@
  * be touched by any other code.
  *----------
  */
-typedef struct TupleTableSlot
+
+/* true = slot is empty */
+#define			TTS_ISEMPTY			(1 << 1)
+/* should pfree tts_tuple? */
+#define			TTS_SHOULDFREE		(1 << 2)
+/* should pfree tts_mintuple? */
+#define			TTS_SHOULDFREEMIN	(1 << 3)
+/* saved state for slot_deform_tuple */
+#define			TTS_SLOW			(1 << 4)
+/* fixed tuple descriptor */
+#define			TTS_FIXED			(1 << 5)
+
+
+typedef enum TupleTableSlotType
+{
+	TTS_TYPE_VIRTUAL,
+	TTS_TYPE_HEAPTUPLE,
+	TTS_TYPE_MINIMALTUPLE,
+	TTS_TYPE_BUFFER
+} TupleTableSlotType;
+
+struct TupleTableSlot;
+typedef struct TupleTableSlot TupleTableSlot;
+
+typedef struct TupleTableSlotOps
+{
+	void (*init)(TupleTableSlot *slot);
+	void (*release)(TupleTableSlot *slot);
+
+	void (*clear)(TupleTableSlot *slot);
+
+	void (*getsomeattrs)(TupleTableSlot *slot, int natts);
+
+	void (*materialize)(TupleTableSlot *slot);
+
+	HeapTuple (*get_heap_tuple)(TupleTableSlot *slot);
+	MinimalTuple (*get_minimal_tuple)(TupleTableSlot *slot);
+
+	HeapTuple (*copy_heap_tuple)(TupleTableSlot *slot);
+	MinimalTuple (*copy_minimal_tuple)(TupleTableSlot *slot);
+
+} TupleTableSlotOps;
+
+extern Datum ExecFetchSlotTupleDatum(TupleTableSlot *slot);
+
+extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
+			 TupleTableSlot *srcslot);
+
+/* virtual or base type */
+struct TupleTableSlot
 {
 	NodeTag		type;
-	bool		tts_isempty;	/* true = slot is empty */
-	bool		tts_shouldFree; /* should pfree tts_tuple? */
-	bool		tts_shouldFreeMin;	/* should pfree tts_mintuple? */
-	bool		tts_slow;		/* saved state for slot_deform_tuple */
-	HeapTuple	tts_tuple;		/* physical tuple, or NULL if virtual */
-	TupleDesc	tts_tupleDescriptor;	/* slot's tuple descriptor */
-	MemoryContext tts_mcxt;		/* slot itself is in this context */
-	Buffer		tts_buffer;		/* tuple's buffer, or InvalidBuffer */
-	int			tts_nvalid;		/* # of valid values in tts_values */
-	Datum	   *tts_values;		/* current per-attribute values */
-	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 */
-	long		tts_off;		/* saved state for slot_deform_tuple */
-	bool		tts_fixedTupleDescriptor; /* descriptor can't be changed */
-} TupleTableSlot;
 
-#define TTS_HAS_PHYSICAL_TUPLE(slot)  \
-	((slot)->tts_tuple != NULL && (slot)->tts_tuple != &((slot)->tts_minhdr))
+	uint16		flags;
+	uint16		nvalid;		/* # of valid values in tts_values */
+
+	const TupleTableSlotOps *const cb;
+
+	TupleDesc	tupleDescriptor;	/* slot's tuple descriptor */
+
+	Datum	   *values;		/* current per-attribute values */
+	bool	   *nulls;		/* current per-attribute null flags */
+
+	/* can we optimize away? */
+	MemoryContext mcxt;		/* slot itself is in this context */
+};
+
+
+typedef struct HeapTupleTableSlot
+{
+	TupleTableSlot base;
+	HeapTuple	tuple;		/* physical tuple */
+	uint32		off;		/* saved state for slot_deform_tuple */
+} HeapTupleTableSlot;
+
+/* heap tuple residing in a buffer */
+typedef struct BufferHeapTupleTableSlot
+{
+	HeapTupleTableSlot base;
+	Buffer		buffer;		/* tuple's buffer, or InvalidBuffer */
+} BufferHeapTupleTableSlot;
+
+typedef struct MinimalTupleTableSlot
+{
+	TupleTableSlot base;
+	HeapTuple	tuple;		/* tuple wrapper */
+	MinimalTuple mintuple;	/* minimal tuple, or NULL if none */
+	HeapTupleData minhdr;	/* workspace for minimal-tuple-only case */
+	uint32		off;		/* saved state for slot_deform_tuple */
+} MinimalTupleTableSlot;
 
 /*
  * TupIsNull -- is a TupleTableSlot empty?
  */
 #define TupIsNull(slot) \
-	((slot) == NULL || (slot)->tts_isempty)
+	((slot) == NULL || (slot)->flags & TTS_ISEMPTY)
 
 /* in executor/execTuples.c */
-extern TupleTableSlot *MakeTupleTableSlot(TupleDesc desc);
-extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable, TupleDesc desc);
+extern TupleTableSlot *MakeTupleTableSlot(TupleDesc desc, TupleTableSlotType st);
+extern TupleTableSlot *ExecAllocTableSlot(List **tupleTable, TupleDesc desc, TupleTableSlotType st);
 extern void ExecResetTupleTable(List *tupleTable, bool shouldFree);
-extern TupleTableSlot *MakeSingleTupleTableSlot(TupleDesc tupdesc);
+extern TupleTableSlot *MakeSingleTupleTableSlot(TupleDesc tupdesc, TupleTableSlotType st);
 extern void ExecDropSingleTupleTableSlot(TupleTableSlot *slot);
 extern void ExecSetSlotDescriptor(TupleTableSlot *slot, TupleDesc tupdesc);
+
 extern TupleTableSlot *ExecStoreTuple(HeapTuple tuple,
 			   TupleTableSlot *slot,
 			   Buffer buffer,
@@ -153,22 +222,95 @@ extern TupleTableSlot *ExecStoreTuple(HeapTuple tuple,
 extern TupleTableSlot *ExecStoreMinimalTuple(MinimalTuple mtup,
 					  TupleTableSlot *slot,
 					  bool shouldFree);
-extern TupleTableSlot *ExecClearTuple(TupleTableSlot *slot);
+
 extern TupleTableSlot *ExecStoreVirtualTuple(TupleTableSlot *slot);
 extern TupleTableSlot *ExecStoreAllNullTuple(TupleTableSlot *slot);
-extern HeapTuple ExecCopySlotTuple(TupleTableSlot *slot);
-extern MinimalTuple ExecCopySlotMinimalTuple(TupleTableSlot *slot);
+
 extern HeapTuple ExecFetchSlotTuple(TupleTableSlot *slot);
 extern MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *slot);
 extern Datum ExecFetchSlotTupleDatum(TupleTableSlot *slot);
-extern HeapTuple ExecMaterializeSlot(TupleTableSlot *slot);
+
 extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
 			 TupleTableSlot *srcslot);
 
+
+static inline TupleTableSlot *
+ExecClearTuple(TupleTableSlot *slot)
+{
+	slot->cb->clear(slot);
+	return slot;
+}
+
+static inline HeapTuple
+ExecMaterializeSlot(TupleTableSlot *slot)
+{
+	slot->cb->materialize(slot);
+	return slot->cb->get_heap_tuple(slot);
+}
+
+static inline HeapTuple
+ExecCopySlotTuple(TupleTableSlot *slot)
+{
+	/*
+	 * sanity checks
+	 */
+	Assert(slot != NULL);
+	Assert(!(slot->flags & TTS_ISEMPTY));
+	return slot->cb->copy_heap_tuple(slot);
+}
+
+static inline MinimalTuple
+ExecCopySlotMinimalTuple(TupleTableSlot *slot)
+{
+	return slot->cb->copy_minimal_tuple(slot);
+}
+
 /* in access/common/heaptuple.c */
-extern Datum slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull);
-extern void slot_getallattrs(TupleTableSlot *slot);
-extern void slot_getsomeattrs(TupleTableSlot *slot, int attnum);
-extern bool slot_attisnull(TupleTableSlot *slot, int attnum);
+
+static inline Datum
+slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
+{
+	Assert(attnum > 0);
+
+	if (attnum > slot->nvalid)
+		slot->cb->getsomeattrs(slot, attnum);
+
+	Assert(attnum <= slot->nvalid);
+
+	*isnull = slot->nulls[attnum - 1];
+	return slot->values[attnum - 1];
+}
+
+static inline void
+slot_getallattrs(TupleTableSlot *slot)
+{
+	int			tdesc_natts = slot->tupleDescriptor->natts;
+
+	/* Quick out if we have 'em all already */
+	if (slot->nvalid == tdesc_natts)
+		return;
+	slot->cb->getsomeattrs(slot, tdesc_natts);
+}
+
+
+static inline void
+slot_getsomeattrs(TupleTableSlot *slot, int attnum)
+{
+	/* FIXME: we're sometimes called with attnum == 0 which is wrong */
+	Assert(attnum >= 0);
+
+	if (attnum > slot->nvalid)
+		slot->cb->getsomeattrs(slot, attnum);
+}
+
+static inline bool
+slot_attisnull(TupleTableSlot *slot, int attnum)
+{
+	bool isnull;
+
+	slot_getattr(slot, attnum, &isnull);
+
+	return isnull;
+}
 
 #endif							/* TUPTABLE_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a953820f43a..d7c54057ae5 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -992,6 +992,9 @@ typedef struct ModifyTableState
 	OnConflictAction mt_onconflict; /* ON CONFLICT type */
 	List	   *mt_arbiterindexes;	/* unique index OIDs to arbitrate taking
 									 * alt path */
+
+	TupleTableSlot *mt_scan;	/* input */
+
 	TupleTableSlot *mt_existing;	/* slot to store existing target tuple in */
 	List	   *mt_excludedtlist;	/* the excluded pseudo relation's tlist  */
 	TupleTableSlot *mt_conflproj;	/* CONFLICT ... SET ... projection target */
-- 
2.15.1.354.g95ec6b1b33.dirty

