diff --git a/contrib/dblink/dblink.c b/contrib/dblink/dblink.c
index 18ae318..54473be 100644
--- a/contrib/dblink/dblink.c
+++ b/contrib/dblink/dblink.c
@@ -2368,6 +2368,9 @@ get_attnum_pk_pos(int *pkattnums, int pknumatts, int key)
 	return -1;
 }
 
+/*
+ * FIXME this probably needs to be tweaked.
+ */
 static HeapTuple
 get_tuple_of_interest(Relation rel, int *pkattnums, int pknumatts, char **src_pkattvals)
 {
diff --git a/contrib/spi/timetravel.c b/contrib/spi/timetravel.c
index a37cbee..d7e6e50 100644
--- a/contrib/spi/timetravel.c
+++ b/contrib/spi/timetravel.c
@@ -314,6 +314,7 @@ timetravel(PG_FUNCTION_ARGS)
 		Oid		   *ctypes;
 		char		sql[8192];
 		char		separ = ' ';
+		Form_pg_attribute *attrs;
 
 		/* allocate ctypes for preparation */
 		ctypes = (Oid *) palloc(natts * sizeof(Oid));
@@ -322,10 +323,11 @@ timetravel(PG_FUNCTION_ARGS)
 		 * Construct query: INSERT INTO _relation_ VALUES ($1, ...)
 		 */
 		snprintf(sql, sizeof(sql), "INSERT INTO %s VALUES (", relname);
+		attrs = TupleDescGetSortedAttrs(tupdesc);
 		for (i = 1; i <= natts; i++)
 		{
-			ctypes[i - 1] = SPI_gettypeid(tupdesc, i);
-			if (!(tupdesc->attrs[i - 1]->attisdropped)) /* skip dropped columns */
+			ctypes[i - 1] = SPI_gettypeid(tupdesc, attrs[i - 1]->attnum);
+			if (!(attrs[i - 1]->attisdropped)) /* skip dropped columns */
 			{
 				snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%c$%d", separ, i);
 				separ = ',';
diff --git a/src/backend/access/brin/brin_tuple.c b/src/backend/access/brin/brin_tuple.c
index af649c0..137446a 100644
--- a/src/backend/access/brin/brin_tuple.c
+++ b/src/backend/access/brin/brin_tuple.c
@@ -159,7 +159,7 @@ brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple,
 	len = hoff = MAXALIGN(len);
 
 	data_len = heap_compute_data_size(brtuple_disk_tupdesc(brdesc),
-									  values, nulls);
+									  values, nulls, false);
 
 	len += data_len;
 
@@ -177,6 +177,7 @@ brin_form_tuple(BrinDesc *brdesc, BlockNumber blkno, BrinMemTuple *tuple,
 	heap_fill_tuple(brtuple_disk_tupdesc(brdesc),
 					values,
 					nulls,
+					false,
 					(char *) rettuple + hoff,
 					data_len,
 					&phony_infomask,
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 009ebe7..5dfbed5 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -79,11 +79,15 @@
 /*
  * heap_compute_data_size
  *		Determine size of the data area of a tuple to be constructed
+ *
+ * logical_order means that the values and isnull arrays are sorted
+ * following attlognum.
  */
 Size
 heap_compute_data_size(TupleDesc tupleDesc,
 					   Datum *values,
-					   bool *isnull)
+					   bool *isnull,
+					   bool logical_order)
 {
 	Size		data_length = 0;
 	int			i;
@@ -93,11 +97,14 @@ heap_compute_data_size(TupleDesc tupleDesc,
 	for (i = 0; i < numberOfAttributes; i++)
 	{
 		Datum		val;
+		int			idx;
 
-		if (isnull[i])
+		idx = logical_order ? att[i]->attlognum - 1 : i;
+
+		if (isnull[idx])
 			continue;
 
-		val = values[i];
+		val = values[idx];
 
 		if (ATT_IS_PACKABLE(att[i]) &&
 			VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
@@ -124,6 +131,9 @@ heap_compute_data_size(TupleDesc tupleDesc,
  * heap_fill_tuple
  *		Load data portion of a tuple from values/isnull arrays
  *
+ * logical_order means that the values and isnull arrays are sorted
+ * following attlognum.
+ *
  * We also fill the null bitmap (if any) and set the infomask bits
  * that reflect the tuple's data contents.
  *
@@ -132,6 +142,7 @@ heap_compute_data_size(TupleDesc tupleDesc,
 void
 heap_fill_tuple(TupleDesc tupleDesc,
 				Datum *values, bool *isnull,
+				bool logical_order,
 				char *data, Size data_size,
 				uint16 *infomask, bits8 *bit)
 {
@@ -162,6 +173,13 @@ heap_fill_tuple(TupleDesc tupleDesc,
 	for (i = 0; i < numberOfAttributes; i++)
 	{
 		Size		data_length;
+		int			idx;
+		Datum		value;
+		bool		thisnull;
+
+		idx = logical_order ? att[i]->attlognum - 1 : i;
+
+		thisnull = isnull[idx];
 
 		if (bit != NULL)
 		{
@@ -174,7 +192,7 @@ heap_fill_tuple(TupleDesc tupleDesc,
 				bitmask = 1;
 			}
 
-			if (isnull[i])
+			if (thisnull)
 			{
 				*infomask |= HEAP_HASNULL;
 				continue;
@@ -183,6 +201,8 @@ heap_fill_tuple(TupleDesc tupleDesc,
 			*bitP |= bitmask;
 		}
 
+		value = values[idx];
+
 		/*
 		 * XXX we use the att_align macros on the pointer value itself, not on
 		 * an offset.  This is a bit of a hack.
@@ -192,13 +212,13 @@ heap_fill_tuple(TupleDesc tupleDesc,
 		{
 			/* pass-by-value */
 			data = (char *) att_align_nominal(data, att[i]->attalign);
-			store_att_byval(data, values[i], att[i]->attlen);
+			store_att_byval(data, value, att[i]->attlen);
 			data_length = att[i]->attlen;
 		}
 		else if (att[i]->attlen == -1)
 		{
 			/* varlena */
-			Pointer		val = DatumGetPointer(values[i]);
+			Pointer		val = DatumGetPointer(value);
 
 			*infomask |= HEAP_HASVARWIDTH;
 			if (VARATT_IS_EXTERNAL(val))
@@ -236,8 +256,8 @@ heap_fill_tuple(TupleDesc tupleDesc,
 			/* cstring ... never needs alignment */
 			*infomask |= HEAP_HASVARWIDTH;
 			Assert(att[i]->attalign == 'c');
-			data_length = strlen(DatumGetCString(values[i])) + 1;
-			memcpy(data, DatumGetPointer(values[i]), data_length);
+			data_length = strlen(DatumGetCString(value)) + 1;
+			memcpy(data, DatumGetPointer(value), data_length);
 		}
 		else
 		{
@@ -245,7 +265,7 @@ heap_fill_tuple(TupleDesc tupleDesc,
 			data = (char *) att_align_nominal(data, att[i]->attalign);
 			Assert(att[i]->attlen > 0);
 			data_length = att[i]->attlen;
-			memcpy(data, DatumGetPointer(values[i]), data_length);
+			memcpy(data, DatumGetPointer(value), data_length);
 		}
 
 		data += data_length;
@@ -660,9 +680,10 @@ heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc)
  * The result is allocated in the current memory context.
  */
 HeapTuple
-heap_form_tuple(TupleDesc tupleDescriptor,
-				Datum *values,
-				bool *isnull)
+heap_form_tuple_extended(TupleDesc tupleDescriptor,
+						 Datum *values,
+						 bool *isnull,
+						 int flags)
 {
 	HeapTuple	tuple;			/* return tuple */
 	HeapTupleHeader td;			/* tuple data */
@@ -672,6 +693,7 @@ heap_form_tuple(TupleDesc tupleDescriptor,
 	bool		hasnull = false;
 	int			numberOfAttributes = tupleDescriptor->natts;
 	int			i;
+	bool		logical_order = (flags & HTOPT_LOGICAL_ORDER) != 0;
 
 	if (numberOfAttributes > MaxTupleAttributeNumber)
 		ereport(ERROR,
@@ -704,7 +726,8 @@ heap_form_tuple(TupleDesc tupleDescriptor,
 
 	hoff = len = MAXALIGN(len); /* align user data safely */
 
-	data_len = heap_compute_data_size(tupleDescriptor, values, isnull);
+	data_len = heap_compute_data_size(tupleDescriptor, values, isnull,
+									  logical_order);
 
 	len += data_len;
 
@@ -737,6 +760,7 @@ heap_form_tuple(TupleDesc tupleDescriptor,
 	heap_fill_tuple(tupleDescriptor,
 					values,
 					isnull,
+					logical_order,
 					(char *) td + hoff,
 					data_len,
 					&td->t_infomask,
@@ -887,7 +911,7 @@ heap_modifytuple(HeapTuple tuple,
 }
 
 /*
- * heap_deform_tuple
+ * heap_deform_tuple_extended
  *		Given a tuple, extract data into values/isnull arrays; this is
  *		the inverse of heap_form_tuple.
  *
@@ -904,8 +928,8 @@ heap_modifytuple(HeapTuple tuple,
  *		noncacheable attribute offsets are involved.
  */
 void
-heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
-				  Datum *values, bool *isnull)
+heap_deform_tuple_extended(HeapTuple tuple, TupleDesc tupleDesc,
+						   Datum *values, bool *isnull, int flags)
 {
 	HeapTupleHeader tup = tuple->t_data;
 	bool		hasnulls = HeapTupleHasNulls(tuple);
@@ -917,6 +941,7 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 	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? */
+	bool		logical_order = (flags & HTOPT_LOGICAL_ORDER) != 0;
 
 	natts = HeapTupleHeaderGetNatts(tup);
 
@@ -934,16 +959,18 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 	for (attnum = 0; attnum < natts; attnum++)
 	{
 		Form_pg_attribute thisatt = att[attnum];
+		int		fillattnum = logical_order ?
+			thisatt->attlognum - 1 : thisatt->attnum - 1;
 
-		if (hasnulls && att_isnull(attnum, bp))
+		if (hasnulls && att_isnull(thisatt->attnum - 1, bp))
 		{
-			values[attnum] = (Datum) 0;
-			isnull[attnum] = true;
+			values[fillattnum] = (Datum) 0;
+			isnull[fillattnum] = true;
 			slow = true;		/* can't use attcacheoff anymore */
 			continue;
 		}
 
-		isnull[attnum] = false;
+		isnull[fillattnum] = false;
 
 		if (!slow && thisatt->attcacheoff >= 0)
 			off = thisatt->attcacheoff;
@@ -974,7 +1001,7 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 				thisatt->attcacheoff = off;
 		}
 
-		values[attnum] = fetchatt(thisatt, tp + off);
+		values[fillattnum] = fetchatt(thisatt, tp + off);
 
 		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
 
@@ -985,6 +1012,8 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 	/*
 	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
 	 * rest as null
+	 *
+	 * FIXME -- this is wrong if HTOPT_LOGICAL_ORDER
 	 */
 	for (; attnum < tdesc_natts; attnum++)
 	{
@@ -1398,12 +1427,17 @@ heap_freetuple(HeapTuple htup)
  * "minimal" tuple lacking a HeapTupleData header as well as room for system
  * columns.
  *
+ * If the HTOPT_LOGICAL_ORDER flag is set, the values and isnull arrays are
+ * sorted in logical order, so we re-sort them to build the tuple in correct
+ * physical order.
+ *
  * The result is allocated in the current memory context.
  */
 MinimalTuple
 heap_form_minimal_tuple(TupleDesc tupleDescriptor,
 						Datum *values,
-						bool *isnull)
+						bool *isnull,
+						int flags)
 {
 	MinimalTuple tuple;			/* return tuple */
 	Size		len,
@@ -1412,6 +1446,7 @@ heap_form_minimal_tuple(TupleDesc tupleDescriptor,
 	bool		hasnull = false;
 	int			numberOfAttributes = tupleDescriptor->natts;
 	int			i;
+	bool		logical_order = (flags & HTOPT_LOGICAL_ORDER) != 0;
 
 	if (numberOfAttributes > MaxTupleAttributeNumber)
 		ereport(ERROR,
@@ -1444,7 +1479,7 @@ heap_form_minimal_tuple(TupleDesc tupleDescriptor,
 
 	hoff = len = MAXALIGN(len); /* align user data safely */
 
-	data_len = heap_compute_data_size(tupleDescriptor, values, isnull);
+	data_len = heap_compute_data_size(tupleDescriptor, values, isnull, logical_order);
 
 	len += data_len;
 
@@ -1466,6 +1501,7 @@ heap_form_minimal_tuple(TupleDesc tupleDescriptor,
 	heap_fill_tuple(tupleDescriptor,
 					values,
 					isnull,
+					logical_order,
 					(char *) tuple + hoff,
 					data_len,
 					&tuple->t_infomask,
diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c
index 8d9a893..16a45c2 100644
--- a/src/backend/access/common/indextuple.c
+++ b/src/backend/access/common/indextuple.c
@@ -121,10 +121,10 @@ index_form_tuple(TupleDesc tupleDescriptor,
 	hoff = IndexInfoFindDataOffset(infomask);
 #ifdef TOAST_INDEX_HACK
 	data_size = heap_compute_data_size(tupleDescriptor,
-									   untoasted_values, isnull);
+									   untoasted_values, isnull, false);
 #else
 	data_size = heap_compute_data_size(tupleDescriptor,
-									   values, isnull);
+									   values, isnull, false);
 #endif
 	size = hoff + data_size;
 	size = MAXALIGN(size);		/* be conservative */
@@ -139,6 +139,7 @@ index_form_tuple(TupleDesc tupleDescriptor,
 					values,
 #endif
 					isnull,
+					false,
 					(char *) tp + hoff,
 					data_size,
 					&tupmask,
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index c7fa727..4c74df3 100644
--- a/src/backend/access/common/printtup.c
+++ b/src/backend/access/common/printtup.c
@@ -199,6 +199,10 @@ SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist, int16 *formats)
 	pq_beginmessage(&buf, 'T'); /* tuple descriptor message type */
 	pq_sendint(&buf, natts, 2); /* # of attrs in tuples */
 
+	/*
+	 * The attributes in the slot's descriptor are already in logical order;
+	 * we don't editorialize on the ordering here.
+	 */
 	for (i = 0; i < natts; ++i)
 	{
 		Oid			atttypid = attrs[i]->atttypid;
@@ -327,7 +331,8 @@ printtup(TupleTableSlot *slot, DestReceiver *self)
 	pq_sendint(&buf, natts, 2);
 
 	/*
-	 * send the attributes of this tuple
+	 * Send the attributes of this tuple.  Note the attributes of the slot's
+	 * descriptor are already in logical order.
 	 */
 	for (i = 0; i < natts; ++i)
 	{
@@ -430,7 +435,8 @@ printtup_20(TupleTableSlot *slot, DestReceiver *self)
 		pq_sendint(&buf, j, 1);
 
 	/*
-	 * send the attributes of this tuple
+	 * Send the attributes of this tuple.  Note the attributes of the slot's
+	 * descriptor are already in logical order.
 	 */
 	for (i = 0; i < natts; ++i)
 	{
@@ -517,7 +523,8 @@ debugStartup(DestReceiver *self, int operation, TupleDesc typeinfo)
 	int			i;
 
 	/*
-	 * show the return type of the tuples
+	 * Show the return type of the tuples.  Note the attributes of the slot's
+	 * descriptor are already in logical order.
 	 */
 	for (i = 0; i < natts; ++i)
 		printatt((unsigned) i + 1, attinfo[i], NULL);
@@ -540,6 +547,10 @@ debugtup(TupleTableSlot *slot, DestReceiver *self)
 	Oid			typoutput;
 	bool		typisvarlena;
 
+	/*
+	 * Send the attributes of this tuple.  Note the attributes of the slot's
+	 * descriptor are already in logical order.
+	 */
 	for (i = 0; i < natts; ++i)
 	{
 		attr = slot_getattr(slot, i + 1, &isnull);
@@ -612,7 +623,8 @@ printtup_internal_20(TupleTableSlot *slot, DestReceiver *self)
 		pq_sendint(&buf, j, 1);
 
 	/*
-	 * send the attributes of this tuple
+	 * Send the attributes of this tuple.  Note the attributes of the slot's
+	 * descriptor are already in logical order.
 	 */
 	for (i = 0; i < natts; ++i)
 	{
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index f3b3689..1f4c86a 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -25,6 +25,7 @@
 #include "parser/parse_type.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/memutils.h"
 #include "utils/resowner_private.h"
 #include "utils/syscache.h"
 
@@ -87,6 +88,7 @@ CreateTemplateTupleDesc(int natts, bool hasoid)
 	 * Initialize other fields of the tupdesc.
 	 */
 	desc->natts = natts;
+	desc->logattrs = NULL;
 	desc->constr = NULL;
 	desc->tdtypeid = RECORDOID;
 	desc->tdtypmod = -1;
@@ -120,6 +122,7 @@ CreateTupleDesc(int natts, bool hasoid, Form_pg_attribute *attrs)
 	desc = (TupleDesc) palloc(sizeof(struct tupleDesc));
 	desc->attrs = attrs;
 	desc->natts = natts;
+	desc->logattrs = NULL;
 	desc->constr = NULL;
 	desc->tdtypeid = RECORDOID;
 	desc->tdtypmod = -1;
@@ -154,6 +157,8 @@ CreateTupleDescCopy(TupleDesc tupdesc)
 	desc->tdtypeid = tupdesc->tdtypeid;
 	desc->tdtypmod = tupdesc->tdtypmod;
 
+	Assert(desc->logattrs == NULL);
+
 	return desc;
 }
 
@@ -251,6 +256,7 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
 	 * bit to avoid a useless O(N^2) penalty.
 	 */
 	dst->attrs[dstAttno - 1]->attnum = dstAttno;
+	dst->attrs[dstAttno - 1]->attlognum = dstAttno;
 	dst->attrs[dstAttno - 1]->attcacheoff = -1;
 
 	/* since we're not copying constraints or defaults, clear these */
@@ -301,6 +307,9 @@ FreeTupleDesc(TupleDesc tupdesc)
 		pfree(tupdesc->constr);
 	}
 
+	if (tupdesc->logattrs)
+		pfree(tupdesc->logattrs);
+
 	pfree(tupdesc);
 }
 
@@ -345,7 +354,7 @@ DecrTupleDescRefCount(TupleDesc tupdesc)
  * Note: we deliberately do not check the attrelid and tdtypmod fields.
  * This allows typcache.c to use this routine to see if a cached record type
  * matches a requested type, and is harmless for relcache.c's uses.
- * We don't compare tdrefcount, either.
+ * We don't compare tdrefcount nor logattrs, either.
  */
 bool
 equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
@@ -386,6 +395,13 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (attr1->attlen != attr2->attlen)
 			return false;
+		if (attr1->attphysnum != attr2->attphysnum)
+			return false;
+		/* intentionally do not compare attlognum */
+#if 0
+		if (attr1->attlognum != attr2->attlognum)
+			return false;
+#endif
 		if (attr1->attndims != attr2->attndims)
 			return false;
 		if (attr1->atttypmod != attr2->atttypmod)
@@ -529,6 +545,8 @@ TupleDescInitEntry(TupleDesc desc,
 	att->atttypmod = typmod;
 
 	att->attnum = attributeNumber;
+	att->attphysnum = attributeNumber;
+	att->attlognum = attributeNumber;
 	att->attndims = attdim;
 
 	att->attnotnull = false;
@@ -574,6 +592,27 @@ TupleDescInitEntryCollation(TupleDesc desc,
 	desc->attrs[attributeNumber - 1]->attcollation = collationid;
 }
 
+/*
+ * TupleDescInitEntryLognum
+ *
+ * Assign a nondefault lognum to a previously initialized tuple descriptor
+ * entry.
+ */
+void
+TupleDescInitEntryLognum(TupleDesc desc,
+						 AttrNumber attributeNumber,
+						 int attlognum)
+{
+	/*
+	 * sanity checks
+	 */
+	AssertArg(PointerIsValid(desc));
+	AssertArg(attributeNumber >= 1);
+	AssertArg(attributeNumber <= desc->natts);
+
+	desc->attrs[attributeNumber - 1]->attlognum = attlognum;
+}
+
 
 /*
  * BuildDescForRelation
@@ -666,6 +705,8 @@ BuildDescForRelation(List *schema)
 		desc->constr = NULL;
 	}
 
+	Assert(desc->logattrs == NULL);
+
 	return desc;
 }
 
@@ -726,5 +767,49 @@ BuildDescFromLists(List *names, List *types, List *typmods, List *collations)
 		TupleDescInitEntryCollation(desc, attnum, attcollation);
 	}
 
+	Assert(desc->logattrs == NULL);
 	return desc;
 }
+
+/*
+ * qsort callback for TupleDescGetSortedAttrs
+ */
+static int
+cmplognum(const void *attr1, const void *attr2)
+{
+	Form_pg_attribute	att1 = *(Form_pg_attribute *) attr1;
+	Form_pg_attribute	att2 = *(Form_pg_attribute *) attr2;
+
+	if (att1->attlognum < att2->attlognum)
+		return -1;
+	if (att1->attlognum > att2->attlognum)
+		return 1;
+	return 0;
+}
+
+/*
+ * Return the array of attrs sorted by logical position
+ */
+Form_pg_attribute *
+TupleDescGetSortedAttrs(TupleDesc desc)
+{
+	if (desc->logattrs == NULL)
+	{
+		Form_pg_attribute *attrs;
+
+		/*
+		 * logattrs must be allocated in the same memcxt as the tupdesc it
+		 * belongs to, so that it isn't reset ahead of time.
+		 */
+		attrs = MemoryContextAlloc(GetMemoryChunkContext(desc),
+								   sizeof(Form_pg_attribute) * desc->natts);
+		memcpy(attrs, desc->attrs,
+			   sizeof(Form_pg_attribute) * desc->natts);
+
+		qsort(attrs, desc->natts, sizeof(Form_pg_attribute), cmplognum);
+
+		desc->logattrs = attrs;
+	}
+
+	return desc->logattrs;
+}
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index ce44bbd..017713d 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -658,8 +658,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 	 * Look for attributes with attstorage 'x' to compress.  Also find large
 	 * attributes with attstorage 'x' or 'e', and store them external.
 	 */
-	while (heap_compute_data_size(tupleDesc,
-								  toast_values, toast_isnull) > maxDataLen)
+	while (heap_compute_data_size(tupleDesc, toast_values, toast_isnull,
+								  false) > maxDataLen)
 	{
 		int			biggest_attno = -1;
 		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
@@ -748,8 +748,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 	 * Second we look for attributes of attstorage 'x' or 'e' that are still
 	 * inline.  But skip this if there's no toast table to push them to.
 	 */
-	while (heap_compute_data_size(tupleDesc,
-								  toast_values, toast_isnull) > maxDataLen &&
+	while (heap_compute_data_size(tupleDesc, toast_values, toast_isnull,
+								  false) > maxDataLen &&
 		   rel->rd_rel->reltoastrelid != InvalidOid)
 	{
 		int			biggest_attno = -1;
@@ -799,8 +799,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 	 * Round 3 - this time we take attributes with storage 'm' into
 	 * compression
 	 */
-	while (heap_compute_data_size(tupleDesc,
-								  toast_values, toast_isnull) > maxDataLen)
+	while (heap_compute_data_size(tupleDesc, toast_values, toast_isnull,
+								  false) > maxDataLen)
 	{
 		int			biggest_attno = -1;
 		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
@@ -862,8 +862,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 	 */
 	maxDataLen = TOAST_TUPLE_TARGET_MAIN - hoff;
 
-	while (heap_compute_data_size(tupleDesc,
-								  toast_values, toast_isnull) > maxDataLen &&
+	while (heap_compute_data_size(tupleDesc, toast_values, toast_isnull,
+								  false) > maxDataLen &&
 		   rel->rd_rel->reltoastrelid != InvalidOid)
 	{
 		int			biggest_attno = -1;
@@ -937,8 +937,8 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		if (olddata->t_infomask & HEAP_HASOID)
 			new_header_len += sizeof(Oid);
 		new_header_len = MAXALIGN(new_header_len);
-		new_data_len = heap_compute_data_size(tupleDesc,
-											  toast_values, toast_isnull);
+		new_data_len = heap_compute_data_size(tupleDesc, toast_values,
+											  toast_isnull, false);
 		new_tuple_len = new_header_len + new_data_len;
 
 		/*
@@ -964,6 +964,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
 		heap_fill_tuple(tupleDesc,
 						toast_values,
 						toast_isnull,
+						false,
 						(char *) new_data + new_header_len,
 						new_data_len,
 						&(new_data->t_infomask),
@@ -1170,8 +1171,8 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
 	if (tup->t_infomask & HEAP_HASOID)
 		new_header_len += sizeof(Oid);
 	new_header_len = MAXALIGN(new_header_len);
-	new_data_len = heap_compute_data_size(tupleDesc,
-										  toast_values, toast_isnull);
+	new_data_len = heap_compute_data_size(tupleDesc, toast_values,
+										  toast_isnull, false);
 	new_tuple_len = new_header_len + new_data_len;
 
 	new_data = (HeapTupleHeader) palloc0(new_tuple_len);
@@ -1194,6 +1195,7 @@ toast_flatten_tuple_to_datum(HeapTupleHeader tup,
 	heap_fill_tuple(tupleDesc,
 					toast_values,
 					toast_isnull,
+					false,
 					(char *) new_data + new_header_len,
 					new_data_len,
 					&(new_data->t_infomask),
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index 4a542e6..a3464e1 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -705,7 +705,9 @@ DefineAttr(char *name, char *type, int attnum)
 
 	namestrcpy(&attrtypes[attnum]->attname, name);
 	elog(DEBUG4, "column %s %s", NameStr(attrtypes[attnum]->attname), type);
-	attrtypes[attnum]->attnum = attnum + 1;		/* fillatt */
+	attrtypes[attnum]->attnum = attnum + 1;
+	attrtypes[attnum]->attphysnum = attnum + 1;
+	attrtypes[attnum]->attlognum = attnum + 1;
 
 	typeoid = gettype(type);
 
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index ca89879..85a46a3 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -198,6 +198,8 @@ foreach my $catname (@{ $catalogs->{names} })
 				$attnum++;
 				my $row = emit_pgattr_row($table_name, $attr, $priornotnull);
 				$row->{attnum}        = $attnum;
+				$row->{attphysnum}    = $attnum;
+				$row->{attlognum}     = $attnum;
 				$row->{attstattarget} = '-1';
 				$priornotnull &= ($row->{attnotnull} eq 't');
 
@@ -235,6 +237,8 @@ foreach my $catname (@{ $catalogs->{names} })
 					$attnum--;
 					my $row = emit_pgattr_row($table_name, $attr, 1);
 					$row->{attnum}        = $attnum;
+					$row->{attphysnum}    = $attnum;
+					$row->{attlognum}     = $attnum;
 					$row->{attstattarget} = '0';
 
 					# some catalogs don't have oids
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index e523ee9..93c182c 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -136,37 +136,49 @@ static List *insert_ordered_unique_oid(List *list, Oid datum);
 
 static FormData_pg_attribute a1 = {
 	0, {"ctid"}, TIDOID, 0, sizeof(ItemPointerData),
-	SelfItemPointerAttributeNumber, 0, -1, -1,
+	SelfItemPointerAttributeNumber, SelfItemPointerAttributeNumber,
+	SelfItemPointerAttributeNumber,
+	0, -1, -1,
 	false, 'p', 's', true, false, false, true, 0
 };
 
 static FormData_pg_attribute a2 = {
 	0, {"oid"}, OIDOID, 0, sizeof(Oid),
-	ObjectIdAttributeNumber, 0, -1, -1,
+	ObjectIdAttributeNumber, ObjectIdAttributeNumber,
+	ObjectIdAttributeNumber,
+	0, -1, -1,
 	true, 'p', 'i', true, false, false, true, 0
 };
 
 static FormData_pg_attribute a3 = {
 	0, {"xmin"}, XIDOID, 0, sizeof(TransactionId),
-	MinTransactionIdAttributeNumber, 0, -1, -1,
+	MinTransactionIdAttributeNumber, MinTransactionIdAttributeNumber,
+	MinTransactionIdAttributeNumber,
+	0, -1, -1,
 	true, 'p', 'i', true, false, false, true, 0
 };
 
 static FormData_pg_attribute a4 = {
 	0, {"cmin"}, CIDOID, 0, sizeof(CommandId),
-	MinCommandIdAttributeNumber, 0, -1, -1,
+	MinCommandIdAttributeNumber, MinCommandIdAttributeNumber,
+	MinCommandIdAttributeNumber,
+	0, -1, -1,
 	true, 'p', 'i', true, false, false, true, 0
 };
 
 static FormData_pg_attribute a5 = {
 	0, {"xmax"}, XIDOID, 0, sizeof(TransactionId),
-	MaxTransactionIdAttributeNumber, 0, -1, -1,
+	MaxTransactionIdAttributeNumber, MaxTransactionIdAttributeNumber,
+	MaxTransactionIdAttributeNumber,
+	0, -1, -1,
 	true, 'p', 'i', true, false, false, true, 0
 };
 
 static FormData_pg_attribute a6 = {
 	0, {"cmax"}, CIDOID, 0, sizeof(CommandId),
-	MaxCommandIdAttributeNumber, 0, -1, -1,
+	MaxCommandIdAttributeNumber, MaxCommandIdAttributeNumber,
+	MaxCommandIdAttributeNumber,
+	0, -1, -1,
 	true, 'p', 'i', true, false, false, true, 0
 };
 
@@ -178,7 +190,9 @@ static FormData_pg_attribute a6 = {
  */
 static FormData_pg_attribute a7 = {
 	0, {"tableoid"}, OIDOID, 0, sizeof(Oid),
-	TableOidAttributeNumber, 0, -1, -1,
+	TableOidAttributeNumber, TableOidAttributeNumber,
+	TableOidAttributeNumber,
+	0, -1, -1,
 	true, 'p', 'i', true, false, false, true, 0
 };
 
@@ -615,6 +629,8 @@ InsertPgAttributeTuple(Relation pg_attribute_rel,
 	values[Anum_pg_attribute_attstattarget - 1] = Int32GetDatum(new_attribute->attstattarget);
 	values[Anum_pg_attribute_attlen - 1] = Int16GetDatum(new_attribute->attlen);
 	values[Anum_pg_attribute_attnum - 1] = Int16GetDatum(new_attribute->attnum);
+	values[Anum_pg_attribute_attphysnum - 1] = Int16GetDatum(new_attribute->attphysnum);
+	values[Anum_pg_attribute_attlognum - 1] = Int16GetDatum(new_attribute->attlognum);
 	values[Anum_pg_attribute_attndims - 1] = Int32GetDatum(new_attribute->attndims);
 	values[Anum_pg_attribute_attcacheoff - 1] = Int32GetDatum(new_attribute->attcacheoff);
 	values[Anum_pg_attribute_atttypmod - 1] = Int32GetDatum(new_attribute->atttypmod);
@@ -2174,6 +2190,7 @@ AddRelationNewConstraints(Relation rel,
 	foreach(cell, newColDefaults)
 	{
 		RawColumnDefault *colDef = (RawColumnDefault *) lfirst(cell);
+		/* FIXME -- does this need to change? apparently not, but it's suspicious */
 		Form_pg_attribute atp = rel->rd_att->attrs[colDef->attnum - 1];
 
 		expr = cookDefault(pstate, colDef->raw_default,
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 844d413..1b62405 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -348,6 +348,8 @@ ConstructTupleDescriptor(Relation heapRelation,
 			 * attr
 			 */
 			to->attnum = i + 1;
+			to->attlognum = i + 1;
+			to->attphysnum = i + 1;
 
 			to->attstattarget = -1;
 			to->attcacheoff = -1;
@@ -382,6 +384,8 @@ ConstructTupleDescriptor(Relation heapRelation,
 			 * Assign some of the attributes values. Leave the rest as 0.
 			 */
 			to->attnum = i + 1;
+			to->attlognum = i + 1;
+			to->attphysnum = i + 1;
 			to->atttypid = keyType;
 			to->attlen = typeTup->typlen;
 			to->attbyval = typeTup->typbyval;
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 08abe14..764ce77 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -158,7 +158,7 @@ typedef struct CopyStateData
 	bool		file_has_oids;
 	FmgrInfo	oid_in_function;
 	Oid			oid_typioparam;
-	FmgrInfo   *in_functions;	/* array of input functions for each attrs */
+	FmgrInfo   *in_functions;	/* array of input functions for each attr */
 	Oid		   *typioparams;	/* array of element types for in_functions */
 	int		   *defmap;			/* array of default att numbers */
 	ExprState **defexprs;		/* array of default att expressions */
@@ -4296,7 +4296,7 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist)
 	if (attnamelist == NIL)
 	{
 		/* Generate default column list */
-		Form_pg_attribute *attr = tupDesc->attrs;
+		Form_pg_attribute *attr = TupleDescGetSortedAttrs(tupDesc);
 		int			attr_count = tupDesc->natts;
 		int			i;
 
@@ -4304,7 +4304,7 @@ CopyGetAttnums(TupleDesc tupDesc, Relation rel, List *attnamelist)
 		{
 			if (attr[i]->attisdropped)
 				continue;
-			attnums = lappend_int(attnums, i + 1);
+			attnums = lappend_int(attnums, attr[i]->attnum);
 		}
 	}
 	else
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1e737a0..ea9490f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1485,7 +1485,8 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		TupleDesc	tupleDesc;
 		TupleConstr *constr;
 		AttrNumber *newattno;
-		AttrNumber	parent_attno;
+		AttrNumber	parent_colctr;
+		Form_pg_attribute *parent_attrs;
 
 		/*
 		 * A self-exclusive lock is needed here.  If two backends attempt to
@@ -1542,6 +1543,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			parentsWithOids++;
 
 		tupleDesc = RelationGetDescr(relation);
+		parent_attrs = TupleDescGetSortedAttrs(tupleDesc);
 		constr = tupleDesc->constr;
 
 		/*
@@ -1552,10 +1554,17 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 		newattno = (AttrNumber *)
 			palloc0(tupleDesc->natts * sizeof(AttrNumber));
 
-		for (parent_attno = 1; parent_attno <= tupleDesc->natts;
-			 parent_attno++)
+		/*
+		 * parent_colctr is the index into the logical-ordered array of parent
+		 * columns; parent_attno is the attnum of each column.  The newattno
+		 * map entries must use the latter for numbering; the former is a loop
+		 * counter only.
+		 */
+		for (parent_colctr = 1; parent_colctr <= tupleDesc->natts;
+			 parent_colctr++)
 		{
-			Form_pg_attribute attribute = tupleDesc->attrs[parent_attno - 1];
+			Form_pg_attribute attribute = parent_attrs[parent_colctr - 1];
+			AttrNumber	parent_attno = attribute->attnum;
 			char	   *attributeName = NameStr(attribute->attname);
 			int			exist_attno;
 			ColumnDef  *def;
@@ -4727,6 +4736,8 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	attribute.attcacheoff = -1;
 	attribute.atttypmod = typmod;
 	attribute.attnum = newattnum;
+	attribute.attlognum = newattnum;
+	attribute.attphysnum = newattnum;
 	attribute.attbyval = tform->typbyval;
 	attribute.attndims = list_length(colDef->typeName->arrayBounds);
 	attribute.attstorage = tform->typstorage;
diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 88af735..6896098 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -1189,6 +1189,9 @@ ExecEvalParamExtern(ExprState *exprstate, ExprContext *econtext,
  *		to use these.  Ex: overpaid(EMP) might call GetAttributeByNum().
  *		Note: these are actually rather slow because they do a typcache
  *		lookup on each call.
+ *
+ *	FIXME -- probably these functions should consider attrno a logical column
+ *	number
  */
 Datum
 GetAttributeByNum(HeapTupleHeader tuple,
@@ -3289,7 +3292,8 @@ ExecEvalRow(RowExprState *rstate,
 		i++;
 	}
 
-	tuple = heap_form_tuple(rstate->tupdesc, values, isnull);
+	tuple = heap_form_tuple_extended(rstate->tupdesc, values, isnull,
+									 HTOPT_LOGICAL_ORDER);
 
 	pfree(values);
 	pfree(isnull);
@@ -4035,6 +4039,7 @@ ExecEvalFieldSelect(FieldSelectState *fstate,
 	TupleDesc	tupDesc;
 	Form_pg_attribute attr;
 	HeapTupleData tmptup;
+	Form_pg_attribute *attrs;
 
 	tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone);
 
@@ -4062,7 +4067,8 @@ ExecEvalFieldSelect(FieldSelectState *fstate,
 	if (fieldnum > tupDesc->natts)		/* should never happen */
 		elog(ERROR, "attribute number %d exceeds number of columns %d",
 			 fieldnum, tupDesc->natts);
-	attr = tupDesc->attrs[fieldnum - 1];
+	attrs = TupleDescGetSortedAttrs(tupDesc);
+	attr = attrs[fieldnum - 1];
 
 	/* Check for dropped column, and force a NULL result if so */
 	if (attr->attisdropped)
@@ -4085,7 +4091,7 @@ ExecEvalFieldSelect(FieldSelectState *fstate,
 	tmptup.t_data = tuple;
 
 	result = heap_getattr(&tmptup,
-						  fieldnum,
+						  attr->attnum,
 						  tupDesc,
 						  isNull);
 	return result;
@@ -4111,6 +4117,7 @@ ExecEvalFieldStore(FieldStoreState *fstate,
 	bool	   *isnull;
 	Datum		save_datum;
 	bool		save_isNull;
+	Form_pg_attribute *attrs;
 	ListCell   *l1,
 			   *l2;
 
@@ -4122,6 +4129,7 @@ ExecEvalFieldStore(FieldStoreState *fstate,
 	/* Lookup tupdesc if first time through or after rescan */
 	tupDesc = get_cached_rowtype(fstore->resulttype, -1,
 								 &fstate->argdesc, econtext);
+	attrs = TupleDescGetSortedAttrs(tupDesc);
 
 	/* Allocate workspace */
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
@@ -4142,7 +4150,8 @@ ExecEvalFieldStore(FieldStoreState *fstate,
 		tmptup.t_tableOid = InvalidOid;
 		tmptup.t_data = tuphdr;
 
-		heap_deform_tuple(&tmptup, tupDesc, values, isnull);
+		heap_deform_tuple_extended(&tmptup, tupDesc, values, isnull,
+								   HTOPT_LOGICAL_ORDER);
 	}
 	else
 	{
@@ -4160,8 +4169,10 @@ ExecEvalFieldStore(FieldStoreState *fstate,
 	{
 		ExprState  *newval = (ExprState *) lfirst(l1);
 		AttrNumber	fieldnum = lfirst_int(l2);
+		AttrNumber	attnum = attrs[fieldnum - 1]->attnum;
+
 
-		Assert(fieldnum > 0 && fieldnum <= tupDesc->natts);
+		Assert(attnum > 0 && attnum <= tupDesc->natts);
 
 		/*
 		 * Use the CaseTestExpr mechanism to pass down the old value of the
@@ -4172,19 +4183,20 @@ ExecEvalFieldStore(FieldStoreState *fstate,
 		 * assignment can't be within a CASE either.  (So saving and restoring
 		 * the caseValue is just paranoia, but let's do it anyway.)
 		 */
-		econtext->caseValue_datum = values[fieldnum - 1];
-		econtext->caseValue_isNull = isnull[fieldnum - 1];
+		econtext->caseValue_datum = values[attnum - 1];
+		econtext->caseValue_isNull = isnull[attnum - 1];
 
-		values[fieldnum - 1] = ExecEvalExpr(newval,
-											econtext,
-											&isnull[fieldnum - 1],
-											NULL);
+		values[attnum - 1] = ExecEvalExpr(newval,
+										  econtext,
+										  &isnull[attnum - 1],
+										  NULL);
 	}
 
 	econtext->caseValue_datum = save_datum;
 	econtext->caseValue_isNull = save_isNull;
 
-	tuple = heap_form_tuple(tupDesc, values, isnull);
+	tuple = heap_form_tuple_extended(tupDesc, values, isnull,
+									 HTOPT_LOGICAL_ORDER);
 
 	pfree(values);
 	pfree(isnull);
@@ -4830,7 +4842,7 @@ ExecInitExpr(Expr *node, PlanState *parent)
 				BlessTupleDesc(rstate->tupdesc);
 				/* Set up evaluation, skipping any deleted columns */
 				Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts);
-				attrs = rstate->tupdesc->attrs;
+				attrs = TupleDescGetSortedAttrs(rstate->tupdesc);
 				i = 0;
 				foreach(l, rowexpr->args)
 				{
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c
index 1319519..476fa18 100644
--- a/src/backend/executor/execScan.c
+++ b/src/backend/executor/execScan.c
@@ -271,11 +271,12 @@ tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, TupleDesc tupdesc
 	int			attrno;
 	bool		hasoid;
 	ListCell   *tlist_item = list_head(tlist);
+	Form_pg_attribute *attrs = TupleDescGetSortedAttrs(tupdesc);
 
 	/* Check the tlist attributes */
 	for (attrno = 1; attrno <= numattrs; attrno++)
 	{
-		Form_pg_attribute att_tup = tupdesc->attrs[attrno - 1];
+		Form_pg_attribute att_tup = attrs[attrno - 1];
 		Var		   *var;
 
 		if (tlist_item == NULL)
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 0811941..7e6c9d5 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -599,7 +599,8 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
 	 */
 	return heap_form_minimal_tuple(slot->tts_tupleDescriptor,
 								   slot->tts_values,
-								   slot->tts_isnull);
+								   slot->tts_isnull,
+								   HTOPT_LOGICAL_ORDER);
 }
 
 /* --------------------------------
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 4d11260..69a8ab6 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -1657,6 +1657,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 	{
 		/* Returns a rowtype */
 		TupleDesc	tupdesc;
+		Form_pg_attribute *attrs;
 		int			tupnatts;	/* physical number of columns in tuple */
 		int			tuplogcols; /* # of nondeleted columns in tuple */
 		int			colindex;	/* physical column index */
@@ -1721,6 +1722,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 		 * result columns if the caller asked for that.
 		 */
 		tupnatts = tupdesc->natts;
+		attrs = TupleDescGetSortedAttrs(tupdesc);
 		tuplogcols = 0;			/* we'll count nondeleted cols as we go */
 		colindex = 0;
 		newtlist = NIL;			/* these are only used if modifyTargetList */
@@ -1749,7 +1751,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 							 errmsg("return type mismatch in function declared to return %s",
 									format_type_be(rettype)),
 					errdetail("Final statement returns too many columns.")));
-				attr = tupdesc->attrs[colindex - 1];
+				attr = attrs[colindex - 1];
 				if (attr->attisdropped && modifyTargetList)
 				{
 					Expr	   *null_expr;
@@ -1806,7 +1808,7 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 		/* remaining columns in tupdesc had better all be dropped */
 		for (colindex++; colindex <= tupnatts; colindex++)
 		{
-			if (!tupdesc->attrs[colindex - 1]->attisdropped)
+			if (!attrs[colindex - 1]->attisdropped)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
 						 errmsg("return type mismatch in function declared to return %s",
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 6b1bf7b..2081e54 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -2006,6 +2006,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	COPY_SCALAR_FIELD(rtekind);
 	COPY_SCALAR_FIELD(relid);
 	COPY_SCALAR_FIELD(relkind);
+	COPY_NODE_FIELD(lognums);
 	COPY_NODE_FIELD(subquery);
 	COPY_SCALAR_FIELD(security_barrier);
 	COPY_SCALAR_FIELD(jointype);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index edbd09f..5631dc0 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -24,6 +24,7 @@
 #include <ctype.h>
 
 #include "lib/stringinfo.h"
+#include "nodes/execnodes.h"
 #include "nodes/plannodes.h"
 #include "nodes/relation.h"
 #include "utils/datum.h"
@@ -1440,6 +1441,22 @@ _outTargetEntry(StringInfo str, const TargetEntry *node)
 }
 
 static void
+_outGenericExprState(StringInfo str, const GenericExprState *node)
+{
+	WRITE_NODE_TYPE("GENERICEXPRSTATE");
+
+	WRITE_NODE_FIELD(arg);
+}
+
+static void
+_outExprState(StringInfo str, const ExprState *node)
+{
+	WRITE_NODE_TYPE("EXPRSTATE");
+
+	WRITE_NODE_FIELD(expr);
+}
+
+static void
 _outRangeTblRef(StringInfo str, const RangeTblRef *node)
 {
 	WRITE_NODE_TYPE("RANGETBLREF");
@@ -2420,6 +2437,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 		case RTE_RELATION:
 			WRITE_OID_FIELD(relid);
 			WRITE_CHAR_FIELD(relkind);
+			WRITE_NODE_FIELD(lognums);
 			break;
 		case RTE_SUBQUERY:
 			WRITE_NODE_FIELD(subquery);
@@ -3073,6 +3091,12 @@ _outNode(StringInfo str, const void *obj)
 			case T_FromExpr:
 				_outFromExpr(str, obj);
 				break;
+			case T_GenericExprState:
+				_outGenericExprState(str, obj);
+				break;
+			case T_ExprState:
+				_outExprState(str, obj);
+				break;
 
 			case T_Path:
 				_outPath(str, obj);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index a3efdd4..38dc982 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -1216,6 +1216,7 @@ _readRangeTblEntry(void)
 		case RTE_RELATION:
 			READ_OID_FIELD(relid);
 			READ_CHAR_FIELD(relkind);
+			READ_NODE_FIELD(lognums);
 			break;
 		case RTE_SUBQUERY:
 			READ_NODE_FIELD(subquery);
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 9cb1378..a3d66b2 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -1749,7 +1749,7 @@ pullup_replace_vars_callback(Var *var,
 		 * expansion with varlevelsup = 0, and then adjust if needed.
 		 */
 		expandRTE(rcon->target_rte,
-				  var->varno, 0 /* not varlevelsup */ , var->location,
+				  var->varno, 0 /* not varlevelsup */ , var->location, false,
 				  (var->vartype != RECORDOID),
 				  &colnames, &fields);
 		/* Adjust the generated per-field Vars, but don't insert PHVs */
diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 4ab12e5..c226079 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -216,6 +216,7 @@ expand_targetlist(List *tlist, int command_type,
 	 */
 	rel = heap_open(getrelid(result_relation, range_table), NoLock);
 
+	/* FIXME --- do we need a different order of attributes here? */
 	numattrs = RelationGetNumberOfAttributes(rel);
 
 	for (attrno = 1; attrno <= numattrs; attrno++)
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index b2becfa..f63f201 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -857,17 +857,19 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
 	int			attrno,
 				numattrs;
 	List	   *colvars;
+	Form_pg_attribute *attrs;
 
 	switch (rte->rtekind)
 	{
 		case RTE_RELATION:
 			/* Assume we already have adequate lock */
 			relation = heap_open(rte->relid, NoLock);
+			attrs = TupleDescGetSortedAttrs(RelationGetDescr(relation));
 
 			numattrs = RelationGetNumberOfAttributes(relation);
 			for (attrno = 1; attrno <= numattrs; attrno++)
 			{
-				Form_pg_attribute att_tup = relation->rd_att->attrs[attrno - 1];
+				Form_pg_attribute att_tup = attrs[attrno - 1];
 
 				if (att_tup->attisdropped)
 				{
@@ -917,7 +919,7 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
 		case RTE_VALUES:
 		case RTE_CTE:
 			/* Not all of these can have dropped cols, but share code anyway */
-			expandRTE(rte, varno, 0, -1, true /* include dropped */ ,
+			expandRTE(rte, varno, 0, -1, true /* include dropped */ , false,
 					  NULL, &colvars);
 			foreach(l, colvars)
 			{
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index cc569ed..6c79bd3 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -682,7 +682,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		/*
 		 * Generate list of Vars referencing the RTE
 		 */
-		expandRTE(rte, rtr->rtindex, 0, -1, false, NULL, &exprList);
+		expandRTE(rte, rtr->rtindex, 0, -1, false, false, NULL, &exprList);
 	}
 	else
 	{
@@ -1209,7 +1209,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	 * Generate a targetlist as though expanding "*"
 	 */
 	Assert(pstate->p_next_resno == 1);
-	qry->targetList = expandRelAttrs(pstate, rte, rtindex, 0, -1);
+	qry->targetList = expandRelAttrs(pstate, rte, rtindex, 0, false, -1);
 
 	/*
 	 * The grammar allows attaching ORDER BY, LIMIT, and FOR UPDATE to a
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 4931dca..3b33f82 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -879,9 +879,9 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		 *
 		 * Note: expandRTE returns new lists, safe for me to modify
 		 */
-		expandRTE(l_rte, l_rtindex, 0, -1, false,
+		expandRTE(l_rte, l_rtindex, 0, -1, false, true,
 				  &l_colnames, &l_colvars);
-		expandRTE(r_rte, r_rtindex, 0, -1, false,
+		expandRTE(r_rte, r_rtindex, 0, -1, false, true,
 				  &r_colnames, &r_colvars);
 
 		/*
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index 8416d36..dc8f2e1 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -906,6 +906,7 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 	int			i;
 	int			ucolno;
 	ListCell   *arg;
+	Form_pg_attribute	*attrs;
 
 	if (node && IsA(node, RowExpr))
 	{
@@ -924,7 +925,7 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 		RangeTblEntry *rte;
 
 		rte = GetRTEByRangeTablePosn(pstate, rtindex, sublevels_up);
-		expandRTE(rte, rtindex, sublevels_up, vlocation, false,
+		expandRTE(rte, rtindex, sublevels_up, vlocation, false, false,
 				  NULL, &args);
 	}
 	else
@@ -939,6 +940,7 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 	newargs = NIL;
 	ucolno = 1;
 	arg = list_head(args);
+	attrs = TupleDescGetSortedAttrs(tupdesc);
 	for (i = 0; i < tupdesc->natts; i++)
 	{
 		Node	   *expr;
@@ -946,7 +948,7 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 		Oid			exprtype;
 
 		/* Fill in NULLs for dropped columns in rowtype */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (attrs[i]->attisdropped)
 		{
 			/*
 			 * can't use atttypid here, but it doesn't really matter what type
@@ -970,8 +972,8 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 
 		cexpr = coerce_to_target_type(pstate,
 									  expr, exprtype,
-									  tupdesc->attrs[i]->atttypid,
-									  tupdesc->attrs[i]->atttypmod,
+									  attrs[i]->atttypid,
+									  attrs[i]->atttypmod,
 									  ccontext,
 									  COERCE_IMPLICIT_CAST,
 									  -1);
@@ -983,7 +985,7 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 							format_type_be(targetTypeId)),
 					 errdetail("Cannot cast type %s to %s in column %d.",
 							   format_type_be(exprtype),
-							   format_type_be(tupdesc->attrs[i]->atttypid),
+							   format_type_be(attrs[i]->atttypid),
 							   ucolno),
 					 parser_coercion_errposition(pstate, location, expr)));
 		newargs = lappend(newargs, cexpr);
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index 9ebd3fd..909f397 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -1759,6 +1759,7 @@ ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg,
 {
 	TupleDesc	tupdesc;
 	int			i;
+	Form_pg_attribute *attrs;
 
 	/*
 	 * Special case for whole-row Vars so that we can resolve (foo.*).bar even
@@ -1796,9 +1797,10 @@ ParseComplexProjection(ParseState *pstate, char *funcname, Node *first_arg,
 		return NULL;			/* unresolvable RECORD type */
 	Assert(tupdesc);
 
+	attrs = TupleDescGetSortedAttrs(tupdesc);
 	for (i = 0; i < tupdesc->natts; i++)
 	{
-		Form_pg_attribute att = tupdesc->attrs[i];
+		Form_pg_attribute att = attrs[i];
 
 		if (strcmp(funcname, NameStr(att->attname)) == 0 &&
 			!att->attisdropped)
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 478584d..41a1464 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -43,12 +43,12 @@ static void markRTEForSelectPriv(ParseState *pstate, RangeTblEntry *rte,
 					 int rtindex, AttrNumber col);
 static void expandRelation(Oid relid, Alias *eref,
 			   int rtindex, int sublevels_up,
-			   int location, bool include_dropped,
+			   int location, bool include_dropped, bool logical_sort,
 			   List **colnames, List **colvars);
 static void expandTupleDesc(TupleDesc tupdesc, Alias *eref,
 				int count, int offset,
 				int rtindex, int sublevels_up,
-				int location, bool include_dropped,
+				int location, bool include_dropped, bool logical_sort,
 				List **colnames, List **colvars);
 static int	specialAttNum(const char *attname);
 static bool isQueryUsingTempRelation_walker(Node *node, void *context);
@@ -519,6 +519,12 @@ GetCTEForRTE(ParseState *pstate, RangeTblEntry *rte, int rtelevelsup)
 	return NULL;				/* keep compiler quiet */
 }
 
+static int16
+get_attnum_by_lognum(RangeTblEntry *rte, int16 attlognum)
+{
+	return list_nth_int(rte->lognums, attlognum - 1);
+}
+
 /*
  * scanRTEForColumn
  *	  Search the column names of a single RTE for the given name.
@@ -561,6 +567,8 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,
 						 errmsg("column reference \"%s\" is ambiguous",
 								colname),
 						 parser_errposition(pstate, location)));
+			if (rte->lognums)
+				attnum = get_attnum_by_lognum(rte, attnum);
 			var = make_var(pstate, rte, attnum, location);
 			/* Require read access to the column */
 			markVarForSelectPriv(pstate, var, rte);
@@ -830,14 +838,19 @@ markVarForSelectPriv(ParseState *pstate, Var *var, RangeTblEntry *rte)
  * empty strings for any dropped columns, so that it will be one-to-one with
  * physical column numbers.
  *
+ * If lognums is not NULL, it will be filled with a map from logical column
+ * numbers to attnum; that way, the nth element of eref->colnames corresponds
+ * to the attnum found in the nth element of lognums.
+ *
  * It is an error for there to be more aliases present than required.
  */
 static void
-buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
+buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref, List **lognums)
 {
 	int			maxattrs = tupdesc->natts;
 	ListCell   *aliaslc;
 	int			numaliases;
+	Form_pg_attribute *attrs;
 	int			varattno;
 	int			numdropped = 0;
 
@@ -856,9 +869,11 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
 		numaliases = 0;
 	}
 
+	attrs = TupleDescGetSortedAttrs(tupdesc);
+
 	for (varattno = 0; varattno < maxattrs; varattno++)
 	{
-		Form_pg_attribute attr = tupdesc->attrs[varattno];
+		Form_pg_attribute attr = attrs[varattno];
 		Value	   *attrname;
 
 		if (attr->attisdropped)
@@ -883,6 +898,9 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
 		}
 
 		eref->colnames = lappend(eref->colnames, attrname);
+
+		if (lognums)
+			*lognums = lappend_int(*lognums, attr->attnum);
 	}
 
 	/* Too many user-supplied aliases? */
@@ -1030,7 +1048,7 @@ addRangeTableEntry(ParseState *pstate,
 	 * and/or actual column names.
 	 */
 	rte->eref = makeAlias(refname, NIL);
-	buildRelationAliases(rel->rd_att, alias, rte->eref);
+	buildRelationAliases(rel->rd_att, alias, rte->eref, &rte->lognums);
 
 	/*
 	 * Drop the rel refcount, but keep the access lock till end of transaction
@@ -1090,7 +1108,7 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	 * and/or actual column names.
 	 */
 	rte->eref = makeAlias(refname, NIL);
-	buildRelationAliases(rel->rd_att, alias, rte->eref);
+	buildRelationAliases(rel->rd_att, alias, rte->eref, &rte->lognums);
 
 	/*
 	 * Set flags and access permissions.
@@ -1422,7 +1440,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	}
 
 	/* Use the tupdesc while assigning column aliases for the RTE */
-	buildRelationAliases(tupdesc, alias, eref);
+	buildRelationAliases(tupdesc, alias, eref, NULL);
 
 	/*
 	 * Set flags and access permissions.
@@ -1787,13 +1805,16 @@ addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
  * values to use in the created Vars.  Ordinarily rtindex should match the
  * actual position of the RTE in its rangetable.
  *
+ * If logical_sort is true, then the resulting lists are sorted by logical
+ * column number (attlognum); otherwise use regular attnum.
+ *
  * The output lists go into *colnames and *colvars.
  * If only one of the two kinds of output list is needed, pass NULL for the
  * output pointer for the unwanted one.
  */
 void
 expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
-		  int location, bool include_dropped,
+		  int location, bool include_dropped, bool logical_sort,
 		  List **colnames, List **colvars)
 {
 	int			varattno;
@@ -1808,8 +1829,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 		case RTE_RELATION:
 			/* Ordinary relation RTE */
 			expandRelation(rte->relid, rte->eref,
-						   rtindex, sublevels_up, location,
-						   include_dropped, colnames, colvars);
+						   rtindex, sublevels_up, location, include_dropped,
+						   logical_sort, colnames, colvars);
 			break;
 		case RTE_SUBQUERY:
 			{
@@ -1875,7 +1896,8 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 						expandTupleDesc(tupdesc, rte->eref,
 										rtfunc->funccolcount, atts_done,
 										rtindex, sublevels_up, location,
-										include_dropped, colnames, colvars);
+										include_dropped, logical_sort,
+										colnames, colvars);
 					}
 					else if (functypclass == TYPEFUNC_SCALAR)
 					{
@@ -2127,7 +2149,7 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
  */
 static void
 expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
-			   int location, bool include_dropped,
+			   int location, bool include_dropped, bool logical_sort,
 			   List **colnames, List **colvars)
 {
 	Relation	rel;
@@ -2136,7 +2158,7 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 	rel = relation_open(relid, AccessShareLock);
 	expandTupleDesc(rel->rd_att, eref, rel->rd_att->natts, 0,
 					rtindex, sublevels_up,
-					location, include_dropped,
+					location, include_dropped, logical_sort,
 					colnames, colvars);
 	relation_close(rel, AccessShareLock);
 }
@@ -2153,11 +2175,17 @@ expandRelation(Oid relid, Alias *eref, int rtindex, int sublevels_up,
 static void
 expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 				int rtindex, int sublevels_up,
-				int location, bool include_dropped,
+				int location, bool include_dropped, bool logical_sort,
 				List **colnames, List **colvars)
 {
 	ListCell   *aliascell = list_head(eref->colnames);
-	int			varattno;
+	int			attnum;
+	Form_pg_attribute *attrs;
+
+	if (logical_sort)
+		attrs = TupleDescGetSortedAttrs(tupdesc);
+	else
+		attrs = tupdesc->attrs;
 
 	if (colnames)
 	{
@@ -2171,9 +2199,10 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 	}
 
 	Assert(count <= tupdesc->natts);
-	for (varattno = 0; varattno < count; varattno++)
+	for (attnum = 0; attnum < count; attnum++)
 	{
-		Form_pg_attribute attr = tupdesc->attrs[varattno];
+		Form_pg_attribute attr = attrs[attnum];
+		int		varattno = attr->attnum - 1;
 
 		if (attr->attisdropped)
 		{
@@ -2240,7 +2269,7 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
  */
 List *
 expandRelAttrs(ParseState *pstate, RangeTblEntry *rte,
-			   int rtindex, int sublevels_up, int location)
+			   int rtindex, int sublevels_up, bool logical_sort, int location)
 {
 	List	   *names,
 			   *vars;
@@ -2248,7 +2277,7 @@ expandRelAttrs(ParseState *pstate, RangeTblEntry *rte,
 			   *var;
 	List	   *te_list = NIL;
 
-	expandRTE(rte, rtindex, sublevels_up, location, false,
+	expandRTE(rte, rtindex, sublevels_up, location, false, logical_sort,
 			  &names, &vars);
 
 	/*
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 328e0c6..5227c73 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -896,7 +896,7 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
 		/*
 		 * Generate default column list for INSERT.
 		 */
-		Form_pg_attribute *attr = pstate->p_target_relation->rd_att->attrs;
+		Form_pg_attribute *attr = TupleDescGetSortedAttrs(pstate->p_target_relation->rd_att);
 		int			numcol = pstate->p_target_relation->rd_rel->relnatts;
 		int			i;
 
@@ -913,7 +913,7 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
 			col->val = NULL;
 			col->location = -1;
 			cols = lappend(cols, col);
-			*attrnos = lappend_int(*attrnos, i + 1);
+			*attrnos = lappend_int(*attrnos, attr[i]->attnum);
 		}
 	}
 	else
@@ -931,7 +931,7 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
 			char	   *name = col->name;
 			int			attrno;
 
-			/* Lookup column name, ereport on failure */
+			/* Lookup column number, ereport on failure */
 			attrno = attnameAttNum(pstate->p_target_relation, name, false);
 			if (attrno == InvalidAttrNumber)
 				ereport(ERROR,
@@ -1184,6 +1184,7 @@ ExpandAllTables(ParseState *pstate, int location)
 											RTERangeTablePosn(pstate, rte,
 															  NULL),
 											0,
+											true,
 											location));
 	}
 
@@ -1252,14 +1253,14 @@ ExpandSingleTable(ParseState *pstate, RangeTblEntry *rte,
 	{
 		/* expandRelAttrs handles permissions marking */
 		return expandRelAttrs(pstate, rte, rtindex, sublevels_up,
-							  location);
+							  true, location);
 	}
 	else
 	{
 		List	   *vars;
 		ListCell   *l;
 
-		expandRTE(rte, rtindex, sublevels_up, location, false,
+		expandRTE(rte, rtindex, sublevels_up, location, false, true,
 				  NULL, &vars);
 
 		/*
@@ -1296,6 +1297,7 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 	TupleDesc	tupleDesc;
 	int			numAttrs;
 	int			i;
+	Form_pg_attribute *attr;
 
 	/*
 	 * If the rowtype expression is a whole-row Var, we can expand the fields
@@ -1342,9 +1344,10 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 
 	/* Generate a list of references to the individual fields */
 	numAttrs = tupleDesc->natts;
+	attr = TupleDescGetSortedAttrs(tupleDesc);
 	for (i = 0; i < numAttrs; i++)
 	{
-		Form_pg_attribute att = tupleDesc->attrs[i];
+		Form_pg_attribute att = attr[i];
 		FieldSelect *fselect;
 
 		if (att->attisdropped)
@@ -1413,7 +1416,7 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
 				   *lvar;
 		int			i;
 
-		expandRTE(rte, var->varno, 0, var->location, false,
+		expandRTE(rte, var->varno, 0, var->location, false, false,
 				  &names, &vars);
 
 		tupleDesc = CreateTemplateTupleDesc(list_length(vars), false);
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index c9e4b68..ddee31d 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1326,7 +1326,7 @@ ReplaceVarsFromTargetList_callback(Var *var,
 		 */
 		expandRTE(rcon->target_rte,
 				  var->varno, var->varlevelsup, var->location,
-				  (var->vartype != RECORDOID),
+				  (var->vartype != RECORDOID), false,
 				  &colnames, &fields);
 		/* Adjust the generated per-field Vars... */
 		fields = (List *) replace_rte_variables_mutator((Node *) fields,
diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c
index 9543d01..04cdf11 100644
--- a/src/backend/utils/adt/rowtypes.c
+++ b/src/backend/utils/adt/rowtypes.c
@@ -89,6 +89,7 @@ record_in(PG_FUNCTION_ARGS)
 	Datum	   *values;
 	bool	   *nulls;
 	StringInfoData buf;
+	Form_pg_attribute *attrs;
 
 	/*
 	 * Use the passed type unless it's RECORD; we can't support input of
@@ -138,6 +139,8 @@ record_in(PG_FUNCTION_ARGS)
 		my_extra->ncolumns = ncolumns;
 	}
 
+	attrs = TupleDescGetSortedAttrs(tupdesc);
+
 	values = (Datum *) palloc(ncolumns * sizeof(Datum));
 	nulls = (bool *) palloc(ncolumns * sizeof(bool));
 
@@ -159,15 +162,17 @@ record_in(PG_FUNCTION_ARGS)
 
 	for (i = 0; i < ncolumns; i++)
 	{
-		ColumnIOData *column_info = &my_extra->columns[i];
-		Oid			column_type = tupdesc->attrs[i]->atttypid;
+		Form_pg_attribute	attr = attrs[i];
+		int16		attnum = attr->attnum - 1;
+		ColumnIOData *column_info = &my_extra->columns[attnum];
+		Oid			column_type = attr->atttypid;
 		char	   *column_data;
 
 		/* Ignore dropped columns in datatype, but fill with nulls */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (attr->attisdropped)
 		{
-			values[i] = (Datum) 0;
-			nulls[i] = true;
+			values[attnum] = (Datum) 0;
+			nulls[attnum] = true;
 			continue;
 		}
 
@@ -188,7 +193,7 @@ record_in(PG_FUNCTION_ARGS)
 		if (*ptr == ',' || *ptr == ')')
 		{
 			column_data = NULL;
-			nulls[i] = true;
+			nulls[attnum] = true;
 		}
 		else
 		{
@@ -233,7 +238,7 @@ record_in(PG_FUNCTION_ARGS)
 			}
 
 			column_data = buf.data;
-			nulls[i] = false;
+			nulls[attnum] = false;
 		}
 
 		/*
@@ -249,10 +254,10 @@ record_in(PG_FUNCTION_ARGS)
 			column_info->column_type = column_type;
 		}
 
-		values[i] = InputFunctionCall(&column_info->proc,
-									  column_data,
-									  column_info->typioparam,
-									  tupdesc->attrs[i]->atttypmod);
+		values[attnum] = InputFunctionCall(&column_info->proc,
+										   column_data,
+										   column_info->typioparam,
+										   attr->atttypmod);
 
 		/*
 		 * Prep for next column
@@ -311,6 +316,7 @@ record_out(PG_FUNCTION_ARGS)
 	Datum	   *values;
 	bool	   *nulls;
 	StringInfoData buf;
+	Form_pg_attribute	*attrs;
 
 	/* Extract type info from the tuple itself */
 	tupType = HeapTupleHeaderGetTypeId(rec);
@@ -352,6 +358,8 @@ record_out(PG_FUNCTION_ARGS)
 		my_extra->ncolumns = ncolumns;
 	}
 
+	attrs = TupleDescGetSortedAttrs(tupdesc);
+
 	values = (Datum *) palloc(ncolumns * sizeof(Datum));
 	nulls = (bool *) palloc(ncolumns * sizeof(bool));
 
@@ -365,22 +373,24 @@ record_out(PG_FUNCTION_ARGS)
 
 	for (i = 0; i < ncolumns; i++)
 	{
-		ColumnIOData *column_info = &my_extra->columns[i];
-		Oid			column_type = tupdesc->attrs[i]->atttypid;
+		Form_pg_attribute attrib = attrs[i];
+		int16		attnum = attrib->attnum - 1;
+		ColumnIOData *column_info = &my_extra->columns[attnum];
+		Oid			column_type = attrib->atttypid;
 		Datum		attr;
 		char	   *value;
 		char	   *tmp;
 		bool		nq;
 
 		/* Ignore dropped columns in datatype */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (attrib->attisdropped)
 			continue;
 
 		if (needComma)
 			appendStringInfoChar(&buf, ',');
 		needComma = true;
 
-		if (nulls[i])
+		if (nulls[attnum])
 		{
 			/* emit nothing... */
 			continue;
@@ -399,7 +409,7 @@ record_out(PG_FUNCTION_ARGS)
 			column_info->column_type = column_type;
 		}
 
-		attr = values[i];
+		attr = values[attnum];
 		value = OutputFunctionCall(&column_info->proc, attr);
 
 		/* Detect whether we need double quotes for this value */
@@ -464,6 +474,7 @@ record_recv(PG_FUNCTION_ARGS)
 	int			i;
 	Datum	   *values;
 	bool	   *nulls;
+	Form_pg_attribute *attrs;
 
 	/*
 	 * Use the passed type unless it's RECORD; we can't support input of
@@ -507,6 +518,7 @@ record_recv(PG_FUNCTION_ARGS)
 		my_extra->ncolumns = ncolumns;
 	}
 
+	attrs = TupleDescGetSortedAttrs(tupdesc);
 	values = (Datum *) palloc(ncolumns * sizeof(Datum));
 	nulls = (bool *) palloc(ncolumns * sizeof(bool));
 
@@ -529,8 +541,10 @@ record_recv(PG_FUNCTION_ARGS)
 	/* Process each column */
 	for (i = 0; i < ncolumns; i++)
 	{
-		ColumnIOData *column_info = &my_extra->columns[i];
-		Oid			column_type = tupdesc->attrs[i]->atttypid;
+		Form_pg_attribute   attr = attrs[i];
+		int16       attnum = attr->attnum - 1;
+		ColumnIOData *column_info = &my_extra->columns[attnum];
+		Oid			column_type = attr->atttypid;
 		Oid			coltypoid;
 		int			itemlen;
 		StringInfoData item_buf;
@@ -538,10 +552,10 @@ record_recv(PG_FUNCTION_ARGS)
 		char		csave;
 
 		/* Ignore dropped columns in datatype, but fill with nulls */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (attr->attisdropped)
 		{
-			values[i] = (Datum) 0;
-			nulls[i] = true;
+			values[attnum] = (Datum) 0;
+			nulls[attnum] = true;
 			continue;
 		}
 
@@ -564,7 +578,7 @@ record_recv(PG_FUNCTION_ARGS)
 		{
 			/* -1 length means NULL */
 			bufptr = NULL;
-			nulls[i] = true;
+			nulls[attnum] = true;
 			csave = 0;			/* keep compiler quiet */
 		}
 		else
@@ -586,7 +600,7 @@ record_recv(PG_FUNCTION_ARGS)
 			buf->data[buf->cursor] = '\0';
 
 			bufptr = &item_buf;
-			nulls[i] = false;
+			nulls[attnum] = false;
 		}
 
 		/* Now call the column's receiveproc */
@@ -600,10 +614,10 @@ record_recv(PG_FUNCTION_ARGS)
 			column_info->column_type = column_type;
 		}
 
-		values[i] = ReceiveFunctionCall(&column_info->proc,
-										bufptr,
-										column_info->typioparam,
-										tupdesc->attrs[i]->atttypmod);
+		values[attnum] = ReceiveFunctionCall(&column_info->proc,
+											 bufptr,
+											 column_info->typioparam,
+											 attr->atttypmod);
 
 		if (bufptr)
 		{
@@ -654,6 +668,7 @@ record_send(PG_FUNCTION_ARGS)
 	Datum	   *values;
 	bool	   *nulls;
 	StringInfoData buf;
+	Form_pg_attribute	*attrs;
 
 	/* Extract type info from the tuple itself */
 	tupType = HeapTupleHeaderGetTypeId(rec);
@@ -695,6 +710,8 @@ record_send(PG_FUNCTION_ARGS)
 		my_extra->ncolumns = ncolumns;
 	}
 
+	attrs = TupleDescGetSortedAttrs(tupdesc);
+
 	values = (Datum *) palloc(ncolumns * sizeof(Datum));
 	nulls = (bool *) palloc(ncolumns * sizeof(bool));
 
@@ -715,13 +732,15 @@ record_send(PG_FUNCTION_ARGS)
 
 	for (i = 0; i < ncolumns; i++)
 	{
-		ColumnIOData *column_info = &my_extra->columns[i];
-		Oid			column_type = tupdesc->attrs[i]->atttypid;
+		Form_pg_attribute attrib = attrs[i];
+		int16		attnum = attrib->attnum - 1;
+		ColumnIOData *column_info = &my_extra->columns[attnum];
+		Oid			column_type = tupdesc->attrs[attnum]->atttypid;
 		Datum		attr;
 		bytea	   *outputbytes;
 
 		/* Ignore dropped columns in datatype */
-		if (tupdesc->attrs[i]->attisdropped)
+		if (attrib->attisdropped)
 			continue;
 
 		pq_sendint(&buf, column_type, sizeof(Oid));
@@ -746,7 +765,7 @@ record_send(PG_FUNCTION_ARGS)
 			column_info->column_type = column_type;
 		}
 
-		attr = values[i];
+		attr = values[attnum];
 		outputbytes = SendFunctionCall(&column_info->proc, attr);
 		pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
 		pq_sendbytes(&buf, VARDATA(outputbytes),
diff --git a/src/backend/utils/sort/tuplestore.c b/src/backend/utils/sort/tuplestore.c
index a69aae3..d05da9a 100644
--- a/src/backend/utils/sort/tuplestore.c
+++ b/src/backend/utils/sort/tuplestore.c
@@ -731,7 +731,7 @@ tuplestore_putvalues(Tuplestorestate *state, TupleDesc tdesc,
 	MinimalTuple tuple;
 	MemoryContext oldcxt = MemoryContextSwitchTo(state->context);
 
-	tuple = heap_form_minimal_tuple(tdesc, values, isnull);
+	tuple = heap_form_minimal_tuple(tdesc, values, isnull, 0);
 	USEMEM(state, GetMemoryChunkSpace(tuple));
 
 	tuplestore_puttuple_common(state, (void *) tuple);
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index 300c2a5..8a8f243 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -720,11 +720,24 @@ extern Datum fastgetattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
 	)
 
 
+/*
+ * Option flags for several of the functions below.
+ */
+/* indicates that the various values arrays are in logical column order */
+#define HTOPT_LOGICAL_ORDER            (1 << 0)
+
+/* backwards-compatibility macros */
+#define heap_form_tuple(tupdesc, values, isnull) \
+		heap_form_tuple_extended((tupdesc), (values), (isnull), 0)
+#define heap_deform_tuple(tuple, tupdesc, values, isnull) \
+		heap_deform_tuple_extended((tuple), (tupdesc), (values), (isnull), 0)
+
 /* prototypes for functions in common/heaptuple.c */
 extern Size heap_compute_data_size(TupleDesc tupleDesc,
-					   Datum *values, bool *isnull);
+					   Datum *values, bool *isnull, bool logical_order);
 extern void heap_fill_tuple(TupleDesc tupleDesc,
 				Datum *values, bool *isnull,
+				bool logical_order,
 				char *data, Size data_size,
 				uint16 *infomask, bits8 *bit);
 extern bool heap_attisnull(HeapTuple tup, int attnum);
@@ -735,15 +748,16 @@ extern Datum heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
 extern HeapTuple heap_copytuple(HeapTuple tuple);
 extern void heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest);
 extern Datum heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc);
-extern HeapTuple heap_form_tuple(TupleDesc tupleDescriptor,
-				Datum *values, bool *isnull);
+extern HeapTuple heap_form_tuple_extended(TupleDesc tupleDescriptor,
+						 Datum *values, bool *isnull, int flags);
 extern HeapTuple heap_modify_tuple(HeapTuple tuple,
 				  TupleDesc tupleDesc,
 				  Datum *replValues,
 				  bool *replIsnull,
 				  bool *doReplace);
-extern void heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
-				  Datum *values, bool *isnull);
+extern void heap_deform_tuple_extended(HeapTuple tuple, TupleDesc tupleDesc,
+						   Datum *values, bool *isnull, int flags);
+
 
 /* these three are deprecated versions of the three above: */
 extern HeapTuple heap_formtuple(TupleDesc tupleDescriptor,
@@ -757,7 +771,7 @@ extern void heap_deformtuple(HeapTuple tuple, TupleDesc tupleDesc,
 				 Datum *values, char *nulls);
 extern void heap_freetuple(HeapTuple htup);
 extern MinimalTuple heap_form_minimal_tuple(TupleDesc tupleDescriptor,
-						Datum *values, bool *isnull);
+						Datum *values, bool *isnull, int flags);
 extern void heap_free_minimal_tuple(MinimalTuple mtup);
 extern MinimalTuple heap_copy_minimal_tuple(MinimalTuple mtup);
 extern HeapTuple heap_tuple_from_minimal_tuple(MinimalTuple mtup);
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index 083f4bd..f02cad8 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -60,6 +60,11 @@ typedef struct tupleConstr
  * row type, or a value >= 0 to allow the rowtype to be looked up in the
  * typcache.c type cache.
  *
+ * We keep an array of attribute sorted by attlognum.  This helps *-expansion.
+ * The array is initially set to NULL, and is only populated on first access;
+ * those wanting to access it should always do it through
+ * TupleDescGetSortedAttrs.
+ *
  * Tuple descriptors that live in caches (relcache or typcache, at present)
  * are reference-counted: they can be deleted when their reference count goes
  * to zero.  Tuple descriptors created by the executor need no reference
@@ -73,6 +78,7 @@ typedef struct tupleDesc
 	int			natts;			/* number of attributes in the tuple */
 	Form_pg_attribute *attrs;
 	/* attrs[N] is a pointer to the description of Attribute Number N+1 */
+	Form_pg_attribute *logattrs;	/* array of attributes sorted by attlognum */
 	TupleConstr *constr;		/* constraints, or NULL if none */
 	Oid			tdtypeid;		/* composite type ID for tuple type */
 	int32		tdtypmod;		/* typmod for tuple type */
@@ -123,8 +129,14 @@ extern void TupleDescInitEntryCollation(TupleDesc desc,
 							AttrNumber attributeNumber,
 							Oid collationid);
 
+extern void TupleDescInitEntryLognum(TupleDesc desc,
+						 AttrNumber attributeNumber,
+						 int attlognum);
+
 extern TupleDesc BuildDescForRelation(List *schema);
 
 extern TupleDesc BuildDescFromLists(List *names, List *types, List *typmods, List *collations);
 
+extern Form_pg_attribute *TupleDescGetSortedAttrs(TupleDesc desc);
+
 #endif   /* TUPDESC_H */
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 391d568..cd671a4 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -63,19 +63,26 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
 	int16		attlen;
 
 	/*
-	 * attnum is the "attribute number" for the attribute:	A value that
-	 * uniquely identifies this attribute within its class. For user
-	 * attributes, Attribute numbers are greater than 0 and not greater than
-	 * the number of attributes in the class. I.e. if the Class pg_class says
-	 * that Class XYZ has 10 attributes, then the user attribute numbers in
-	 * Class pg_attribute must be 1-10.
-	 *
+	 * attnum uniquely identifies the column within its class, throughout its
+	 * lifetime.  For user attributes, Attribute numbers are greater than 0 and
+	 * less than or equal to the number of attributes in the class. For
+	 * instance, if the Class pg_class says that Class XYZ has 10 attributes,
+	 * then the user attribute numbers in Class pg_attribute must be 1-10.
 	 * System attributes have attribute numbers less than 0 that are unique
 	 * within the class, but not constrained to any particular range.
 	 *
-	 * Note that (attnum - 1) is often used as the index to an array.
+	 * attphysnum (physical position) specifies the position in which the
+	 * column is stored in physical tuples.  This might differ from attnum if
+	 * there are useful optimizations in storage space, for example alignment
+	 * considerations.
+	 *
+	 * attlognum (logical position) specifies the position in which the column
+	 * is expanded in "SELECT * FROM rel", INSERT queries that don't specify a
+	 * explicite column list, and the like.
 	 */
 	int16		attnum;
+	int16		attphysnum;
+	int16		attlognum;
 
 	/*
 	 * attndims is the declared number of dimensions, if an array type,
@@ -188,28 +195,31 @@ typedef FormData_pg_attribute *Form_pg_attribute;
  * ----------------
  */
 
-#define Natts_pg_attribute				21
+#define Natts_pg_attribute				23
 #define Anum_pg_attribute_attrelid		1
 #define Anum_pg_attribute_attname		2
 #define Anum_pg_attribute_atttypid		3
 #define Anum_pg_attribute_attstattarget 4
 #define Anum_pg_attribute_attlen		5
 #define Anum_pg_attribute_attnum		6
-#define Anum_pg_attribute_attndims		7
-#define Anum_pg_attribute_attcacheoff	8
-#define Anum_pg_attribute_atttypmod		9
-#define Anum_pg_attribute_attbyval		10
-#define Anum_pg_attribute_attstorage	11
-#define Anum_pg_attribute_attalign		12
-#define Anum_pg_attribute_attnotnull	13
-#define Anum_pg_attribute_atthasdef		14
-#define Anum_pg_attribute_attisdropped	15
-#define Anum_pg_attribute_attislocal	16
-#define Anum_pg_attribute_attinhcount	17
-#define Anum_pg_attribute_attcollation	18
-#define Anum_pg_attribute_attacl		19
-#define Anum_pg_attribute_attoptions	20
-#define Anum_pg_attribute_attfdwoptions 21
+#define Anum_pg_attribute_attphysnum	7
+#define Anum_pg_attribute_attlognum		8
+#define Anum_pg_attribute_attndims		9
+#define Anum_pg_attribute_attcacheoff	10
+#define Anum_pg_attribute_atttypmod		11
+#define Anum_pg_attribute_attbyval		12
+#define Anum_pg_attribute_attstorage	13
+#define Anum_pg_attribute_attalign		14
+#define Anum_pg_attribute_attnotnull	15
+#define Anum_pg_attribute_atthasdef		16
+#define Anum_pg_attribute_attisdropped	17
+#define Anum_pg_attribute_attislocal	18
+#define Anum_pg_attribute_attinhcount	19
+#define Anum_pg_attribute_attcollation	20
+#define Anum_pg_attribute_attacl		21
+#define Anum_pg_attribute_attoptions	22
+#define Anum_pg_attribute_attfdwoptions	23
+
 
 
 /* ----------------
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 1054cd0..6eff578 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -142,7 +142,7 @@ typedef FormData_pg_class *Form_pg_class;
  */
 DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 23 0 f f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
 DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 27 0 t f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 5eaa435..8319d45 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -747,10 +747,11 @@ typedef struct RangeTblEntry
 	 */
 
 	/*
-	 * Fields valid for a plain relation RTE (else zero):
+	 * Fields valid for a plain relation RTE (else zero/NIL):
 	 */
 	Oid			relid;			/* OID of the relation */
 	char		relkind;		/* relation kind (see pg_class.relkind) */
+	List	   *lognums;		/* int list of logical column numbers */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 6d9f3d9..7227df5 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -146,8 +146,8 @@ typedef struct Var
 	Expr		xpr;
 	Index		varno;			/* index of this var's relation in the range
 								 * table, or INNER_VAR/OUTER_VAR/INDEX_VAR */
-	AttrNumber	varattno;		/* attribute number of this var, or zero for
-								 * all */
+	AttrNumber	varattno;		/* identity attribute number (attnum) of this
+								 * var, or zero for all */
 	Oid			vartype;		/* pg_type OID for the type of this var */
 	int32		vartypmod;		/* pg_attribute typmod value */
 	Oid			varcollid;		/* OID of collation, or InvalidOid if none */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index d8b9493..b30d779 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -89,10 +89,10 @@ extern void errorMissingRTE(ParseState *pstate, RangeVar *relation) __attribute_
 extern void errorMissingColumn(ParseState *pstate,
 	   char *relname, char *colname, int location) __attribute__((noreturn));
 extern void expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
-		  int location, bool include_dropped,
+		  int location, bool include_dropped, bool logical_sort,
 		  List **colnames, List **colvars);
 extern List *expandRelAttrs(ParseState *pstate, RangeTblEntry *rte,
-			   int rtindex, int sublevels_up, int location);
+			   int rtindex, int sublevels_up, bool logical_sort, int location);
 extern int	attnameAttNum(Relation rd, const char *attname, bool sysColOK);
 extern Name attnumAttName(Relation rd, int attid);
 extern Oid	attnumTypeId(Relation rd, int attid);
diff --git a/src/test/regress/expected/col_order.out b/src/test/regress/expected/col_order.out
new file mode 100644
index 0000000..45d6918
--- /dev/null
+++ b/src/test/regress/expected/col_order.out
@@ -0,0 +1,286 @@
+drop table if exists foo, bar, baz cascade;
+NOTICE:  table "foo" does not exist, skipping
+NOTICE:  table "bar" does not exist, skipping
+NOTICE:  table "baz" does not exist, skipping
+create table foo (
+	a int default 42,
+	b timestamp default '1975-02-15 12:00',
+	c text);
+insert into foo values (142857, '1888-04-29', 'hello world');
+begin;
+update pg_attribute set attlognum = 1 where attname = 'c' and attrelid = 'foo'::regclass;
+update pg_attribute set attlognum = 2 where attname = 'a' and attrelid = 'foo'::regclass;
+update pg_attribute set attlognum = 3 where attname = 'b' and attrelid = 'foo'::regclass;
+commit;
+insert into foo values ('column c', 123, '2010-03-03 10:10:10');
+insert into foo (c, a, b) values ('c again', 456, '2010-03-03 11:12:13');
+insert into foo values ('and c', 789);	-- defaults column b
+insert into foo (c, b) values ('the c', '1975-01-10 08:00');	-- defaults column a
+select * from foo;
+      c      |   a    |            b             
+-------------+--------+--------------------------
+ hello world | 142857 | Sun Apr 29 00:00:00 1888
+ column c    |    123 | Wed Mar 03 10:10:10 2010
+ c again     |    456 | Wed Mar 03 11:12:13 2010
+ and c       |    789 | Sat Feb 15 12:00:00 1975
+ the c       |     42 | Fri Jan 10 08:00:00 1975
+(5 rows)
+
+select foo from foo;
+                        foo                        
+---------------------------------------------------
+ ("hello world",142857,"Sun Apr 29 00:00:00 1888")
+ ("column c",123,"Wed Mar 03 10:10:10 2010")
+ ("c again",456,"Wed Mar 03 11:12:13 2010")
+ ("and c",789,"Sat Feb 15 12:00:00 1975")
+ ("the c",42,"Fri Jan 10 08:00:00 1975")
+(5 rows)
+
+select foo.* from foo;
+      c      |   a    |            b             
+-------------+--------+--------------------------
+ hello world | 142857 | Sun Apr 29 00:00:00 1888
+ column c    |    123 | Wed Mar 03 10:10:10 2010
+ c again     |    456 | Wed Mar 03 11:12:13 2010
+ and c       |    789 | Sat Feb 15 12:00:00 1975
+ the c       |     42 | Fri Jan 10 08:00:00 1975
+(5 rows)
+
+select a,c,b from foo;
+   a    |      c      |            b             
+--------+-------------+--------------------------
+ 142857 | hello world | Sun Apr 29 00:00:00 1888
+    123 | column c    | Wed Mar 03 10:10:10 2010
+    456 | c again     | Wed Mar 03 11:12:13 2010
+    789 | and c       | Sat Feb 15 12:00:00 1975
+     42 | the c       | Fri Jan 10 08:00:00 1975
+(5 rows)
+
+select c,b,a from foo;
+      c      |            b             |   a    
+-------------+--------------------------+--------
+ hello world | Sun Apr 29 00:00:00 1888 | 142857
+ column c    | Wed Mar 03 10:10:10 2010 |    123
+ c again     | Wed Mar 03 11:12:13 2010 |    456
+ and c       | Sat Feb 15 12:00:00 1975 |    789
+ the c       | Fri Jan 10 08:00:00 1975 |     42
+(5 rows)
+
+select a from foo;
+   a    
+--------
+ 142857
+    123
+    456
+    789
+     42
+(5 rows)
+
+select b from foo;
+            b             
+--------------------------
+ Sun Apr 29 00:00:00 1888
+ Wed Mar 03 10:10:10 2010
+ Wed Mar 03 11:12:13 2010
+ Sat Feb 15 12:00:00 1975
+ Fri Jan 10 08:00:00 1975
+(5 rows)
+
+select c from foo;
+      c      
+-------------
+ hello world
+ column c
+ c again
+ and c
+ the c
+(5 rows)
+
+select (foo).* from foo;
+      c      |   a    |            b             
+-------------+--------+--------------------------
+ hello world | 142857 | Sun Apr 29 00:00:00 1888
+ column c    |    123 | Wed Mar 03 10:10:10 2010
+ c again     |    456 | Wed Mar 03 11:12:13 2010
+ and c       |    789 | Sat Feb 15 12:00:00 1975
+ the c       |     42 | Fri Jan 10 08:00:00 1975
+(5 rows)
+
+select ROW((foo).*) from foo;
+                        row                        
+---------------------------------------------------
+ ("hello world",142857,"Sun Apr 29 00:00:00 1888")
+ ("column c",123,"Wed Mar 03 10:10:10 2010")
+ ("c again",456,"Wed Mar 03 11:12:13 2010")
+ ("and c",789,"Sat Feb 15 12:00:00 1975")
+ ("the c",42,"Fri Jan 10 08:00:00 1975")
+(5 rows)
+
+select ROW((foo).*)::foo from foo;
+                        row                        
+---------------------------------------------------
+ ("hello world",142857,"Sun Apr 29 00:00:00 1888")
+ ("column c",123,"Wed Mar 03 10:10:10 2010")
+ ("c again",456,"Wed Mar 03 11:12:13 2010")
+ ("and c",789,"Sat Feb 15 12:00:00 1975")
+ ("the c",42,"Fri Jan 10 08:00:00 1975")
+(5 rows)
+
+select (ROW((foo).*)::foo).* from foo;
+      c      |   a    |            b             
+-------------+--------+--------------------------
+ hello world | 142857 | Sun Apr 29 00:00:00 1888
+ column c    |    123 | Wed Mar 03 10:10:10 2010
+ c again     |    456 | Wed Mar 03 11:12:13 2010
+ and c       |    789 | Sat Feb 15 12:00:00 1975
+ the c       |     42 | Fri Jan 10 08:00:00 1975
+(5 rows)
+
+create function f() returns setof foo language sql as $$
+select * from foo;
+$$;
+select * from f();
+      c      |   a    |            b             
+-------------+--------+--------------------------
+ hello world | 142857 | Sun Apr 29 00:00:00 1888
+ column c    |    123 | Wed Mar 03 10:10:10 2010
+ c again     |    456 | Wed Mar 03 11:12:13 2010
+ and c       |    789 | Sat Feb 15 12:00:00 1975
+ the c       |     42 | Fri Jan 10 08:00:00 1975
+(5 rows)
+
+insert into foo
+	select (row('ah', 1126, '2012-10-15')::foo).*
+	returning *;
+ c  |  a   |            b             
+----+------+--------------------------
+ ah | 1126 | Mon Oct 15 00:00:00 2012
+(1 row)
+
+insert into foo
+	select (row('eh', 1125, '2012-10-16')::foo).*
+	returning foo.*;
+ c  |  a   |            b             
+----+------+--------------------------
+ eh | 1125 | Tue Oct 16 00:00:00 2012
+(1 row)
+
+insert into foo values
+	('values one', 1, '2008-10-20'),
+	('values two', 2, '2004-08-15');
+copy foo from stdin;
+select * from foo order by 2;
+      c      |   a    |            b             
+-------------+--------+--------------------------
+ values one  |      1 | Mon Oct 20 00:00:00 2008
+ values two  |      2 | Sun Aug 15 00:00:00 2004
+ the c       |     42 | Fri Jan 10 08:00:00 1975
+ column c    |    123 | Wed Mar 03 10:10:10 2010
+ c again     |    456 | Wed Mar 03 11:12:13 2010
+ and c       |    789 | Sat Feb 15 12:00:00 1975
+ copy one    |   1001 | Thu Dec 10 23:54:00 1998
+ copy two    |   1002 | Thu Aug 01 09:22:00 1996
+ eh          |   1125 | Tue Oct 16 00:00:00 2012
+ ah          |   1126 | Mon Oct 15 00:00:00 2012
+ hello world | 142857 | Sun Apr 29 00:00:00 1888
+(11 rows)
+
+-- Test some joins
+create table bar (x text, y int default 142857, z timestamp );
+insert into bar values ('oh no', default, '1937-04-28');
+insert into bar values ('oh yes', 42, '1492-12-31');
+begin;
+update pg_attribute set attlognum = 3 where attname = 'x' and attrelid = 'bar'::regclass;
+update pg_attribute set attlognum = 1 where attname = 'z' and attrelid = 'bar'::regclass;
+commit;
+select foo.* from bar, foo where bar.y = foo.a;
+      c      |   a    |            b             
+-------------+--------+--------------------------
+ the c       |     42 | Fri Jan 10 08:00:00 1975
+ hello world | 142857 | Sun Apr 29 00:00:00 1888
+(2 rows)
+
+select bar.* from bar, foo where bar.y = foo.a;
+            z             |   y    |   x    
+--------------------------+--------+--------
+ Sat Dec 31 00:00:00 1492 |     42 | oh yes
+ Wed Apr 28 00:00:00 1937 | 142857 | oh no
+(2 rows)
+
+select * from bar, foo where bar.y = foo.a;
+            z             |   y    |   x    |      c      |   a    |            b             
+--------------------------+--------+--------+-------------+--------+--------------------------
+ Sat Dec 31 00:00:00 1492 |     42 | oh yes | the c       |     42 | Fri Jan 10 08:00:00 1975
+ Wed Apr 28 00:00:00 1937 | 142857 | oh no  | hello world | 142857 | Sun Apr 29 00:00:00 1888
+(2 rows)
+
+select * from foo join bar on (foo.a = bar.y);
+      c      |   a    |            b             |            z             |   y    |   x    
+-------------+--------+--------------------------+--------------------------+--------+--------
+ the c       |     42 | Fri Jan 10 08:00:00 1975 | Sat Dec 31 00:00:00 1492 |     42 | oh yes
+ hello world | 142857 | Sun Apr 29 00:00:00 1888 | Wed Apr 28 00:00:00 1937 | 142857 | oh no
+(2 rows)
+
+alter table bar rename y to a;
+select * from foo natural join bar;
+   a    |      c      |            b             |            z             |   x    
+--------+-------------+--------------------------+--------------------------+--------
+     42 | the c       | Fri Jan 10 08:00:00 1975 | Sat Dec 31 00:00:00 1492 | oh yes
+ 142857 | hello world | Sun Apr 29 00:00:00 1888 | Wed Apr 28 00:00:00 1937 | oh no
+(2 rows)
+
+select * from foo join bar using (a);
+   a    |      c      |            b             |            z             |   x    
+--------+-------------+--------------------------+--------------------------+--------
+     42 | the c       | Fri Jan 10 08:00:00 1975 | Sat Dec 31 00:00:00 1492 | oh yes
+ 142857 | hello world | Sun Apr 29 00:00:00 1888 | Wed Apr 28 00:00:00 1937 | oh no
+(2 rows)
+
+create table baz (e point) inherits (foo, bar); -- fail to merge defaults
+NOTICE:  merging multiple inherited definitions of column "a"
+ERROR:  column "a" inherits conflicting default values
+HINT:  To resolve the conflict, specify a default explicitly.
+create table baz (e point, a int default 23) inherits (foo, bar);
+NOTICE:  merging multiple inherited definitions of column "a"
+NOTICE:  merging column "a" with inherited definition
+insert into baz (e) values ('(1,1)');
+select * from foo;
+      c      |   a    |            b             
+-------------+--------+--------------------------
+ hello world | 142857 | Sun Apr 29 00:00:00 1888
+ column c    |    123 | Wed Mar 03 10:10:10 2010
+ c again     |    456 | Wed Mar 03 11:12:13 2010
+ and c       |    789 | Sat Feb 15 12:00:00 1975
+ the c       |     42 | Fri Jan 10 08:00:00 1975
+ ah          |   1126 | Mon Oct 15 00:00:00 2012
+ eh          |   1125 | Tue Oct 16 00:00:00 2012
+ values one  |      1 | Mon Oct 20 00:00:00 2008
+ values two  |      2 | Sun Aug 15 00:00:00 2004
+ copy one    |   1001 | Thu Dec 10 23:54:00 1998
+ copy two    |   1002 | Thu Aug 01 09:22:00 1996
+             |     23 | Sat Feb 15 12:00:00 1975
+(12 rows)
+
+select * from bar;
+            z             |   a    |   x    
+--------------------------+--------+--------
+ Wed Apr 28 00:00:00 1937 | 142857 | oh no
+ Sat Dec 31 00:00:00 1492 |     42 | oh yes
+                          |     23 | 
+(3 rows)
+
+select * from baz;
+ c | a  |            b             | z | x |   e   
+---+----+--------------------------+---+---+-------
+   | 23 | Sat Feb 15 12:00:00 1975 |   |   | (1,1)
+(1 row)
+
+create table quux (a int, b int[], c int);
+begin;
+update pg_attribute set attlognum = 1 where attnum = 2 and attrelid = 'quux'::regclass;
+update pg_attribute set attlognum = 2 where attnum = 1 and attrelid = 'quux'::regclass;
+commit;
+select * from quux where (a,c) in ( select a,c from quux );
+ERROR:  failed to find unique expression in subplan tlist
+drop table foo, bar, baz, quux cascade;
+NOTICE:  drop cascades to function f()
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 62cc198..d2c7e52 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -30,7 +30,7 @@ test: point lseg line box path polygon circle date time timetz timestamp timesta
 # geometry depends on point, lseg, box, path, polygon and circle
 # horology depends on interval, timetz, timestamp, timestamptz, reltime and abstime
 # ----------
-test: geometry horology regex oidjoins type_sanity opr_sanity
+test: geometry horology regex oidjoins type_sanity opr_sanity col_order
 
 # ----------
 # These four each depend on the previous one
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 07fc827..d8a045a 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -49,6 +49,7 @@ test: regex
 test: oidjoins
 test: type_sanity
 test: opr_sanity
+test: col_order
 test: insert
 test: create_function_1
 test: create_type
diff --git a/src/test/regress/sql/col_order.sql b/src/test/regress/sql/col_order.sql
new file mode 100644
index 0000000..556c30e
--- /dev/null
+++ b/src/test/regress/sql/col_order.sql
@@ -0,0 +1,85 @@
+drop table if exists foo, bar, baz cascade;
+create table foo (
+	a int default 42,
+	b timestamp default '1975-02-15 12:00',
+	c text);
+insert into foo values (142857, '1888-04-29', 'hello world');
+
+begin;
+update pg_attribute set attlognum = 1 where attname = 'c' and attrelid = 'foo'::regclass;
+update pg_attribute set attlognum = 2 where attname = 'a' and attrelid = 'foo'::regclass;
+update pg_attribute set attlognum = 3 where attname = 'b' and attrelid = 'foo'::regclass;
+commit;
+
+insert into foo values ('column c', 123, '2010-03-03 10:10:10');
+insert into foo (c, a, b) values ('c again', 456, '2010-03-03 11:12:13');
+insert into foo values ('and c', 789);	-- defaults column b
+insert into foo (c, b) values ('the c', '1975-01-10 08:00');	-- defaults column a
+
+select * from foo;
+select foo from foo;
+select foo.* from foo;
+select a,c,b from foo;
+select c,b,a from foo;
+select a from foo;
+select b from foo;
+select c from foo;
+select (foo).* from foo;
+select ROW((foo).*) from foo;
+select ROW((foo).*)::foo from foo;
+select (ROW((foo).*)::foo).* from foo;
+
+create function f() returns setof foo language sql as $$
+select * from foo;
+$$;
+select * from f();
+
+insert into foo
+	select (row('ah', 1126, '2012-10-15')::foo).*
+	returning *;
+insert into foo
+	select (row('eh', 1125, '2012-10-16')::foo).*
+	returning foo.*;
+
+insert into foo values
+	('values one', 1, '2008-10-20'),
+	('values two', 2, '2004-08-15');
+
+copy foo from stdin;
+copy one	1001	1998-12-10 23:54
+copy two	1002	1996-08-01 09:22
+\.
+select * from foo order by 2;
+
+-- Test some joins
+create table bar (x text, y int default 142857, z timestamp );
+insert into bar values ('oh no', default, '1937-04-28');
+insert into bar values ('oh yes', 42, '1492-12-31');
+begin;
+update pg_attribute set attlognum = 3 where attname = 'x' and attrelid = 'bar'::regclass;
+update pg_attribute set attlognum = 1 where attname = 'z' and attrelid = 'bar'::regclass;
+commit;
+select foo.* from bar, foo where bar.y = foo.a;
+select bar.* from bar, foo where bar.y = foo.a;
+select * from bar, foo where bar.y = foo.a;
+select * from foo join bar on (foo.a = bar.y);
+alter table bar rename y to a;
+select * from foo natural join bar;
+select * from foo join bar using (a);
+
+create table baz (e point) inherits (foo, bar); -- fail to merge defaults
+create table baz (e point, a int default 23) inherits (foo, bar);
+insert into baz (e) values ('(1,1)');
+select * from foo;
+select * from bar;
+select * from baz;
+
+create table quux (a int, b int[], c int);
+begin;
+update pg_attribute set attlognum = 1 where attnum = 2 and attrelid = 'quux'::regclass;
+update pg_attribute set attlognum = 2 where attnum = 1 and attrelid = 'quux'::regclass;
+commit;
+select * from quux where (a,c) in ( select a,c from quux );
+
+
+drop table foo, bar, baz, quux cascade;
