diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 0f051a36866d258ccfe8991634f7703cc4a3b5af..83af1f93f70e7500a75c7b7fc209e7581d54d0e0 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.100 2005/03/12 20:25:06 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/xfunc.sgml,v 1.101 2005/03/16 21:38:04 tgl Exp $
 -->
 
  <sect1 id="xfunc">
@@ -2219,7 +2219,7 @@ CREATE FUNCTION c_overpaid(emp, integer) RETURNS boolean
      case, you first need to obtain or construct a <structname>TupleDesc</>
      descriptor for the tuple structure.  When working with Datums, you
      pass the <structname>TupleDesc</> to <function>BlessTupleDesc</>,
-     and then call <function>heap_formtuple</> for each row.  When working
+     and then call <function>heap_form_tuple</> for each row.  When working
      with C strings, you pass the <structname>TupleDesc</> to
      <function>TupleDescGetAttInMetadata</>, and then call
      <function>BuildTupleFromCStrings</> for each row.  In the case of a
@@ -2264,7 +2264,7 @@ AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc)
     <para>
      When working with Datums, use
 <programlisting>
-HeapTuple heap_formtuple(TupleDesc tupdesc, Datum *values, char *nulls)
+HeapTuple heap_form_tuple(TupleDesc tupdesc, Datum *values, bool *isnull)
 </programlisting>
      to build a <structname>HeapTuple</> given user data in Datum form.
     </para>
@@ -2383,7 +2383,7 @@ typedef struct
      *
      * tuple_desc is for use when returning tuples (i.e. composite data types)
      * and is only needed if you are going to build the tuples with
-     * heap_formtuple() rather than with BuildTupleFromCStrings().  Note that
+     * heap_form_tuple() rather than with BuildTupleFromCStrings().  Note that
      * the TupleDesc pointer stored here should usually have been run through
      * BlessTupleDesc() first.
      */
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 22d5e44dc3f01e5b2bb38295d287610680564713..88df2231795e6e1a730b1342e97341688c4eb050 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -4,16 +4,19 @@
  *	  This file contains heap tuple accessor and mutator routines, as well
  *	  as various tuple utilities.
  *
+ * NOTE: there is massive duplication of code in this module to
+ * support both the convention that a null is marked by a bool TRUE,
+ * and the convention that a null is marked by a char 'n'.  The latter
+ * convention is deprecated but it'll probably be a long time before
+ * we can get rid of it entirely.
+ *
+ *
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.97 2005/03/14 04:41:12 tgl Exp $
- *
- * NOTES
- *	  The old interface functions have been converted to macros
- *	  and moved to heapam.h
+ *	  $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.98 2005/03/16 21:38:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -31,10 +34,38 @@
  * ----------------------------------------------------------------
  */
 
+/*
+ * heap_compute_data_size
+ *		Determine size of the data area of a tuple to be constructed
+ */
+Size
+heap_compute_data_size(TupleDesc tupleDesc,
+					   Datum *values,
+					   bool *isnull)
+{
+	Size		data_length = 0;
+	int			i;
+	int			numberOfAttributes = tupleDesc->natts;
+	Form_pg_attribute *att = tupleDesc->attrs;
+
+	for (i = 0; i < numberOfAttributes; i++)
+	{
+		if (isnull[i])
+			continue;
+
+		data_length = att_align(data_length, att[i]->attalign);
+		data_length = att_addlength(data_length, att[i]->attlen, values[i]);
+	}
+
+	return data_length;
+}
+
 /* ----------------
  *		ComputeDataSize
  *
  * Determine size of the data area of a tuple to be constructed
+ *
+ * OLD API with char 'n'/' ' convention for indicating nulls
  * ----------------
  */
 Size
@@ -59,10 +90,107 @@ ComputeDataSize(TupleDesc tupleDesc,
 	return data_length;
 }
 
+/*
+ * 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.
+ */
+void
+heap_fill_tuple(TupleDesc tupleDesc,
+				Datum *values, bool *isnull,
+				char *data, uint16 *infomask, bits8 *bit)
+{
+	bits8	   *bitP;
+	int			bitmask;
+	int			i;
+	int			numberOfAttributes = tupleDesc->natts;
+	Form_pg_attribute *att = tupleDesc->attrs;
+
+	if (bit != NULL)
+	{
+		bitP = &bit[-1];
+		bitmask = CSIGNBIT;
+	}
+	else
+	{
+		/* just to keep compiler quiet */
+		bitP = NULL;
+		bitmask = 0;
+	}
+
+	*infomask &= ~(HEAP_HASNULL | HEAP_HASVARWIDTH | HEAP_HASEXTENDED);
+
+	for (i = 0; i < numberOfAttributes; i++)
+	{
+		Size		data_length;
+
+		if (bit != NULL)
+		{
+			if (bitmask != CSIGNBIT)
+				bitmask <<= 1;
+			else
+			{
+				bitP += 1;
+				*bitP = 0x0;
+				bitmask = 1;
+			}
+
+			if (isnull[i])
+			{
+				*infomask |= HEAP_HASNULL;
+				continue;
+			}
+
+			*bitP |= bitmask;
+		}
+
+		/* XXX we are aligning the pointer itself, not the offset */
+		data = (char *) att_align((long) data, att[i]->attalign);
+
+		if (att[i]->attbyval)
+		{
+			/* pass-by-value */
+			store_att_byval(data, values[i], att[i]->attlen);
+			data_length = att[i]->attlen;
+		}
+		else if (att[i]->attlen == -1)
+		{
+			/* varlena */
+			*infomask |= HEAP_HASVARWIDTH;
+			if (VARATT_IS_EXTERNAL(values[i]))
+				*infomask |= HEAP_HASEXTERNAL;
+			if (VARATT_IS_COMPRESSED(values[i]))
+				*infomask |= HEAP_HASCOMPRESSED;
+			data_length = VARATT_SIZE(DatumGetPointer(values[i]));
+			memcpy(data, DatumGetPointer(values[i]), data_length);
+		}
+		else if (att[i]->attlen == -2)
+		{
+			/* cstring */
+			*infomask |= HEAP_HASVARWIDTH;
+			data_length = strlen(DatumGetCString(values[i])) + 1;
+			memcpy(data, DatumGetPointer(values[i]), data_length);
+		}
+		else
+		{
+			/* fixed-length pass-by-reference */
+			Assert(att[i]->attlen > 0);
+			data_length = att[i]->attlen;
+			memcpy(data, DatumGetPointer(values[i]), data_length);
+		}
+
+		data += data_length;
+	}
+}
+
 /* ----------------
  *		DataFill
  *
  * Load data portion of a tuple from values/nulls arrays
+ *
+ * OLD API with char 'n'/' ' convention for indicating nulls
  * ----------------
  */
 void
@@ -162,19 +290,19 @@ DataFill(char *data,
  */
 
 /* ----------------
- *		heap_attisnull	- returns 1 iff tuple attribute is not present
+ *		heap_attisnull	- returns TRUE iff tuple attribute is not present
  * ----------------
  */
-int
+bool
 heap_attisnull(HeapTuple tup, int attnum)
 {
 	if (attnum > (int) tup->t_data->t_natts)
-		return 1;
+		return true;
 
 	if (attnum > 0)
 	{
 		if (HeapTupleNoNulls(tup))
-			return 0;
+			return false;
 		return att_isnull(attnum - 1, tup->t_data->t_bits);
 	}
 
@@ -194,7 +322,7 @@ heap_attisnull(HeapTuple tup, int attnum)
 			elog(ERROR, "invalid attnum: %d", attnum);
 	}
 
-	return 0;
+	return false;
 }
 
 /* ----------------
@@ -215,7 +343,7 @@ heap_attisnull(HeapTuple tup, int attnum)
  *		you cache the offsets once, examining all the other tuples using
  *		the same attribute descriptor will go much quicker. -cim 5/4/91
  *
- *		NOTE: if you need to change this code, see also heap_deformtuple.
+ *		NOTE: if you need to change this code, see also heap_deform_tuple.
  * ----------------
  */
 Datum
@@ -227,7 +355,7 @@ nocachegetattr(HeapTuple tuple,
 	HeapTupleHeader tup = tuple->t_data;
 	Form_pg_attribute *att = tupleDesc->attrs;
 	char	   *tp;				/* ptr to att in tuple */
-	bits8	   *bp = tup->t_bits;		/* ptr to null bitmask in tuple */
+	bits8	   *bp = tup->t_bits;		/* ptr to null bitmap in tuple */
 	bool		slow = false;	/* do we have to walk nulls? */
 
 	(void) isnull;				/* not used */
@@ -385,11 +513,11 @@ nocachegetattr(HeapTuple tuple,
 		/*
 		 * Now we know that we have to walk the tuple CAREFULLY.
 		 *
-		 * Note - This loop is a little tricky.  On iteration i we first set
-		 * the offset for attribute i and figure out how much the offset
-		 * should be incremented.  Finally, we need to align the offset
-		 * based on the size of attribute i+1 (for which the offset has
-		 * been computed). -mer 12 Dec 1991
+		 * Note - This loop is a little tricky.  For each non-null attribute,
+		 * we have to first account for alignment padding before the attr,
+		 * then advance over the attr based on its length.  Nulls have no
+		 * storage and no alignment padding either.  We can use/set attcacheoff
+		 * until we pass either a null or a var-width attribute.
 		 */
 
 		for (i = 0; i < attnum; i++)
@@ -400,7 +528,7 @@ nocachegetattr(HeapTuple tuple,
 				continue;
 			}
 
-			/* If we know the next offset, we can skip the rest */
+			/* If we know the next offset, we can skip the alignment calc */
 			if (usecache && att[i]->attcacheoff != -1)
 				off = att[i]->attcacheoff;
 			else
@@ -552,6 +680,110 @@ heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest)
 	memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len);
 }
 
+/*
+ * 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,
+				Datum *values,
+				bool *isnull)
+{
+	HeapTuple	tuple;			/* return tuple */
+	HeapTupleHeader td;			/* tuple data */
+	unsigned long len;
+	int			hoff;
+	bool		hasnull = false;
+	Form_pg_attribute *att = tupleDescriptor->attrs;
+	int			numberOfAttributes = tupleDescriptor->natts;
+	int			i;
+
+	if (numberOfAttributes > MaxTupleAttributeNumber)
+		ereport(ERROR,
+				(errcode(ERRCODE_TOO_MANY_COLUMNS),
+				 errmsg("number of columns (%d) exceeds limit (%d)",
+						numberOfAttributes, MaxTupleAttributeNumber)));
+
+	/*
+	 * Check for nulls and embedded tuples; expand any toasted attributes
+	 * in embedded tuples.	This preserves the invariant that toasting can
+	 * only go one level deep.
+	 *
+	 * We can skip calling toast_flatten_tuple_attribute() if the attribute
+	 * couldn't possibly be of composite type.  All composite datums are
+	 * varlena and have alignment 'd'; furthermore they aren't arrays.
+	 * Also, if an attribute is already toasted, it must have been sent to
+	 * disk already and so cannot contain toasted attributes.
+	 */
+	for (i = 0; i < numberOfAttributes; i++)
+	{
+		if (isnull[i])
+			hasnull = true;
+		else if (att[i]->attlen == -1 &&
+				 att[i]->attalign == 'd' &&
+				 att[i]->attndims == 0 &&
+				 !VARATT_IS_EXTENDED(values[i]))
+		{
+			values[i] = toast_flatten_tuple_attribute(values[i],
+													  att[i]->atttypid,
+													  att[i]->atttypmod);
+		}
+	}
+
+	/*
+	 * Determine total space needed
+	 */
+	len = offsetof(HeapTupleHeaderData, t_bits);
+
+	if (hasnull)
+		len += BITMAPLEN(numberOfAttributes);
+
+	if (tupleDescriptor->tdhasoid)
+		len += sizeof(Oid);
+
+	hoff = len = MAXALIGN(len); /* align user data safely */
+
+	len += heap_compute_data_size(tupleDescriptor, values, isnull);
+
+	/*
+	 * Allocate and zero the space needed.	Note that the tuple body and
+	 * HeapTupleData management structure are allocated in one chunk.
+	 */
+	tuple = (HeapTuple) palloc0(HEAPTUPLESIZE + len);
+	tuple->t_datamcxt = CurrentMemoryContext;
+	tuple->t_data = td = (HeapTupleHeader) ((char *) tuple + HEAPTUPLESIZE);
+
+	/*
+	 * And fill in the information.  Note we fill the Datum fields even
+	 * though this tuple may never become a Datum.
+	 */
+	tuple->t_len = len;
+	ItemPointerSetInvalid(&(tuple->t_self));
+	tuple->t_tableOid = InvalidOid;
+
+	HeapTupleHeaderSetDatumLength(td, len);
+	HeapTupleHeaderSetTypeId(td, tupleDescriptor->tdtypeid);
+	HeapTupleHeaderSetTypMod(td, tupleDescriptor->tdtypmod);
+
+	td->t_natts = numberOfAttributes;
+	td->t_hoff = hoff;
+
+	if (tupleDescriptor->tdhasoid)		/* else leave infomask = 0 */
+		td->t_infomask = HEAP_HASOID;
+
+	heap_fill_tuple(tupleDescriptor,
+					values,
+					isnull,
+					(char *) td + hoff,
+					&td->t_infomask,
+					(hasnull ? td->t_bits : NULL));
+
+	return tuple;
+}
+
 /* ----------------
  *		heap_formtuple
  *
@@ -559,6 +791,8 @@ heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest)
  *
  *		Null attributes are indicated by a 'n' in the appropriate byte
  *		of nulls[]. Non-null attributes are indicated by a ' ' (space).
+ *
+ * OLD API with char 'n'/' ' convention for indicating nulls
  * ----------------
  */
 HeapTuple
@@ -658,11 +892,84 @@ heap_formtuple(TupleDesc tupleDescriptor,
 	return tuple;
 }
 
+/*
+ * heap_modify_tuple
+ *		form a new tuple from an old tuple and a set of replacement values.
+ *
+ * The replValues, replIsnull, and doReplace arrays must be of the length
+ * indicated by tupleDesc->natts.  The new tuple is constructed using the data
+ * from replValues/replIsnull at columns where doReplace is true, and using
+ * the data from the old tuple at columns where doReplace is false.
+ *
+ * The result is allocated in the current memory context.
+ */
+HeapTuple
+heap_modify_tuple(HeapTuple tuple,
+				  TupleDesc tupleDesc,
+				  Datum *replValues,
+				  bool *replIsnull,
+				  bool *doReplace)
+{
+	int			numberOfAttributes = tupleDesc->natts;
+	int			attoff;
+	Datum	   *values;
+	bool	   *isnull;
+	HeapTuple	newTuple;
+
+	/*
+	 * allocate and fill values and isnull arrays from either the tuple or
+	 * the repl information, as appropriate.
+	 *
+	 * NOTE: it's debatable whether to use heap_deform_tuple() here or just
+	 * heap_getattr() only the non-replaced colums.  The latter could win
+	 * if there are many replaced columns and few non-replaced ones.
+	 * However, heap_deform_tuple costs only O(N) while the heap_getattr
+	 * way would cost O(N^2) if there are many non-replaced columns, so it
+	 * seems better to err on the side of linear cost.
+	 */
+	values = (Datum *) palloc(numberOfAttributes * sizeof(Datum));
+	isnull = (bool *) palloc(numberOfAttributes * sizeof(bool));
+
+	heap_deform_tuple(tuple, tupleDesc, values, isnull);
+
+	for (attoff = 0; attoff < numberOfAttributes; attoff++)
+	{
+		if (doReplace[attoff])
+		{
+			values[attoff] = replValues[attoff];
+			isnull[attoff] = replIsnull[attoff];
+		}
+	}
+
+	/*
+	 * create a new tuple from the values and isnull arrays
+	 */
+	newTuple = heap_form_tuple(tupleDesc, values, isnull);
+
+	pfree(values);
+	pfree(isnull);
+
+	/*
+	 * copy the identification info of the old tuple: t_ctid, t_self, and
+	 * OID (if any)
+	 */
+	newTuple->t_data->t_ctid = tuple->t_data->t_ctid;
+	newTuple->t_self = tuple->t_self;
+	newTuple->t_tableOid = tuple->t_tableOid;
+	if (tupleDesc->tdhasoid)
+		HeapTupleSetOid(newTuple, HeapTupleGetOid(tuple));
+
+	return newTuple;
+}
+
 /* ----------------
  *		heap_modifytuple
  *
  *		forms a new tuple from an old tuple and a set of replacement values.
  *		returns a new palloc'ed tuple.
+ *
+ * OLD API with char 'n'/' ' convention for indicating nulls, and
+ * char 'r'/' ' convention for indicating whether to replace columns.
  * ----------------
  */
 HeapTuple
@@ -727,6 +1034,93 @@ heap_modifytuple(HeapTuple tuple,
 	return newTuple;
 }
 
+/*
+ * heap_deform_tuple
+ *		Given a tuple, extract data into values/isnull arrays; this is
+ *		the inverse of heap_form_tuple.
+ *
+ *		Storage for the values/isnull arrays is provided by the caller;
+ *		it should be sized according to tupleDesc->natts not tuple->t_natts.
+ *
+ *		Note that for pass-by-reference datatypes, the pointer placed
+ *		in the Datum will point into the given tuple.
+ *
+ *		When all or most of a tuple's fields need to be extracted,
+ *		this routine will be significantly quicker than a loop around
+ *		heap_getattr; the loop will become O(N^2) as soon as any
+ *		noncacheable attribute offsets are involved.
+ */
+void
+heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
+				  Datum *values, bool *isnull)
+{
+	HeapTupleHeader tup = tuple->t_data;
+	bool		hasnulls = HeapTupleHasNulls(tuple);
+	Form_pg_attribute *att = tupleDesc->attrs;
+	int			tdesc_natts = tupleDesc->natts;
+	int			natts;			/* number of atts to extract */
+	int			attnum;
+	char	   *tp;				/* ptr to tuple data */
+	long		off;			/* offset in tuple data */
+	bits8	   *bp = tup->t_bits;		/* ptr to null bitmap in tuple */
+	bool		slow = false;	/* can we use/set attcacheoff? */
+
+	natts = tup->t_natts;
+
+	/*
+	 * In inheritance situations, it is possible that the given tuple
+	 * actually has more fields than the caller is expecting.  Don't run
+	 * off the end of the caller's arrays.
+	 */
+	natts = Min(natts, tdesc_natts);
+
+	tp = (char *) tup + tup->t_hoff;
+
+	off = 0;
+
+	for (attnum = 0; attnum < natts; attnum++)
+	{
+		Form_pg_attribute thisatt = att[attnum];
+
+		if (hasnulls && att_isnull(attnum, bp))
+		{
+			values[attnum] = (Datum) 0;
+			isnull[attnum] = true;
+			slow = true;		/* can't use attcacheoff anymore */
+			continue;
+		}
+
+		isnull[attnum] = false;
+
+		if (!slow && thisatt->attcacheoff >= 0)
+			off = thisatt->attcacheoff;
+		else
+		{
+			off = att_align(off, thisatt->attalign);
+
+			if (!slow)
+				thisatt->attcacheoff = off;
+		}
+
+		values[attnum] = fetchatt(thisatt, tp + off);
+
+		off = att_addlength(off, thisatt->attlen, tp + off);
+
+		if (thisatt->attlen <= 0)
+			slow = true;		/* can't use attcacheoff anymore */
+	}
+
+	/*
+	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
+	 * rest as null
+	 */
+	for (; attnum < tdesc_natts; attnum++)
+	{
+		values[attnum] = (Datum) 0;
+		isnull[attnum] = true;
+	}
+}
+
 /* ----------------
  *		heap_deformtuple
  *
@@ -743,6 +1137,8 @@ heap_modifytuple(HeapTuple tuple,
  *		this routine will be significantly quicker than a loop around
  *		heap_getattr; the loop will become O(N^2) as soon as any
  *		noncacheable attribute offsets are involved.
+ *
+ * OLD API with char 'n'/' ' convention for indicating nulls
  * ----------------
  */
 void
@@ -759,7 +1155,7 @@ heap_deformtuple(HeapTuple tuple,
 	int			attnum;
 	char	   *tp;				/* ptr to tuple data */
 	long		off;			/* offset in tuple data */
-	bits8	   *bp = tup->t_bits;		/* ptr to null bitmask in tuple */
+	bits8	   *bp = tup->t_bits;		/* ptr to null bitmap in tuple */
 	bool		slow = false;	/* can we use/set attcacheoff? */
 
 	natts = tup->t_natts;
@@ -818,42 +1214,38 @@ heap_deformtuple(HeapTuple tuple,
 	}
 }
 
-/* ----------------
- *		slot_deformtuple
+/*
+ * slot_deform_tuple
+ *		Given a TupleTableSlot, extract data from the slot's physical tuple
+ *		into its Datum/isnull arrays.  Data is extracted up through the
+ *		natts'th column (caller must ensure this is a legal column number).
  *
- *		Given a TupleTableSlot, extract data into cache_values array 
- *		from the slot's tuple.
- *
- *		This is essentially an incremental version of heap_deformtuple:
+ *		This is essentially an incremental version of heap_deform_tuple:
  *		on each call we extract attributes up to the one needed, without
  *		re-computing information about previously extracted attributes.
- *		slot->cache_natts is the number of attributes already extracted.
- *
- *		This only gets called from slot_getattr.  Note that slot_getattr
- *		must check for a null attribute since we don't create an array
- *		of null indicators.
- * ----------------
+ *		slot->tts_nvalid is the number of attributes already extracted.
  */
 static void
-slot_deformtuple(TupleTableSlot *slot, int natts)
+slot_deform_tuple(TupleTableSlot *slot, int natts)
 {
-	HeapTuple		tuple = slot->val;
-	TupleDesc		tupleDesc = slot->ttc_tupleDescriptor;
-	Datum	   *values = slot->cache_values;
+	HeapTuple		tuple = slot->tts_tuple;
+	TupleDesc		tupleDesc = slot->tts_tupleDescriptor;
+	Datum	   *values = slot->tts_values;
+	bool	   *isnull = slot->tts_isnull;
 	HeapTupleHeader	tup = tuple->t_data;
 	bool		hasnulls = HeapTupleHasNulls(tuple);
 	Form_pg_attribute *att = tupleDesc->attrs;
 	int			attnum;
 	char	   *tp;					/* ptr to tuple data */
 	long		off;				/* offset in tuple data */
-	bits8	   *bp = tup->t_bits;	/* ptr to null bitmask in tuple */
+	bits8	   *bp = tup->t_bits;	/* ptr to null bitmap in tuple */
 	bool		slow;				/* can we use/set attcacheoff? */
 
 	/*
 	 * Check whether the first call for this tuple, and initialize or
 	 * restore loop state.
 	 */
-	attnum = slot->cache_natts;
+	attnum = slot->tts_nvalid;
 	if (attnum == 0)
 	{
 		/* Start from the first attribute */
@@ -863,8 +1255,8 @@ slot_deformtuple(TupleTableSlot *slot, int natts)
 	else
 	{
 		/* Restore state from previous execution */
-		off = slot->cache_off;
-		slow = slot->cache_slow;
+		off = slot->tts_off;
+		slow = slot->tts_slow;
 	}
 
 	tp = (char *) tup + tup->t_hoff;
@@ -876,10 +1268,13 @@ slot_deformtuple(TupleTableSlot *slot, int natts)
 		if (hasnulls && att_isnull(attnum, bp))
 		{
 			values[attnum] = (Datum) 0;
+			isnull[attnum] = true;
 			slow = true;        /* can't use attcacheoff anymore */
 			continue;
 		}
 
+		isnull[attnum] = false;
+
 		if (!slow && thisatt->attcacheoff >= 0)
 			off = thisatt->attcacheoff;
 		else
@@ -901,48 +1296,81 @@ slot_deformtuple(TupleTableSlot *slot, int natts)
 	/*
 	 * Save state for next execution
 	 */
-	slot->cache_natts = attnum;
-	slot->cache_off = off;
-	slot->cache_slow = slow;
+	slot->tts_nvalid = attnum;
+	slot->tts_off = off;
+	slot->tts_slow = slow;
 }
 
-/* --------------------------------
- *		slot_getattr
- *
+/*
+ * 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->val;
-	TupleDesc		tupleDesc = slot->ttc_tupleDescriptor;
+	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");
 		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
+	}
 
 	/*
-	 * check if attnum is out of range according to either the tupdesc
-	 * or the tuple itself; if so return NULL
+	 * fast path if desired attribute already cached
 	 */
-	tup = tuple->t_data;
+	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;
+	}
 
