From 864d2e675dd531c7cd65cc16adef9fc830994957 Mon Sep 17 00:00:00 2001
From: Nikita Malakhov <n.malakhov@postgrespro.ru>
Date: Tue, 21 Apr 2026 14:01:19 +0300
Subject: [PATCH] [POC] Current copyTuple and copySlot machinery supposes that
 source and destination slots and tuples always match. This patch modifies
 copy internals to consider more complex case and copy only actual attributes,
 and if destination slot/tuple has more they are set to null. This is a proof
 of concept patch which shows such mechanics does not break anything and
 allows to use it in cases when we have to pass or receive not matching number
 of attributes.

---
 src/backend/access/common/heaptuple.c | 195 ++++++++++++++++++++-----
 src/backend/executor/execTuples.c     | 196 +++++++++++++++++++++++---
 src/backend/optimizer/util/tlist.c    |  39 +++--
 src/include/access/htup_details.h     |  14 ++
 src/include/executor/tuptable.h       |  47 ++++++
 5 files changed, 424 insertions(+), 67 deletions(-)

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index f30346469ed..ff4bdda0327 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -58,9 +58,12 @@
 #include "postgres.h"
 
 #include "access/heaptoast.h"
+#include "access/htup.h"
+#include "access/htup_details.h"
 #include "access/sysattr.h"
 #include "access/tupdesc_details.h"
 #include "common/hashfn.h"
+#include "executor/tuptable.h"
 #include "utils/datum.h"
 #include "utils/expandeddatum.h"
 #include "utils/hsearch.h"
@@ -212,17 +215,18 @@ getmissingattr(TupleDesc tupleDesc,
 }
 
 /*
- * heap_compute_data_size
+ * heap_compute_data_size_ext
  *		Determine size of the data area of a tuple to be constructed
  */
 Size
