So I've been updating my very old patch to allow logical and physical
column reordering.  Here's a WIP first cut for examination.  There are
plenty of rough edges here; most importantly there is no UI at all for
column reordering other than direct UPDATEs of pg_attribute, which most
likely falls afoul of cache invalidation problems.  For now I'm focusing
on correct processing when columns are moved logically; I haven't yet
started to see how to move columns physically, but I think that part is
much more localized than the logical one.

Just as a reminder, this effort is an implementation of ideas that have
been discussed previously; in particular, see these threads:

http://www.postgresql.org/message-id/20414.1166719...@sss.pgh.pa.us (2006)
http://www.postgresql.org/message-id/6843.1172126...@sss.pgh.pa.us (2007)
http://www.postgresql.org/message-id/23035.1227659...@sss.pgh.pa.us (2008)

To recap, this is based on the idea of having three numbers for each
attribute rather than a single attnum; the first of these is attnum (a
number that uniquely identifies an attribute since its inception and may
or may not have any relationship to its storage position and the place
it expands to through user interaction).  The second is attphysnum,
which indicates where it is stored in the physical structure.  The third
is attlognum, which indicates where it expands in "*", where must its
values be placed in COPY or VALUES lists, etc --- the logical position
as the user sees it.

The first thing where this matters is tuple descriptor expansion in
parse analysis; at this stage, things such as "*" (in "select *") are
turned into a target list, which must be sorted according to attlognum.
To achieve this I added a new routine to tupledescs,
TupleDescGetSortedAttrs() which computes a new Attribute array and
caches it in the TupleDesc for later uses; this array points to the
same elements in the normal attribute list but is order by attlognum.

Additionally there are a number of places that iterate on such target
lists and use the iterator as the attribute number; those were modified
to have a separate attribute number as attnum within the loop.

Another place that needs tweaking is heapam.c, which must construct a
physical tuple from Datum/nulls arrays (heap_form_tuple).  In some cases
the input arrays are sorted in logical column order.  I have opted to
add a flag that indicates whether the array is in logical order; if it
is the routines compute the correct physical order.  (Actually as I
mentioned above I still haven't made any effort to make sure they work
in the case that attnum differs from attphysnum, but this should be
reasonably contained changes.)


The part where I stopped just before sending the current state is this
error message:

alvherre=# select * from quux where (a,c) in ( select a,c from quux );
select * from quux where (a,c) in ( select a,c from quux );
ERROR:  failed to find unique expression in subplan tlist

I'm going to see about it while I get feedback on the rest of this patch; in
particular, extra test cases that fail to work when columns have been
moved around are welcome, so that I can add them to the regress test.
What I have now is the basics I'm building as I go along.  The
regression tests show examples of some logical column renumbering (which
can be done after the table already contains some data) but none of
physical column renumbering (which can only be done when the table is
completely empty.)  My hunch is that the sample foo, bar, baz, quux
tables should present plenty of opportunities to display brokenness in
the planner and executor.


PS: Phil Currier allegedly had a patch back in 2007-2008 that did this,
or something very similar ... though he never posted a single bit of it,
and then he vanished without a trace.  If he's still available it would
be nice to see his WIP patch, even if outdated, as it might serve as
inspiration and let us know what other places need tweaking.

-- 
Álvaro Herrera                http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
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;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to