-	if (attnum > tup->t_natts || attnum > tupleDesc->natts)
+	/*
+	 * 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 > tup->t_natts)
 	{
 		*isnull = true;
 		return (Datum) 0;
 	}
 
 	/*
-	 * check if target attribute is null
+	 * check if target attribute is null: no point in groveling through tuple
 	 */
 	if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits))
 	{
@@ -963,30 +1391,151 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 	}
 
 	/*
-	 * If attribute wasn't already extracted, extract it and preceding
-	 * attributes.
+	 * 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 = tuple->t_data->t_natts;
+	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
 	 */
-	if (attnum > slot->cache_natts)
+	attno = tuple->t_data->t_natts;
+	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++)
 	{
-		/*
-		 * If first time for this TupleTableSlot, allocate the cache
-		 * workspace.  It must have the same lifetime as the slot, so allocate
-		 * it in the slot's own context.  We size the array according to what
-		 * the tupdesc says, NOT the tuple.
-		 */
-		if (slot->cache_values == NULL)
-			slot->cache_values = (Datum *)
-				MemoryContextAlloc(slot->ttc_mcxt,
-								   tupleDesc->natts * sizeof(Datum));
+		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;
 
-		slot_deformtuple(slot, attnum);
+	/*
+	 * system attributes are handled by heap_attisnull
+	 */
+	if (attnum <= 0)
+	{
+		if (tuple == NULL)		/* internal error */
+			elog(ERROR, "cannot extract system attribute from virtual tuple");
+		return heap_attisnull(tuple, attnum);
 	}
 
 	/*
-	 * The result is acquired from cache_values array.
+	 * fast path if desired attribute already cached
 	 */
-	*isnull = false;
-	return slot->cache_values[attnum - 1];
+	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);
 }
 
 /* ----------------
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index 3ce44009b919e1b8dd56a8534dabdc574b211445..6f44533822baa7e35a70994e9447aa24e993074a 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/common/printtup.c,v 1.86 2004/12/31 21:59:07 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/common/printtup.c,v 1.87 2005/03/16 21:38:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -25,12 +25,9 @@
 
 static void printtup_startup(DestReceiver *self, int operation,
 				 TupleDesc typeinfo);
-static void printtup(HeapTuple tuple, TupleDesc typeinfo,
-		 DestReceiver *self);
-static void printtup_20(HeapTuple tuple, TupleDesc typeinfo,
-			DestReceiver *self);
-static void printtup_internal_20(HeapTuple tuple, TupleDesc typeinfo,
-					 DestReceiver *self);
+static void printtup(TupleTableSlot *slot, DestReceiver *self);
+static void printtup_20(TupleTableSlot *slot, DestReceiver *self);
+static void printtup_internal_20(TupleTableSlot *slot, DestReceiver *self);
 static void printtup_shutdown(DestReceiver *self);
 static void printtup_destroy(DestReceiver *self);
 
@@ -65,8 +62,6 @@ typedef struct
 	TupleDesc	attrinfo;		/* The attr info we are set up for */
 	int			nattrs;
 	PrinttupAttrInfo *myinfo;	/* Cached info about each attr */
-	Datum	   *values;			/* preallocated space for deformtuple */
-	char	   *nulls;
 } DR_printtup;
 
 /* ----------------
@@ -79,7 +74,7 @@ printtup_create_DR(CommandDest dest, Portal portal)
 	DR_printtup *self = (DR_printtup *) palloc(sizeof(DR_printtup));
 
 	if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 3)
-		self->pub.receiveTuple = printtup;
+		self->pub.receiveSlot = printtup;
 	else
 	{
 		/*
@@ -88,9 +83,9 @@ printtup_create_DR(CommandDest dest, Portal portal)
 		 * sufficient to look at the first one.
 		 */
 		if (portal->formats && portal->formats[0] != 0)
-			self->pub.receiveTuple = printtup_internal_20;
+			self->pub.receiveSlot = printtup_internal_20;
 		else
-			self->pub.receiveTuple = printtup_20;
+			self->pub.receiveSlot = printtup_20;
 	}
 	self->pub.rStartup = printtup_startup;
 	self->pub.rShutdown = printtup_shutdown;
@@ -105,8 +100,6 @@ printtup_create_DR(CommandDest dest, Portal portal)
 	self->attrinfo = NULL;
 	self->nattrs = 0;
 	self->myinfo = NULL;
-	self->values = NULL;
-	self->nulls = NULL;
 
 	return (DestReceiver *) self;
 }
@@ -251,12 +244,6 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 	if (myState->myinfo)
 		pfree(myState->myinfo);
 	myState->myinfo = NULL;
-	if (myState->values)
-		pfree(myState->values);
-	myState->values = NULL;
-	if (myState->nulls)
-		pfree(myState->nulls);
-	myState->nulls = NULL;
 
 	myState->attrinfo = typeinfo;
 	myState->nattrs = numAttrs;
@@ -265,8 +252,6 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 
 	myState->myinfo = (PrinttupAttrInfo *)
 		palloc0(numAttrs * sizeof(PrinttupAttrInfo));
