diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 4355c0df4c704b744daca974b92f3c445dbbfaef..266cf3bdcc2178a641f59555e1a50fa9c59ea9bb 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.90 2004/04/01 21:28:43 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.91 2004/06/04 20:35:21 tgl Exp $
  *
  * NOTES
  *	  The old interface functions have been converted to macros
@@ -37,10 +37,10 @@
  */
 Size
 ComputeDataSize(TupleDesc tupleDesc,
-				Datum *value,
+				Datum *values,
 				char *nulls)
 {
-	uint32		data_length = 0;
+	Size		data_length = 0;
 	int			i;
 	int			numberOfAttributes = tupleDesc->natts;
 	Form_pg_attribute *att = tupleDesc->attrs;
@@ -51,7 +51,7 @@ ComputeDataSize(TupleDesc tupleDesc,
 			continue;
 
 		data_length = att_align(data_length, att[i]->attalign);
-		data_length = att_addlength(data_length, att[i]->attlen, value[i]);
+		data_length = att_addlength(data_length, att[i]->attlen, values[i]);
 	}
 
 	return data_length;
@@ -59,19 +59,20 @@ ComputeDataSize(TupleDesc tupleDesc,
 
 /* ----------------
  *		DataFill
+ *
+ * Load data portion of a tuple from values/nulls arrays
  * ----------------
  */
 void
 DataFill(char *data,
 		 TupleDesc tupleDesc,
-		 Datum *value,
+		 Datum *values,
 		 char *nulls,
 		 uint16 *infomask,
 		 bits8 *bit)
 {
-	bits8	   *bitP = 0;
-	int			bitmask = 0;
-	Size		data_length;
+	bits8	   *bitP;
+	int			bitmask;
 	int			i;
 	int			numberOfAttributes = tupleDesc->natts;
 	Form_pg_attribute *att = tupleDesc->attrs;
@@ -81,11 +82,19 @@ DataFill(char *data,
 		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)
@@ -112,33 +121,33 @@ DataFill(char *data,
 		if (att[i]->attbyval)
 		{
 			/* pass-by-value */
-			store_att_byval(data, value[i], att[i]->attlen);
+			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(value[i]))
+			if (VARATT_IS_EXTERNAL(values[i]))
 				*infomask |= HEAP_HASEXTERNAL;
-			if (VARATT_IS_COMPRESSED(value[i]))
+			if (VARATT_IS_COMPRESSED(values[i]))
 				*infomask |= HEAP_HASCOMPRESSED;
-			data_length = VARATT_SIZE(DatumGetPointer(value[i]));
-			memcpy(data, DatumGetPointer(value[i]), data_length);
+			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(value[i])) + 1;
-			memcpy(data, DatumGetPointer(value[i]), data_length);
+			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(value[i]), data_length);
+			memcpy(data, DatumGetPointer(values[i]), data_length);
 		}
 
 		data += data_length;
@@ -160,27 +169,28 @@ heap_attisnull(HeapTuple tup, int attnum)
 	if (attnum > (int) tup->t_data->t_natts)
 		return 1;
 
-	if (HeapTupleNoNulls(tup))
-		return 0;
-
 	if (attnum > 0)
+	{
+		if (HeapTupleNoNulls(tup))
+			return 0;
 		return att_isnull(attnum - 1, tup->t_data->t_bits);
-	else
-		switch (attnum)
-		{
-			case TableOidAttributeNumber:
-			case SelfItemPointerAttributeNumber:
-			case ObjectIdAttributeNumber:
-			case MinTransactionIdAttributeNumber:
-			case MinCommandIdAttributeNumber:
-			case MaxTransactionIdAttributeNumber:
-			case MaxCommandIdAttributeNumber:
-				/* these are never null */
-				break;
-
-			default:
-				elog(ERROR, "invalid attnum: %d", attnum);
-		}
+	}
+
+	switch (attnum)
+	{
+		case TableOidAttributeNumber:
+		case SelfItemPointerAttributeNumber:
+		case ObjectIdAttributeNumber:
+		case MinTransactionIdAttributeNumber:
+		case MinCommandIdAttributeNumber:
+		case MaxTransactionIdAttributeNumber:
+		case MaxCommandIdAttributeNumber:
+			/* these are never null */
+			break;
+
+		default:
+			elog(ERROR, "invalid attnum: %d", attnum);
+	}
 
 	return 0;
 }