-heap_compute_data_size(TupleDesc tupleDesc,
+heap_compute_data_size_ext(TupleDesc tupleDesc,
 					   const Datum *values,
-					   const bool *isnull)
+					   const bool *isnull,
+					   int attnum)
 {
 	Size		data_length = 0;
 	int			i;
-	int			numberOfAttributes = tupleDesc->natts;
+	int			numberOfAttributes = (attnum >= 0 ? attnum : tupleDesc->natts);
 
 	for (i = 0; i < numberOfAttributes; i++)
 	{
@@ -266,6 +270,18 @@ heap_compute_data_size(TupleDesc tupleDesc,
 	return data_length;
 }
 
+/*
+ * heap_compute_data_size
+ *		Determine size of the data area of a tuple to be constructed
+ */
+Size
+heap_compute_data_size(TupleDesc tupleDesc,
+					   const Datum *values,
+					   const bool *isnull)
+{
+	return heap_compute_data_size_ext(tupleDesc, values, isnull, -1);
+}
+
 /*
  * Per-attribute helper for heap_fill_tuple and other routines building tuples.
  *
@@ -398,10 +414,10 @@ fill_val(CompactAttribute *att,
  * NOTE: it is now REQUIRED that the caller have pre-zeroed the data area.
  */
 void
-heap_fill_tuple(TupleDesc tupleDesc,
+heap_fill_tuple_ext(TupleDesc tupleDesc,
 				const Datum *values, const bool *isnull,
 				char *data, Size data_size,
-				uint16 *infomask, uint8 *bit)
+				uint16 *infomask, uint8 *bit, int natts)
 {
 	uint8	   *bitP;
 	int			bitmask;
@@ -412,6 +428,9 @@ heap_fill_tuple(TupleDesc tupleDesc,
 	char	   *start = data;
 #endif
 
+	if(natts > 0)
+		numberOfAttributes = natts;
+
 	if (bit != NULL)
 	{
 		bitP = &bit[-1];
@@ -442,6 +461,25 @@ heap_fill_tuple(TupleDesc tupleDesc,
 	Assert((data - start) == data_size);
 }
 
+/*
+ * heap_fill_tuple
+ *		Load data portion of a tuple from values/isnull arrays
+ *
+ * We also fill the null bitmap (if any) and set the infomask bits
+ * that reflect the tuple's data contents.
+ *
+ * NOTE: it is now REQUIRED that the caller have pre-zeroed the data area.
+ */
+void
+heap_fill_tuple(TupleDesc tupleDesc,
+				const Datum *values, const bool *isnull,
+				char *data, Size data_size,
+				uint16 *infomask, uint8 *bit)
+{
+	heap_fill_tuple_ext(tupleDesc, values, isnull,
+				data, data_size, infomask, bit, -1);
+}
+
 
 /* ----------------------------------------------------------------
  *						heap tuple interface
@@ -685,6 +723,9 @@ heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull)
 HeapTuple
 heap_copytuple(HeapTuple tuple)
 {
+	return heap_copytuple_ext(tuple, NULL, -1);
+
+	/*
 	HeapTuple	newTuple;
 
 	if (!HeapTupleIsValid(tuple) || tuple->t_data == NULL)
@@ -697,13 +738,53 @@ heap_copytuple(HeapTuple tuple)
 	newTuple->t_data = (HeapTupleHeader) ((char *) newTuple + HEAPTUPLESIZE);
 	memcpy(newTuple->t_data, tuple->t_data, tuple->t_len);
 	return newTuple;
+	*/
+}
+
+/* ----------------
+ *		heap_copytuple_ext
+ *
+ *		returns a copy of a tuple consists of attnum atts of original
+ *
+ * The HeapTuple struct, tuple header, and tuple data are all allocated
+ * as a single palloc() block.
+ * ----------------
+ */
+HeapTuple
+heap_copytuple_ext(HeapTuple tuple, void *slotptr, int dstnatts)
+{
+	Size tsz = 0;
+	HeapTuple	newTuple;
+	TupleTableSlot *slot = NULL;
+
+	if (!HeapTupleIsValid(tuple) || tuple->t_data == NULL)
+		return NULL;
+
+	tsz = tuple->t_len;
+	if(slotptr != NULL)
+	{
+		slot = (TupleTableSlot *) slotptr;
+		if(dstnatts <= 0)
+			dstnatts = slot->tts_tupleDescriptor->natts;
+
+		if(dstnatts < slot->tts_tupleDescriptor->natts)
+			tsz = heap_compute_data_size_ext(slot->tts_tupleDescriptor, slot->tts_values, slot->tts_isnull, dstnatts);
+	}
+	newTuple = (HeapTuple) palloc(HEAPTUPLESIZE + tsz);
+	newTuple->t_len = tsz;
+	newTuple->t_self = tuple->t_self;
+	newTuple->t_tableOid = tuple->t_tableOid;
+
+	newTuple->t_data = (HeapTupleHeader) ((char *) newTuple + HEAPTUPLESIZE);
+	memcpy((char *) newTuple->t_data, (char *) tuple->t_data, tsz);
+	return newTuple;
 }
 
 /* ----------------
  *		heap_copytuple_with_tuple
  *
  *		copy a tuple into a caller-supplied HeapTuple management struct
- *
+ * 
  * Note that after calling this function, the "dest" HeapTuple will not be
  * allocated as a single palloc() block (unlike with heap_copytuple()).
  * ----------------
@@ -1014,17 +1095,11 @@ heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc)
 	return PointerGetDatum(td);
 }
 
-/*
- * heap_form_tuple
- *		construct a tuple from the given values[] and isnull[] arrays,
- *		which are of the length indicated by tupleDescriptor->natts
- *
- * The result is allocated in the current memory context.
- */
 HeapTuple
-heap_form_tuple(TupleDesc tupleDescriptor,
+heap_form_tuple_ext(TupleDesc tupleDescriptor,
 				const Datum *values,
-				const bool *isnull)
+				const bool *isnull,
+				int natts)
 {
 	HeapTuple	tuple;			/* return tuple */
 	HeapTupleHeader td;			/* tuple data */
@@ -1035,6 +1110,9 @@ heap_form_tuple(TupleDesc tupleDescriptor,
 	int			numberOfAttributes = tupleDescriptor->natts;
 	int			i;
 
+	if(natts > 0)
+		numberOfAttributes = natts;
+
 	if (numberOfAttributes > MaxTupleAttributeNumber)
 		ereport(ERROR,
 				(errcode(ERRCODE_TOO_MANY_COLUMNS),
@@ -1063,7 +1141,10 @@ heap_form_tuple(TupleDesc tupleDescriptor,
 
 	hoff = len = MAXALIGN(len); /* align user data safely */
 
-	data_len = heap_compute_data_size(tupleDescriptor, values, isnull);
+	if(natts > 0)
+		data_len = heap_compute_data_size_ext(tupleDescriptor, values, isnull, natts);
+	else
+		data_len = heap_compute_data_size(tupleDescriptor, values, isnull);
 
 	len += data_len;
 
@@ -1092,7 +1173,17 @@ heap_form_tuple(TupleDesc tupleDescriptor,
 	HeapTupleHeaderSetNatts(td, numberOfAttributes);
 	td->t_hoff = hoff;
 
-	heap_fill_tuple(tupleDescriptor,
+	if(natts > 0)
+		heap_fill_tuple_ext(tupleDescriptor,
+					values,
+					isnull,
+					(char *) td + hoff,
+					data_len,
+					&td->t_infomask,
+					(hasnull ? td->t_bits : NULL),
+					natts);
+	else
+		heap_fill_tuple(tupleDescriptor,
 					values,
 					isnull,
 					(char *) td + hoff,
@@ -1103,6 +1194,24 @@ heap_form_tuple(TupleDesc tupleDescriptor,
 	return tuple;
 }
 
+/*
+ * heap_form_tuple
+ *		construct a tuple from the given values[] and isnull[] arrays,
+ *		which are of the length indicated by tupleDescriptor->natts
+ *
+ * The result is allocated in the current memory context.
+ */
+HeapTuple
+heap_form_tuple(TupleDesc tupleDescriptor,
+				const Datum *values,
+				const bool *isnull)
+{
+	return heap_form_tuple_ext(tupleDescriptor,
+				values,
+				isnull,
+				-1);
+}
+
 /*
  * heap_modify_tuple
  *		form a new tuple from an old tuple and a set of replacement values.
@@ -1375,22 +1484,12 @@ heap_freetuple(HeapTuple htup)
 }
 
 
-/*
- * heap_form_minimal_tuple
- *		construct a MinimalTuple from the given values[] and isnull[] arrays,
- *		which are of the length indicated by tupleDescriptor->natts
- *
- * This is exactly like heap_form_tuple() except that the result is a
- * "minimal" tuple lacking a HeapTupleData header as well as room for system
- * columns.
- *
- * The result is allocated in the current memory context.
- */
 MinimalTuple
-heap_form_minimal_tuple(TupleDesc tupleDescriptor,
+heap_form_minimal_tuple_ext(TupleDesc tupleDescriptor,
 						const Datum *values,
 						const bool *isnull,
-						Size extra)
+						Size extra,
+						int natts)
 {
 	MinimalTuple tuple;			/* return tuple */
 	char	   *mem;
@@ -1403,6 +1502,9 @@ heap_form_minimal_tuple(TupleDesc tupleDescriptor,
 
 	Assert(extra == MAXALIGN(extra));
 
+	if(natts > 0)
+		numberOfAttributes = natts;
+
 	if (numberOfAttributes > MaxTupleAttributeNumber)
 		ereport(ERROR,
 				(errcode(ERRCODE_TOO_MANY_COLUMNS),
@@ -1431,7 +1533,7 @@ heap_form_minimal_tuple(TupleDesc tupleDescriptor,
 
 	hoff = len = MAXALIGN(len); /* align user data safely */
 
-	data_len = heap_compute_data_size(tupleDescriptor, values, isnull);
+	data_len = heap_compute_data_size_ext(tupleDescriptor, values, isnull, natts);
 
 	len += data_len;
 
@@ -1448,17 +1550,42 @@ heap_form_minimal_tuple(TupleDesc tupleDescriptor,
 	HeapTupleHeaderSetNatts(tuple, numberOfAttributes);
 	tuple->t_hoff = hoff + MINIMAL_TUPLE_OFFSET;
 
-	heap_fill_tuple(tupleDescriptor,
+	heap_fill_tuple_ext(tupleDescriptor,
 					values,
 					isnull,
 					(char *) tuple + hoff,
 					data_len,
 					&tuple->t_infomask,
-					(hasnull ? tuple->t_bits : NULL));
+					(hasnull ? tuple->t_bits : NULL),
+					natts);
 
 	return tuple;
 }
 
+/*
+ * heap_form_minimal_tuple
+ *		construct a MinimalTuple from the given values[] and isnull[] arrays,
+ *		which are of the length indicated by tupleDescriptor->natts
+ *
+ * This is exactly like heap_form_tuple() except that the result is a
+ * "minimal" tuple lacking a HeapTupleData header as well as room for system
+ * columns.
+ *
+ * The result is allocated in the current memory context.
+ */
+MinimalTuple
+heap_form_minimal_tuple(TupleDesc tupleDescriptor,
+						const Datum *values,
+						const bool *isnull,
+						Size extra)
+{
+	return heap_form_minimal_tuple_ext(tupleDescriptor,
+						values,
+						isnull,
+						extra,
+						-1);
+}
+
 /*
  * heap_free_minimal_tuple
  */
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index f08982a43cc..c98b49486b0 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -80,6 +80,7 @@ static inline void tts_buffer_heap_store_tuple(TupleTableSlot *slot,
 											   bool transfer_pin);
 static void tts_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, bool shouldFree);
 
+static void tts_heap_materialize_ext(TupleTableSlot *slot, int natts);
 
 const TupleTableSlotOps TTSOpsVirtual;
 const TupleTableSlotOps TTSOpsHeapTuple;
@@ -269,24 +270,61 @@ static void
 tts_virtual_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
 	TupleDesc	srcdesc = srcslot->tts_tupleDescriptor;
+	TupleDesc	dstdesc = dstslot->tts_tupleDescriptor;
+	int attnum = srcdesc->natts;
 
 	tts_virtual_clear(dstslot);
 
 	slot_getallattrs(srcslot);
 
-	for (int natt = 0; natt < srcdesc->natts; natt++)
+	if(dstdesc->natts < srcdesc->natts)
+		attnum = dstdesc->natts;
+
+	for (int natt = 0; natt < attnum; natt++)
 	{
 		dstslot->tts_values[natt] = srcslot->tts_values[natt];
 		dstslot->tts_isnull[natt] = srcslot->tts_isnull[natt];
 	}
 
-	dstslot->tts_nvalid = srcdesc->natts;
+	if(dstdesc->natts > srcdesc->natts)
+	{
+		for (int natt = srcdesc->natts; natt < dstdesc->natts; natt++)
+		{
+			dstslot->tts_values[natt] = (Datum) 0;
+			dstslot->tts_isnull[natt] = true;
+		}
+	}
+
+	dstslot->tts_nvalid = dstdesc->natts; //srcdesc->natts;
 	dstslot->tts_flags &= ~TTS_FLAG_EMPTY;
 
 	/* make sure storage doesn't depend on external memory */
 	tts_virtual_materialize(dstslot);
 }
 
+static HeapTuple
+tts_virtual_copy_heap_tuple_natts(TupleTableSlot *slot, int natts)
+{
+	Assert(!TTS_EMPTY(slot));
+
+	return heap_form_tuple_ext(slot->tts_tupleDescriptor,
+						   slot->tts_values,
+						   slot->tts_isnull,
+						   natts);
+}
+
+static MinimalTuple
+tts_virtual_copy_minimal_tuple_natts(TupleTableSlot *slot, int natts)
+{
+	Assert(!TTS_EMPTY(slot));
+
+	return heap_form_minimal_tuple_ext(slot->tts_tupleDescriptor,
+								   slot->tts_values,
+								   slot->tts_isnull,
+								   0,
+								   natts);
+}
+
 static HeapTuple
 tts_virtual_copy_heap_tuple(TupleTableSlot *slot)
 {
@@ -397,6 +435,12 @@ tts_heap_is_current_xact_tuple(TupleTableSlot *slot)
 
 static void
 tts_heap_materialize(TupleTableSlot *slot)
+{
+	tts_heap_materialize_ext(slot, -1);
+}
+
+static void
+tts_heap_materialize_ext(TupleTableSlot *slot, int natts)
 {
 	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
 	MemoryContext oldContext;
@@ -417,9 +461,10 @@ tts_heap_materialize(TupleTableSlot *slot)
 	hslot->off = 0;
 
 	if (!hslot->tuple)
-		hslot->tuple = heap_form_tuple(slot->tts_tupleDescriptor,
+		hslot->tuple = heap_form_tuple_ext(slot->tts_tupleDescriptor,
 									   slot->tts_values,
-									   slot->tts_isnull);
+									   slot->tts_isnull,
+									   natts);
 	else
 	{
 		/*
@@ -427,7 +472,7 @@ tts_heap_materialize(TupleTableSlot *slot)
 		 * context of the given slot (else it would have TTS_FLAG_SHOULDFREE
 		 * set).  Copy the tuple into the given slot's memory context.
 		 */
-		hslot->tuple = heap_copytuple(hslot->tuple);
+		hslot->tuple = heap_copytuple_ext(hslot->tuple, slot, natts);
 	}
 
 	slot->tts_flags |= TTS_FLAG_SHOULDFREE;
@@ -483,6 +528,29 @@ tts_heap_copy_minimal_tuple(TupleTableSlot *slot, Size extra)
 	return minimal_tuple_from_heap_tuple(hslot->tuple, extra);
 }
 
+static HeapTuple
+tts_heap_copy_heap_tuple_natts(TupleTableSlot *slot, int attnum)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	Assert(!TTS_EMPTY(slot));
+	if (!hslot->tuple)
+		tts_heap_materialize_ext(slot, attnum);
+
+	return heap_copytuple_ext(hslot->tuple, slot, attnum);
+}
+
+static MinimalTuple
+tts_heap_copy_minimal_tuple_natts(TupleTableSlot *slot, int attnum)
+{
+	HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot;
+
+	if (!hslot->tuple)
+		tts_heap_materialize_ext(slot, attnum);
+
+	return minimal_tuple_from_heap_tuple(hslot->tuple, 0);
+}
+
 static void
 tts_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, bool shouldFree)
 {
@@ -584,7 +652,7 @@ tts_minimal_is_current_xact_tuple(TupleTableSlot *slot)
 }
 
 static void
-tts_minimal_materialize(TupleTableSlot *slot)
+tts_minimal_materialize_ext(TupleTableSlot *slot, int natts)
 {
 	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
 	MemoryContext oldContext;
@@ -606,10 +674,11 @@ tts_minimal_materialize(TupleTableSlot *slot)
 
 	if (!mslot->mintuple)
 	{
-		mslot->mintuple = heap_form_minimal_tuple(slot->tts_tupleDescriptor,
+		mslot->mintuple = heap_form_minimal_tuple_ext(slot->tts_tupleDescriptor,
 												  slot->tts_values,
 												  slot->tts_isnull,
-												  0);
+												  0,
+												  natts);
 	}
 	else
 	{
@@ -632,16 +701,32 @@ tts_minimal_materialize(TupleTableSlot *slot)
 	MemoryContextSwitchTo(oldContext);
 }
 
+static void
+tts_minimal_materialize(TupleTableSlot *slot)
+{
+	tts_minimal_materialize_ext(slot, -1);
+}
+
 static void
 tts_minimal_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
 	MemoryContext oldcontext;
 	MinimalTuple mintuple;
 
-	oldcontext = MemoryContextSwitchTo(dstslot->tts_mcxt);
-	mintuple = ExecCopySlotMinimalTuple(srcslot);
-	MemoryContextSwitchTo(oldcontext);
-
+	if(dstslot->tts_tupleDescriptor->natts != srcslot->tts_tupleDescriptor->natts)
+	{
+		int natts = (dstslot->tts_tupleDescriptor->natts < srcslot->tts_tupleDescriptor->natts ? dstslot->tts_tupleDescriptor->natts : srcslot->tts_tupleDescriptor->natts);
+		oldcontext = MemoryContextSwitchTo(dstslot->tts_mcxt);
+		mintuple = ExecCopySlotMinimalTupleNatts(srcslot, natts);
+		MemoryContextSwitchTo(oldcontext);
+	}
+	else
+	{
+		oldcontext = MemoryContextSwitchTo(dstslot->tts_mcxt);
+		mintuple = ExecCopySlotMinimalTuple(srcslot);
+		MemoryContextSwitchTo(oldcontext);
+	}
+	dstslot->tts_remoteOid = srcslot->tts_remoteOid;
 	ExecStoreMinimalTuple(mintuple, dstslot, true);
 }
 
@@ -678,6 +763,28 @@ tts_minimal_copy_minimal_tuple(TupleTableSlot *slot, Size extra)
 	return heap_copy_minimal_tuple(mslot->mintuple, extra);
 }
 
+static HeapTuple
+tts_minimal_copy_heap_tuple_natts(TupleTableSlot *slot, int natts)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	if (!mslot->mintuple)
+		tts_minimal_materialize_ext(slot, natts);
+
+	return heap_tuple_from_minimal_tuple(mslot->mintuple);
+}
+
+static MinimalTuple
+tts_minimal_copy_minimal_tuple_natts(TupleTableSlot *slot, int natts)
+{
+	MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot;
+
+	if (!mslot->mintuple)
+		tts_minimal_materialize_ext(slot, natts);
+
+	return heap_copy_minimal_tuple(mslot->mintuple, 0);
+}
+
 static void
 tts_minimal_store_tuple(TupleTableSlot *slot, MinimalTuple mtup, bool shouldFree)
 {
@@ -801,7 +908,7 @@ tts_buffer_is_current_xact_tuple(TupleTableSlot *slot)
 }
 
 static void
-tts_buffer_heap_materialize(TupleTableSlot *slot)
+tts_buffer_heap_materialize_ext(TupleTableSlot *slot, int natts)
 {
 	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
 	MemoryContext oldContext;
@@ -830,13 +937,14 @@ tts_buffer_heap_materialize(TupleTableSlot *slot)
 		 * tuples in a buffer slot, which then also needs to be
 		 * materializable.
 		 */
-		bslot->base.tuple = heap_form_tuple(slot->tts_tupleDescriptor,
+		bslot->base.tuple = heap_form_tuple_ext(slot->tts_tupleDescriptor,
 											slot->tts_values,
-											slot->tts_isnull);
+											slot->tts_isnull,
+											natts);
 	}
 	else
 	{
-		bslot->base.tuple = heap_copytuple(bslot->base.tuple);
+		bslot->base.tuple = heap_copytuple_ext(bslot->base.tuple, slot, natts);
 
 		/*
 		 * A heap tuple stored in a BufferHeapTupleTableSlot should have a
@@ -859,6 +967,12 @@ tts_buffer_heap_materialize(TupleTableSlot *slot)
 	MemoryContextSwitchTo(oldContext);
 }
 
+static void
+tts_buffer_heap_materialize(TupleTableSlot *slot)
+{
+	tts_buffer_heap_materialize_ext(slot, -1);
+}
+
 static void
 tts_buffer_heap_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
@@ -874,12 +988,14 @@ tts_buffer_heap_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 		TTS_SHOULDFREE(srcslot) ||
 		!bsrcslot->base.tuple)
 	{
+		int natts;
 		MemoryContext oldContext;
 
+		natts = (dstslot->tts_tupleDescriptor->natts < srcslot->tts_tupleDescriptor->natts ? dstslot->tts_tupleDescriptor->natts : srcslot->tts_tupleDescriptor->natts);
 		ExecClearTuple(dstslot);
 		dstslot->tts_flags &= ~TTS_FLAG_EMPTY;
 		oldContext = MemoryContextSwitchTo(dstslot->tts_mcxt);
-		bdstslot->base.tuple = ExecCopySlotHeapTuple(srcslot);
+		bdstslot->base.tuple = ExecCopySlotHeapTupleNatts(srcslot, natts);
 		dstslot->tts_flags |= TTS_FLAG_SHOULDFREE;
 		MemoryContextSwitchTo(oldContext);
 	}
@@ -914,6 +1030,19 @@ tts_buffer_heap_get_heap_tuple(TupleTableSlot *slot)
 	return bslot->base.tuple;
 }
 
+static HeapTuple
+tts_buffer_heap_copy_heap_tuple_natts(TupleTableSlot *slot, int attnum)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	Assert(!TTS_EMPTY(slot));
+
+	if (!bslot->base.tuple)
+		tts_buffer_heap_materialize_ext(slot, attnum);
+
+	return heap_copytuple_ext(bslot->base.tuple, slot, attnum);
+}
+
 static HeapTuple
 tts_buffer_heap_copy_heap_tuple(TupleTableSlot *slot)
 {
@@ -927,6 +1056,19 @@ tts_buffer_heap_copy_heap_tuple(TupleTableSlot *slot)
 	return heap_copytuple(bslot->base.tuple);
 }
 
+static MinimalTuple
+tts_buffer_copy_minimal_tuple_natts(TupleTableSlot *slot, int natts)
+{
+	BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot;
+
+	Assert(!TTS_EMPTY(slot));
+
+	if (!bslot->base.tuple)
+		tts_buffer_heap_materialize_ext(slot, natts);
+
+	return minimal_tuple_from_heap_tuple(bslot->base.tuple, 0);
+}
+
 static MinimalTuple
 tts_buffer_heap_copy_minimal_tuple(TupleTableSlot *slot, Size extra)
 {
@@ -1274,7 +1416,9 @@ const TupleTableSlotOps TTSOpsVirtual = {
 	.get_heap_tuple = NULL,
 	.get_minimal_tuple = NULL,
 	.copy_heap_tuple = tts_virtual_copy_heap_tuple,
-	.copy_minimal_tuple = tts_virtual_copy_minimal_tuple
+	.copy_minimal_tuple = tts_virtual_copy_minimal_tuple,
+	.copy_heap_tuple_ext = tts_virtual_copy_heap_tuple_natts,
+	.copy_minimal_tuple_ext = tts_virtual_copy_minimal_tuple_natts
 };
 
 const TupleTableSlotOps TTSOpsHeapTuple = {
@@ -1292,7 +1436,9 @@ const TupleTableSlotOps TTSOpsHeapTuple = {
 	/* A heap tuple table slot can not "own" a minimal tuple. */
 	.get_minimal_tuple = NULL,
 	.copy_heap_tuple = tts_heap_copy_heap_tuple,
-	.copy_minimal_tuple = tts_heap_copy_minimal_tuple
+	.copy_minimal_tuple = tts_heap_copy_minimal_tuple,
+	.copy_heap_tuple_ext = tts_heap_copy_heap_tuple_natts,
+	.copy_minimal_tuple_ext = tts_heap_copy_minimal_tuple_natts
 };
 
 const TupleTableSlotOps TTSOpsMinimalTuple = {
@@ -1310,7 +1456,9 @@ const TupleTableSlotOps TTSOpsMinimalTuple = {
 	.get_heap_tuple = NULL,
 	.get_minimal_tuple = tts_minimal_get_minimal_tuple,
 	.copy_heap_tuple = tts_minimal_copy_heap_tuple,
-	.copy_minimal_tuple = tts_minimal_copy_minimal_tuple
+	.copy_minimal_tuple = tts_minimal_copy_minimal_tuple,
+	.copy_heap_tuple_ext = tts_minimal_copy_heap_tuple_natts,
+	.copy_minimal_tuple_ext = tts_minimal_copy_minimal_tuple_natts
 };
 
 const TupleTableSlotOps TTSOpsBufferHeapTuple = {
@@ -1328,7 +1476,9 @@ const TupleTableSlotOps TTSOpsBufferHeapTuple = {
 	/* A buffer heap tuple table slot can not "own" a minimal tuple. */
 	.get_minimal_tuple = NULL,
 	.copy_heap_tuple = tts_buffer_heap_copy_heap_tuple,
-	.copy_minimal_tuple = tts_buffer_heap_copy_minimal_tuple
+	.copy_minimal_tuple = tts_buffer_heap_copy_minimal_tuple,
+	.copy_heap_tuple_ext = tts_buffer_heap_copy_heap_tuple_natts,
+	.copy_minimal_tuple_ext = tts_buffer_copy_minimal_tuple_natts
 };
 
 
@@ -1867,8 +2017,7 @@ ExecStoreAllNullTuple(TupleTableSlot *slot)
  * Until the slot is materialized, the contents of the slot depend on the
  * datum.
  */
-void
-ExecStoreHeapTupleDatum(Datum data, TupleTableSlot *slot)
+void ExecStoreHeapTupleDatum(Datum data, TupleTableSlot *slot)
 {
 	HeapTupleData tuple = {0};
 	HeapTupleHeader td;
@@ -1878,6 +2027,7 @@ ExecStoreHeapTupleDatum(Datum data, TupleTableSlot *slot)
 	tuple.t_len = HeapTupleHeaderGetDatumLength(td);
 	tuple.t_self = td->t_ctid;
 	tuple.t_data = td;
+	tuple.t_tableOid = InvalidOid;
 
 	ExecClearTuple(slot);
 
diff --git a/src/backend/optimizer/util/tlist.c b/src/backend/optimizer/util/tlist.c
index 752ea9222f6..3c99c72d38c 100644
--- a/src/backend/optimizer/util/tlist.c
+++ b/src/backend/optimizer/util/tlist.c
@@ -329,18 +329,37 @@ apply_tlist_labeling(List *dest_tlist, List *src_tlist)
 	ListCell   *ld,
 			   *ls;
 
-	Assert(list_length(dest_tlist) == list_length(src_tlist));
-	forboth(ld, dest_tlist, ls, src_tlist)
+	foreach(ld, dest_tlist)
 	{
+		bool has_src = false;
 		TargetEntry *dest_tle = (TargetEntry *) lfirst(ld);
-		TargetEntry *src_tle = (TargetEntry *) lfirst(ls);
-
-		Assert(dest_tle->resno == src_tle->resno);
-		dest_tle->resname = src_tle->resname;
-		dest_tle->ressortgroupref = src_tle->ressortgroupref;
-		dest_tle->resorigtbl = src_tle->resorigtbl;
-		dest_tle->resorigcol = src_tle->resorigcol;
-		dest_tle->resjunk = src_tle->resjunk;
+
+		foreach(ls, src_tlist)
+		{
+			TargetEntry *src_tle = (TargetEntry *) lfirst(ls);
+
+			if(dest_tle->resno == src_tle->resno)
+			{
+				has_src = true;
+				dest_tle->resname = src_tle->resname;
+				dest_tle->ressortgroupref = src_tle->ressortgroupref;
+				dest_tle->resorigtbl = src_tle->resorigtbl;
+				dest_tle->resorigcol = src_tle->resorigcol;
+				dest_tle->resjunk = src_tle->resjunk;
+				break;
+			}
+		}
+
+		if(!has_src)
+		{
+			TargetEntry *new_entry = palloc0(sizeof(TargetEntry));
+			new_entry->resname = dest_tle->resname;
+			new_entry->ressortgroupref = dest_tle->ressortgroupref;
+			new_entry->resorigtbl = dest_tle->resorigtbl;
+			new_entry->resorigcol = dest_tle->resorigcol;
+			new_entry->resjunk = dest_tle->resjunk;
+			src_tlist = lappend(src_tlist, new_entry);
+		}
 	}
 }
 
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 77a6c48fd71..338c123402f 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -794,10 +794,19 @@ HeapTupleClearHeapOnly(const HeapTupleData *tuple)
 /* prototypes for functions in common/heaptuple.c */
 extern Size heap_compute_data_size(TupleDesc tupleDesc,
 								   const Datum *values, const bool *isnull);
+extern Size heap_compute_data_size_ext(TupleDesc tupleDesc,
+									   const Datum *values,
+									   const bool *isnull,
+									   int attnum);
+
 extern void heap_fill_tuple(TupleDesc tupleDesc,
 							const Datum *values, const bool *isnull,
 							char *data, Size data_size,
 							uint16 *infomask, uint8 *bit);
+extern void heap_fill_tuple_ext(TupleDesc tupleDesc,
+								const Datum *values, const bool *isnull,
+								char *data, Size data_size,
+								uint16 *infomask, uint8 *bit, int natts);
 extern bool heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc);
 extern Datum nocachegetattr(HeapTuple tup, int attnum,
 							TupleDesc tupleDesc);
@@ -806,10 +815,13 @@ extern Datum heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
 extern Datum getmissingattr(TupleDesc tupleDesc,
 							int attnum, bool *isnull);
 extern HeapTuple heap_copytuple(HeapTuple tuple);
+extern HeapTuple heap_copytuple_ext(HeapTuple tuple, void *slot, int dstnatts);
 extern void heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest);
 extern Datum heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc);
 extern HeapTuple heap_form_tuple(TupleDesc tupleDescriptor,
 								 const Datum *values, const bool *isnull);
+extern HeapTuple heap_form_tuple_ext(TupleDesc tupleDescriptor,
+								 const Datum *values, const bool *isnull, int natts);
 extern HeapTuple heap_modify_tuple(HeapTuple tuple,
 								   TupleDesc tupleDesc,
 								   const Datum *replValues,
@@ -827,6 +839,8 @@ extern void heap_freetuple(HeapTuple htup);
 extern MinimalTuple heap_form_minimal_tuple(TupleDesc tupleDescriptor,
 											const Datum *values, const bool *isnull,
 											Size extra);
+extern MinimalTuple heap_form_minimal_tuple_ext(TupleDesc tupleDescriptor,
+												const Datum *values, const bool *isnull, Size extra, int natts);
 extern void heap_free_minimal_tuple(MinimalTuple mtup);
 extern MinimalTuple heap_copy_minimal_tuple(MinimalTuple mtup, Size extra);
 extern HeapTuple heap_tuple_from_minimal_tuple(MinimalTuple mtup);
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index d7e8e72aeae..75e0d69d46d 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -141,6 +141,7 @@ typedef struct TupleTableSlot
 	MemoryContext tts_mcxt;		/* slot itself is in this context */
 	ItemPointerData tts_tid;	/* stored tuple's tid */
 	Oid			tts_tableOid;	/* table oid of tuple */
+	Oid			tts_remoteOid;
 } TupleTableSlot;
 
 /* routines for a TupleTableSlot implementation */
@@ -239,6 +240,26 @@ struct TupleTableSlotOps
 	 * with the minimal tuple without the need for an additional allocation.
 	 */
 	MinimalTuple (*copy_minimal_tuple) (TupleTableSlot *slot, Size extra);
+
+	/*
+	 * Return a copy of heap tuple's N atts representing the contents of the slot.
+	 * The copy needs to be palloc'd in the current memory context. The slot
+	 * itself is expected to remain unaffected. It is *not* expected to have
+	 * meaningful "system columns" in the copy. The copy is not be "owned" by
+	 * the slot i.e. the caller has to take responsibility to free memory
+	 * consumed by the slot.
+	 */
+	HeapTuple (*copy_heap_tuple_ext) (TupleTableSlot *slot, int natts);
+
+	/*
+	 * Return a copy of minimal tuple's N atts representing the contents of the slot.
+	 * The copy needs to be palloc'd in the current memory context. The slot
+	 * itself is expected to remain unaffected. It is *not* expected to have
+	 * meaningful "system columns" in the copy. The copy is not be "owned" by
+	 * the slot i.e. the caller has to take responsibility to free memory
+	 * consumed by the slot.
+	 */
+	MinimalTuple (*copy_minimal_tuple_ext) (TupleTableSlot *slot, int natts);
 };
 
 /*
@@ -500,6 +521,14 @@ ExecMaterializeSlot(TupleTableSlot *slot)
  * ExecCopySlotHeapTuple - return HeapTuple allocated in caller's context
  */
 static inline HeapTuple
+ExecCopySlotHeapTupleNatts(TupleTableSlot *slot, int natts)
+{
+	Assert(!TTS_EMPTY(slot));
+
+	return slot->tts_ops->copy_heap_tuple_ext(slot, natts);
+}
+
+ static inline HeapTuple
 ExecCopySlotHeapTuple(TupleTableSlot *slot)
 {
 	Assert(!TTS_EMPTY(slot));
@@ -507,6 +536,15 @@ ExecCopySlotHeapTuple(TupleTableSlot *slot)
 	return slot->tts_ops->copy_heap_tuple(slot);
 }
 
+/*
+ * ExecCopySlotMinimalTuple - return MinimalTuple allocated in caller's context
+ */
+static inline MinimalTuple
+ExecCopySlotMinimalTupleNatts(TupleTableSlot *slot, int natts)
+{
+	return slot->tts_ops->copy_minimal_tuple_ext(slot, natts);
+}
+
 /*
  * ExecCopySlotMinimalTuple - return MinimalTuple allocated in caller's context
  */
@@ -544,9 +582,18 @@ ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
 {
 	Assert(!TTS_EMPTY(srcslot));
 	Assert(srcslot != dstslot);
+
+	/* This assert supposes that we always have same number
+	 * of attributes and is not relevant in new case
+	 */
+/*
 	Assert(dstslot->tts_tupleDescriptor->natts ==
 		   srcslot->tts_tupleDescriptor->natts);
+*/
 
+	/* If source slot has less attributes then target - copy source and
+	 * set target to nulls. Otherwise copy only leading attributes and set
+	 * target natts to source counter */
 	dstslot->tts_ops->copyslot(dstslot, srcslot);
 
 	return dstslot;
-- 
2.43.0