-	myState->values = (Datum *) palloc(numAttrs * sizeof(Datum));
-	myState->nulls = (char *) palloc(numAttrs * sizeof(char));
 
 	for (i = 0; i < numAttrs; i++)
 	{
@@ -302,8 +287,9 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
  * ----------------
  */
 static void
-printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
+printtup(TupleTableSlot *slot, DestReceiver *self)
 {
+	TupleDesc typeinfo = slot->tts_tupleDescriptor;
 	DR_printtup *myState = (DR_printtup *) self;
 	StringInfoData buf;
 	int			natts = typeinfo->natts;
@@ -313,10 +299,8 @@ printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	if (myState->attrinfo != typeinfo || myState->nattrs != natts)
 		printtup_prepare_info(myState, typeinfo, natts);
 
-	/*
-	 * deconstruct the tuple (faster than a heap_getattr loop)
-	 */
-	heap_deformtuple(tuple, typeinfo, myState->values, myState->nulls);
+	/* Make sure the tuple is fully deconstructed */
+	slot_getallattrs(slot);
 
 	/*
 	 * Prepare a DataRow message
@@ -331,10 +315,10 @@ printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	for (i = 0; i < natts; ++i)
 	{
 		PrinttupAttrInfo *thisState = myState->myinfo + i;
-		Datum		origattr = myState->values[i],
+		Datum		origattr = slot->tts_values[i],
 					attr;
 
-		if (myState->nulls[i] == 'n')
+		if (slot->tts_isnull[i])
 		{
 			pq_sendint(&buf, -1, 4);
 			continue;
@@ -389,8 +373,9 @@ printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
  * ----------------
  */
 static void
-printtup_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
+printtup_20(TupleTableSlot *slot, DestReceiver *self)
 {
+	TupleDesc typeinfo = slot->tts_tupleDescriptor;
 	DR_printtup *myState = (DR_printtup *) self;
 	StringInfoData buf;
 	int			natts = typeinfo->natts;
@@ -402,10 +387,8 @@ printtup_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	if (myState->attrinfo != typeinfo || myState->nattrs != natts)
 		printtup_prepare_info(myState, typeinfo, natts);
 
-	/*
-	 * deconstruct the tuple (faster than a heap_getattr loop)
-	 */
-	heap_deformtuple(tuple, typeinfo, myState->values, myState->nulls);
+	/* Make sure the tuple is fully deconstructed */
+	slot_getallattrs(slot);
 
 	/*
 	 * tell the frontend to expect new tuple data (in ASCII style)
@@ -419,7 +402,7 @@ printtup_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	k = 1 << 7;
 	for (i = 0; i < natts; ++i)
 	{
-		if (myState->nulls[i] != 'n')
+		if (slot->tts_isnull[i])
 			j |= k;				/* set bit if not null */
 		k >>= 1;
 		if (k == 0)				/* end of byte? */
@@ -438,11 +421,11 @@ printtup_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	for (i = 0; i < natts; ++i)
 	{
 		PrinttupAttrInfo *thisState = myState->myinfo + i;
-		Datum		origattr = myState->values[i],
+		Datum		origattr = slot->tts_values[i],
 					attr;
 		char	   *outputstr;
 
-		if (myState->nulls[i] == 'n')
+		if (slot->tts_isnull[i])
 			continue;
 
 		Assert(thisState->format == 0);
@@ -483,12 +466,6 @@ printtup_shutdown(DestReceiver *self)
 	if (myState->myinfo)
 		pfree(myState->myinfo);
 	myState->myinfo = NULL;
-	if (myState->values)
-		pfree(myState->values);
-	myState->values = NULL;
-	if (myState->nulls)
-		pfree(myState->nulls);
-	myState->nulls = NULL;
 
 	myState->attrinfo = NULL;
 }
@@ -548,8 +525,9 @@ debugStartup(DestReceiver *self, int operation, TupleDesc typeinfo)
  * ----------------
  */
 void
-debugtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
+debugtup(TupleTableSlot *slot, DestReceiver *self)
 {
+	TupleDesc	typeinfo = slot->tts_tupleDescriptor;
 	int			natts = typeinfo->natts;
 	int			i;
 	Datum		origattr,
@@ -562,7 +540,7 @@ debugtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 
 	for (i = 0; i < natts; ++i)
 	{
-		origattr = heap_getattr(tuple, i + 1, typeinfo, &isnull);
+		origattr = slot_getattr(slot, i + 1, &isnull);
 		if (isnull)
 			continue;
 		getTypeOutputInfo(typeinfo->attrs[i]->atttypid,
@@ -603,8 +581,9 @@ debugtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
  * ----------------
  */
 static void
-printtup_internal_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
+printtup_internal_20(TupleTableSlot *slot, DestReceiver *self)
 {
+	TupleDesc typeinfo = slot->tts_tupleDescriptor;
 	DR_printtup *myState = (DR_printtup *) self;
 	StringInfoData buf;
 	int			natts = typeinfo->natts;
@@ -616,10 +595,8 @@ printtup_internal_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	if (myState->attrinfo != typeinfo || myState->nattrs != natts)
 		printtup_prepare_info(myState, typeinfo, natts);
 
-	/*
-	 * deconstruct the tuple (faster than a heap_getattr loop)
-	 */
-	heap_deformtuple(tuple, typeinfo, myState->values, myState->nulls);
+	/* Make sure the tuple is fully deconstructed */
+	slot_getallattrs(slot);
 
 	/*
 	 * tell the frontend to expect new tuple data (in binary style)
@@ -633,7 +610,7 @@ printtup_internal_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	k = 1 << 7;
 	for (i = 0; i < natts; ++i)
 	{
-		if (myState->nulls[i] != 'n')
+		if (slot->tts_isnull[i])
 			j |= k;				/* set bit if not null */
 		k >>= 1;
 		if (k == 0)				/* end of byte? */
@@ -652,11 +629,11 @@ printtup_internal_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	for (i = 0; i < natts; ++i)
 	{
 		PrinttupAttrInfo *thisState = myState->myinfo + i;
-		Datum		origattr = myState->values[i],
+		Datum		origattr = slot->tts_values[i],
 					attr;
 		bytea	   *outputbytes;
 
-		if (myState->nulls[i] == 'n')
+		if (slot->tts_isnull[i])
 			continue;
 
 		Assert(thisState->format == 1);
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index a3bcca272e373f4f7740ec9813b45fd72a2ab1d5..48c8fb072f23ed0e2d58edff40b3d96dfbc9b063 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.246 2005/03/07 04:42:16 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/index.c,v 1.247 2005/03/16 21:38:04 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -894,8 +894,7 @@ BuildIndexInfo(Relation index)
  *			Construct Datum[] and nullv[] arrays for a new index tuple.
  *
  *	indexInfo		Info about the index
- *	heapTuple		Heap tuple for which we must prepare an index entry
- *	heapDescriptor	tupledesc for heap tuple
+ *	slot			Heap tuple for which we must prepare an index entry
  *	estate			executor state for evaluating any index expressions
  *	datum			Array of index Datums (output area)
  *	nullv			Array of is-null indicators (output area)
@@ -910,8 +909,7 @@ BuildIndexInfo(Relation index)
  */
 void
 FormIndexDatum(IndexInfo *indexInfo,
-			   HeapTuple heapTuple,
-			   TupleDesc heapDescriptor,
+			   TupleTableSlot *slot,
 			   EState *estate,
 			   Datum *datum,
 			   char *nullv)
@@ -927,7 +925,7 @@ FormIndexDatum(IndexInfo *indexInfo,
 			ExecPrepareExpr((Expr *) indexInfo->ii_Expressions,
 							estate);
 		/* Check caller has set up context correctly */
-		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple->val == heapTuple);
+		Assert(GetPerTupleExprContext(estate)->ecxt_scantuple == slot);
 	}
 	indexpr_item = list_head(indexInfo->ii_ExpressionsState);
 
@@ -943,7 +941,7 @@ FormIndexDatum(IndexInfo *indexInfo,
 			 * Plain index column; get the value we need directly from the
 			 * heap tuple.
 			 */
-			iDatum = heap_getattr(heapTuple, keycol, heapDescriptor, &isNull);
+			iDatum = slot_getattr(slot, keycol, &isNull);
 		}
 		else
 		{
@@ -1336,12 +1334,10 @@ IndexBuildHeapScan(Relation heapRelation,
 {
 	HeapScanDesc scan;
 	HeapTuple	heapTuple;
-	TupleDesc	heapDescriptor;
 	Datum		attdata[INDEX_MAX_KEYS];
 	char		nulls[INDEX_MAX_KEYS];
 	double		reltuples;
 	List	   *predicate;
-	TupleTable	tupleTable;
 	TupleTableSlot *slot;
 	EState	   *estate;
 	ExprContext *econtext;
@@ -1353,41 +1349,21 @@ IndexBuildHeapScan(Relation heapRelation,
 	 */
 	Assert(OidIsValid(indexRelation->rd_rel->relam));
 
-	heapDescriptor = RelationGetDescr(heapRelation);
-
 	/*
 	 * Need an EState for evaluation of index expressions and
-	 * partial-index predicates.
+	 * partial-index predicates.  Also a slot to hold the current tuple.
 	 */
 	estate = CreateExecutorState();
 	econtext = GetPerTupleExprContext(estate);
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation));
 
-	/*
-	 * If this is a predicate (partial) index, we will need to evaluate
-	 * the predicate using ExecQual, which requires the current tuple to
-	 * be in a slot of a TupleTable.  Likewise if there are any
-	 * expressions.
-	 */
-	if (indexInfo->ii_Predicate != NIL || indexInfo->ii_Expressions != NIL)
-	{
-		tupleTable = ExecCreateTupleTable(1);
-		slot = ExecAllocTableSlot(tupleTable);
-		ExecSetSlotDescriptor(slot, heapDescriptor, false);
-
-		/* Arrange for econtext's scan tuple to be the tuple under test */
-		econtext->ecxt_scantuple = slot;
+	/* Arrange for econtext's scan tuple to be the tuple under test */
+	econtext->ecxt_scantuple = slot;
 
-		/* Set up execution state for predicate. */
-		predicate = (List *)
-			ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
-							estate);
-	}
-	else
-	{
-		tupleTable = NULL;
-		slot = NULL;
-		predicate = NIL;
-	}
+	/* Set up execution state for predicate, if any. */
+	predicate = (List *)
+		ExecPrepareExpr((Expr *) indexInfo->ii_Predicate,
+						estate);
 
 	/*
 	 * Ok, begin our scan of the base relation.  We use SnapshotAny
@@ -1511,8 +1487,7 @@ IndexBuildHeapScan(Relation heapRelation,
 		MemoryContextReset(econtext->ecxt_per_tuple_memory);
 
 		/* Set up for predicate or expression evaluation */
-		if (slot)
-			ExecStoreTuple(heapTuple, slot, InvalidBuffer, false);
+		ExecStoreTuple(heapTuple, slot, InvalidBuffer, false);
 
 		/*
 		 * In a partial index, discard tuples that don't satisfy the
@@ -1534,8 +1509,7 @@ IndexBuildHeapScan(Relation heapRelation,
 		 * evaluation of any expressions needed.
 		 */
 		FormIndexDatum(indexInfo,
-					   heapTuple,
-					   heapDescriptor,
+					   slot,
 					   estate,
 					   attdata,
 					   nulls);
@@ -1553,8 +1527,7 @@ IndexBuildHeapScan(Relation heapRelation,
 
 	heap_endscan(scan);
 
-	if (tupleTable)
-		ExecDropTupleTable(tupleTable, true);
+	ExecDropSingleTupleTableSlot(slot);
 
 	FreeExecutorState(estate);
 
diff --git a/src/backend/catalog/indexing.c b/src/backend/catalog/indexing.c
index 00365cc3d48a5869b40554889b14475b05fd017d..90ffbe828a73bd06cbd304dd65895d33d5a2c273 100644
--- a/src/backend/catalog/indexing.c
+++ b/src/backend/catalog/indexing.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/catalog/indexing.c,v 1.107 2004/12/31 21:59:38 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/catalog/indexing.c,v 1.108 2005/03/16 21:38:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -73,19 +73,24 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 	int			numIndexes;
 	RelationPtr relationDescs;
 	Relation	heapRelation;
-	TupleDesc	heapDescriptor;
+	TupleTableSlot *slot;
 	IndexInfo **indexInfoArray;
 	Datum		datum[INDEX_MAX_KEYS];
 	char		nullv[INDEX_MAX_KEYS];
 
 	/*
-	 * Get information from the state structure.
+	 * Get information from the state structure.  Fall out if nothing to do.
 	 */
 	numIndexes = indstate->ri_NumIndices;
+	if (numIndexes == 0)
+		return;
 	relationDescs = indstate->ri_IndexRelationDescs;
 	indexInfoArray = indstate->ri_IndexRelationInfo;
 	heapRelation = indstate->ri_RelationDesc;
-	heapDescriptor = RelationGetDescr(heapRelation);
+
+	/* Need a slot to hold the tuple being examined */
+	slot = MakeSingleTupleTableSlot(RelationGetDescr(heapRelation));
+	ExecStoreTuple(heapTuple, slot, InvalidBuffer, false);
 
 	/*
 	 * for each index, form and insert the index tuple
@@ -106,11 +111,10 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 
 		/*
 		 * FormIndexDatum fills in its datum and null parameters with
-		 * attribute information taken from the given heap tuple.
+		 * attribute information taken from the given tuple.
 		 */
 		FormIndexDatum(indexInfo,
-					   heapTuple,
-					   heapDescriptor,
+					   slot,
 					   NULL,	/* no expression eval to do */
 					   datum,
 					   nullv);
@@ -128,6 +132,8 @@ CatalogIndexInsert(CatalogIndexState indstate, HeapTuple heapTuple)
 		if (result)
 			pfree(result);
 	}
+
+	ExecDropSingleTupleTableSlot(slot);
 }
 
 /*
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index ff20cf969671b7d00737ae1534dd7fc46e9a1609..b757512b4632def51cd95245597fade97923f19f 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.82 2005/02/11 00:41:12 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.83 2005/03/16 21:38:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -448,14 +448,11 @@ compute_index_stats(Relation onerel, double totalrows,
 {
 	MemoryContext ind_context,
 				old_context;
-	TupleDesc	heapDescriptor;
 	Datum		attdata[INDEX_MAX_KEYS];
 	char		nulls[INDEX_MAX_KEYS];
 	int			ind,
 				i;
 
-	heapDescriptor = RelationGetDescr(onerel);
-
 	ind_context = AllocSetContextCreate(anl_context,
 										"Analyze Index",
 										ALLOCSET_DEFAULT_MINSIZE,
@@ -468,7 +465,6 @@ compute_index_stats(Relation onerel, double totalrows,
 		AnlIndexData *thisdata = &indexdata[ind];
 		IndexInfo  *indexInfo = thisdata->indexInfo;
 		int			attr_cnt = thisdata->attr_cnt;
-		TupleTable	tupleTable;
 		TupleTableSlot *slot;
 		EState	   *estate;
 		ExprContext *econtext;
@@ -492,9 +488,7 @@ compute_index_stats(Relation onerel, double totalrows,
 		estate = CreateExecutorState();
 		econtext = GetPerTupleExprContext(estate);
 		/* Need a slot to hold the current heap tuple, too */
-		tupleTable = ExecCreateTupleTable(1);
-		slot = ExecAllocTableSlot(tupleTable);
-		ExecSetSlotDescriptor(slot, heapDescriptor, false);
+		slot = MakeSingleTupleTableSlot(RelationGetDescr(onerel));
 
 		/* Arrange for econtext's scan tuple to be the tuple under test */
 		econtext->ecxt_scantuple = slot;
@@ -532,8 +526,7 @@ compute_index_stats(Relation onerel, double totalrows,
 				 * convenient.
 				 */
 				FormIndexDatum(indexInfo,
-							   heapTuple,
-							   heapDescriptor,
+							   slot,
 							   estate,
 							   attdata,
 							   nulls);
@@ -585,7 +578,7 @@ compute_index_stats(Relation onerel, double totalrows,
 		/* And clean up */
 		MemoryContextSwitchTo(ind_context);
 
-		ExecDropTupleTable(tupleTable, true);
+		ExecDropSingleTupleTableSlot(slot);
 		FreeExecutorState(estate);
 		MemoryContextResetAndDeleteChildren(ind_context);
 	}
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index ed815098aba7577ce2b29286bf108f8e40bc3a12..d193d1dd3118a3a62f445304d898cdf86b232dc4 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.237 2005/03/12 05:41:34 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/copy.c,v 1.238 2005/03/16 21:38:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1487,7 +1487,6 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
 	bool		isnull;
 	ResultRelInfo *resultRelInfo;
 	EState	   *estate = CreateExecutorState(); /* for ExecConstraints() */
-	TupleTable	tupleTable;
 	TupleTableSlot *slot;
 	bool		file_has_oids;
 	int		   *defmap;
@@ -1518,10 +1517,8 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
 	estate->es_num_result_relations = 1;
 	estate->es_result_relation_info = resultRelInfo;
 
-	/* Set up a dummy tuple table too */
-	tupleTable = ExecCreateTupleTable(1);
-	slot = ExecAllocTableSlot(tupleTable);
-	ExecSetSlotDescriptor(slot, tupDesc, false);
+	/* Set up a tuple slot too */
+	slot = MakeSingleTupleTableSlot(tupDesc);
 
 	econtext = GetPerTupleExprContext(estate);
 
@@ -1989,7 +1986,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
 	pfree(constraintexprs);
 	pfree(force_notnull);
 
-	ExecDropTupleTable(tupleTable, true);
+	ExecDropSingleTupleTableSlot(slot);
 
 	ExecCloseIndices(resultRelInfo);
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index fddfd13c71671e1318a5cf69016e0558d5b8a5df..819ac84e1e8e0f2ab7a30cb7166afa066924e77a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.146 2005/02/09 23:17:26 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.147 2005/03/16 21:38:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2455,7 +2455,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
 	{
 		ExprContext *econtext;
 		Datum	   *values;
-		char	   *nulls;
+		bool	   *isnull;
 		TupleTableSlot *oldslot;
 		TupleTableSlot *newslot;
 		HeapScanDesc scan;
@@ -2471,17 +2471,15 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
 		 * the tuples are the same, the tupDescs might not be (consider
 		 * ADD COLUMN without a default).
 		 */
-		oldslot = MakeTupleTableSlot();
-		ExecSetSlotDescriptor(oldslot, oldTupDesc, false);
-		newslot = MakeTupleTableSlot();
-		ExecSetSlotDescriptor(newslot, newTupDesc, false);
+		oldslot = MakeSingleTupleTableSlot(oldTupDesc);
+		newslot = MakeSingleTupleTableSlot(newTupDesc);
 
-		/* Preallocate values/nulls arrays */
+		/* Preallocate values/isnull arrays */
 		i = Max(newTupDesc->natts, oldTupDesc->natts);
 		values = (Datum *) palloc(i * sizeof(Datum));
-		nulls = (char *) palloc(i * sizeof(char));
+		isnull = (bool *) palloc(i * sizeof(bool));
 		memset(values, 0, i * sizeof(Datum));
-		memset(nulls, 'n', i * sizeof(char));
+		memset(isnull, true, i * sizeof(bool));
 
 		/*
 		 * Any attributes that are dropped according to the new tuple
@@ -2512,11 +2510,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
 			if (newrel)
 			{
 				/* Extract data from old tuple */
-				heap_deformtuple(tuple, oldTupDesc, values, nulls);
+				heap_deform_tuple(tuple, oldTupDesc, values, isnull);
 
 				/* Set dropped attributes to null in new tuple */
 				foreach (lc, dropped_attrs)
-					nulls[lfirst_int(lc)] = 'n';
+					isnull[lfirst_int(lc)] = true;
 
 				/*
 				 * Process supplied expressions to replace selected
@@ -2528,16 +2526,11 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
 				foreach(l, tab->newvals)
 				{
 					NewColumnValue *ex = lfirst(l);
-					bool		isNull;
 
 					values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate,
 														  econtext,
-														  &isNull,
+														  &isnull[ex->attnum - 1],
 														  NULL);
-					if (isNull)
-						nulls[ex->attnum - 1] = 'n';
-					else
-						nulls[ex->attnum - 1] = ' ';
 				}
 
 				/*
@@ -2545,7 +2538,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
 				 * pfree it, since the per-tuple memory context will
 				 * be reset shortly.
 				 */
-				tuple = heap_formtuple(newTupDesc, values, nulls);
+				tuple = heap_form_tuple(newTupDesc, values, isnull);
 			}
 
 			/* Now check any constraints on the possibly-changed tuple */
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 64a7f92560c5df0e85a864fbdddea02cd27984cc..a0ee3e8f355459743a2506bc45f9a2a796f01197 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -13,7 +13,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.303 2005/03/04 20:21:06 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.304 2005/03/16 21:38:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -114,7 +114,6 @@ typedef struct ExecContextData
 {
 	ResultRelInfo *resultRelInfo;
 	EState	   *estate;
-	TupleTable	tupleTable;
 	TupleTableSlot *slot;
 } ExecContextData;
 typedef ExecContextData *ExecContext;
@@ -141,16 +140,14 @@ ExecContext_Init(ExecContext ec, Relation rel)
 	ec->estate->es_num_result_relations = 1;
 	ec->estate->es_result_relation_info = ec->resultRelInfo;
 
-	/* Set up a dummy tuple table too */
-	ec->tupleTable = ExecCreateTupleTable(1);
-	ec->slot = ExecAllocTableSlot(ec->tupleTable);
-	ExecSetSlotDescriptor(ec->slot, tupdesc, false);
+	/* Set up a tuple slot too */
+	ec->slot = MakeSingleTupleTableSlot(tupdesc);
 }
 
 static void
 ExecContext_Finish(ExecContext ec)
 {
-	ExecDropTupleTable(ec->tupleTable, true);
+	ExecDropSingleTupleTableSlot(ec->slot);
 	ExecCloseIndices(ec->resultRelInfo);
 	FreeExecutorState(ec->estate);
 }
diff --git a/src/backend/executor/execGrouping.c b/src/backend/executor/execGrouping.c
index 1d6364a4152b31209d787108a1160118ea813aab..37db2bcd2f66759a9e55df570a745aded415c155 100644
--- a/src/backend/executor/execGrouping.c
+++ b/src/backend/executor/execGrouping.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execGrouping.c,v 1.13 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execGrouping.c,v 1.14 2005/03/16 21:38:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -41,8 +41,7 @@ static int TupleHashTableMatch(const void *key1, const void *key2,
  * This actually implements SQL's notion of "not distinct".  Two nulls
  * match, a null and a not-null don't match.
  *
- * tuple1, tuple2: the tuples to compare
- * tupdesc: tuple descriptor applying to both tuples
+ * slot1, slot2: the tuples to compare (must have same columns!)
  * numCols: the number of attributes to be examined
  * matchColIdx: array of attribute column numbers
  * eqFunctions: array of fmgr lookup info for the equality functions to use
@@ -51,9 +50,8 @@ static int TupleHashTableMatch(const void *key1, const void *key2,
  * NB: evalContext is reset each time!
  */
 bool
-execTuplesMatch(HeapTuple tuple1,
-				HeapTuple tuple2,
-				TupleDesc tupdesc,
+execTuplesMatch(TupleTableSlot *slot1,
+				TupleTableSlot *slot2,
 				int numCols,
 				AttrNumber *matchColIdx,
 				FmgrInfo *eqfunctions,
@@ -84,15 +82,9 @@ execTuplesMatch(HeapTuple tuple1,
 		bool		isNull1,
 					isNull2;
 
-		attr1 = heap_getattr(tuple1,
-							 att,
-							 tupdesc,
-							 &isNull1);
+		attr1 = slot_getattr(slot1, att, &isNull1);
 
-		attr2 = heap_getattr(tuple2,
-							 att,
-							 tupdesc,
-							 &isNull2);
+		attr2 = slot_getattr(slot2, att, &isNull2);
 
 		if (isNull1 != isNull2)
 		{
@@ -129,9 +121,8 @@ execTuplesMatch(HeapTuple tuple1,
  * Parameters are identical to execTuplesMatch.
  */
 bool
-execTuplesUnequal(HeapTuple tuple1,
-				  HeapTuple tuple2,
-				  TupleDesc tupdesc,
+execTuplesUnequal(TupleTableSlot *slot1,
+				  TupleTableSlot *slot2,
 				  int numCols,
 				  AttrNumber *matchColIdx,
 				  FmgrInfo *eqfunctions,
@@ -162,18 +153,12 @@ execTuplesUnequal(HeapTuple tuple1,
 		bool		isNull1,
 					isNull2;
 
-		attr1 = heap_getattr(tuple1,
-							 att,
-							 tupdesc,
-							 &isNull1);
+		attr1 = slot_getattr(slot1, att, &isNull1);
 
 		if (isNull1)
 			continue;			/* can't prove anything here */
 
-		attr2 = heap_getattr(tuple2,
-							 att,
-							 tupdesc,
-							 &isNull2);
+		attr2 = slot_getattr(slot2, att, &isNull2);
 
 		if (isNull2)
 			continue;			/* can't prove anything here */
@@ -312,6 +297,8 @@ BuildTupleHashTable(int numCols, AttrNumber *keyColIdx,
 	hashtable->tablecxt = tablecxt;
 	hashtable->tempcxt = tempcxt;
 	hashtable->entrysize = entrysize;
+	hashtable->tableslot = NULL;		/* will be made on first lookup */
+	hashtable->inputslot = NULL;
 
 	MemSet(&hash_ctl, 0, sizeof(hash_ctl));
 	hash_ctl.keysize = sizeof(TupleHashEntryData);
@@ -342,13 +329,27 @@ TupleHashEntry
 LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot *slot,
 					 bool *isnew)
 {
-	HeapTuple	tuple = slot->val;
-	TupleDesc	tupdesc = slot->ttc_tupleDescriptor;
 	TupleHashEntry entry;
 	MemoryContext oldContext;
 	TupleHashTable saveCurHT;
+	TupleHashEntryData dummy;
 	bool		found;
 
+	/* If first time through, clone the input slot to make table slot */
+	if (hashtable->tableslot == NULL)
+	{
+		TupleDesc	tupdesc;
+
+		oldContext = MemoryContextSwitchTo(hashtable->tablecxt);
+		/*
+		 * We copy the input tuple descriptor just for safety --- we assume
+		 * all input tuples will have equivalent descriptors.
+		 */
+		tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor);
+		hashtable->tableslot = MakeSingleTupleTableSlot(tupdesc);
+		MemoryContextSwitchTo(oldContext);
+	}
+
 	/* Need to run the hash functions in short-lived context */
 	oldContext = MemoryContextSwitchTo(hashtable->tempcxt);
 
@@ -358,13 +359,14 @@ LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot *slot,
 	 * We save and restore CurTupleHashTable just in case someone manages to
 	 * invoke this code re-entrantly.
 	 */
-	hashtable->tupdesc = tupdesc;
+	hashtable->inputslot = slot;
 	saveCurHT = CurTupleHashTable;
 	CurTupleHashTable = hashtable;
 
 	/* Search the hash table */
+	dummy.firstTuple = NULL;	/* flag to reference inputslot */
 	entry = (TupleHashEntry) hash_search(hashtable->hashtab,
-										 &tuple,
+										 &dummy,
 										 isnew ? HASH_ENTER : HASH_FIND,
 										 &found);
 
@@ -392,7 +394,7 @@ LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot *slot,
 
 			/* Copy the first tuple into the table context */
 			MemoryContextSwitchTo(hashtable->tablecxt);
-			entry->firstTuple = heap_copytuple(tuple);
+			entry->firstTuple = ExecCopySlotTuple(slot);
 
 			*isnew = true;
 		}
@@ -408,9 +410,12 @@ LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot *slot,
 /*
  * Compute the hash value for a tuple
  *
- * The passed-in key is a pointer to a HeapTuple pointer -- this is either
- * the firstTuple field of a TupleHashEntry struct, or the key value passed
- * to hash_search.	We ignore the keysize.
+ * The passed-in key is a pointer to TupleHashEntryData.  In an actual
+ * hash table entry, the firstTuple field therein points to a physical
+ * tuple.  LookupTupleHashEntry sets up a dummy TupleHashEntryData with
+ * a NULL firstTuple field --- that cues us to look at the inputslot instead.
+ * This convention avoids the need to materialize virtual input tuples
+ * unless they actually need to get copied into the table.
  *
  * CurTupleHashTable must be set before calling this, since dynahash.c
  * doesn't provide any API that would let us get at the hashtable otherwise.
@@ -421,14 +426,27 @@ LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot *slot,
 static uint32
 TupleHashTableHash(const void *key, Size keysize)
 {
-	HeapTuple	tuple = *(const HeapTuple *) key;
+	HeapTuple	tuple = ((const TupleHashEntryData *) key)->firstTuple;
+	TupleTableSlot *slot;
 	TupleHashTable hashtable = CurTupleHashTable;
 	int			numCols = hashtable->numCols;
 	AttrNumber *keyColIdx = hashtable->keyColIdx;
-	TupleDesc	tupdesc = hashtable->tupdesc;
 	uint32		hashkey = 0;
 	int			i;
 
+	if (tuple == NULL)
+	{
+		/* Process the current input tuple for the table */
+		slot = hashtable->inputslot;
+	}
+	else
+	{
+		/* Process a tuple already stored in the table */
+		/* (this case never actually occurs in current dynahash.c code) */
+		slot = hashtable->tableslot;
+		ExecStoreTuple(tuple, slot, InvalidBuffer, false);
+	}
+
 	for (i = 0; i < numCols; i++)
 	{
 		AttrNumber	att = keyColIdx[i];
@@ -438,7 +456,7 @@ TupleHashTableHash(const void *key, Size keysize)
 		/* rotate hashkey left 1 bit at each step */
 		hashkey = (hashkey << 1) | ((hashkey & 0x80000000) ? 1 : 0);
 
-		attr = heap_getattr(tuple, att, tupdesc, &isNull);
+		attr = slot_getattr(slot, att, &isNull);
 
 		if (!isNull)			/* treat nulls as having hash key 0 */
 		{
@@ -456,7 +474,7 @@ TupleHashTableHash(const void *key, Size keysize)
 /*
  * See whether two tuples (presumably of the same hash value) match
  *
- * As above, the passed pointers are pointers to HeapTuple pointers.
+ * As above, the passed pointers are pointers to TupleHashEntryData.
  *
  * CurTupleHashTable must be set before calling this, since dynahash.c
  * doesn't provide any API that would let us get at the hashtable otherwise.
@@ -467,13 +485,28 @@ TupleHashTableHash(const void *key, Size keysize)
 static int
 TupleHashTableMatch(const void *key1, const void *key2, Size keysize)
 {
-	HeapTuple	tuple1 = *(const HeapTuple *) key1;
-	HeapTuple	tuple2 = *(const HeapTuple *) key2;
+	HeapTuple	tuple1 = ((const TupleHashEntryData *) key1)->firstTuple;
+#ifdef USE_ASSERT_CHECKING
+	HeapTuple	tuple2 = ((const TupleHashEntryData *) key2)->firstTuple;
+#endif
+	TupleTableSlot *slot1;
+	TupleTableSlot *slot2;
 	TupleHashTable hashtable = CurTupleHashTable;
 
-	if (execTuplesMatch(tuple1,
-						tuple2,
-						hashtable->tupdesc,
+	/*
+	 * We assume that dynahash.c will only ever call us with the first
+	 * argument being an actual table entry, and the second argument being
+	 * LookupTupleHashEntry's dummy TupleHashEntryData.  The other direction
+	 * could be supported too, but is not currently used by dynahash.c.
+	 */
+	Assert(tuple1 != NULL);
+	slot1 = hashtable->tableslot;
+	ExecStoreTuple(tuple1, slot1, InvalidBuffer, false);
+	Assert(tuple2 == NULL);
+	slot2 = hashtable->inputslot;
+
+	if (execTuplesMatch(slot1,
+						slot2,
 						hashtable->numCols,
 						hashtable->keyColIdx,
 						hashtable->eqfunctions,
diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c
index f747976acfae2dc54941723b268a57d8fb46c10a..2dfd90b51fa7ddfab3a8365b580fdd785d276530 100644
--- a/src/backend/executor/execJunk.c
+++ b/src/backend/executor/execJunk.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execJunk.c,v 1.47 2005/03/14 04:41:12 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execJunk.c,v 1.48 2005/03/16 21:38:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,10 +42,10 @@
  * We then execute the plan ignoring the "resjunk" attributes.
  *
  * Finally, when at the top level we get back a tuple, we can call
- * 'ExecGetJunkAttribute' to retrieve the value of the junk attributes we
- * are interested in, and 'ExecRemoveJunk' to remove all the junk attributes
- * from a tuple. This new "clean" tuple is then printed, replaced, deleted
- * or inserted.
+ * ExecGetJunkAttribute to retrieve the value of the junk attributes we
+ * are interested in, and ExecFilterJunk or ExecRemoveJunk to remove all
+ * the junk attributes from a tuple. This new "clean" tuple is then printed,
+ * replaced, deleted or inserted.
  *
  *-------------------------------------------------------------------------
  */
@@ -75,6 +75,14 @@ ExecInitJunkFilter(List *targetList, bool hasoid, TupleTableSlot *slot)
 	 */
 	cleanTupType = ExecCleanTypeFromTL(targetList, hasoid);
 
+	/*
+	 * Use the given slot, or make a new slot if we weren't given one.
+	 */
+	if (slot)
+		ExecSetSlotDescriptor(slot, cleanTupType, false);
+	else
+		slot = MakeSingleTupleTableSlot(cleanTupType);
+
 	/*
 	 * Now calculate the mapping between the original tuple's attributes and
 	 * the "clean" tuple's attributes.
@@ -115,9 +123,6 @@ ExecInitJunkFilter(List *targetList, bool hasoid, TupleTableSlot *slot)
 	junkfilter->jf_cleanMap = cleanMap;
 	junkfilter->jf_resultSlot = slot;
 
-	if (slot)
-		ExecSetSlotDescriptor(slot, cleanTupType, false);
-
 	return junkfilter;
 }
 
@@ -142,6 +147,14 @@ ExecInitJunkFilterConversion(List *targetList,
 	ListCell   *t;
 	int			i;
 
+	/*
+	 * Use the given slot, or make a new slot if we weren't given one.
+	 */
+	if (slot)
+		ExecSetSlotDescriptor(slot, cleanTupType, false);
+	else
+		slot = MakeSingleTupleTableSlot(cleanTupType);
+
 	/*
 	 * Calculate the mapping between the original tuple's attributes and
 	 * the "clean" tuple's attributes.
@@ -188,9 +201,6 @@ ExecInitJunkFilterConversion(List *targetList,
 	junkfilter->jf_cleanMap = cleanMap;
 	junkfilter->jf_resultSlot = slot;
 
-	if (slot)
-		ExecSetSlotDescriptor(slot, cleanTupType, false);
-
 	return junkfilter;
 }
 
@@ -234,115 +244,78 @@ ExecGetJunkAttribute(JunkFilter *junkfilter,
 }
 
 /*
- * ExecRemoveJunk
+ * ExecFilterJunk
  *
- * Construct and return a tuple with all the junk attributes removed.
- *
- * Note: for historical reasons, this does not store the constructed
- * tuple into the junkfilter's resultSlot.  The caller should do that
- * if it wants to.
+ * Construct and return a slot with all the junk attributes removed.
  */
-HeapTuple
-ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot)
+TupleTableSlot *
+ExecFilterJunk(JunkFilter *junkfilter, TupleTableSlot *slot)
 {
-#define PREALLOC_SIZE	64
-	HeapTuple	tuple;
-	HeapTuple	cleanTuple;
+	TupleTableSlot *resultSlot;
 	AttrNumber *cleanMap;
 	TupleDesc	cleanTupType;
-	TupleDesc	tupType;
 	int			cleanLength;
-	int			oldLength;
 	int			i;
 	Datum	   *values;
-	char	   *nulls;
+	bool	   *isnull;
 	Datum	   *old_values;
-	char	   *old_nulls;
-	Datum		values_array[PREALLOC_SIZE];
-	Datum		old_values_array[PREALLOC_SIZE];
-	char		nulls_array[PREALLOC_SIZE];
-	char		old_nulls_array[PREALLOC_SIZE];
+	bool	   *old_isnull;
 
 	/*
-	 * get info from the slot and the junk filter
+	 * Extract all the values of the old tuple.
 	 */
-	tuple = slot->val;
-	tupType = slot->ttc_tupleDescriptor;
-	oldLength = tupType->natts + 1;			/* +1 for NULL */
+	slot_getallattrs(slot);
+	old_values = slot->tts_values;
+	old_isnull = slot->tts_isnull;
 
+	/*
+	 * get info from the junk filter
+	 */
 	cleanTupType = junkfilter->jf_cleanTupType;
 	cleanLength = cleanTupType->natts;
 	cleanMap = junkfilter->jf_cleanMap;
+	resultSlot = junkfilter->jf_resultSlot;
 
 	/*
-	 * Create the arrays that will hold the attribute values and the null
-	 * information for the old tuple and new "clean" tuple.
-	 *
-	 * Note: we use memory on the stack to optimize things when we are
-	 * dealing with a small number of attributes. for large tuples we just
-	 * use palloc.
+	 * Prepare to build a virtual result tuple.
 	 */
-	if (cleanLength > PREALLOC_SIZE)
-	{
-		values = (Datum *) palloc(cleanLength * sizeof(Datum));
-		nulls = (char *) palloc(cleanLength * sizeof(char));
-	}
-	else
-	{
-		values = values_array;
-		nulls = nulls_array;
-	}
-	if (oldLength > PREALLOC_SIZE)
-	{
-		old_values = (Datum *) palloc(oldLength * sizeof(Datum));
-		old_nulls = (char *) palloc(oldLength * sizeof(char));
-	}
-	else
-	{
-		old_values = old_values_array;
-		old_nulls = old_nulls_array;
-	}
-
-	/*
-	 * Extract all the values of the old tuple, offsetting the arrays
-	 * so that old_values[0] is NULL and old_values[1] is the first
-	 * source attribute; this exactly matches the numbering convention
-	 * in cleanMap.
-	 */
-	heap_deformtuple(tuple, tupType, old_values + 1, old_nulls + 1);
-	old_values[0] = (Datum) 0;
-	old_nulls[0] = 'n';
+	ExecClearTuple(resultSlot);
+	values = resultSlot->tts_values;
+	isnull = resultSlot->tts_isnull;
 
 	/*
-	 * Transpose into proper fields of the new tuple.
+	 * Transpose data into proper fields of the new tuple.
 	 */
 	for (i = 0; i < cleanLength; i++)
 	{
 		int			j = cleanMap[i];
 
-		values[i] = old_values[j];
-		nulls[i] = old_nulls[j];
+		if (j == 0)
+		{
+			values[i] = (Datum) 0;
+			isnull[i] = true;
+		}
+		else
+		{
+			values[i] = old_values[j - 1];
+			isnull[i] = old_isnull[j - 1];
+		}
 	}
 
 	/*
-	 * Now form the new tuple.
-	 */
-	cleanTuple = heap_formtuple(cleanTupType, values, nulls);
-
-	/*
-	 * We are done.  Free any space allocated for 'values' and 'nulls' and
-	 * return the new tuple.
+	 * And return the virtual tuple.
 	 */
-	if (values != values_array)
-	{
-		pfree(values);
-		pfree(nulls);
-	}
-	if (old_values != old_values_array)
-	{
-		pfree(old_values);
-		pfree(old_nulls);
-	}
+	return ExecStoreVirtualTuple(resultSlot);
+}
 
-	return cleanTuple;
+/*
+ * ExecRemoveJunk
+ *
+ * Convenience routine to generate a physical clean tuple,
+ * rather than just a virtual slot.
+ */
+HeapTuple
+ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot)
+{
+	return ExecCopySlotTuple(ExecFilterJunk(junkfilter, slot));
 }
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 1e7a1b5440f87b6dfa53eba3988283806015d4c2..0294063d3f50edf92f6e30933db958e68704a458 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.241 2005/01/14 17:53:33 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.242 2005/03/16 21:38:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1082,7 +1082,6 @@ lnext:	;
 		if ((junkfilter = estate->es_junkFilter) != NULL)
 		{
 			Datum		datum;
-			HeapTuple	newTuple;
 			bool		isNull;
 
 			/*
@@ -1180,13 +1179,7 @@ lnext:	;
 			 * Finally create a new "clean" tuple with all junk attributes
 			 * removed
 			 */
-			newTuple = ExecRemoveJunk(junkfilter, slot);
-
-			slot = ExecStoreTuple(newTuple,		/* tuple to store */
-								  junkfilter->jf_resultSlot,	/* dest slot */
-								  InvalidBuffer,		/* this tuple has no
-														 * buffer */
-								  true);		/* tuple should be pfreed */
+			slot = ExecFilterJunk(junkfilter, slot);
 		}
 
 		/*
@@ -1276,15 +1269,6 @@ ExecSelect(TupleTableSlot *slot,
 		   DestReceiver *dest,
 		   EState *estate)
 {
-	HeapTuple	tuple;
-	TupleDesc	attrtype;
-
-	/*
-	 * get the heap tuple out of the tuple table slot
-	 */
-	tuple = slot->val;
-	attrtype = slot->ttc_tupleDescriptor;
-
 	/*
 	 * insert the tuple into the "into relation"
 	 *
@@ -1292,15 +1276,20 @@ ExecSelect(TupleTableSlot *slot,
 	 */
 	if (estate->es_into_relation_descriptor != NULL)
 	{
+		HeapTuple	tuple;
+
+		tuple = ExecCopySlotTuple(slot);
 		heap_insert(estate->es_into_relation_descriptor, tuple,
 					estate->es_snapshot->curcid);
+		/* we know there are no indexes to update */
+		heap_freetuple(tuple);
 		IncrAppended();
 	}
 
 	/*
 	 * send the tuple to the destination
 	 */
-	(*dest->receiveTuple) (tuple, attrtype, dest);
+	(*dest->receiveSlot) (slot, dest);
 	IncrRetrieved();
 	(estate->es_processed)++;
 }
@@ -1325,9 +1314,10 @@ ExecInsert(TupleTableSlot *slot,
 	Oid			newId;
 
 	/*
-	 * get the heap tuple out of the tuple table slot
+	 * get the heap tuple out of the tuple table slot, making sure
+	 * we have a writable copy
 	 */
-	tuple = slot->val;
+	tuple = ExecMaterializeSlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -1520,9 +1510,10 @@ ExecUpdate(TupleTableSlot *slot,
 		elog(ERROR, "cannot UPDATE during bootstrap");
 
 	/*
-	 * get the heap tuple out of the tuple table slot
+	 * get the heap tuple out of the tuple table slot, making sure
+	 * we have a writable copy
 	 */
-	tuple = slot->val;
+	tuple = ExecMaterializeSlot(slot);
 
 	/*
 	 * get information on the (current) result relation
@@ -1604,10 +1595,8 @@ lreplace:;
 				if (!TupIsNull(epqslot))
 				{
 					*tupleid = ctid;
-					tuple = ExecRemoveJunk(estate->es_junkFilter, epqslot);
-					slot = ExecStoreTuple(tuple,
-									estate->es_junkFilter->jf_resultSlot,
-										  InvalidBuffer, true);
+					slot = ExecFilterJunk(estate->es_junkFilter, epqslot);
+					tuple = ExecMaterializeSlot(slot);
 					goto lreplace;
 				}
 			}
@@ -1712,7 +1701,6 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 				TupleTableSlot *slot, EState *estate)
 {
 	Relation	rel = resultRelInfo->ri_RelationDesc;
-	HeapTuple	tuple = slot->val;
 	TupleConstr *constr = rel->rd_att->constr;
 
 	Assert(constr);
@@ -1725,7 +1713,7 @@ ExecConstraints(ResultRelInfo *resultRelInfo,
 		for (attrChk = 1; attrChk <= natts; attrChk++)
 		{
 			if (rel->rd_att->attrs[attrChk - 1]->attnotnull &&
-				heap_attisnull(tuple, attrChk))
+				slot_attisnull(slot, attrChk))
 				ereport(ERROR,
 						(errcode(ERRCODE_NOT_NULL_VIOLATION),
 						 errmsg("null value in column \"%s\" violates not-null constraint",
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 87cc201a3e3cf5412c2ebd4d3a523415b880bbdf..3613d31a0561030ab5b0d97635c39529c1a3a7be 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.172 2005/03/14 04:41:12 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.173 2005/03/16 21:38:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -479,7 +479,7 @@ ExecEvalVar(ExprState *exprstate, ExprContext *econtext,
 	 */
 	if (attnum > 0)
 	{
-		TupleDesc	tuple_type = slot->ttc_tupleDescriptor;
+		TupleDesc	tuple_type = slot->tts_tupleDescriptor;
 
 		/*
 		 * This assert checks that the attnum is valid.
@@ -1333,10 +1333,7 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 			}
 			else
 			{
-				char		nullflag;
-
-				nullflag = fcinfo.isnull ? 'n' : ' ';
-				tuple = heap_formtuple(tupdesc, &result, &nullflag);
+				tuple = heap_form_tuple(tupdesc, &result, &fcinfo.isnull);
 			}
 
 			oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
@@ -1384,14 +1381,14 @@ no_function_result:
 		{
 			int			natts = expectedDesc->natts;
 			Datum	   *nulldatums;
-			char	   *nullflags;
+			bool	   *nullflags;
 			HeapTuple	tuple;
 
 			MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
 			nulldatums = (Datum *) palloc0(natts * sizeof(Datum));
-			nullflags = (char *) palloc(natts * sizeof(char));
-			memset(nullflags, 'n', natts * sizeof(char));
-			tuple = heap_formtuple(expectedDesc, nulldatums, nullflags);
+			nullflags = (bool *) palloc(natts * sizeof(bool));
+			memset(nullflags, true, natts * sizeof(bool));
+			tuple = heap_form_tuple(expectedDesc, nulldatums, nullflags);
 			MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
 			tuplestore_puttuple(tupstore, tuple);
 		}
@@ -1843,9 +1840,9 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
 	HeapTupleData tmptup;
 	AttrNumber *attrMap = cstate->attrMap;
 	Datum	   *invalues = cstate->invalues;
-	char	   *innulls = cstate->innulls;
+	bool	   *inisnull = cstate->inisnull;
 	Datum	   *outvalues = cstate->outvalues;
-	char	   *outnulls = cstate->outnulls;
+	bool	   *outisnull = cstate->outisnull;
 	int			i;
 	int			outnatts = cstate->outdesc->natts;
 
@@ -1861,7 +1858,7 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
 	Assert(HeapTupleHeaderGetTypMod(tuple) == cstate->indesc->tdtypmod);
 
 	/*
-	 * heap_deformtuple needs a HeapTuple not a bare HeapTupleHeader.
+	 * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader.
 	 */
 	tmptup.t_len = HeapTupleHeaderGetDatumLength(tuple);
 	tmptup.t_data = tuple;
@@ -1872,9 +1869,9 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
 	 * source attribute; this exactly matches the numbering convention
 	 * in attrMap.
 	 */
-	heap_deformtuple(&tmptup, cstate->indesc, invalues + 1, innulls + 1);
+	heap_deform_tuple(&tmptup, cstate->indesc, invalues + 1, inisnull + 1);
 	invalues[0] = (Datum) 0;
-	innulls[0] = 'n';
+	inisnull[0] = true;
 
 	/*
 	 * Transpose into proper fields of the new tuple.
@@ -1884,13 +1881,13 @@ ExecEvalConvertRowtype(ConvertRowtypeExprState *cstate,
 		int			j = attrMap[i];
 
 		outvalues[i] = invalues[j];
-		outnulls[i] = innulls[j];
+		outisnull[i] = inisnull[j];
 	}
 
 	/*
 	 * Now form the new tuple.
 	 */
-	result = heap_formtuple(cstate->outdesc, outvalues, outnulls);
+	result = heap_form_tuple(cstate->outdesc, outvalues, outisnull);
 
 	return HeapTupleGetDatum(result);
 }
@@ -2187,7 +2184,7 @@ ExecEvalRow(RowExprState *rstate,
 {
 	HeapTuple	tuple;
 	Datum	   *values;
-	char	   *nulls;
+	bool	   *isnull;
 	int			natts;
 	ListCell   *arg;
 	int			i;
@@ -2200,27 +2197,25 @@ ExecEvalRow(RowExprState *rstate,
 	/* Allocate workspace */
 	natts = rstate->tupdesc->natts;
 	values = (Datum *) palloc0(natts * sizeof(Datum));
-	nulls = (char *) palloc(natts * sizeof(char));
+	isnull = (bool *) palloc(natts * sizeof(bool));
 
 	/* preset to nulls in case rowtype has some later-added columns */
-	memset(nulls, 'n', natts * sizeof(char));
+	memset(isnull, true, natts * sizeof(bool));
 
 	/* Evaluate field values */
 	i = 0;
 	foreach(arg, rstate->args)
 	{
 		ExprState  *e = (ExprState *) lfirst(arg);
-		bool		eisnull;
 
-		values[i] = ExecEvalExpr(e, econtext, &eisnull, NULL);
-		nulls[i] = eisnull ? 'n' : ' ';
+		values[i] = ExecEvalExpr(e, econtext, &isnull[i], NULL);
 		i++;
 	}
 
-	tuple = heap_formtuple(rstate->tupdesc, values, nulls);
+	tuple = heap_form_tuple(rstate->tupdesc, values, isnull);
 
 	pfree(values);
-	pfree(nulls);
+	pfree(isnull);
 
 	return HeapTupleGetDatum(tuple);
 }
@@ -2628,7 +2623,7 @@ ExecEvalFieldStore(FieldStoreState *fstate,
 	Datum		tupDatum;
 	TupleDesc	tupDesc;
 	Datum	   *values;
-	char	   *nulls;
+	bool	   *isnull;
 	Datum		save_datum;
 	bool		save_isNull;
 	ListCell   *l1,
@@ -2658,12 +2653,12 @@ ExecEvalFieldStore(FieldStoreState *fstate,
 
 	/* Allocate workspace */
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
-	nulls = (char *) palloc(tupDesc->natts * sizeof(char));
+	isnull = (bool *) palloc(tupDesc->natts * sizeof(bool));
 
 	if (!*isNull)
 	{
 		/*
-		 * heap_deformtuple needs a HeapTuple not a bare HeapTupleHeader.
+		 * heap_deform_tuple needs a HeapTuple not a bare HeapTupleHeader.
 		 * We set all the fields in the struct just in case.
 		 */
 		HeapTupleHeader tuphdr;
@@ -2675,12 +2670,12 @@ ExecEvalFieldStore(FieldStoreState *fstate,
 		tmptup.t_tableOid = InvalidOid;
 		tmptup.t_data = tuphdr;
 
-		heap_deformtuple(&tmptup, tupDesc, values, nulls);
+		heap_deform_tuple(&tmptup, tupDesc, values, isnull);
 	}
 	else
 	{
 		/* Convert null input tuple into an all-nulls row */
-		memset(nulls, 'n', tupDesc->natts * sizeof(char));
+		memset(isnull, true, tupDesc->natts * sizeof(bool));
 	}
 
 	/* Result is never null */
@@ -2693,7 +2688,6 @@ ExecEvalFieldStore(FieldStoreState *fstate,
 	{
 		ExprState  *newval = (ExprState *) lfirst(l1);
 		AttrNumber	fieldnum = lfirst_int(l2);
-		bool		eisnull;
 
 		Assert(fieldnum > 0 && fieldnum <= tupDesc->natts);
 
@@ -2705,22 +2699,21 @@ ExecEvalFieldStore(FieldStoreState *fstate,
 		 * the value would be needed.
 		 */
 		econtext->caseValue_datum = values[fieldnum - 1];
-		econtext->caseValue_isNull = (nulls[fieldnum - 1] == 'n');
+		econtext->caseValue_isNull = isnull[fieldnum - 1];
 
 		values[fieldnum - 1] = ExecEvalExpr(newval,
 											econtext,
-											&eisnull,
+											&isnull[fieldnum - 1],
 											NULL);
-		nulls[fieldnum - 1] = eisnull ? 'n' : ' ';
 	}
 
 	econtext->caseValue_datum = save_datum;
 	econtext->caseValue_isNull = save_isNull;
 
-	tuple = heap_formtuple(tupDesc, values, nulls);
+	tuple = heap_form_tuple(tupDesc, values, isnull);
 
 	pfree(values);
-	pfree(nulls);
+	pfree(isnull);
 
 	return HeapTupleGetDatum(tuple);
 }
@@ -3074,10 +3067,10 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				/* preallocate workspace for Datum arrays */
 				n = cstate->indesc->natts + 1;	/* +1 for NULL */
 				cstate->invalues = (Datum *) palloc(n * sizeof(Datum));
-				cstate->innulls = (char *) palloc(n * sizeof(char));
+				cstate->inisnull = (bool *) palloc(n * sizeof(bool));
 				n = cstate->outdesc->natts;
 				cstate->outvalues = (Datum *) palloc(n * sizeof(Datum));
-				cstate->outnulls = (char *) palloc(n * sizeof(char));
+				cstate->outisnull = (bool *) palloc(n * sizeof(bool));
 				state = (ExprState *) cstate;
 			}
 			break;
@@ -3479,55 +3472,37 @@ ExecCleanTargetListLength(List *targetlist)
 	return len;
 }
 
-/* ----------------------------------------------------------------
- *		ExecTargetList
- *
+/*
+ * ExecTargetList
  *		Evaluates a targetlist with respect to the given
- *		expression context and returns a tuple.
+ *		expression context.  Returns TRUE if we were able to create
+ *		a result, FALSE if we have exhausted a set-valued expression.
  *
- * The caller must pass workspace for the values and nulls arrays
- * as well as the itemIsDone array.  This convention saves palloc'ing
- * workspace on each call, and some callers may find it useful to examine
- * the values array directly.
+ * Results are stored into the passed values and isnull arrays.
+ * The caller must provide an itemIsDone array that persists across calls.
  *
  * As with ExecEvalExpr, the caller should pass isDone = NULL if not
  * prepared to deal with sets of result tuples.  Otherwise, a return
  * of *isDone = ExprMultipleResult signifies a set element, and a return
  * of *isDone = ExprEndResult signifies end of the set of tuple.
- * ----------------------------------------------------------------
  */
-static HeapTuple
+static bool
 ExecTargetList(List *targetlist,
-			   TupleDesc targettype,
 			   ExprContext *econtext,
 			   Datum *values,
-			   char *nulls,
+			   bool *isnull,
 			   ExprDoneCond *itemIsDone,
 			   ExprDoneCond *isDone)
 {
 	MemoryContext oldContext;
 	ListCell   *tl;
-	bool		isNull;
 	bool		haveDoneSets;
 
-	/*
-	 * debugging stuff
-	 */
-	EV_printf("ExecTargetList: tl is ");
-	EV_nodeDisplay(targetlist);
-	EV_printf("\n");
-
 	/*
 	 * Run in short-lived per-tuple context while computing expressions.
 	 */
 	oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
 
-	/*
-	 * There used to be some klugy and demonstrably broken code here that
-	 * special-cased the situation where targetlist == NIL.  Now we just
-	 * fall through and return an empty-but-valid tuple.
-	 */
-
 	/*
 	 * evaluate all the expressions in the target list
 	 */
@@ -3544,9 +3519,8 @@ ExecTargetList(List *targetlist,
 
 		values[resind] = ExecEvalExpr(gstate->arg,
 									  econtext,
-									  &isNull,
+									  &isnull[resind],
 									  &itemIsDone[resind]);
-		nulls[resind] = isNull ? 'n' : ' ';
 
 		if (itemIsDone[resind] != ExprSingleResult)
 		{
@@ -3581,7 +3555,7 @@ ExecTargetList(List *targetlist,
 			 */
 			*isDone = ExprEndResult;
 			MemoryContextSwitchTo(oldContext);
-			return NULL;
+			return false;
 		}
 		else
 		{
@@ -3599,9 +3573,8 @@ ExecTargetList(List *targetlist,
 				{
 					values[resind] = ExecEvalExpr(gstate->arg,
 												  econtext,
-												  &isNull,
+												  &isnull[resind],
 												  &itemIsDone[resind]);
-					nulls[resind] = isNull ? 'n' : ' ';
 
 					if (itemIsDone[resind] == ExprEndResult)
 					{
@@ -3632,75 +3605,129 @@ ExecTargetList(List *targetlist,
 
 					while (itemIsDone[resind] == ExprMultipleResult)
 					{
-						(void) ExecEvalExpr(gstate->arg,
-											econtext,
-											&isNull,
-											&itemIsDone[resind]);
+						values[resind] = ExecEvalExpr(gstate->arg,
+													  econtext,
+													  &isnull[resind],
+													  &itemIsDone[resind]);
 					}
 				}
 
 				MemoryContextSwitchTo(oldContext);
-				return NULL;
+				return false;
 			}
 		}
 	}
 
+	/* Report success */
+	MemoryContextSwitchTo(oldContext);
+
+	return true;
+}
+
+/*
+ * ExecVariableList
+ *		Evaluates a simple-Variable-list projection.
+ *
+ * Results are stored into the passed values and isnull arrays.
+ */
+static void
+ExecVariableList(ProjectionInfo *projInfo,
+				 Datum *values,
+				 bool *isnull)
+{
+	ExprContext *econtext = projInfo->pi_exprContext;
+	int		   *varSlotOffsets = projInfo->pi_varSlotOffsets;
+	int		   *varNumbers = projInfo->pi_varNumbers;
+	int			i;
+
 	/*
-	 * form the new result tuple (in the caller's memory context!)
+	 * Force extraction of all input values that we need.
 	 */
-	MemoryContextSwitchTo(oldContext);
+	if (projInfo->pi_lastInnerVar > 0)
+		slot_getsomeattrs(econtext->ecxt_innertuple,
+						  projInfo->pi_lastInnerVar);
+	if (projInfo->pi_lastOuterVar > 0)
+		slot_getsomeattrs(econtext->ecxt_outertuple,
+						  projInfo->pi_lastOuterVar);
+	if (projInfo->pi_lastScanVar > 0)
+		slot_getsomeattrs(econtext->ecxt_scantuple,
+						  projInfo->pi_lastScanVar);
 
-	return heap_formtuple(targettype, values, nulls);
+	/*
+	 * Assign to result by direct extraction of fields from source
+	 * slots ... a mite ugly, but fast ...
+	 */
+	for (i = list_length(projInfo->pi_targetlist) - 1; i >= 0; i--)
+	{
+		char	   *slotptr = ((char *) econtext) + varSlotOffsets[i];
+		TupleTableSlot *varSlot = *((TupleTableSlot **) slotptr);
+		int			varNumber = varNumbers[i] - 1;
+
+		values[i] = varSlot->tts_values[varNumber];
+		isnull[i] = varSlot->tts_isnull[varNumber];
+	}
 }
 
-/* ----------------------------------------------------------------
- *		ExecProject
+/*
+ * ExecProject
  *
  *		projects a tuple based on projection info and stores
- *		it in the specified tuple table slot.
- *
- *		Note: someday soon the executor can be extended to eliminate
- *			  redundant projections by storing pointers to datums
- *			  in the tuple table and then passing these around when
- *			  possible.  this should make things much quicker.
- *			  -cim 6/3/91
- * ----------------------------------------------------------------
+ *		it in the previously specified tuple table slot.
+ *
+ *		Note: the result is always a virtual tuple; therefore it
+ *		may reference the contents of the exprContext's scan tuples
+ *		and/or temporary results constructed in the exprContext.
+ *		If the caller wishes the result to be valid longer than that
+ *		data will be valid, he must call ExecMaterializeSlot on the
+ *		result slot.
  */
 TupleTableSlot *
 ExecProject(ProjectionInfo *projInfo, ExprDoneCond *isDone)
 {
 	TupleTableSlot *slot;
-	TupleDesc	tupType;
-	HeapTuple	newTuple;
 
 	/*
 	 * sanity checks
 	 */
-	if (projInfo == NULL)
-		return NULL;
+	Assert(projInfo != NULL);
 
 	/*
 	 * get the projection info we want
 	 */
 	slot = projInfo->pi_slot;
-	tupType = slot->ttc_tupleDescriptor;
 
 	/*
-	 * form a new result tuple (if possible --- result can be NULL)
+	 * Clear any former contents of the result slot.  This makes it
+	 * safe for us to use the slot's Datum/isnull arrays as workspace.
+	 * (Also, we can return the slot as-is if we decide no rows can
+	 * be projected.)
 	 */
-	newTuple = ExecTargetList(projInfo->pi_targetlist,
-							  tupType,
-							  projInfo->pi_exprContext,
-							  projInfo->pi_tupValues,
-							  projInfo->pi_tupNulls,
-							  projInfo->pi_itemIsDone,
-							  isDone);
+	ExecClearTuple(slot);
 
 	/*
-	 * store the tuple in the projection slot and return the slot.
+	 * form a new result tuple (if possible); if successful, mark the result
+	 * slot as containing a valid virtual tuple
 	 */
-	return ExecStoreTuple(newTuple,		/* tuple to store */
-						  slot, /* slot to store in */
-						  InvalidBuffer,		/* tuple has no buffer */
-						  true);
+	if (projInfo->pi_isVarList)
+	{
+		/* simple Var list: this always succeeds with one result row */
+		if (isDone)
+			*isDone = ExprSingleResult;
+		ExecVariableList(projInfo,
+						 slot->tts_values,
+						 slot->tts_isnull);
+		ExecStoreVirtualTuple(slot);
+	}
+	else
+	{
+		if (ExecTargetList(projInfo->pi_targetlist,
+						   projInfo->pi_exprContext,
+						   slot->tts_values,
+						   slot->tts_isnull,
+						   projInfo->pi_itemIsDone,
+						   isDone))
+			ExecStoreVirtualTuple(slot);
+	}
+
+	return slot;
 }
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c
index f4d8eba354d3de39a71d5618629aa40bcfe10f1c..1e80fa7be06a7603475baf76b908d61d3c43559a 100644
--- a/src/backend/executor/execScan.c
+++ b/src/backend/executor/execScan.c
@@ -12,7 +12,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execScan.c,v 1.34 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execScan.c,v 1.35 2005/03/16 21:38:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -106,10 +106,7 @@ ExecScan(ScanState *node,
 		if (TupIsNull(slot))
 		{
 			if (projInfo)
-				return ExecStoreTuple(NULL,
-									  projInfo->pi_slot,
-									  InvalidBuffer,
-									  true);
+				return ExecClearTuple(projInfo->pi_slot);
 			else
 				return slot;
 		}
@@ -183,7 +180,7 @@ ExecAssignScanProjectionInfo(ScanState *node)
 	if (tlist_matches_tupdesc(&node->ps,
 							  scan->plan.targetlist,
 							  scan->scanrelid,
-							node->ss_ScanTupleSlot->ttc_tupleDescriptor))
+							  node->ss_ScanTupleSlot->tts_tupleDescriptor))
 		node->ps.ps_ProjInfo = NULL;
 	else
 		ExecAssignProjectionInfo(&node->ps);
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 8574efc95d83687169bb74dd6ba3b94d1551d358..22af86b27f54322251ab703b98373b2f953091c7 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.84 2005/03/14 04:41:12 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execTuples.c,v 1.85 2005/03/16 21:38:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -30,12 +30,13 @@
  *		ExecAllocTableSlot		- find an available slot in the table
  *
  *	 SLOT ACCESSORS
- *		ExecStoreTuple			- store a tuple in the table
- *		ExecClearTuple			- clear contents of a table slot
  *		ExecSetSlotDescriptor	- set a slot's tuple descriptor
- *
- *	 SLOT STATUS PREDICATES
- *		TupIsNull				- true when slot contains no tuple (macro)
+ *		ExecStoreTuple			- store a physical tuple in the slot
+ *		ExecClearTuple			- clear contents of a slot
+ *		ExecStoreVirtualTuple	- mark slot as containing a virtual tuple
+ *		ExecCopySlotTuple		- build a physical tuple from a slot
+ *		ExecMaterializeSlot		- convert virtual to physical storage
+ *		ExecCopySlot			- copy one slot's contents to another
  *
  *	 CONVENIENCE INITIALIZATION ROUTINES
  *		ExecInitResultTupleSlot    \	convenience routines to initialize
@@ -81,10 +82,8 @@
  *		to the slots containing tuples are passed instead of the tuples
  *		themselves.  This facilitates the communication of related information
  *		(such as whether or not a tuple should be pfreed, what buffer contains
- *		this tuple, the tuple's tuple descriptor, etc).   Note that much of
- *		this information is also kept in the ExprContext of each node.
- *		Soon the executor will be redesigned and ExprContext's will contain
- *		only slot pointers.  -cim 3/14/91
+ *		this tuple, the tuple's tuple descriptor, etc).  It also allows us
+ *		to avoid physically constructing projection tuples in many cases.
  */
 #include "postgres.h"
 
@@ -142,14 +141,16 @@ ExecCreateTupleTable(int tableSize)
 		TupleTableSlot *slot = &(newtable->array[i]);
 
 		slot->type = T_TupleTableSlot;
-		slot->val = NULL;
-		slot->ttc_tupleDescriptor = NULL;
-		slot->ttc_shouldFree = false;
-		slot->ttc_shouldFreeDesc = false;
-		slot->ttc_buffer = InvalidBuffer;
-		slot->ttc_mcxt = CurrentMemoryContext;
-		slot->cache_values = NULL;
-		slot->cache_natts = 0;	/* mark slot_getattr state invalid */
+		slot->tts_isempty = true;
+		slot->tts_shouldFree = false;
+		slot->tts_shouldFreeDesc = false;
+		slot->tts_tuple = NULL;
+		slot->tts_tupleDescriptor = NULL;
+		slot->tts_mcxt = CurrentMemoryContext;
+		slot->tts_buffer = InvalidBuffer;
+		slot->tts_nvalid = 0;
+		slot->tts_values = NULL;
+		slot->tts_isnull = NULL;
 	}
 
 	return newtable;
@@ -189,10 +190,12 @@ ExecDropTupleTable(TupleTable table,	/* tuple table */
 			TupleTableSlot *slot = &(table->array[i]);
 
 			ExecClearTuple(slot);
-			if (slot->ttc_shouldFreeDesc)
-				FreeTupleDesc(slot->ttc_tupleDescriptor);
-			if (slot->cache_values)
-				pfree(slot->cache_values);
+			if (slot->tts_shouldFreeDesc)
+				FreeTupleDesc(slot->tts_tupleDescriptor);
+			if (slot->tts_values)
+				pfree(slot->tts_values);
+			if (slot->tts_isnull)
+				pfree(slot->tts_isnull);
 		}
 	}
 
@@ -203,32 +206,61 @@ ExecDropTupleTable(TupleTable table,	/* tuple table */
 }
 
 /* --------------------------------
- *		MakeTupleTableSlot
+ *		MakeSingleTupleTableSlot
  *
- *		This routine makes an empty standalone TupleTableSlot.
- *		It really shouldn't exist, but there are a few places
- *		that do this, so we may as well centralize the knowledge
- *		of what's in one ...
+ *		This is a convenience routine for operations that need a
+ *		standalone TupleTableSlot not gotten from the main executor
+ *		tuple table.  It makes a single slot and initializes it as
+ *		though by ExecSetSlotDescriptor(slot, tupdesc, false).
  * --------------------------------
  */
 TupleTableSlot *
-MakeTupleTableSlot(void)
+MakeSingleTupleTableSlot(TupleDesc tupdesc)
 {
 	TupleTableSlot *slot = makeNode(TupleTableSlot);
 
 	/* This should match ExecCreateTupleTable() */
-	slot->val = NULL;
-	slot->ttc_tupleDescriptor = NULL;
-	slot->ttc_shouldFree = false;
-	slot->ttc_shouldFreeDesc = false;
-	slot->ttc_buffer = InvalidBuffer;
-	slot->ttc_mcxt = CurrentMemoryContext;
-	slot->cache_values = NULL;
-	slot->cache_natts = 0;	/* mark slot_getattr state invalid */
+	slot->tts_isempty = true;
+	slot->tts_shouldFree = false;
+	slot->tts_shouldFreeDesc = false;
+	slot->tts_tuple = NULL;
+	slot->tts_tupleDescriptor = NULL;
+	slot->tts_mcxt = CurrentMemoryContext;
+	slot->tts_buffer = InvalidBuffer;
+	slot->tts_nvalid = 0;
+	slot->tts_values = NULL;
+	slot->tts_isnull = NULL;
+
+	ExecSetSlotDescriptor(slot, tupdesc, false);
 
 	return slot;
 }
 
+/* --------------------------------
+ *		ExecDropSingleTupleTableSlot
+ *
+ *		Release a TupleTableSlot made with MakeSingleTupleTableSlot.
+ * --------------------------------
+ */
+void
+ExecDropSingleTupleTableSlot(TupleTableSlot *slot)
+{
+	/*
+	 * sanity checks
+	 */
+	Assert(slot != NULL);
+
+	ExecClearTuple(slot);
+	if (slot->tts_shouldFreeDesc)
+		FreeTupleDesc(slot->tts_tupleDescriptor);
+	if (slot->tts_values)
+		pfree(slot->tts_values);
+	if (slot->tts_isnull)
+		pfree(slot->tts_isnull);
+
+	pfree(slot);
+}
+
 
 /* ----------------------------------------------------------------
  *				  tuple table slot reservation functions
@@ -274,10 +306,54 @@ ExecAllocTableSlot(TupleTable table)
  * ----------------------------------------------------------------
  */
 
+/* --------------------------------
+ *		ExecSetSlotDescriptor
+ *
+ *		This function is used to set the tuple descriptor associated
+ *		with the slot's tuple.
+ * --------------------------------
+ */
+void
+ExecSetSlotDescriptor(TupleTableSlot *slot,		/* slot to change */
+					  TupleDesc tupdesc,		/* new tuple descriptor */
+					  bool shouldFree)	/* is desc owned by slot? */
+{
+	/* For safety, make sure slot is empty before changing it */
+	ExecClearTuple(slot);
+
+	/*
+	 * 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_shouldFreeDesc)
+		FreeTupleDesc(slot->tts_tupleDescriptor);
+
+	if (slot->tts_values)
+		pfree(slot->tts_values);
+	if (slot->tts_isnull)
+		pfree(slot->tts_isnull);
+
+	/*
+	 * Set up the new descriptor
+	 */
+	slot->tts_tupleDescriptor = tupdesc;
+	slot->tts_shouldFreeDesc = shouldFree;
+
+	/*
+	 * 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));
+}
+
 /* --------------------------------
  *		ExecStoreTuple
  *
- *		This function is used to store a tuple into a specified
+ *		This function is used to store a physical tuple into a specified
  *		slot in the tuple table.
  *
  *		tuple:	tuple to store
@@ -304,6 +380,12 @@ ExecAllocTableSlot(TupleTable table)
  * slot assume ownership of the copy!
  *
  * Return value is just the passed-in slot pointer.
+ *
+ * NOTE: before PostgreSQL 8.1, this function would accept a NULL tuple
+ * pointer and effectively behave like ExecClearTuple (though you could
+ * still specify a buffer to pin, which would be an odd combination).
+ * This saved a couple lines of code in a few places, but seemed more likely
+ * to mask logic errors than to be really useful, so it's now disallowed.
  * --------------------------------
  */
 TupleTableSlot *
@@ -315,29 +397,36 @@ ExecStoreTuple(HeapTuple tuple,
 	/*
 	 * sanity checks
 	 */
+	Assert(tuple != NULL);
 	Assert(slot != NULL);
+	Assert(slot->tts_tupleDescriptor != NULL);
 	/* passing shouldFree=true for a tuple on a disk page is not sane */
 	Assert(BufferIsValid(buffer) ? (!shouldFree) : true);
 
 	/*
 	 * clear out any old contents of the slot
 	 */
-	ExecClearTuple(slot);
+	if (!slot->tts_isempty)
+		ExecClearTuple(slot);
 
 	/*
 	 * store the new tuple into the specified slot.
 	 */
-	slot->val = tuple;
-	slot->ttc_shouldFree = shouldFree;
+	slot->tts_isempty = false;
+	slot->tts_shouldFree = shouldFree;
+	slot->tts_tuple = tuple;
 
 	/*
 	 * 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.
 	 */
-	slot->ttc_buffer = buffer;
+	slot->tts_buffer = buffer;
 	if (BufferIsValid(buffer))
 		IncrBufferRefCount(buffer);
 
+	/* Mark extracted state invalid */
+	slot->tts_nvalid = 0;
+
 	return slot;
 }
 
@@ -358,63 +447,231 @@ ExecClearTuple(TupleTableSlot *slot)	/* slot in which to store tuple */
 	Assert(slot != NULL);
 
 	/*
-	 * Free the old contents of the specified slot if necessary.  (Note:
-	 * we allow slot->val to be null even when shouldFree is true, because
-	 * there are a few callers of ExecStoreTuple that are too lazy to
-	 * distinguish whether they are passing a NULL tuple, and always pass
-	 * shouldFree = true.)
+	 * Free the old physical tuple if necessary.
 	 */
-	if (slot->ttc_shouldFree && slot->val != NULL)
-		heap_freetuple(slot->val);
+	if (slot->tts_shouldFree)
+		heap_freetuple(slot->tts_tuple);
 
-	slot->val = NULL;
-	slot->ttc_shouldFree = false;
+	slot->tts_tuple = NULL;
+	slot->tts_shouldFree = false;
 
 	/*
 	 * Drop the pin on the referenced buffer, if there is one.
 	 */
-	if (BufferIsValid(slot->ttc_buffer))
-		ReleaseBuffer(slot->ttc_buffer);
+	if (BufferIsValid(slot->tts_buffer))
+		ReleaseBuffer(slot->tts_buffer);
 
-	slot->ttc_buffer = InvalidBuffer;
+	slot->tts_buffer = InvalidBuffer;
 
 	/*
-	 * mark slot_getattr state invalid
+	 * Mark it empty.
 	 */
-	slot->cache_natts = 0;
+	slot->tts_isempty = true;
+	slot->tts_nvalid = 0;
 
 	return slot;
 }
 
 /* --------------------------------
- *		ExecSetSlotDescriptor
+ *		ExecStoreVirtualTuple
+ *			Mark a slot as containing a virtual tuple.
  *
- *		This function is used to set the tuple descriptor associated
- *		with the slot's tuple.
+ * The protocol for loading a slot with virtual tuple data is:
+ *		* Call ExecClearTuple to mark the slot empty.
+ *		* Store data into the Datum/isnull arrays.
+ *		* Call ExecStoreVirtualTuple to mark the slot valid.
+ * This is a bit unclean but it avoids one round of data copying.
  * --------------------------------
  */
-void
-ExecSetSlotDescriptor(TupleTableSlot *slot,		/* slot to change */
-					  TupleDesc tupdesc,		/* new tuple descriptor */
-					  bool shouldFree)	/* is desc owned by slot? */
+TupleTableSlot *
+ExecStoreVirtualTuple(TupleTableSlot *slot)
 {
-	if (slot->ttc_shouldFreeDesc)
-		FreeTupleDesc(slot->ttc_tupleDescriptor);
+	/*
+	 * sanity checks
+	 */
+	Assert(slot != NULL);
+	Assert(slot->tts_tupleDescriptor != NULL);
+	Assert(slot->tts_isempty);
 
-	slot->ttc_tupleDescriptor = tupdesc;
-	slot->ttc_shouldFreeDesc = shouldFree;
+	slot->tts_isempty = false;
+	slot->tts_nvalid = slot->tts_tupleDescriptor->natts;
 
+	return slot;
+}
+
+/* --------------------------------
+ *		ExecStoreAllNullTuple
+ *			Set up the slot to contain a null in every column.
+ *
+ * At first glance this might sound just like ExecClearTuple, but it's
+ * entirely different: the slot ends up full, not empty.
+ * --------------------------------
+ */
+TupleTableSlot *
+ExecStoreAllNullTuple(TupleTableSlot *slot)
+{
 	/*
-	 * mark slot_getattr state invalid
+	 * sanity checks
 	 */
-	slot->cache_natts = 0;
+	Assert(slot != NULL);
+	Assert(slot->tts_tupleDescriptor != NULL);
+
+	/* Clear any old contents */
+	ExecClearTuple(slot);
 
 	/*
-	 * release any old cache array since tupledesc's natts may have changed
+	 * Fill all the columns of the virtual tuple with nulls
 	 */
-	if (slot->cache_values)
-		pfree(slot->cache_values);
-	slot->cache_values = NULL;
+	MemSet(slot->tts_values, 0,
+		   slot->tts_tupleDescriptor->natts * sizeof(Datum));
+	memset(slot->tts_isnull, true,
+		   slot->tts_tupleDescriptor->natts * sizeof(bool));
+
+	return ExecStoreVirtualTuple(slot);
+}
+
+/* --------------------------------
+ *		ExecCopySlotTuple
+ *			Obtain a copy of a slot's physical tuple.  The copy is
+ *			palloc'd in the current memory context.
+ *
+ *		This works even if the slot contains a virtual 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 then just copy it.
+	 */
+	if (slot->tts_tuple)
+		return heap_copytuple(slot->tts_tuple);
+
+	/*
+	 * Otherwise we need to build a tuple from the Datum array.
+	 */
+	return heap_form_tuple(slot->tts_tupleDescriptor,
+						   slot->tts_values,
+						   slot->tts_isnull);
+}
+
+/* --------------------------------
+ *		ExecFetchSlotTuple
+ *			Fetch the slot's physical tuple.
+ *
+ *		If the slot contains a virtual tuple, we convert it to physical
+ *		form.  The slot retains ownership of the physical tuple.
+ *
+ * The difference between this and ExecMaterializeSlot() is that this
+ * does not guarantee that the contained tuple is local storage.
+ * Hence, the result must be treated as read-only.
+ * --------------------------------
+ */
+HeapTuple
+ExecFetchSlotTuple(TupleTableSlot *slot)
+{
+	/*
+	 * sanity checks
+	 */
+	Assert(slot != NULL);
+	Assert(!slot->tts_isempty);
+
+	/*
+	 * If we have a physical tuple then just return it.
+	 */
+	if (slot->tts_tuple)
+		return slot->tts_tuple;
+
+	/*
+	 * Otherwise materialize the slot...
+	 */
+	return ExecMaterializeSlot(slot);
+}
+
+/* --------------------------------
+ *		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)
+{
+	HeapTuple	newTuple;
+	MemoryContext oldContext;
+
+	/*
+	 * sanity checks
+	 */
+	Assert(slot != NULL);
+	Assert(!slot->tts_isempty);
+
+	/*
+	 * If we have a 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 tuple, and then store it as the new slot
+	 * value.  (Note: tts_nvalid will be reset to zero here.  There are
+	 * cases in which this could be optimized but it's probably not worth
+	 * worrying about.)
+	 *
+	 * 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);
+	newTuple = ExecCopySlotTuple(slot);
+	MemoryContextSwitchTo(oldContext);
+
+	ExecStoreTuple(newTuple, slot, InvalidBuffer, true);
+
+	return slot->tts_tuple;
+}
+
+/* --------------------------------
+ *		ExecCopySlot
+ *			Copy the source slot's contents into the destination slot.
+ *
+ *		The destination acquires a private copy that will not go away
+ *		if the source is cleared.
+ *
+ *		The caller must ensure the slots have compatible tupdescs.
+ * --------------------------------
+ */
+TupleTableSlot *
+ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot)
+{
+	HeapTuple	newTuple;
+	MemoryContext oldContext;
+
+	/*
+	 * 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);
+
+	return ExecStoreTuple(newTuple, dstslot, InvalidBuffer, true);
 }
 
 
@@ -474,25 +731,10 @@ TupleTableSlot *
 ExecInitNullTupleSlot(EState *estate, TupleDesc tupType)
 {
 	TupleTableSlot *slot = ExecInitExtraTupleSlot(estate);
-	struct tupleDesc nullTupleDesc;
-	HeapTuple	nullTuple;
-	Datum		values[1];
-	char		nulls[1];
-
-	/*
-	 * Since heap_getattr() will treat attributes beyond a tuple's t_natts
-	 * as being NULL, we can make an all-nulls tuple just by making it be
-	 * of zero length.	However, the slot descriptor must match the real
-	 * tupType.
-	 */
-	nullTupleDesc = *tupType;
-	nullTupleDesc.natts = 0;
-
-	nullTuple = heap_formtuple(&nullTupleDesc, values, nulls);
 
 	ExecSetSlotDescriptor(slot, tupType, false);
 
-	return ExecStoreTuple(nullTuple, slot, InvalidBuffer, true);
+	return ExecStoreAllNullTuple(slot);
 }
 
 /* ----------------------------------------------------------------
@@ -623,10 +865,7 @@ TupleDescGetSlot(TupleDesc tupdesc)
 	BlessTupleDesc(tupdesc);
 
 	/* Make a standalone slot */
-	slot = MakeTupleTableSlot();
-
-	/* Bind the tuple description to the slot */
-	ExecSetSlotDescriptor(slot, tupdesc, true);
+	slot = MakeSingleTupleTableSlot(tupdesc);
 
 	/* Return the slot */
 	return slot;
@@ -759,6 +998,7 @@ begin_tup_output_tupdesc(DestReceiver *dest, TupleDesc tupdesc)
 	tstate = (TupOutputState *) palloc(sizeof(TupOutputState));
 
 	tstate->metadata = TupleDescGetAttInMetadata(tupdesc);
+	tstate->slot = MakeSingleTupleTableSlot(tupdesc);
 	tstate->dest = dest;
 
 	(*tstate->dest->rStartup) (tstate->dest, (int) CMD_SELECT, tupdesc);
@@ -771,6 +1011,9 @@ begin_tup_output_tupdesc(DestReceiver *dest, TupleDesc tupdesc)
  *
  * values is a list of the external C string representations of the values
  * to be projected.
+ *
+ * XXX This could be made more efficient, since in reality we probably only
+ * need a virtual tuple.
  */
 void
 do_tup_output(TupOutputState *tstate, char **values)
@@ -778,12 +1021,14 @@ do_tup_output(TupOutputState *tstate, char **values)
 	/* build a tuple from the input strings using the tupdesc */
 	HeapTuple	tuple = BuildTupleFromCStrings(tstate->metadata, values);
 
+	/* put it in a slot */
+	ExecStoreTuple(tuple, tstate->slot, InvalidBuffer, true);
+
 	/* send the tuple to the receiver */
-	(*tstate->dest->receiveTuple) (tuple,
-								   tstate->metadata->tupdesc,
-								   tstate->dest);
+	(*tstate->dest->receiveSlot) (tstate->slot, tstate->dest);
+
 	/* clean up */
-	heap_freetuple(tuple);
+	ExecClearTuple(tstate->slot);
 }
 
 /*
@@ -816,6 +1061,7 @@ end_tup_output(TupOutputState *tstate)
 {
 	(*tstate->dest->rShutdown) (tstate->dest);
 	/* note that destroying the dest is not ours to do */
+	ExecDropSingleTupleTableSlot(tstate->slot);
 	/* XXX worth cleaning up the attinmetadata? */
 	pfree(tstate);
 }
diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c
index 1718739fd6ce31aeea1d94ae02f28752e33861cc..74567b04417cac4c14cfd2d7b3efe72dec1304b8 100644
--- a/src/backend/executor/execUtils.c
+++ b/src/backend/executor/execUtils.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.117 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execUtils.c,v 1.118 2005/03/16 21:38:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -485,7 +485,7 @@ ExecGetResultType(PlanState *planstate)
 {
 	TupleTableSlot *slot = planstate->ps_ResultTupleSlot;
 
-	return slot->ttc_tupleDescriptor;
+	return slot->tts_tupleDescriptor;
 }
 
 /* ----------------
@@ -504,17 +504,99 @@ ExecBuildProjectionInfo(List *targetList,
 {
 	ProjectionInfo *projInfo = makeNode(ProjectionInfo);
 	int			len;
+	bool		isVarList;
+	ListCell   *tl;
 
 	len = ExecTargetListLength(targetList);
 
 	projInfo->pi_targetlist = targetList;
 	projInfo->pi_exprContext = econtext;
 	projInfo->pi_slot = slot;
-	if (len > 0)
+
+	/*
+	 * Determine whether the target list consists entirely of simple Var
+	 * references (ie, references to non-system attributes).  If so,
+	 * we can use the simpler ExecVariableList instead of ExecTargetList.
+	 */
+	isVarList = true;
+	foreach(tl, targetList)
+	{
+		GenericExprState *gstate = (GenericExprState *) lfirst(tl);
+		Var		   *variable = (Var *) gstate->arg->expr;
+
+		if (variable == NULL ||
+			!IsA(variable, Var) ||
+			variable->varattno <= 0)
+		{
+			isVarList = false;
+			break;
+		}
+	}
+	projInfo->pi_isVarList = isVarList;
+
+	if (isVarList)
+	{
+		int		   *varSlotOffsets;
+		int		   *varNumbers;
+		AttrNumber	lastInnerVar = 0;
+		AttrNumber	lastOuterVar = 0;
+		AttrNumber	lastScanVar = 0;
+
+		projInfo->pi_itemIsDone = NULL;	/* not needed */
+		projInfo->pi_varSlotOffsets = varSlotOffsets = (int *)
+			palloc0(len * sizeof(int));
+		projInfo->pi_varNumbers = varNumbers = (int *)
+			palloc0(len * sizeof(int));
+
+		/*
+		 * Set up the data needed by ExecVariableList.  The slots in which
+		 * the variables can be found at runtime are denoted by the offsets
+		 * of their slot pointers within the econtext.  This rather grotty
+		 * representation is needed because the caller may not have given
+		 * us the real econtext yet (see hacks in nodeSubplan.c).
+		 */
+		foreach(tl, targetList)
+		{
+			GenericExprState *gstate = (GenericExprState *) lfirst(tl);
+			Var		   *variable = (Var *) gstate->arg->expr;
+			AttrNumber	attnum = variable->varattno;
+			TargetEntry *tle = (TargetEntry *) gstate->xprstate.expr;
+			AttrNumber	resind = tle->resdom->resno - 1;
+
+			Assert(resind >= 0 && resind < len);
+			varNumbers[resind] = attnum;
+
+			switch (variable->varno)
+			{
+				case INNER:
+					varSlotOffsets[resind] = offsetof(ExprContext,
+													  ecxt_innertuple);
+					lastInnerVar = Max(lastInnerVar, attnum);
+					break;
+
+				case OUTER:
+					varSlotOffsets[resind] = offsetof(ExprContext,
+													  ecxt_outertuple);
+					lastOuterVar = Max(lastOuterVar, attnum);
+					break;
+
+				default:
+					varSlotOffsets[resind] = offsetof(ExprContext,
+													  ecxt_scantuple);
+					lastScanVar = Max(lastScanVar, attnum);
+					break;
+			}
+		}
+		projInfo->pi_lastInnerVar = lastInnerVar;
+		projInfo->pi_lastOuterVar = lastOuterVar;
+		projInfo->pi_lastScanVar = lastScanVar;
+	}
+	else
 	{
-		projInfo->pi_tupValues = (Datum *) palloc(len * sizeof(Datum));
-		projInfo->pi_tupNulls = (char *) palloc(len * sizeof(char));
-		projInfo->pi_itemIsDone = (ExprDoneCond *) palloc(len * sizeof(ExprDoneCond));
+		projInfo->pi_itemIsDone = (ExprDoneCond *)
+			palloc(len * sizeof(ExprDoneCond));
+		projInfo->pi_varSlotOffsets = NULL;
+		projInfo->pi_varNumbers = NULL;
 	}
 
 	return projInfo;
@@ -582,7 +664,7 @@ ExecGetScanType(ScanState *scanstate)
 {
 	TupleTableSlot *slot = scanstate->ss_ScanTupleSlot;
 
-	return slot->ttc_tupleDescriptor;
+	return slot->tts_tupleDescriptor;
 }
 
 /* ----------------
@@ -772,20 +854,16 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 					  EState *estate,
 					  bool is_vacuum)
 {
-	HeapTuple	heapTuple;
 	ResultRelInfo *resultRelInfo;
 	int			i;
 	int			numIndices;
 	RelationPtr relationDescs;
 	Relation	heapRelation;
-	TupleDesc	heapDescriptor;
 	IndexInfo **indexInfoArray;
 	ExprContext *econtext;
 	Datum		datum[INDEX_MAX_KEYS];
 	char		nullv[INDEX_MAX_KEYS];
 
-	heapTuple = slot->val;
-
 	/*
 	 * Get information from the result relation info structure.
 	 */
@@ -794,7 +872,6 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 	relationDescs = resultRelInfo->ri_IndexRelationDescs;
 	indexInfoArray = resultRelInfo->ri_IndexRelationInfo;
 	heapRelation = resultRelInfo->ri_RelationDesc;
-	heapDescriptor = RelationGetDescr(heapRelation);
 
 	/*
 	 * We will use the EState's per-tuple context for evaluating
@@ -844,12 +921,11 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 
 		/*
 		 * FormIndexDatum fills in its datum and null parameters with
-		 * attribute information taken from the given heap tuple. It also
+		 * attribute information taken from the given tuple. It also
 		 * computes any expressions needed.
 		 */
 		FormIndexDatum(indexInfo,
-					   heapTuple,
-					   heapDescriptor,
+					   slot,
 					   estate,
 					   datum,
 					   nullv);
@@ -860,9 +936,9 @@ ExecInsertIndexTuples(TupleTableSlot *slot,
 		 * need to move dead tuples that have the same keys as live ones.
 		 */
 		result = index_insert(relationDescs[i], /* index relation */
-							  datum,	/* array of heaptuple Datums */
+							  datum,	/* array of index Datums */
 							  nullv,	/* info on nulls */
-							  &(heapTuple->t_self),		/* tid of heap tuple */
+							  tupleid,	/* tid of heap tuple */
 							  heapRelation,
 				  relationDescs[i]->rd_index->indisunique && !is_vacuum);
 
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 9ce1c9850388ba64beb347d8e9752d7dcfa2a670..df54b56ff575e3acff53aef749d12b9c0609e87a 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.91 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/functions.c,v 1.92 2005/03/16 21:38:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -456,8 +456,6 @@ postquel_execute(execution_state *es,
 				 SQLFunctionCachePtr fcache)
 {
 	TupleTableSlot *slot;
-	HeapTuple	tup;
-	TupleDesc	tupDesc;
 	Datum		value;
 
 	if (es->status == F_EXEC_START)
@@ -512,7 +510,7 @@ postquel_execute(execution_state *es,
 
 		/*
 		 * Compress out the HeapTuple header data.  We assume that
-		 * heap_formtuple made the tuple with header and body in one
+		 * heap_form_tuple made the tuple with header and body in one
 		 * palloc'd chunk.  We want to return a pointer to the chunk
 		 * start so that it will work if someone tries to free it.
 		 */
@@ -534,7 +532,8 @@ postquel_execute(execution_state *es,
 		else
 		{
 			/* function is declared to return RECORD */
-			tupDesc = fcache->junkFilter->jf_cleanTupType;
+			TupleDesc	tupDesc = fcache->junkFilter->jf_cleanTupType;
+
 			if (tupDesc->tdtypeid == RECORDOID &&
 				tupDesc->tdtypmod < 0)
 				assign_record_type_typmod(tupDesc);
@@ -556,10 +555,7 @@ postquel_execute(execution_state *es,
 		 * column of the SELECT result, and then copy into current
 		 * execution context if needed.
 		 */
-		tup = slot->val;
-		tupDesc = slot->ttc_tupleDescriptor;
-
-		value = heap_getattr(tup, 1, tupDesc, &(fcinfo->isnull));
+		value = slot_getattr(slot, 1, &(fcinfo->isnull));
 
 		if (!fcinfo->isnull)
 			value = datumCopy(value, fcache->typbyval, fcache->typlen);
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 8f641cc843b5725f53b58bd643e0a6e08b19a4b6..1e211803df10d5502b6d458dbc2511d286963e86 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -61,7 +61,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.129 2005/03/12 20:25:06 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.130 2005/03/16 21:38:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -748,7 +748,7 @@ agg_retrieve_direct(AggState *aggstate)
 				 * Make a copy of the first input tuple; we will use this
 				 * for comparisons (in group mode) and for projection.
 				 */
-				aggstate->grp_firstTuple = heap_copytuple(outerslot->val);
+				aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
 			}
 			else
 			{
@@ -813,9 +813,8 @@ agg_retrieve_direct(AggState *aggstate)
 				 */
 				if (node->aggstrategy == AGG_SORTED)
 				{
-					if (!execTuplesMatch(firstSlot->val,
-										 outerslot->val,
-										 firstSlot->ttc_tupleDescriptor,
+					if (!execTuplesMatch(firstSlot,
+										 outerslot,
 										 node->numCols, node->grpColIdx,
 										 aggstate->eqfunctions,
 									  tmpcontext->ecxt_per_tuple_memory))
@@ -823,7 +822,7 @@ agg_retrieve_direct(AggState *aggstate)
 						/*
 						 * Save the first input tuple of the next group.
 						 */
-						aggstate->grp_firstTuple = heap_copytuple(outerslot->val);
+						aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot);
 						break;
 					}
 				}
@@ -863,31 +862,11 @@ agg_retrieve_direct(AggState *aggstate)
 		 */
 		if (TupIsNull(firstSlot))
 		{
-			TupleDesc	tupType;
-
 			/* Should only happen in non-grouped mode */
 			Assert(node->aggstrategy == AGG_PLAIN);
 			Assert(aggstate->agg_done);
 
-			tupType = firstSlot->ttc_tupleDescriptor;
-			/* watch out for zero-column input tuples, though... */
-			if (tupType && tupType->natts > 0)
-			{
-				HeapTuple	nullsTuple;
-				Datum	   *dvalues;
-				char	   *dnulls;
-
-				dvalues = (Datum *) palloc0(sizeof(Datum) * tupType->natts);
-				dnulls = (char *) palloc(sizeof(char) * tupType->natts);
-				MemSet(dnulls, 'n', sizeof(char) * tupType->natts);
-				nullsTuple = heap_formtuple(tupType, dvalues, dnulls);
-				ExecStoreTuple(nullsTuple,
-							   firstSlot,
-							   InvalidBuffer,
-							   true);
-				pfree(dvalues);
-				pfree(dnulls);
-			}
+			ExecStoreAllNullTuple(firstSlot);
 		}
 
 		/*
diff --git a/src/backend/executor/nodeFunctionscan.c b/src/backend/executor/nodeFunctionscan.c
index be1fbb051557389d46dabb850e034d4b91904904..edf5c67635263132c1cd5105f83082d4ae66db35 100644
--- a/src/backend/executor/nodeFunctionscan.c
+++ b/src/backend/executor/nodeFunctionscan.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.30 2005/01/27 06:36:42 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeFunctionscan.c,v 1.31 2005/03/16 21:38:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -99,7 +99,10 @@ FunctionNext(FunctionScanState *node)
 										ScanDirectionIsForward(direction),
 										&should_free);
 	slot = node->ss.ss_ScanTupleSlot;
-	return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
+	if (heapTuple)
+		return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
+	else
+		return ExecClearTuple(slot);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index f8bd18f3499980ac18f221732caad0d75ac99d2a..e16a228fa157e6ce5014046fc754cce4f95f211f 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -15,7 +15,7 @@
  *	  locate group boundaries.
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeGroup.c,v 1.60 2005/03/10 23:21:21 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeGroup.c,v 1.61 2005/03/16 21:38:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -36,11 +36,9 @@ TupleTableSlot *
 ExecGroup(GroupState *node)
 {
 	ExprContext *econtext;
-	TupleDesc	tupdesc;
 	int			numCols;
 	AttrNumber *grpColIdx;
-	HeapTuple	outerTuple;
-	HeapTuple	firsttuple;
+	TupleTableSlot *firsttupleslot;
 	TupleTableSlot *outerslot;
 
 	/*
@@ -49,10 +47,14 @@ ExecGroup(GroupState *node)
 	if (node->grp_done)
 		return NULL;
 	econtext = node->ss.ps.ps_ExprContext;
-	tupdesc = ExecGetScanType(&node->ss);
 	numCols = ((Group *) node->ss.ps.plan)->numCols;
 	grpColIdx = ((Group *) node->ss.ps.plan)->grpColIdx;
 
+	/*
+	 * The ScanTupleSlot holds the (copied) first tuple of each group.
+	 */
+	firsttupleslot = node->ss.ss_ScanTupleSlot;
+
 	/*
 	 * We need not call ResetExprContext here because execTuplesMatch will
 	 * reset the per-tuple memory context once per input tuple.
@@ -62,8 +64,7 @@ ExecGroup(GroupState *node)
 	 * If first time through, acquire first input tuple and determine
 	 * whether to return it or not.
 	 */
-	firsttuple = node->grp_firstTuple;
-	if (firsttuple == NULL)
+	if (TupIsNull(firsttupleslot))
 	{
 		outerslot = ExecProcNode(outerPlanState(node));
 		if (TupIsNull(outerslot))
@@ -72,13 +73,9 @@ ExecGroup(GroupState *node)
 			node->grp_done = TRUE;
 			return NULL;
 		}
-		node->grp_firstTuple = firsttuple = heap_copytuple(outerslot->val);
-		/* Set up tuple as input for qual test and projection */
-		ExecStoreTuple(firsttuple,
-					   node->ss.ss_ScanTupleSlot,
-					   InvalidBuffer,
-					   false);
-		econtext->ecxt_scantuple = node->ss.ss_ScanTupleSlot;
+		/* Copy tuple, set up as input for qual test and projection */
+		ExecCopySlot(firsttupleslot, outerslot);
+		econtext->ecxt_scantuple = firsttupleslot;
 		/*
 		 * Check the qual (HAVING clause); if the group does not match,
 		 * ignore it and fall into scan loop.
@@ -112,14 +109,12 @@ ExecGroup(GroupState *node)
 				node->grp_done = TRUE;
 				return NULL;
 			}
-			outerTuple = outerslot->val;
 
 			/*
 			 * Compare with first tuple and see if this tuple is of the same
 			 * group.  If so, ignore it and keep scanning.
 			 */
-			if (!execTuplesMatch(firsttuple, outerTuple,
-								 tupdesc,
+			if (!execTuplesMatch(firsttupleslot, outerslot,
 								 numCols, grpColIdx,
 								 node->eqfunctions,
 								 econtext->ecxt_per_tuple_memory))
@@ -129,14 +124,9 @@ ExecGroup(GroupState *node)
 		 * We have the first tuple of the next input group.  See if we
 		 * want to return it.
 		 */
-		heap_freetuple(firsttuple);
-		node->grp_firstTuple = firsttuple = heap_copytuple(outerTuple);
-		/* Set up tuple as input for qual test and projection */
-		ExecStoreTuple(firsttuple,
-					   node->ss.ss_ScanTupleSlot,
-					   InvalidBuffer,
-					   false);
-		econtext->ecxt_scantuple = node->ss.ss_ScanTupleSlot;
+		/* Copy tuple, set up as input for qual test and projection */
+		ExecCopySlot(firsttupleslot, outerslot);
+		econtext->ecxt_scantuple = firsttupleslot;
 		/*
 		 * Check the qual (HAVING clause); if the group does not match,
 		 * ignore it and loop back to scan the rest of the group.
@@ -173,7 +163,6 @@ ExecInitGroup(Group *node, EState *estate)
 	grpstate = makeNode(GroupState);
 	grpstate->ss.ps.plan = (Plan *) node;
 	grpstate->ss.ps.state = estate;
-	grpstate->grp_firstTuple = NULL;
 	grpstate->grp_done = FALSE;
 
 	/*
@@ -255,11 +244,8 @@ void
 ExecReScanGroup(GroupState *node, ExprContext *exprCtxt)
 {
 	node->grp_done = FALSE;
-	if (node->grp_firstTuple != NULL)
-	{
-		heap_freetuple(node->grp_firstTuple);
-		node->grp_firstTuple = NULL;
-	}
+	/* must clear first tuple */
+	ExecClearTuple(node->ss.ss_ScanTupleSlot);
 
 	if (((PlanState *) node)->lefttree &&
 		((PlanState *) node)->lefttree->chgParam == NULL)
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index ded952a43e51fcf13dce86f47429895b37a55d06..daf24c3d5ab53b8fffe90f88446610f1953ee7d4 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeHash.c,v 1.90 2005/03/13 19:59:40 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeHash.c,v 1.91 2005/03/16 21:38:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -75,7 +75,7 @@ ExecHash(HashState *node)
 		/* We have to compute the hash value */
 		econtext->ecxt_innertuple = slot;
 		hashvalue = ExecHashGetHashValue(hashtable, econtext, hashkeys);
-		ExecHashTableInsert(hashtable, slot->val, hashvalue);
+		ExecHashTableInsert(hashtable, ExecFetchSlotTuple(slot), hashvalue);
 	}
 
 	/*
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 4f4eb701d3ca508b20765a997c19c5052bb3357a..26d7bde3353abdd3a9f67747183077f06d9473f7 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeHashjoin.c,v 1.68 2005/03/06 22:15:04 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeHashjoin.c,v 1.69 2005/03/16 21:38:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -188,7 +188,8 @@ ExecHashJoin(HashJoinState *node)
 				 * Save it in the corresponding outer-batch file.
 				 */
 				Assert(batchno > hashtable->curbatch);
-				ExecHashJoinSaveTuple(outerTupleSlot->val, hashvalue,
+				ExecHashJoinSaveTuple(ExecFetchSlotTuple(outerTupleSlot),
+									  hashvalue,
 									  &hashtable->outerBatchFile[batchno]);
 				node->hj_NeedNewOuter = true;
 				continue;	/* loop around for a new outer tuple */
@@ -652,7 +653,9 @@ start_over:
 			 * NOTE: some tuples may be sent to future batches.  Also,
 			 * it is possible for hashtable->nbatch to be increased here!
 			 */
-			ExecHashTableInsert(hashtable, slot->val, hashvalue);
+			ExecHashTableInsert(hashtable,
+								ExecFetchSlotTuple(slot),
+								hashvalue);
 		}
 
 		/*
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index a7c91093adb272c70f8ac6fc2f479a3abb066100..6546f4797082450a0f8340088e77cc64dd899d29 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeIndexscan.c,v 1.99 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeIndexscan.c,v 1.100 2005/03/16 21:38:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -165,7 +165,7 @@ IndexNext(IndexScanState *node)
 				break;
 		}
 		if (qual == NULL)		/* would not be returned by indices */
-			slot->val = NULL;
+			ExecClearTuple(slot);
 
 		/* Flag for the next call that no more tuples */
 		estate->es_evTupleNull[scanrelid - 1] = true;
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index b4942d6290754675699a988cc49b7a7fe79ce909..40e0283e86f68b67ada4c67fb23631f9e0fb4935 100644
--- a/src/backend/executor/nodeLimit.c
+++ b/src/backend/executor/nodeLimit.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeLimit.c,v 1.20 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeLimit.c,v 1.21 2005/03/16 21:38:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -38,7 +38,6 @@ TupleTableSlot *				/* return: a tuple or NULL */
 ExecLimit(LimitState *node)
 {
 	ScanDirection direction;
-	TupleTableSlot *resultTupleSlot;
 	TupleTableSlot *slot;
 	PlanState  *outerPlan;
 
@@ -47,7 +46,6 @@ ExecLimit(LimitState *node)
 	 */
 	direction = node->ps.state->es_direction;
 	outerPlan = outerPlanState(node);
-	resultTupleSlot = node->ps.ps_ResultTupleSlot;
 
 	/*
 	 * The main logic is a simple state machine.
@@ -219,12 +217,7 @@ ExecLimit(LimitState *node)
 	/* Return the current tuple */
 	Assert(!TupIsNull(slot));
 
-	ExecStoreTuple(slot->val,
-				   resultTupleSlot,
-				   InvalidBuffer,
-				   false);		/* tuple does not belong to slot */
-
-	return resultTupleSlot;
+	return slot;
 }
 
 /*
@@ -324,7 +317,7 @@ ExecInitLimit(Limit *node, EState *estate)
 #define LIMIT_NSLOTS 1
 
 	/*
-	 * Tuple table initialization
+	 * Tuple table initialization (XXX not actually used...)
 	 */
 	ExecInitResultTupleSlot(estate, &limitstate->ps);
 
@@ -363,10 +356,6 @@ void
 ExecEndLimit(LimitState *node)
 {
 	ExecFreeExprContext(&node->ps);
-
-	/* clean up tuple table */
-	ExecClearTuple(node->ps.ps_ResultTupleSlot);
-
 	ExecEndNode(outerPlanState(node));
 }
 
@@ -377,8 +366,6 @@ ExecReScanLimit(LimitState *node, ExprContext *exprCtxt)
 	/* resetting lstate will force offset/limit recalculation */
 	node->lstate = LIMIT_INITIAL;
 
-	ExecClearTuple(node->ps.ps_ResultTupleSlot);
-
 	/*
 	 * if chgParam of subnode is not null then plan will be re-scanned by
 	 * first ExecProcNode.
diff --git a/src/backend/executor/nodeMaterial.c b/src/backend/executor/nodeMaterial.c
index 08b584946393482e8e3053afdfc781927e6e56a3..fe128595576be83d16cce089126d4463eb0a2079 100644
--- a/src/backend/executor/nodeMaterial.c
+++ b/src/backend/executor/nodeMaterial.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeMaterial.c,v 1.48 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeMaterial.c,v 1.49 2005/03/16 21:38:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -127,7 +127,7 @@ ExecMaterial(MaterialState *node)
 			node->eof_underlying = true;
 			return NULL;
 		}
-		heapTuple = outerslot->val;
+		heapTuple = ExecFetchSlotTuple(outerslot);
 		should_free = false;
 
 		/*
@@ -139,10 +139,13 @@ ExecMaterial(MaterialState *node)
 	}
 
 	/*
-	 * Return the obtained tuple.
+	 * Return the obtained tuple, if any.
 	 */
 	slot = (TupleTableSlot *) node->ss.ps.ps_ResultTupleSlot;
-	return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
+	if (heapTuple)
+		return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
+	else
+		return ExecClearTuple(slot);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index ade0d9c69ed30a2e1f27f35a6a1c5b2724a9382b..bb93e2367d011ef16bdbb809e613de305f50517c 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeMergejoin.c,v 1.69 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeMergejoin.c,v 1.70 2005/03/16 21:38:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -80,10 +80,8 @@ static bool MergeCompare(List *eqQual, List *compareQual, ExprContext *econtext)
 
 #define MarkInnerTuple(innerTupleSlot, mergestate) \
 ( \
-	ExecStoreTuple(heap_copytuple((innerTupleSlot)->val), \
-				   (mergestate)->mj_MarkedTupleSlot, \
-				   InvalidBuffer, \
-				   true) \
+	ExecCopySlot((mergestate)->mj_MarkedTupleSlot, \
+				 (innerTupleSlot)) \
 )
 
 
@@ -246,8 +244,7 @@ ExecMergeTupleDumpOuter(MergeJoinState *mergestate)
 	if (TupIsNull(outerSlot))
 		printf("(nil)\n");
 	else
-		MJ_debugtup(outerSlot->val,
-					outerSlot->ttc_tupleDescriptor);
+		MJ_debugtup(outerSlot);
 }
 
 static void
@@ -259,8 +256,7 @@ ExecMergeTupleDumpInner(MergeJoinState *mergestate)
 	if (TupIsNull(innerSlot))
 		printf("(nil)\n");
 	else
-		MJ_debugtup(innerSlot->val,
-					innerSlot->ttc_tupleDescriptor);
+		MJ_debugtup(innerSlot);
 }
 
 static void
@@ -272,8 +268,7 @@ ExecMergeTupleDumpMarked(MergeJoinState *mergestate)
 	if (TupIsNull(markedSlot))
 		printf("(nil)\n");
 	else
-		MJ_debugtup(markedSlot->val,
-					markedSlot->ttc_tupleDescriptor);
+		MJ_debugtup(markedSlot);
 }
 
 static void
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 621911652a9fd2d6bfdd9906a2f2d69693d8b59d..f5a284a26bffd5c86d10a29f62e99a815881cf03 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeSeqscan.c,v 1.51 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeSeqscan.c,v 1.52 2005/03/16 21:38:07 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -110,12 +110,12 @@ SeqNext(SeqScanState *node)
 	 * refcount of the buffer; the refcount will not be dropped until the
 	 * tuple table slot is cleared.
 	 */
-
-	slot = ExecStoreTuple(tuple,	/* tuple to store */
-						  slot, /* slot to store in */
-						  scandesc->rs_cbuf,	/* buffer associated with
+	if (tuple)
+		ExecStoreTuple(tuple,					/* tuple to store */
+					   slot,					/* slot to store in */
+					   scandesc->rs_cbuf,		/* buffer associated with
 												 * this tuple */
-						  false);		/* don't pfree this pointer */
+					   false);		/* don't pfree this pointer */
 
 	return slot;
 }
diff --git a/src/backend/executor/nodeSetOp.c b/src/backend/executor/nodeSetOp.c
index 2ecd2fda9f8d4facbd350a51a32279fb838d3a65..a0de4db74d4e5aa60bd2ac03b53eec53b9976a7b 100644
--- a/src/backend/executor/nodeSetOp.c
+++ b/src/backend/executor/nodeSetOp.c
@@ -21,7 +21,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeSetOp.c,v 1.15 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeSetOp.c,v 1.16 2005/03/16 21:38:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -49,14 +49,12 @@ ExecSetOp(SetOpState *node)
 	SetOp	   *plannode = (SetOp *) node->ps.plan;
 	TupleTableSlot *resultTupleSlot;
 	PlanState  *outerPlan;
-	TupleDesc	tupDesc;
 
 	/*
 	 * get information from the node
 	 */
 	outerPlan = outerPlanState(node);
 	resultTupleSlot = node->ps.ps_ResultTupleSlot;
-	tupDesc = ExecGetResultType(&node->ps);
 
 	/*
 	 * If the previously-returned tuple needs to be returned more than
@@ -105,11 +103,7 @@ ExecSetOp(SetOpState *node)
 			 */
 			if (node->subplan_done)
 				return NULL;	/* no more tuples */
-			ExecStoreTuple(heap_copytuple(inputTupleSlot->val),
-						   resultTupleSlot,
-						   InvalidBuffer,
-						   true);		/* free copied tuple at
-										 * ExecClearTuple */
+			ExecCopySlot(resultTupleSlot, inputTupleSlot);
 			node->numLeft = 0;
 			node->numRight = 0;
 			endOfGroup = false;
@@ -127,9 +121,8 @@ ExecSetOp(SetOpState *node)
 			 * Else test if the new tuple and the previously saved tuple
 			 * match.
 			 */
-			if (execTuplesMatch(inputTupleSlot->val,
-								resultTupleSlot->val,
-								tupDesc,
+			if (execTuplesMatch(inputTupleSlot,
+								resultTupleSlot,
 								plannode->numCols, plannode->dupColIdx,
 								node->eqfunctions,
 								node->tempContext))
@@ -189,9 +182,8 @@ ExecSetOp(SetOpState *node)
 			int			flag;
 			bool		isNull;
 
-			flag = DatumGetInt32(heap_getattr(inputTupleSlot->val,
+			flag = DatumGetInt32(slot_getattr(inputTupleSlot,
 											  plannode->flagColIdx,
-											  tupDesc,
 											  &isNull));
 			Assert(!isNull);
 			if (flag)
diff --git a/src/backend/executor/nodeSort.c b/src/backend/executor/nodeSort.c
index 2823ba7fcd317e989a15d42ba23e3ae173ab7c1d..ef0253741493fd7fa46c4e83d6343da6ae671a75 100644
--- a/src/backend/executor/nodeSort.c
+++ b/src/backend/executor/nodeSort.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeSort.c,v 1.49 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeSort.c,v 1.50 2005/03/16 21:38:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -104,7 +104,8 @@ ExecSort(SortState *node)
 			if (TupIsNull(slot))
 				break;
 
-			tuplesort_puttuple(tuplesortstate, (void *) slot->val);
+			tuplesort_puttuple(tuplesortstate,
+							   (void *) ExecFetchSlotTuple(slot));
 		}
 
 		/*
@@ -136,7 +137,10 @@ ExecSort(SortState *node)
 									   &should_free);
 
 	slot = node->ss.ps.ps_ResultTupleSlot;
-	return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
+	if (heapTuple)
+		return ExecStoreTuple(heapTuple, slot, InvalidBuffer, should_free);
+	else
+		return ExecClearTuple(slot);
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 3dcd03db0728799b29addf022718d68ba051f1bc..7d40fe8b1ed9f70aad6398c8cba9f2a597bbdb24 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.66 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.67 2005/03/16 21:38:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -37,7 +37,8 @@ static Datum ExecScanSubPlan(SubPlanState *node,
 				bool *isNull);
 static void buildSubPlanHash(SubPlanState *node);
 static bool findPartialMatch(TupleHashTable hashtable, TupleTableSlot *slot);
-static bool tupleAllNulls(HeapTuple tuple);
+static bool slotAllNulls(TupleTableSlot *slot);
+static bool slotNoNulls(TupleTableSlot *slot);
 
 
 /* ----------------------------------------------------------------
@@ -78,7 +79,6 @@ ExecHashSubPlan(SubPlanState *node,
 	PlanState  *planstate = node->planstate;
 	ExprContext *innerecontext = node->innerecontext;
 	TupleTableSlot *slot;
-	HeapTuple	tup;
 
 	/* Shouldn't have any direct correlation Vars */
 	if (subplan->parParam != NIL || node->args != NIL)
@@ -105,7 +105,6 @@ ExecHashSubPlan(SubPlanState *node,
 	 */
 	node->projLeft->pi_exprContext = econtext;
 	slot = ExecProject(node->projLeft, NULL);
-	tup = slot->val;
 
 	/*
 	 * Note: because we are typically called in a per-tuple context, we
@@ -137,7 +136,7 @@ ExecHashSubPlan(SubPlanState *node,
 	 * comparison we will not even make, unless there's a chance match of
 	 * hash keys.
 	 */
-	if (HeapTupleNoNulls(tup))
+	if (slotNoNulls(slot))
 	{
 		if (node->havehashrows &&
 			LookupTupleHashEntry(node->hashtable, slot, NULL) != NULL)
@@ -171,7 +170,7 @@ ExecHashSubPlan(SubPlanState *node,
 		ExecClearTuple(slot);
 		return BoolGetDatum(false);
 	}
-	if (tupleAllNulls(tup))
+	if (slotAllNulls(slot))
 	{
 		ExecClearTuple(slot);
 		*isNull = true;
@@ -271,8 +270,7 @@ ExecScanSubPlan(SubPlanState *node,
 		 !TupIsNull(slot);
 		 slot = ExecProcNode(planstate))
 	{
-		HeapTuple	tup = slot->val;
-		TupleDesc	tdesc = slot->ttc_tupleDescriptor;
+		TupleDesc	tdesc = slot->tts_tupleDescriptor;
 		Datum		rowresult = BoolGetDatum(!useOr);
 		bool		rownull = false;
 		int			col = 1;
@@ -303,13 +301,12 @@ ExecScanSubPlan(SubPlanState *node,
 			 * copied tuple for eventual freeing.
 			 */
 			MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-			tup = heap_copytuple(tup);
 			if (node->curTuple)
 				heap_freetuple(node->curTuple);
-			node->curTuple = tup;
+			node->curTuple = ExecCopySlotTuple(slot);
 			MemoryContextSwitchTo(node->sub_estate->es_query_cxt);
 
-			result = heap_getattr(tup, col, tdesc, isNull);
+			result = heap_getattr(node->curTuple, col, tdesc, isNull);
 			/* keep scanning subplan to make sure there's only one tuple */
 			continue;
 		}
@@ -321,7 +318,7 @@ ExecScanSubPlan(SubPlanState *node,
 
 			found = true;
 			/* stash away current value */
-			dvalue = heap_getattr(tup, 1, tdesc, &disnull);
+			dvalue = slot_getattr(slot, 1, &disnull);
 			astate = accumArrayResult(astate, dvalue, disnull,
 									  tdesc->attrs[0]->atttypid,
 									  oldcontext);
@@ -357,7 +354,7 @@ ExecScanSubPlan(SubPlanState *node,
 			 */
 			prmdata = &(econtext->ecxt_param_exec_vals[paramid]);
 			Assert(prmdata->execPlan == NULL);
-			prmdata->value = heap_getattr(tup, col, tdesc,
+			prmdata->value = slot_getattr(slot, col,
 										  &(prmdata->isnull));
 
 			/*
@@ -554,8 +551,6 @@ buildSubPlanHash(SubPlanState *node)
 		 !TupIsNull(slot);
 		 slot = ExecProcNode(planstate))
 	{
-		HeapTuple	tup = slot->val;
-		TupleDesc	tdesc = slot->ttc_tupleDescriptor;
 		int			col = 1;
 		ListCell   *plst;
 		bool		isnew;
@@ -571,20 +566,16 @@ buildSubPlanHash(SubPlanState *node)
 
 			prmdata = &(innerecontext->ecxt_param_exec_vals[paramid]);
 			Assert(prmdata->execPlan == NULL);
-			prmdata->value = heap_getattr(tup, col, tdesc,
+			prmdata->value = slot_getattr(slot, col,
 										  &(prmdata->isnull));
 			col++;
 		}
 		slot = ExecProject(node->projRight, NULL);
-		tup = slot->val;
 
 		/*
 		 * If result contains any nulls, store separately or not at all.
-		 * (Since we know the projection tuple has no junk columns, we can
-		 * just look at the overall hasnull info bit, instead of groveling
-		 * through the columns.)
 		 */
-		if (HeapTupleNoNulls(tup))
+		if (slotNoNulls(slot))
 		{
 			(void) LookupTupleHashEntry(node->hashtable, slot, &isnew);
 			node->havehashrows = true;
@@ -606,7 +597,8 @@ buildSubPlanHash(SubPlanState *node)
 	 * Since the projected tuples are in the sub-query's context and not
 	 * the main context, we'd better clear the tuple slot before there's
 	 * any chance of a reset of the sub-query's context.  Else we will
-	 * have the potential for a double free attempt.
+	 * have the potential for a double free attempt.  (XXX possibly
+	 * no longer needed, but can't hurt.)
 	 */
 	ExecClearTuple(node->projRight->pi_slot);
 
@@ -626,17 +618,15 @@ findPartialMatch(TupleHashTable hashtable, TupleTableSlot *slot)
 {
 	int			numCols = hashtable->numCols;
 	AttrNumber *keyColIdx = hashtable->keyColIdx;
-	HeapTuple	tuple = slot->val;
-	TupleDesc	tupdesc = slot->ttc_tupleDescriptor;
 	TupleHashIterator hashiter;
 	TupleHashEntry entry;
 
 	ResetTupleHashIterator(hashtable, &hashiter);
 	while ((entry = ScanTupleHashTable(&hashiter)) != NULL)
 	{
-		if (!execTuplesUnequal(entry->firstTuple,
-							   tuple,
-							   tupdesc,
+		ExecStoreTuple(entry->firstTuple, hashtable->tableslot,
+					   InvalidBuffer, false);
+		if (!execTuplesUnequal(hashtable->tableslot, slot,
 							   numCols, keyColIdx,
 							   hashtable->eqfunctions,
 							   hashtable->tempcxt))
@@ -646,17 +636,40 @@ findPartialMatch(TupleHashTable hashtable, TupleTableSlot *slot)
 }
 
 /*
- * tupleAllNulls: is the tuple completely NULL?
+ * slotAllNulls: is the slot completely NULL?
+ *
+ * This does not test for dropped columns, which is OK because we only
+ * use it on projected tuples.
+ */
+static bool
+slotAllNulls(TupleTableSlot *slot)
+{
+	int			ncols = slot->tts_tupleDescriptor->natts;
+	int			i;
+
+	for (i = 1; i <= ncols; i++)
+	{
+		if (!slot_attisnull(slot, i))
+			return false;
+	}
+	return true;
+}
+
+/*
+ * slotNoNulls: is the slot entirely not NULL?
+ *
+ * This does not test for dropped columns, which is OK because we only
+ * use it on projected tuples.
  */
 static bool
-tupleAllNulls(HeapTuple tuple)
+slotNoNulls(TupleTableSlot *slot)
 {
-	int			ncols = tuple->t_data->t_natts;
+	int			ncols = slot->tts_tupleDescriptor->natts;
 	int			i;
 
 	for (i = 1; i <= ncols; i++)
 	{
-		if (!heap_attisnull(tuple, i))
+		if (slot_attisnull(slot, i))
 			return false;
 	}
 	return true;
@@ -932,8 +945,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 		 !TupIsNull(slot);
 		 slot = ExecProcNode(planstate))
 	{
-		HeapTuple	tup = slot->val;
-		TupleDesc	tdesc = slot->ttc_tupleDescriptor;
+		TupleDesc	tdesc = slot->tts_tupleDescriptor;
 		int			i = 1;
 
 		if (subLinkType == EXISTS_SUBLINK)
@@ -956,7 +968,7 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 
 			found = true;
 			/* stash away current value */
-			dvalue = heap_getattr(tup, 1, tdesc, &disnull);
+			dvalue = slot_getattr(slot, 1, &disnull);
 			astate = accumArrayResult(astate, dvalue, disnull,
 									  tdesc->attrs[0]->atttypid,
 									  oldcontext);
@@ -981,10 +993,9 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 		 * freeing.
 		 */
 		MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-		tup = heap_copytuple(tup);
 		if (node->curTuple)
 			heap_freetuple(node->curTuple);
-		node->curTuple = tup;
+		node->curTuple = ExecCopySlotTuple(slot);
 		MemoryContextSwitchTo(node->sub_estate->es_query_cxt);
 
 		/*
@@ -996,7 +1007,8 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext)
 			ParamExecData *prm = &(econtext->ecxt_param_exec_vals[paramid]);
 
 			prm->execPlan = NULL;
-			prm->value = heap_getattr(tup, i, tdesc, &(prm->isnull));
+			prm->value = heap_getattr(node->curTuple, i, tdesc,
+									  &(prm->isnull));
 			i++;
 		}
 	}
diff --git a/src/backend/executor/nodeUnique.c b/src/backend/executor/nodeUnique.c
index dbd628ba8781853063131490f014a664abfb4b7f..10ffddd5cdfbaf4b62d347da0e9093523e2d146d 100644
--- a/src/backend/executor/nodeUnique.c
+++ b/src/backend/executor/nodeUnique.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/nodeUnique.c,v 1.45 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/nodeUnique.c,v 1.46 2005/03/16 21:38:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -44,14 +44,12 @@ ExecUnique(UniqueState *node)
 	TupleTableSlot *resultTupleSlot;
 	TupleTableSlot *slot;
 	PlanState  *outerPlan;
-	TupleDesc	tupDesc;
 
 	/*
 	 * get information from the node
 	 */
 	outerPlan = outerPlanState(node);
 	resultTupleSlot = node->ps.ps_ResultTupleSlot;
-	tupDesc = ExecGetResultType(&node->ps);
 
 	/*
 	 * now loop, returning only non-duplicate tuples. We assume that the
@@ -59,7 +57,7 @@ ExecUnique(UniqueState *node)
 	 *
 	 * We return the first tuple from each group of duplicates (or the last
 	 * tuple of each group, when moving backwards).  At either end of the
-	 * subplan, clear priorTuple so that we correctly return the
+	 * subplan, clear the result slot so that we correctly return the
 	 * first/last tuple when reversing direction.
 	 */
 	for (;;)
@@ -71,16 +69,14 @@ ExecUnique(UniqueState *node)
 		if (TupIsNull(slot))
 		{
 			/* end of subplan; reset in case we change direction */
-			if (node->priorTuple != NULL)
-				heap_freetuple(node->priorTuple);
-			node->priorTuple = NULL;
+			ExecClearTuple(resultTupleSlot);
 			return NULL;
 		}
 
 		/*
 		 * Always return the first/last tuple from the subplan.
 		 */
-		if (node->priorTuple == NULL)
+		if (TupIsNull(resultTupleSlot))
 			break;
 
 		/*
@@ -88,8 +84,7 @@ ExecUnique(UniqueState *node)
 		 * match.  If so then we loop back and fetch another new tuple
 		 * from the subplan.
 		 */
-		if (!execTuplesMatch(slot->val, node->priorTuple,
-							 tupDesc,
+		if (!execTuplesMatch(slot, resultTupleSlot,
 							 plannode->numCols, plannode->uniqColIdx,
 							 node->eqfunctions,
 							 node->tempContext))
@@ -101,28 +96,8 @@ ExecUnique(UniqueState *node)
 	 * any). Save it and return it.  We must copy it because the source
 	 * subplan won't guarantee that this source tuple is still accessible
 	 * after fetching the next source tuple.
-	 *
-	 * Note that we manage the copy ourselves.	We can't rely on the result
-	 * tuple slot to maintain the tuple reference because our caller may
-	 * replace the slot contents with a different tuple.  We assume that
-	 * the caller will no longer be interested in the current tuple after
-	 * he next calls us.
-	 *
-	 * tgl 3/2004: the above concern is no longer valid; junkfilters used to
-	 * modify their input's return slot but don't anymore, and I don't
-	 * think anyplace else does either.  Not worth changing this code
-	 * though.
 	 */
-	if (node->priorTuple != NULL)
-		heap_freetuple(node->priorTuple);
-	node->priorTuple = heap_copytuple(slot->val);
-
-	ExecStoreTuple(node->priorTuple,
-				   resultTupleSlot,
-				   InvalidBuffer,
-				   false);		/* tuple does not belong to slot */
-
-	return resultTupleSlot;
+	return ExecCopySlot(resultTupleSlot, slot);
 }
 
 /* ----------------------------------------------------------------
@@ -144,8 +119,6 @@ ExecInitUnique(Unique *node, EState *estate)
 	uniquestate->ps.plan = (Plan *) node;
 	uniquestate->ps.state = estate;
 
-	uniquestate->priorTuple = NULL;
-
 	/*
 	 * Miscellaneous initialization
 	 *
@@ -220,12 +193,8 @@ ExecEndUnique(UniqueState *node)
 void
 ExecReScanUnique(UniqueState *node, ExprContext *exprCtxt)
 {
+	/* must clear result tuple so first input tuple is returned */
 	ExecClearTuple(node->ps.ps_ResultTupleSlot);
-	if (node->priorTuple != NULL)
-	{
-		heap_freetuple(node->priorTuple);
-		node->priorTuple = NULL;
-	}
 
 	/*
 	 * if chgParam of subnode is not null then plan will be re-scanned by
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index fd860fcb5514e69f7d91a353bfb41a5d058547d0..d26e3068509020ce36ea05a79d2c1f1ecf899652 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.134 2005/02/10 20:36:27 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.135 2005/03/16 21:38:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1191,7 +1191,7 @@ spi_dest_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
  *		of current SPI procedure
  */
 void
-spi_printtup(HeapTuple tuple, TupleDesc tupdesc, DestReceiver *self)
+spi_printtup(TupleTableSlot *slot, DestReceiver *self)
 {
 	SPITupleTable *tuptable;
 	MemoryContext oldcxt;
@@ -1219,7 +1219,8 @@ spi_printtup(HeapTuple tuple, TupleDesc tupdesc, DestReceiver *self)
 								  tuptable->alloced * sizeof(HeapTuple));
 	}
 
-	tuptable->vals[tuptable->alloced - tuptable->free] = heap_copytuple(tuple);
+	tuptable->vals[tuptable->alloced - tuptable->free] =
+		ExecCopySlotTuple(slot);
 	(tuptable->free)--;
 
 	MemoryContextSwitchTo(oldcxt);
diff --git a/src/backend/executor/tstoreReceiver.c b/src/backend/executor/tstoreReceiver.c
index 5cf9d0154481209fba7c195331cb306a29f52952..819fa962b0441c2604be52c025e98d2bd84dc5be 100644
--- a/src/backend/executor/tstoreReceiver.c
+++ b/src/backend/executor/tstoreReceiver.c
@@ -9,7 +9,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/executor/tstoreReceiver.c,v 1.13 2004/12/31 21:59:45 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/tstoreReceiver.c,v 1.14 2005/03/16 21:38:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -40,12 +40,12 @@ tstoreStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo)
  * Receive a tuple from the executor and store it in the tuplestore.
  */
 static void
-tstoreReceiveTuple(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
+tstoreReceiveSlot(TupleTableSlot *slot, DestReceiver *self)
 {
 	TStoreState *myState = (TStoreState *) self;
 	MemoryContext oldcxt = MemoryContextSwitchTo(myState->cxt);
 
-	tuplestore_puttuple(myState->tstore, tuple);
+	tuplestore_puttuple(myState->tstore, ExecFetchSlotTuple(slot));
 
 	MemoryContextSwitchTo(oldcxt);
 }
@@ -77,7 +77,7 @@ CreateTuplestoreDestReceiver(Tuplestorestate *tStore,
 {
 	TStoreState *self = (TStoreState *) palloc(sizeof(TStoreState));
 
-	self->pub.receiveTuple = tstoreReceiveTuple;
+	self->pub.receiveSlot = tstoreReceiveSlot;
 	self->pub.rStartup = tstoreStartupReceiver;
 	self->pub.rShutdown = tstoreShutdownReceiver;
 	self->pub.rDestroy = tstoreDestroyReceiver;
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index 2b6d320344099bd934a834b1271b39e22985df98..e62d731a6cfd5627d541c2bc92c0319889cc5366 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/nodes/print.c,v 1.72 2004/12/31 21:59:55 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/nodes/print.c,v 1.73 2005/03/16 21:38:08 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -468,18 +468,18 @@ print_tl(List *tlist, List *rtable)
 void
 print_slot(TupleTableSlot *slot)
 {
-	if (!slot->val)
+	if (TupIsNull(slot))
 	{
 		printf("tuple is null.\n");
 		return;
 	}
-	if (!slot->ttc_tupleDescriptor)
+	if (!slot->tts_tupleDescriptor)
 	{
 		printf("no tuple descriptor.\n");
 		return;
 	}
 
-	debugtup(slot->val, slot->ttc_tupleDescriptor, NULL);
+	debugtup(slot, NULL);
 }
 
 static char *
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index ca741cd2b3a797b434efb3f23bfaaef8c3c4b4b2..2b60c2c46e365b72128fade196627a622c3643d9 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/dest.c,v 1.64 2004/12/31 22:01:16 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/dest.c,v 1.65 2005/03/16 21:38:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -41,7 +41,7 @@
  * ----------------
  */
 static void
-donothingReceive(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
+donothingReceive(TupleTableSlot *slot, DestReceiver *self)
 {
 }
 
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index d4d5b945946c8f4b00d7e774b8c2c8459138aee4..1f66fda2b7291707b07e1b9d65bd5d2952593a56 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.91 2005/02/10 20:36:28 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/tcop/pquery.c,v 1.92 2005/03/16 21:38:08 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -836,6 +836,9 @@ RunFromStore(Portal portal, ScanDirection direction, long count,
 			 DestReceiver *dest)
 {
 	long		current_tuple_count = 0;
+	TupleTableSlot *slot;
+
+	slot = MakeSingleTupleTableSlot(portal->tupDesc);
 
 	(*dest->rStartup) (dest, CMD_SELECT, portal->tupDesc);
 
@@ -863,10 +866,11 @@ RunFromStore(Portal portal, ScanDirection direction, long count,
 			if (tup == NULL)
 				break;
 
-			(*dest->receiveTuple) (tup, portal->tupDesc, dest);
+			ExecStoreTuple(tup, slot, InvalidBuffer, should_free);
+
+			(*dest->receiveSlot) (slot, dest);
 
-			if (should_free)
-				pfree(tup);
+			ExecClearTuple(slot);
 
 			/*
 			 * check our tuple count.. if we've processed the proper
@@ -881,6 +885,8 @@ RunFromStore(Portal portal, ScanDirection direction, long count,
 
 	(*dest->rShutdown) (dest);
 
+	ExecDropSingleTupleTableSlot(slot);
+
 	return (uint32) current_tuple_count;
 }
 
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 3b73d5ea8a1a8abf28c360878a67da37eb46b07b..5c58f32c43e7d983a3e475bde042e86561fcb9bb 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.95 2005/03/14 04:41:13 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.96 2005/03/16 21:38:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -178,27 +178,42 @@ extern XLogRecPtr log_heap_move(Relation reln, Buffer oldbuf,
 			  Buffer newbuf, HeapTuple newtup);
 
 /* in common/heaptuple.c */
+extern Size heap_compute_data_size(TupleDesc tupleDesc,
+								   Datum *values, bool *isnull);
 extern Size ComputeDataSize(TupleDesc tupleDesc, Datum *values, char *nulls);
+extern void heap_fill_tuple(TupleDesc tupleDesc,
+							Datum *values, bool *isnull,
+							char *data, uint16 *infomask, bits8 *bit);
 extern void DataFill(char *data, TupleDesc tupleDesc,
 		 Datum *values, char *nulls, uint16 *infomask,
 		 bits8 *bit);
-extern int	heap_attisnull(HeapTuple tup, int attnum);
+extern bool heap_attisnull(HeapTuple tup, int attnum);
 extern Datum nocachegetattr(HeapTuple tup, int attnum,
 			   TupleDesc att, bool *isnull);
 extern Datum heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
 				bool *isnull);
 extern HeapTuple heap_copytuple(HeapTuple tuple);
 extern void heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest);
+extern HeapTuple heap_form_tuple(TupleDesc tupleDescriptor,
+			   Datum *values, bool *isnull);
 extern HeapTuple heap_formtuple(TupleDesc tupleDescriptor,
 			   Datum *values, char *nulls);
+extern HeapTuple heap_modify_tuple(HeapTuple tuple,
+				 TupleDesc tupleDesc,
+				 Datum *replValues,
+				 bool *replIsnull,
+				 bool *doReplace);
 extern HeapTuple heap_modifytuple(HeapTuple tuple,
 				 TupleDesc tupleDesc,
 				 Datum *replValues,
 				 char *replNulls,
 				 char *replActions);
+extern void heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
+				 Datum *values, bool *isnull);
 extern void heap_deformtuple(HeapTuple tuple, TupleDesc tupleDesc,
 				 Datum *values, char *nulls);
 extern void heap_freetuple(HeapTuple tuple);
-extern HeapTuple heap_addheader(int natts, bool withoid, Size structlen, void *structure);
+extern HeapTuple heap_addheader(int natts, bool withoid,
+								Size structlen, void *structure);
 
 #endif   /* HEAPAM_H */
diff --git a/src/include/access/printtup.h b/src/include/access/printtup.h
index 7e534d41bf89036ff189d01f8f9bf41b73bb63ba..aa7989f98f0874bf04f98f89d99c9e620469b782 100644
--- a/src/include/access/printtup.h
+++ b/src/include/access/printtup.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/access/printtup.h,v 1.32 2004/12/31 22:03:21 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/access/printtup.h,v 1.33 2005/03/16 21:38:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -23,13 +23,11 @@ extern void SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist,
 
 extern void debugStartup(DestReceiver *self, int operation,
 			 TupleDesc typeinfo);
-extern void debugtup(HeapTuple tuple, TupleDesc typeinfo,
-		 DestReceiver *self);
+extern void debugtup(TupleTableSlot *slot, DestReceiver *self);
 
 /* XXX these are really in executor/spi.c */
 extern void spi_dest_startup(DestReceiver *self, int operation,
 				 TupleDesc typeinfo);
-extern void spi_printtup(HeapTuple tuple, TupleDesc typeinfo,
-			 DestReceiver *self);
+extern void spi_printtup(TupleTableSlot *slot, DestReceiver *self);
 
 #endif   /* PRINTTUP_H */
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 86ca9e0e8b2235dc67abf8cf6e89fb2e4f79a3ac..b7484f4450b2d238d542849d917911d2e5d93cea 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.59 2004/12/31 22:03:24 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/index.h,v 1.60 2005/03/16 21:38:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -46,8 +46,7 @@ extern void index_drop(Oid indexId);
 extern IndexInfo *BuildIndexInfo(Relation index);
 
 extern void FormIndexDatum(IndexInfo *indexInfo,
-			   HeapTuple heapTuple,
-			   TupleDesc heapDescriptor,
+			   TupleTableSlot *slot,
 			   EState *estate,
 			   Datum *datum,
 			   char *nullv);
diff --git a/src/include/executor/execdebug.h b/src/include/executor/execdebug.h
index 9d846042b676ba952706da885e6f14e6339c33a9..58a987107de5f56fbdb91a9332b06cd215a9c5e6 100644
--- a/src/include/executor/execdebug.h
+++ b/src/include/executor/execdebug.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/execdebug.h,v 1.25 2004/12/31 22:03:29 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/executor/execdebug.h,v 1.26 2005/03/16 21:38:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -256,7 +256,7 @@ extern int	NIndexTupleInserted;
 #define MJ_printf(s)					printf(s)
 #define MJ1_printf(s, p)				printf(s, p)
 #define MJ2_printf(s, p1, p2)			printf(s, p1, p2)
-#define MJ_debugtup(tuple, type)		debugtup(tuple, type, NULL)
+#define MJ_debugtup(slot)				debugtup(slot, NULL)
 #define MJ_dump(state)					ExecMergeTupleDump(state)
 #define MJ_DEBUG_QUAL(clause, res) \
   MJ2_printf("  ExecQual(%s, econtext) returns %s\n", \
@@ -276,7 +276,7 @@ extern int	NIndexTupleInserted;
 #define MJ_printf(s)
 #define MJ1_printf(s, p)
 #define MJ2_printf(s, p1, p2)
-#define MJ_debugtup(tuple, type)
+#define MJ_debugtup(slot)
 #define MJ_dump(state)
 #define MJ_DEBUG_QUAL(clause, res)
 #define MJ_DEBUG_MERGE_COMPARE(qual, res)
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 8eb357636ad763d4fcf287388eefd6676091b105..0d3e18ce0ac41291c0458fed5028649a35146807 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.116 2005/03/14 04:41:13 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.117 2005/03/16 21:38:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,17 +17,6 @@
 #include "executor/execdesc.h"
 
 
-/*
- * TupIsNull
- *
- *		This is used mainly to detect when there are no more
- *		tuples to process.
- */
-/* return: true if tuple in slot is NULL, slot is slot to test */
-#define TupIsNull(slot) \
-	((slot) == NULL || (slot)->val == NULL)
-
-
 /*
  * ExecEvalExpr was formerly a function containing a switch statement;
  * now it's just a macro invoking the function pointed to by an ExprState
@@ -50,20 +39,18 @@ extern bool ExecMayReturnRawTuples(PlanState *node);
 /*
  * prototypes from functions in execGrouping.c
  */
-extern bool execTuplesMatch(HeapTuple tuple1,
-				HeapTuple tuple2,
-				TupleDesc tupdesc,
-				int numCols,
-				AttrNumber *matchColIdx,
-				FmgrInfo *eqfunctions,
-				MemoryContext evalContext);
-extern bool execTuplesUnequal(HeapTuple tuple1,
-				  HeapTuple tuple2,
-				  TupleDesc tupdesc,
-				  int numCols,
-				  AttrNumber *matchColIdx,
-				  FmgrInfo *eqfunctions,
-				  MemoryContext evalContext);
+extern bool execTuplesMatch(TupleTableSlot *slot1,
+							TupleTableSlot *slot2,
+							int numCols,
+							AttrNumber *matchColIdx,
+							FmgrInfo *eqfunctions,
+							MemoryContext evalContext);
+extern bool execTuplesUnequal(TupleTableSlot *slot1,
+							  TupleTableSlot *slot2,
+							  int numCols,
+							  AttrNumber *matchColIdx,
+							  FmgrInfo *eqfunctions,
+							  MemoryContext evalContext);
 extern FmgrInfo *execTuplesMatchPrepare(TupleDesc tupdesc,
 					   int numCols,
 					   AttrNumber *matchColIdx);
@@ -92,6 +79,8 @@ extern JunkFilter *ExecInitJunkFilterConversion(List *targetList,
 												TupleTableSlot *slot);
 extern bool ExecGetJunkAttribute(JunkFilter *junkfilter, TupleTableSlot *slot,
 					 char *attrName, Datum *value, bool *isNull);
+extern TupleTableSlot *ExecFilterJunk(JunkFilter *junkfilter,
+									  TupleTableSlot *slot);
 extern HeapTuple ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot);
 
 
@@ -158,17 +147,6 @@ extern void ExecAssignScanProjectionInfo(ScanState *node);
 /*
  * prototypes from functions in execTuples.c
  */
-extern TupleTable ExecCreateTupleTable(int tableSize);
-extern void ExecDropTupleTable(TupleTable table, bool shouldFree);
-extern TupleTableSlot *MakeTupleTableSlot(void);
-extern TupleTableSlot *ExecAllocTableSlot(TupleTable table);
-extern TupleTableSlot *ExecStoreTuple(HeapTuple tuple,
-			   TupleTableSlot *slot,
-			   Buffer buffer,
-			   bool shouldFree);
-extern TupleTableSlot *ExecClearTuple(TupleTableSlot *slot);
-extern void ExecSetSlotDescriptor(TupleTableSlot *slot,
-					  TupleDesc tupdesc, bool shouldFree);
 extern void ExecInitResultTupleSlot(EState *estate, PlanState *planstate);
 extern void ExecInitScanTupleSlot(EState *estate, ScanState *scanstate);
 extern TupleTableSlot *ExecInitExtraTupleSlot(EState *estate);
@@ -183,6 +161,7 @@ typedef struct TupOutputState
 {
 	/* use "struct" here to allow forward reference */
 	struct AttInMetadata *metadata;
+	TupleTableSlot *slot;
 	DestReceiver *dest;
 } TupOutputState;
 
diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h
index 90791366ffb20a60921d46859c0665fd2333e56f..a5193683a28e113897f85b0d97562d0c15fc2bc9 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/tuptable.h,v 1.27 2005/03/14 04:41:13 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/executor/tuptable.h,v 1.28 2005/03/16 21:38:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,43 +17,86 @@
 #include "access/htup.h"
 
 
-/*
- * The executor stores pointers to tuples in a "tuple table"
- * which is composed of TupleTableSlots.  Sometimes the tuples
- * are pointers to buffer pages, while others are pointers to
- * palloc'ed memory; the shouldFree variable tells us whether
- * we may call pfree() on a tuple.  When shouldFree is true,
- * the tuple is "owned" by the TupleTableSlot and should be
- * freed when the slot's reference to the tuple is dropped.
+/*----------
+ * The executor stores tuples in a "tuple table" which is composed of
+ * independent TupleTableSlots.  There are several cases we need to handle:
+ *		1. physical tuple in a disk buffer page
+ *		2. physical tuple constructed in palloc'ed memory
+ *		3. "virtual" tuple consisting of Datum/isnull arrays
+ *
+ * The first two cases are similar in that they both deal with "materialized"
+ * tuples, but resource management is different.  For a tuple in a disk page
+ * we need to hold a pin on the buffer until the TupleTableSlot's reference
+ * to the tuple is dropped; while for a palloc'd tuple we usually want the
+ * tuple pfree'd when the TupleTableSlot's reference is dropped.
+ *
+ * A "virtual" tuple is an optimization used to minimize physical data
+ * copying in a nest of plan nodes.  Any pass-by-reference Datums in the
+ * tuple point to storage that is not directly associated with the
+ * TupleTableSlot; generally they will point to part of a tuple stored in
+ * a lower plan node's output TupleTableSlot, or to a function result
+ * constructed in a plan node's per-tuple econtext.  It is the responsibility
+ * of the generating plan node to be sure these resources are not released
+ * for as long as the virtual tuple needs to be valid.  We only use virtual
+ * tuples in the result slots of plan nodes --- tuples to be copied anywhere
+ * else need to be "materialized" into physical tuples.  Note also that a
+ * virtual tuple does not have any "system columns".
+ *
+ * The Datum/isnull arrays of a TupleTableSlot serve double duty.  When the
+ * slot contains a virtual tuple, they are the authoritative data.  When the
+ * slot contains a physical tuple, the arrays contain data extracted from
+ * the tuple.  (In this state, any pass-by-reference Datums point into
+ * the physical tuple.)  The extracted information is built "lazily",
+ * ie, only as needed.  This serves to avoid repeated extraction of data
+ * from the physical tuple.
+ *
+ * A TupleTableSlot can also be "empty", holding no valid data.  This is
+ * the only valid state for a freshly-created slot that has not yet had a
+ * tuple descriptor assigned to it.  In this state, tts_isempty must be
+ * TRUE, tts_shouldFree FALSE, tts_tuple NULL, tts_buffer InvalidBuffer,
+ * and tts_nvalid zero.
+ *
+ * When tts_shouldFree is true, the physical tuple is "owned" by the slot
+ * and should be freed when the slot's reference to the tuple is dropped.
  *
- * shouldFreeDesc is similar to shouldFree: if it's true, then the
+ * tts_shouldFreeDesc is similar to tts_shouldFree: if it's true, then the
  * tupleDescriptor is "owned" by the TupleTableSlot and should be
  * freed when the slot's reference to the descriptor is dropped.
  *
- * If buffer is not InvalidBuffer, then the slot is holding a pin
+ * If tts_buffer is not InvalidBuffer, then the slot is holding a pin
  * on the indicated buffer page; drop the pin when we release the
- * slot's reference to that buffer.  (shouldFree should always be
- * false in such a case, since presumably val is pointing at the
+ * slot's reference to that buffer.  (tts_shouldFree should always be
+ * false in such a case, since presumably tts_tuple is pointing at the
  * buffer page.)
  *
- * The slot_getattr() routine allows extraction of attribute values from
- * a TupleTableSlot's current tuple.  It is equivalent to heap_getattr()
- * except that it can optimize fetching of multiple values more efficiently.
- * The cache_xxx fields of TupleTableSlot are support for slot_getattr().
+ * tts_nvalid indicates the number of valid columns in the tts_values/isnull
+ * arrays.  When the slot is holding a "virtual" tuple this must be equal
+ * to the descriptor's natts.  When the slot is holding a physical tuple
+ * this is equal to the number of columns we have extracted (we always
+ * extract columns from left to right, so there are no holes).
+ *
+ * tts_values/tts_isnull are allocated when a descriptor is assigned to the
+ * slot; they are of length equal to the descriptor's natts.
+ *
+ * tts_slow/tts_off are saved state for slot_deform_tuple, and should not
+ * be touched by any other code.
+ *----------
  */
 typedef struct TupleTableSlot
 {
 	NodeTag		type;			/* vestigial ... allows IsA tests */
-	HeapTuple	val;			/* current tuple, or NULL if none */
-	TupleDesc	ttc_tupleDescriptor;	/* tuple's descriptor */
-	bool		ttc_shouldFree;			/* should pfree tuple? */
-	bool		ttc_shouldFreeDesc;		/* should pfree descriptor? */
-	Buffer		ttc_buffer;		/* tuple's buffer, or InvalidBuffer */
-	MemoryContext ttc_mcxt;		/* slot itself is in this context */
-	Datum	   *cache_values;	/* currently extracted values */
-	int			cache_natts;	/* # of valid values in cache_values */
-	bool		cache_slow;		/* saved state for slot_getattr */
-	long		cache_off;		/* saved state for slot_getattr */
+	bool		tts_isempty;			/* true = slot is empty */
+	bool		tts_shouldFree;			/* should pfree tuple? */
+	bool		tts_shouldFreeDesc;		/* should pfree descriptor? */
+	bool		tts_slow;		/* saved state for slot_deform_tuple */
+	HeapTuple	tts_tuple;		/* physical tuple, or NULL if none */
+	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 */
+	long		tts_off;		/* saved state for slot_deform_tuple */
 } TupleTableSlot;
 
 /*
@@ -69,7 +112,36 @@ typedef struct TupleTableData
 typedef TupleTableData *TupleTable;
 
 
+/*
+ * TupIsNull -- is a TupleTableSlot empty?
+ */
+#define TupIsNull(slot) \
+	((slot) == NULL || (slot)->tts_isempty)
+
+/* in executor/execTuples.c */
+extern TupleTable ExecCreateTupleTable(int tableSize);
+extern void ExecDropTupleTable(TupleTable table, bool shouldFree);
+extern TupleTableSlot *MakeSingleTupleTableSlot(TupleDesc tupdesc);
+extern void ExecDropSingleTupleTableSlot(TupleTableSlot *slot);
+extern TupleTableSlot *ExecAllocTableSlot(TupleTable table);
+extern void ExecSetSlotDescriptor(TupleTableSlot *slot,
+					  TupleDesc tupdesc, bool shouldFree);
+extern TupleTableSlot *ExecStoreTuple(HeapTuple tuple,
+			   TupleTableSlot *slot,
+			   Buffer buffer,
+			   bool shouldFree);
+extern TupleTableSlot *ExecClearTuple(TupleTableSlot *slot);
+extern TupleTableSlot *ExecStoreVirtualTuple(TupleTableSlot *slot);
+extern TupleTableSlot *ExecStoreAllNullTuple(TupleTableSlot *slot);
+extern HeapTuple ExecCopySlotTuple(TupleTableSlot *slot);
+extern HeapTuple ExecFetchSlotTuple(TupleTableSlot *slot);
+extern HeapTuple ExecMaterializeSlot(TupleTableSlot *slot);
+extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
+									TupleTableSlot *srcslot);
 /* 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);
 
 #endif   /* TUPTABLE_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 6cc43342997d3d9c0ce3c667a1e1dc44ace1cdbf..8e5404b50785278f40c2ecb034ca7469013013bd 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.123 2005/03/06 22:15:05 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.124 2005/03/16 21:38:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -175,20 +175,28 @@ typedef struct ReturnSetInfo
  *		This is all the information needed to perform projections ---
  *		that is, form new tuples by evaluation of targetlist expressions.
  *		Nodes which need to do projections create one of these.
- *		In theory, when a node wants to perform a projection
- *		it should just update this information as necessary and then
- *		call ExecProject().  -cim 6/3/91
  *
  *		ExecProject() evaluates the tlist, forms a tuple, and stores it
- *		in the given slot.	As a side-effect, the actual datum values and
- *		null indicators are placed in the work arrays tupValues/tupNulls.
+ *		in the given slot.	Note that the result will be a "virtual" tuple
+ *		unless ExecMaterializeSlot() is then called to force it to be
+ *		converted to a physical tuple.  The slot must have a tupledesc
+ *		that matches the output of the tlist!
+ *
+ *		The planner very often produces tlists that consist entirely of
+ *		simple Var references (lower levels of a plan tree almost always
+ *		look like that).  So we have an optimization to handle that case
+ *		with minimum overhead.
  *
  *		targetlist		target list for projection
  *		exprContext		expression context in which to evaluate targetlist
  *		slot			slot to place projection result in
- *		tupValues		array of computed values
- *		tupNull			array of null indicators
  *		itemIsDone		workspace for ExecProject
+ *		isVarList		TRUE if simple-Var-list optimization applies
+ *		varSlotOffsets	array indicating which slot each simple Var is from
+ *		varNumbers		array indicating attr numbers of simple Vars
+ *		lastInnerVar	highest attnum from inner tuple slot (0 if none)
+ *		lastOuterVar	highest attnum from outer tuple slot (0 if none)
+ *		lastScanVar		highest attnum from scan tuple slot (0 if none)
  * ----------------
  */
 typedef struct ProjectionInfo
@@ -197,9 +205,13 @@ typedef struct ProjectionInfo
 	List	   *pi_targetlist;
 	ExprContext *pi_exprContext;
 	TupleTableSlot *pi_slot;
-	Datum	   *pi_tupValues;
-	char	   *pi_tupNulls;
 	ExprDoneCond *pi_itemIsDone;
+	bool		pi_isVarList;
+	int		   *pi_varSlotOffsets;
+	int		   *pi_varNumbers;
+	int			pi_lastInnerVar;
+	int			pi_lastOuterVar;
+	int			pi_lastScanVar;
 } ProjectionInfo;
 
 /* ----------------
@@ -222,7 +234,7 @@ typedef struct ProjectionInfo
  *	  cleanMap:			A map with the correspondence between the non-junk
  *						attribute numbers of the "original" tuple and the
  *						attribute numbers of the "clean" tuple.
- *	  resultSlot:		tuple slot that can be used to hold cleaned tuple.
+ *	  resultSlot:		tuple slot used to hold cleaned tuple.
  * ----------------
  */
 typedef struct JunkFilter
@@ -354,7 +366,8 @@ typedef struct TupleHashTableData
 	MemoryContext tablecxt;		/* memory context containing table */
 	MemoryContext tempcxt;		/* context for function evaluations */
 	Size		entrysize;		/* actual size to make each hash entry */
-	TupleDesc	tupdesc;		/* tuple descriptor */
+	TupleTableSlot *tableslot;	/* slot for referencing table entries */
+	TupleTableSlot *inputslot;	/* current input tuple's slot */
 } TupleHashTableData;
 
 typedef HASH_SEQ_STATUS TupleHashIterator;
@@ -589,9 +602,9 @@ typedef struct ConvertRowtypeExprState
 	TupleDesc	outdesc;		/* tupdesc for result rowtype */
 	AttrNumber *attrMap;		/* indexes of input fields, or 0 for null */
 	Datum	   *invalues;		/* workspace for deconstructing source */
-	char	   *innulls;
+	bool	   *inisnull;
 	Datum	   *outvalues;		/* workspace for constructing result */
-	char	   *outnulls;
+	bool	   *outisnull;
 } ConvertRowtypeExprState;
 
 /* ----------------
@@ -1065,7 +1078,6 @@ typedef struct GroupState
 {
 	ScanState	ss;				/* its first field is NodeTag */
 	FmgrInfo   *eqfunctions;	/* per-field lookup data for equality fns */
-	HeapTuple	grp_firstTuple; /* copy of first tuple of current group */
 	bool		grp_done;		/* indicates completion of Group scan */
 } GroupState;
 
@@ -1111,7 +1123,7 @@ typedef struct AggState
  *		Unique nodes are used "on top of" sort nodes to discard
  *		duplicate tuples returned from the sort phase.	Basically
  *		all it does is compare the current tuple from the subplan
- *		with the previously fetched tuple stored in priorTuple.
+ *		with the previously fetched tuple (stored in its result slot).
  *		If the two are identical in all interesting fields, then
  *		we just fetch another tuple from the sort and try again.
  * ----------------
@@ -1120,7 +1132,6 @@ typedef struct UniqueState
 {
 	PlanState	ps;				/* its first field is NodeTag */
 	FmgrInfo   *eqfunctions;	/* per-field lookup data for equality fns */
-	HeapTuple	priorTuple;		/* most recently returned tuple, or NULL */
 	MemoryContext tempContext;	/* short-term context for comparisons */
 } UniqueState;
 
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index 0308a4c8d2c593412c89b9c7f31a3f3808e4d162..3efb1924c592dbc6ed408fc9d7183c4dd3f3c628 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -31,7 +31,7 @@
  * destination.  The executor, as well as utility statements that can return
  * tuples, are passed the resulting DestReceiver* pointer.	Each executor run
  * or utility execution calls the receiver's rStartup method, then the
- * receiveTuple method (zero or more times), then the rShutdown method.
+ * receiveSlot method (zero or more times), then the rShutdown method.
  * The same receiver object may be re-used multiple times; eventually it is
  * destroyed by calling its rDestroy method.
  *
@@ -54,14 +54,14 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/tcop/dest.h,v 1.45 2004/12/31 22:03:44 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/tcop/dest.h,v 1.46 2005/03/16 21:38:10 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 #ifndef DEST_H
 #define DEST_H
 
-#include "access/htup.h"
+#include "executor/tuptable.h"
 
 
 /* buffer size to use for command completion tags */
@@ -92,10 +92,8 @@ typedef enum
  *		In the simplest cases, there is no state info, just the function
  *		pointers that the executor must call.
  *
- * Note: the receiveTuple routine must be passed a TupleDesc identical to the
- * one given to the rStartup routine.  The reason for passing it again is just
- * that some destinations would otherwise need dynamic state merely to
- * remember the tupledesc pointer.
+ * Note: the receiveSlot routine must be passed a slot containing a TupleDesc
+ * identical to the one given to the rStartup routine.
  * ----------------
  */
 typedef struct _DestReceiver DestReceiver;
@@ -103,9 +101,8 @@ typedef struct _DestReceiver DestReceiver;
 struct _DestReceiver
 {
 	/* Called for each tuple to be output: */
-	void		(*receiveTuple) (HeapTuple tuple,
-											 TupleDesc typeinfo,
-											 DestReceiver *self);
+	void		(*receiveSlot) (TupleTableSlot *slot,
+								DestReceiver *self);
 	/* Per-executor-run initialization and shutdown: */
 	void		(*rStartup) (DestReceiver *self,
 										 int operation,