@@ -202,6 +212,8 @@ heap_attisnull(HeapTuple tup, int attnum)
  *		perform well for queries which hit large #'s of tuples.  After
  *		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.
  * ----------------
  */
 Datum
@@ -536,53 +548,18 @@ heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest)
 	memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len);
 }
 
-#ifdef NOT_USED
-/* ----------------
- *		heap_deformtuple
- *
- *		the inverse of heap_formtuple (see below)
- * ----------------
- */
-void
-heap_deformtuple(HeapTuple tuple,
-				 TupleDesc tdesc,
-				 Datum *values,
-				 char *nulls)
-{
-	int			i;
-	int			natts;
-
-	Assert(HeapTupleIsValid(tuple));
-
-	natts = tuple->t_natts;
-	for (i = 0; i < natts; i++)
-	{
-		bool		isnull;
-
-		values[i] = heap_getattr(tuple,
-								 i + 1,
-								 tdesc,
-								 &isnull);
-		if (isnull)
-			nulls[i] = 'n';
-		else
-			nulls[i] = ' ';
-	}
-}
-#endif
-
 /* ----------------
  *		heap_formtuple
  *
- *		constructs a tuple from the given *value and *nulls arrays
+ *		construct a tuple from the given values[] and nulls[] arrays
  *
  *		Null attributes are indicated by a 'n' in the appropriate byte
- *		of *nulls.	Non-null attributes are indicated by a ' ' (space).
+ *		of nulls[].	Non-null attributes are indicated by a ' ' (space).
  * ----------------
  */
 HeapTuple
 heap_formtuple(TupleDesc tupleDescriptor,
-			   Datum *value,
+			   Datum *values,
 			   char *nulls)
 {
 	HeapTuple	tuple;			/* return tuple */
@@ -621,7 +598,7 @@ heap_formtuple(TupleDesc tupleDescriptor,
 
 	hoff = len = MAXALIGN(len); /* align user data safely */
 
-	len += ComputeDataSize(tupleDescriptor, value, nulls);
+	len += ComputeDataSize(tupleDescriptor, values, nulls);
 
 	/*
 	 * Allocate and zero the space needed.  Note that the tuple body and
@@ -651,7 +628,7 @@ heap_formtuple(TupleDesc tupleDescriptor,
 
 	DataFill((char *) td + hoff,
 			 tupleDescriptor,
-			 value,
+			 values,
 			 nulls,
 			 &td->t_infomask,
 			 (hasnull ? td->t_bits : NULL));
@@ -664,68 +641,59 @@ heap_formtuple(TupleDesc tupleDescriptor,
  *
  *		forms a new tuple from an old tuple and a set of replacement values.
  *		returns a new palloc'ed tuple.
+ *
+ *		XXX it is misdesign that this is passed a Relation and not just a
+ *		TupleDesc to describe the tuple structure.
  * ----------------
  */
 HeapTuple
 heap_modifytuple(HeapTuple tuple,
 				 Relation relation,
-				 Datum *replValue,
-				 char *replNull,
-				 char *repl)
+				 Datum *replValues,
+				 char *replNulls,
+				 char *replActions)
 {
+	TupleDesc	tupleDesc = RelationGetDescr(relation);
+	int			numberOfAttributes = tupleDesc->natts;
 	int			attoff;
-	int			numberOfAttributes;
-	Datum	   *value;
+	Datum	   *values;
 	char	   *nulls;
-	bool		isNull;
 	HeapTuple	newTuple;
 
 	/*
-	 * sanity checks
-	 */
-	Assert(HeapTupleIsValid(tuple));
-	Assert(RelationIsValid(relation));
-	Assert(PointerIsValid(replValue));
-	Assert(PointerIsValid(replNull));
-	Assert(PointerIsValid(repl));
-
-	numberOfAttributes = RelationGetForm(relation)->relnatts;
-
-	/*
-	 * allocate and fill *value and *nulls arrays from either the tuple or
+	 * allocate and fill values and nulls arrays from either the tuple or
 	 * the repl information, as appropriate.
+	 *
+	 * NOTE: it's debatable whether to use heap_deformtuple() 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_deformtuple 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.
 	 */
-	value = (Datum *) palloc(numberOfAttributes * sizeof(Datum));
+	values = (Datum *) palloc(numberOfAttributes * sizeof(Datum));
 	nulls = (char *) palloc(numberOfAttributes * sizeof(char));
 
+	heap_deformtuple(tuple, tupleDesc, values, nulls);
+
 	for (attoff = 0; attoff < numberOfAttributes; attoff++)
 	{
-		if (repl[attoff] == ' ')
+		if (replActions[attoff] == 'r')
 		{
-			value[attoff] = heap_getattr(tuple,
-										 AttrOffsetGetAttrNumber(attoff),
-										 RelationGetDescr(relation),
-										 &isNull);
-			nulls[attoff] = (isNull) ? 'n' : ' ';
-
+			values[attoff] = replValues[attoff];
+			nulls[attoff] = replNulls[attoff];
 		}
-		else if (repl[attoff] == 'r')
-		{
-			value[attoff] = replValue[attoff];
-			nulls[attoff] = replNull[attoff];
-		}
-		else
-			elog(ERROR, "unrecognized replace flag: %d", (int) repl[attoff]);
+		else if (replActions[attoff] != ' ')
+			elog(ERROR, "unrecognized replace flag: %d",
+				 (int) replActions[attoff]);
 	}
 
 	/*
-	 * create a new tuple from the *values and *nulls arrays
+	 * create a new tuple from the values and nulls arrays
 	 */
-	newTuple = heap_formtuple(RelationGetDescr(relation),
-							  value,
-							  nulls);
+	newTuple = heap_formtuple(tupleDesc, values, nulls);
 
-	pfree(value);
+	pfree(values);
 	pfree(nulls);
 
 	/*
@@ -735,12 +703,96 @@ heap_modifytuple(HeapTuple tuple,
 	newTuple->t_data->t_ctid = tuple->t_data->t_ctid;
 	newTuple->t_self = tuple->t_self;
 	newTuple->t_tableOid = tuple->t_tableOid;
-	if (relation->rd_rel->relhasoids)
+	if (tupleDesc->tdhasoid)
 		HeapTupleSetOid(newTuple, HeapTupleGetOid(tuple));
 
 	return newTuple;
 }
 
+/* ----------------
+ *		heap_deformtuple
+ *
+ *		Given a tuple, extract data into values/nulls arrays; this is
+ *		the inverse of heap_formtuple.
+ *
+ *		Storage for the values/nulls 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_deformtuple(HeapTuple tuple,
+				 TupleDesc tupleDesc,
+				 Datum *values,
+				 char *nulls)
+{
+	HeapTupleHeader tup = tuple->t_data;
+	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 bitmask in tuple */
+	bool		slow = false;	/* can we use/set attcacheoff? */
+
+	natts = tup->t_natts;
+	/* This min() operation is pure paranoia */
+	natts = Min(natts, tdesc_natts);
+
+	tp = (char *) tup + tup->t_hoff;
+
+	off = 0;
+
+	for (attnum = 0; attnum < natts; attnum++)
+	{
+		if (HeapTupleHasNulls(tuple) && att_isnull(attnum, bp))
+		{
+			values[attnum] = (Datum) 0;
+			nulls[attnum] = 'n';
+			slow = true;		/* can't use attcacheoff anymore */
+			continue;
+		}
+
+		nulls[attnum] = ' ';
+
+		if (!slow && att[attnum]->attcacheoff >= 0)
+		{
+			off = att[attnum]->attcacheoff;
+		}
+		else
+		{
+			off = att_align(off, att[attnum]->attalign);
+
+			if (!slow)
+				att[attnum]->attcacheoff = off;
+		}
+
+		values[attnum] = fetchatt(att[attnum], tp + off);
+
+		off = att_addlength(off, att[attnum]->attlen, tp + off);
+
+		if (att[attnum]->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;
+		nulls[attnum] = 'n';
+	}
+}
 
 /* ----------------
  *		heap_freetuple
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index eb05c994b465784e43a478f9cf7dcce5c927cd76..fc0b8d9e7ace99cde4834cbac01b40e97a99079e 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.81 2004/05/26 04:41:03 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/common/printtup.c,v 1.82 2004/06/04 20:35:21 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -65,6 +65,8 @@ 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;
 
 /* ----------------
@@ -103,6 +105,8 @@ 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;
 }
@@ -243,15 +247,27 @@ printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
 	int16	   *formats = myState->portal->formats;
 	int			i;
 
+	/* get rid of any old data */
 	if (myState->myinfo)
-		pfree(myState->myinfo); /* get rid of any old data */
+		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;
 	if (numAttrs <= 0)
 		return;
+
 	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++)
 	{
 		PrinttupAttrInfo *thisState = myState->myinfo + i;
@@ -297,6 +313,11 @@ 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);
+
 	/*
 	 * Prepare a DataRow message
 	 */
@@ -310,12 +331,10 @@ printtup(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	for (i = 0; i < natts; ++i)
 	{
 		PrinttupAttrInfo *thisState = myState->myinfo + i;
-		Datum		origattr,
+		Datum		origattr = myState->values[i],
 					attr;
-		bool		isnull;
 
-		origattr = heap_getattr(tuple, i + 1, typeinfo, &isnull);
-		if (isnull)
+		if (myState->nulls[i] == 'n')
 		{
 			pq_sendint(&buf, -1, 4);
 			continue;
@@ -383,6 +402,11 @@ 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);
+
 	/*
 	 * tell the frontend to expect new tuple data (in ASCII style)
 	 */
@@ -395,7 +419,7 @@ printtup_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	k = 1 << 7;
 	for (i = 0; i < natts; ++i)
 	{
-		if (!heap_attisnull(tuple, i + 1))
+		if (myState->nulls[i] != 'n')
 			j |= k;				/* set bit if not null */
 		k >>= 1;
 		if (k == 0)				/* end of byte? */
@@ -414,13 +438,11 @@ printtup_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	for (i = 0; i < natts; ++i)
 	{
 		PrinttupAttrInfo *thisState = myState->myinfo + i;
-		Datum		origattr,
+		Datum		origattr = myState->values[i],
 					attr;
-		bool		isnull;
 		char	   *outputstr;
 
-		origattr = heap_getattr(tuple, i + 1, typeinfo, &isnull);
-		if (isnull)
+		if (myState->nulls[i] == 'n')
 			continue;
 
 		Assert(thisState->format == 0);
@@ -461,6 +483,13 @@ 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;
 }
 
@@ -587,6 +616,11 @@ 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);
+
 	/*
 	 * tell the frontend to expect new tuple data (in binary style)
 	 */
@@ -599,7 +633,7 @@ printtup_internal_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	k = 1 << 7;
 	for (i = 0; i < natts; ++i)
 	{
-		if (!heap_attisnull(tuple, i + 1))
+		if (myState->nulls[i] != 'n')
 			j |= k;				/* set bit if not null */
 		k >>= 1;
 		if (k == 0)				/* end of byte? */
@@ -618,13 +652,11 @@ printtup_internal_20(HeapTuple tuple, TupleDesc typeinfo, DestReceiver *self)
 	for (i = 0; i < natts; ++i)
 	{
 		PrinttupAttrInfo *thisState = myState->myinfo + i;
-		Datum		origattr,
+		Datum		origattr = myState->values[i],
 					attr;
-		bool		isnull;
 		bytea	   *outputbytes;
 
-		origattr = heap_getattr(tuple, i + 1, typeinfo, &isnull);
-		if (isnull)
+		if (myState->nulls[i] == 'n')
 			continue;
 
 		Assert(thisState->format == 1);
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index ae4b2c0aa888c07db76e719e9f28c4dee178ca3e..f206a3f28eb5475abad83767dca9341739f41c3c 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.41 2003/11/29 19:51:40 pgsql Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.42 2004/06/04 20:35:21 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -281,15 +281,26 @@ toast_delete(Relation rel, HeapTuple oldtup)
 	Form_pg_attribute *att;
 	int			numAttrs;
 	int			i;
-	Datum		value;
-	bool		isnull;
+	Datum		toast_values[MaxHeapAttributeNumber];
+	char		toast_nulls[MaxHeapAttributeNumber];
 
 	/*
-	 * Get the tuple descriptor, the number of and attribute descriptors.
+	 * Get the tuple descriptor and break down the tuple into fields.
+	 *
+	 * NOTE: it's debatable whether to use heap_deformtuple() here or
+	 * just heap_getattr() only the varlena columns.  The latter could
+	 * win if there are few varlena columns and many non-varlena ones.
+	 * However, heap_deformtuple costs only O(N) while the heap_getattr
+	 * way would cost O(N^2) if there are many varlena columns, so it
+	 * seems better to err on the side of linear cost.  (We won't even
+	 * be here unless there's at least one varlena column, by the way.)
 	 */
 	tupleDesc = rel->rd_att;
-	numAttrs = tupleDesc->natts;
 	att = tupleDesc->attrs;
+	numAttrs = tupleDesc->natts;
+
+	Assert(numAttrs <= MaxHeapAttributeNumber);
+	heap_deformtuple(oldtup, tupleDesc, toast_values, toast_nulls);
 
 	/*
 	 * Check for external stored attributes and delete them from the
@@ -299,8 +310,9 @@ toast_delete(Relation rel, HeapTuple oldtup)
 	{
 		if (att[i]->attlen == -1)
 		{
-			value = heap_getattr(oldtup, i + 1, tupleDesc, &isnull);
-			if (!isnull && VARATT_IS_EXTERNAL(value))
+			Datum	value = toast_values[i];
+
+			if (toast_nulls[i] != 'n' && VARATT_IS_EXTERNAL(value))
 				toast_delete_datum(rel, value);
 		}
 	}
@@ -321,8 +333,6 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup)
 	Form_pg_attribute *att;
 	int			numAttrs;
 	int			i;
-	bool		old_isnull;
-	bool		new_isnull;
 
 	bool		need_change = false;
 	bool		need_free = false;
@@ -333,18 +343,24 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup)
 
 	char		toast_action[MaxHeapAttributeNumber];
 	char		toast_nulls[MaxHeapAttributeNumber];
+	char		toast_oldnulls[MaxHeapAttributeNumber];
 	Datum		toast_values[MaxHeapAttributeNumber];
+	Datum		toast_oldvalues[MaxHeapAttributeNumber];
 	int32		toast_sizes[MaxHeapAttributeNumber];
 	bool		toast_free[MaxHeapAttributeNumber];
 	bool		toast_delold[MaxHeapAttributeNumber];
 
 	/*
-	 * Get the tuple descriptor, the number of and attribute descriptors
-	 * and the location of the tuple values.
+	 * Get the tuple descriptor and break down the tuple(s) into fields.
 	 */
 	tupleDesc = rel->rd_att;
-	numAttrs = tupleDesc->natts;
 	att = tupleDesc->attrs;
+	numAttrs = tupleDesc->natts;
+
+	Assert(numAttrs <= MaxHeapAttributeNumber);
+	heap_deformtuple(newtup, tupleDesc, toast_values, toast_nulls);
+	if (oldtup != NULL)
+		heap_deformtuple(oldtup, tupleDesc, toast_oldvalues, toast_oldnulls);
 
 	/* ----------
 	 * Then collect information about the values given
@@ -353,12 +369,15 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup)
 	 *		' '		default handling
 	 *		'p'		already processed --- don't touch it
 	 *		'x'		incompressible, but OK to move off
+	 *
+	 * NOTE: toast_sizes[i] is only made valid for varlena attributes with
+	 *		toast_action[i] different from 'p'.
 	 * ----------
 	 */
 	memset(toast_action, ' ', numAttrs * sizeof(char));
-	memset(toast_nulls, ' ', numAttrs * sizeof(char));
 	memset(toast_free, 0, numAttrs * sizeof(bool));
 	memset(toast_delold, 0, numAttrs * sizeof(bool));
+
 	for (i = 0; i < numAttrs; i++)
 	{
 		varattrib  *old_value;
@@ -369,27 +388,24 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup)
 			/*
 			 * For UPDATE get the old and new values of this attribute
 			 */
-			old_value = (varattrib *) DatumGetPointer(
-					heap_getattr(oldtup, i + 1, tupleDesc, &old_isnull));
-			toast_values[i] =
-				heap_getattr(newtup, i + 1, tupleDesc, &new_isnull);
+			old_value = (varattrib *) DatumGetPointer(toast_oldvalues[i]);
 			new_value = (varattrib *) DatumGetPointer(toast_values[i]);
 
 			/*
 			 * If the old value is an external stored one, check if it has
 			 * changed so we have to delete it later.
 			 */
-			if (!old_isnull && att[i]->attlen == -1 &&
+			if (att[i]->attlen == -1 && toast_oldnulls[i] != 'n' &&
 				VARATT_IS_EXTERNAL(old_value))
 			{
-				if (new_isnull || !VARATT_IS_EXTERNAL(new_value) ||
+				if (toast_nulls[i] == 'n' || !VARATT_IS_EXTERNAL(new_value) ||
 					old_value->va_content.va_external.va_valueid !=
 					new_value->va_content.va_external.va_valueid ||
 					old_value->va_content.va_external.va_toastrelid !=
 					new_value->va_content.va_external.va_toastrelid)
 				{
 					/*
-					 * The old external store value isn't needed any more
+					 * The old external stored value isn't needed any more
 					 * after the update
 					 */
 					toast_delold[i] = true;
@@ -413,23 +429,21 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup)
 			/*
 			 * For INSERT simply get the new value
 			 */
-			toast_values[i] =
-				heap_getattr(newtup, i + 1, tupleDesc, &new_isnull);
+			new_value = (varattrib *) DatumGetPointer(toast_values[i]);
 		}
 
 		/*
 		 * Handle NULL attributes
 		 */
-		if (new_isnull)
+		if (toast_nulls[i] == 'n')
 		{
 			toast_action[i] = 'p';
-			toast_nulls[i] = 'n';
 			has_nulls = true;
 			continue;
 		}
 
 		/*
-		 * Now look at varsize attributes
+		 * Now look at varlena attributes
 		 */
 		if (att[i]->attlen == -1)
 		{
@@ -461,10 +475,9 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup)
 		else
 		{
 			/*
-			 * Not a variable size attribute, plain storage always
+			 * Not a varlena attribute, plain storage always
 			 */
 			toast_action[i] = 'p';
-			toast_sizes[i] = att[i]->attlen;
 		}
 	}
 
@@ -768,8 +781,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup)
 	if (need_delold)
 		for (i = 0; i < numAttrs; i++)
 			if (toast_delold[i])
-				toast_delete_datum(rel,
-					heap_getattr(oldtup, i + 1, tupleDesc, &old_isnull));
+				toast_delete_datum(rel, toast_oldvalues[i]);
 }
 
 
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 71f5ad0c00173143228e11ece7e27f0f0ed5f0fc..492532363a579c99f2d3eefeaced2a9db9d0bd82 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.109 2004/06/02 21:01:08 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.110 2004/06/04 20:35:21 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2342,10 +2342,10 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
 		newslot = MakeTupleTableSlot();
 		ExecSetSlotDescriptor(newslot, newTupDesc, false);
 
-		/* Preallocate values/nulls arrays (+1 in case natts==0) */
+		/* Preallocate values/nulls arrays */
 		i = Max(newTupDesc->natts, oldTupDesc->natts);
-		values = (Datum *) palloc(i * sizeof(Datum) + 1);
-		nulls = (char *) palloc(i * sizeof(char) + 1);
+		values = (Datum *) palloc(i * sizeof(Datum));
+		nulls = (char *) palloc(i * sizeof(char));
 		memset(values, 0, i * sizeof(Datum));
 		memset(nulls, 'n', i * sizeof(char));
 
@@ -2363,24 +2363,14 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
 				 * Extract data from old tuple.  We can force to null any
 				 * columns that are deleted according to the new tuple.
 				 */
-				int		natts = oldTupDesc->natts;
-				bool	isNull;
+				int		natts = newTupDesc->natts;
+
+				heap_deformtuple(tuple, oldTupDesc, values, nulls);
 
 				for (i = 0; i < natts; i++)
 				{
 					if (newTupDesc->attrs[i]->attisdropped)
 						nulls[i] = 'n';
-					else
-					{
-						values[i] = heap_getattr(tuple,
-												 i + 1,
-												 oldTupDesc,
-												 &isNull);
-						if (isNull)
-							nulls[i] = 'n';
-						else
-							nulls[i] = ' ';
-					}
 				}
 
 				/*
@@ -2393,6 +2383,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap)
 				foreach(l, tab->newvals)
 				{
 					NewColumnValue   *ex = lfirst(l);
+					bool	isNull;
 
 					values[ex->attnum - 1] = ExecEvalExpr(ex->exprstate,
 														  econtext,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index d8a2a5b20f5d91c7dfc32d48ae7edc8c80357857..439ad91cc37e7d91eb9fe56cfcfc7c998f06c825 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.57 2004/05/26 04:41:12 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/typecmds.c,v 1.58 2004/06/04 20:35:21 tgl Exp $
  *
  * DESCRIPTION
  *	  The "DefineFoo" routines take the parse tree and pick out the
@@ -1329,12 +1329,8 @@ AlterDomainNotNull(List *names, bool notNull)
 				for (i = 0; i < rtc->natts; i++)
 				{
 					int			attnum = rtc->atts[i];
-					Datum		d;
-					bool		isNull;
 
-					d = heap_getattr(tuple, attnum, tupdesc, &isNull);
-
-					if (isNull)
+					if (heap_attisnull(tuple, attnum))
 						ereport(ERROR,
 								(errcode(ERRCODE_NOT_NULL_VIOLATION),
 								 errmsg("column \"%s\" of table \"%s\" contains null values",
diff --git a/src/backend/executor/execJunk.c b/src/backend/executor/execJunk.c
index 20c385134b272d95ec8c3afb91c2d1e8d6cef512..8c693ddc98c7062b889122c1891b8254ab334c7f 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.40 2004/05/26 04:41:14 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/execJunk.c,v 1.41 2004/06/04 20:35:21 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -246,12 +246,15 @@ ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot)
 	TupleDesc	cleanTupType;
 	TupleDesc	tupType;
 	int			cleanLength;
-	bool		isNull;
 	int			i;
 	Datum	   *values;
 	char	   *nulls;
+	Datum	   *old_values;
+	char	   *old_nulls;
 	Datum		values_array[64];
+	Datum		old_values_array[64];
 	char		nulls_array[64];
+	char		old_nulls_array[64];
 
 	/*
 	 * get info from the slot and the junk filter
@@ -265,11 +268,15 @@ ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot)
 
 	/*
 	 * Create the arrays that will hold the attribute values and the null
-	 * information for the new "clean" tuple.
+	 * 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.
+	 *
+	 * Note: we could use just one set of arrays if we were willing to
+	 * assume that the resno mapping is monotonic... I think it is, but
+	 * won't take the risk of breaking things right now.
 	 */
 	if (cleanLength > 64)
 	{
@@ -281,36 +288,52 @@ ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot)
 		values = values_array;
 		nulls = nulls_array;
 	}
+	if (tupType->natts > 64)
+	{
+		old_values = (Datum *) palloc(tupType->natts * sizeof(Datum));
+		old_nulls = (char *) palloc(tupType->natts * sizeof(char));
+	}
+	else
+	{
+		old_values = old_values_array;
+		old_nulls = old_nulls_array;
+	}
 
 	/*
-	 * Exctract one by one all the values of the "clean" tuple.
+	 * Extract all the values of the old tuple.
+	 */
+	heap_deformtuple(tuple, tupType, old_values, old_nulls);
+
+	/*
+	 * Transpose into proper fields of the new tuple.
 	 */
 	for (i = 0; i < cleanLength; i++)
 	{
-		values[i] = heap_getattr(tuple, cleanMap[i], tupType, &isNull);
+		int j = cleanMap[i] - 1;
 
-		if (isNull)
-			nulls[i] = 'n';
-		else
-			nulls[i] = ' ';
+		values[i] = old_values[j];
+		nulls[i] = old_nulls[j];
 	}
 
 	/*
 	 * Now form the new tuple.
 	 */
-	cleanTuple = heap_formtuple(cleanTupType,
-								values,
-								nulls);
+	cleanTuple = heap_formtuple(cleanTupType, values, nulls);
 
 	/*
 	 * We are done.  Free any space allocated for 'values' and 'nulls' and
 	 * return the new tuple.
 	 */
-	if (cleanLength > 64)
+	if (values != values_array)
 	{
 		pfree(values);
 		pfree(nulls);
 	}
+	if (old_values != old_values_array)
+	{
+		pfree(old_values);
+		pfree(old_nulls);
+	}
 
 	return cleanTuple;
 }
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 6ec4d810a8e3c0cfcde9d694636b560a60dbfda4..047b8fa2aae66d687427cf97e14283543c5e09d3 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.115 2004/05/30 23:40:26 neilc Exp $
+ *	  $PostgreSQL: pgsql/src/backend/executor/spi.c,v 1.116 2004/06/04 20:35:21 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -427,7 +427,6 @@ SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
 	int			numberOfAttributes;
 	Datum	   *v;
 	char	   *n;
-	bool		isnull;
 	int			i;
 
 	if (rel == NULL || tuple == NULL || natts < 0 || attnum == NULL || Values == NULL)
@@ -448,11 +447,7 @@ SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
 	n = (char *) palloc(numberOfAttributes * sizeof(char));
 
 	/* fetch old values and nulls */
-	for (i = 0; i < numberOfAttributes; i++)
-	{
-		v[i] = heap_getattr(tuple, i + 1, rel->rd_att, &isnull);
-		n[i] = (isnull) ? 'n' : ' ';
-	}
+	heap_deformtuple(tuple, rel->rd_att, v, n);
 
 	/* replace values and nulls */
 	for (i = 0; i < natts; i++)
@@ -474,7 +469,7 @@ SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, int *attnum,
 		mtuple->t_data->t_ctid = tuple->t_data->t_ctid;
 		mtuple->t_self = tuple->t_self;
 		mtuple->t_tableOid = tuple->t_tableOid;
-		if (rel->rd_rel->relhasoids)
+		if (rel->rd_att->tdhasoid)
 			HeapTupleSetOid(mtuple, HeapTupleGetOid(tuple));
 	}
 	else
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index 9f138524155560fd4190eea1c4ce90230b1c8879..5fa50d28e1e8b69bd232fdf96b71cde36edc2656 100644
--- a/src/include/access/heapam.h
+++ b/src/include/access/heapam.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.89 2004/04/21 18:24:26 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.90 2004/06/04 20:35:21 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -184,9 +184,9 @@ extern XLogRecPtr log_heap_move(Relation reln, Buffer oldbuf,
 			  Buffer newbuf, HeapTuple newtup);
 
 /* in common/heaptuple.c */
-extern Size ComputeDataSize(TupleDesc tupleDesc, Datum *value, char *nulls);
+extern Size ComputeDataSize(TupleDesc tupleDesc, Datum *values, char *nulls);
 extern void DataFill(char *data, TupleDesc tupleDesc,
-		 Datum *value, char *nulls, uint16 *infomask,
+		 Datum *values, char *nulls, uint16 *infomask,
 		 bits8 *bit);
 extern int	heap_attisnull(HeapTuple tup, int attnum);
 extern Datum nocachegetattr(HeapTuple tup, int attnum,
@@ -194,9 +194,14 @@ extern Datum nocachegetattr(HeapTuple tup, int attnum,
 extern HeapTuple heap_copytuple(HeapTuple tuple);
 extern void heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest);
 extern HeapTuple heap_formtuple(TupleDesc tupleDescriptor,
-			   Datum *value, char *nulls);
+			   Datum *values, char *nulls);
 extern HeapTuple heap_modifytuple(HeapTuple tuple,
-		Relation relation, Datum *replValue, char *replNull, char *repl);
+								  Relation relation,
+								  Datum *replValues,
+								  char *replNulls,
+								  char *replActions);
+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);