Hi,

attached is the result of my first attempt to make the logical column
ordering patch work. This touches a lot of code in the executor that is
mostly new to me, so if you see something that looks like an obvious
bug, it probably is (so let me know).   


improvements
------------
The main improvements of this version are that:

* initdb actually works (while before it was crashing)

* regression tests work, with two exceptions

  (a) 'subselect' fails because EXPLAIN prints columns in physical order
      (but we expect logical)

  (b) col_order crashes works because of tuple descriptor mismatch in a
      function call (this actually causes a segfault)

The main change is this patch is that tlist_matches_tupdesc() now checks
target list vs. physical attribute order, which may result in doing a
projection (in cases when that would not be done previously).

I don not claim this is the best approach - maybe it would be better to
keep the physical tuple and reorder it lazily. That's why I kept a few
pieces of code (fix_physno_mutator) and a few unused fields in Var.

Over the time I've heard various use cases for this patch, but in most
cases it was quite speculative. If you have an idea where this might be
useful, can you explain it here, or maybe point me to a place where it's
described?

There's also a few FIXMEs, mostly from Alvaro's version of the patch.
Some of them are probably obsolete, but I wasn't 100% sure by that so
I've left them in place until I understand the code sufficiently.


randomized testing
------------------
I've also attached a python script for simple randomized testing. Just
execute it like this:

    $ python randomize-attlognum.py -t test_1 test_2 \
           --init-script attlognum-init.sql \
           --test-script attlognum-test.sql

and it will do this over and over

    $ dropdb test
    $ createdb test
    $ run init script
    $ randomly set attlognums for the tables (test_1 and test_2)
    $ run test script

It does not actually check the result, but my experience is that when
there's a bug in handling the descriptor, it results in segfault pretty
fast (just put some varlena columns into the table).


plans / future
--------------
After discussing this with Alvaro, we've both agreed that this is far
too high-risk change to commit in the very last CF (even if it was in a
better shape). So while it's added to 2015-02 CF, we're aiming for 9.6
if things go well.


regards

-- 
Tomas Vondra                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 c5892d3..7528724 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 0699438..30e496c 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 = TupleDescGetLogSortedAttrs(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/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 6cd4e8e..14787ce 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -79,6 +79,8 @@
 /*
  * heap_compute_data_size
  *		Determine size of the data area of a tuple to be constructed
+ *
+ * Note: input arrays must be in attnum order.
  */
 Size
 heap_compute_data_size(TupleDesc tupleDesc,
@@ -88,16 +90,23 @@ heap_compute_data_size(TupleDesc tupleDesc,
 	Size		data_length = 0;
 	int			i;
 	int			numberOfAttributes = tupleDesc->natts;
-	Form_pg_attribute *att = tupleDesc->attrs;
+	Form_pg_attribute *att = TupleDescGetPhysSortedAttrs(tupleDesc);
 
+	/*
+	 * We need to consider the attributes in physical order for storage, yet
+	 * our input arrays are in attnum order.  In this loop, "i" is an index
+	 * into the attphysnum-sorted attribute array, and idx is an index into the
+	 * input arrays.
+	 */
 	for (i = 0; i < numberOfAttributes; i++)
 	{
 		Datum		val;
+		int			idx = att[i]->attnum - 1;
 
-		if (isnull[i])
+		if (isnull[idx])
 			continue;
 
-		val = values[i];
+		val = values[idx];
 
 		if (ATT_IS_PACKABLE(att[i]) &&
 			VARATT_CAN_MAKE_SHORT(DatumGetPointer(val)))
@@ -127,6 +136,8 @@ heap_compute_data_size(TupleDesc tupleDesc,
  * We also fill the null bitmap (if any) and set the infomask bits
  * that reflect the tuple's data contents.
  *
+ * NB - the input arrays must be in attnum order.
+ *
  * NOTE: it is now REQUIRED that the caller have pre-zeroed the data area.
  */
 void
@@ -139,7 +150,7 @@ heap_fill_tuple(TupleDesc tupleDesc,
 	int			bitmask;
 	int			i;
 	int			numberOfAttributes = tupleDesc->natts;
-	Form_pg_attribute *att = tupleDesc->attrs;
+	Form_pg_attribute *att = TupleDescGetPhysSortedAttrs(tupleDesc);
 
 #ifdef USE_ASSERT_CHECKING
 	char	   *start = data;
@@ -159,9 +170,17 @@ heap_fill_tuple(TupleDesc tupleDesc,
 
 	*infomask &= ~(HEAP_HASNULL | HEAP_HASVARWIDTH | HEAP_HASEXTERNAL);
 
+	/*
+	 * We need to consider the attributes in physical order for storage, yet
+	 * our input arrays are in attnum order.  In this loop, "i" is an index
+	 * into the attphysnum-sorted attribute array, and idx is an index into the
+	 * input arrays.
+	 */
 	for (i = 0; i < numberOfAttributes; i++)
 	{
 		Size		data_length;
+		Datum		value;
+		int			idx = att[i]->attnum - 1;
 
 		if (bit != NULL)
 		{
@@ -174,7 +193,7 @@ heap_fill_tuple(TupleDesc tupleDesc,
 				bitmask = 1;
 			}
 
-			if (isnull[i])
+			if (isnull[idx])
 			{
 				*infomask |= HEAP_HASNULL;
 				continue;
@@ -183,6 +202,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 +213,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 +257,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 +266,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;
@@ -326,10 +347,12 @@ nocachegetattr(HeapTuple tuple,
 {
 	HeapTupleHeader tup = tuple->t_data;
 	Form_pg_attribute *att = tupleDesc->attrs;
+	Form_pg_attribute *physatt = TupleDescGetPhysSortedAttrs(tupleDesc);
 	char	   *tp;				/* ptr to data part of tuple */
 	bits8	   *bp = tup->t_bits;		/* ptr to null bitmap in tuple */
 	bool		slow = false;	/* do we have to walk attrs? */
 	int			off;			/* current offset within data */
+	int			attphysnum;
 
 	/* ----------------
 	 *	 Three cases:
@@ -340,7 +363,9 @@ nocachegetattr(HeapTuple tuple,
 	 * ----------------
 	 */
 
+	/* determine the indexes into the physical and regular attribute arrays */
 	attnum--;
+	attphysnum = att[attnum]->attphysnum - 1;
 
 	if (!HeapTupleNoNulls(tuple))
 	{
@@ -394,9 +419,9 @@ nocachegetattr(HeapTuple tuple,
 		{
 			int			j;
 
-			for (j = 0; j <= attnum; j++)
+			for (j = 0; j <= attphysnum; j++)
 			{
-				if (att[j]->attlen <= 0)
+				if (physatt[j]->attlen <= 1)
 				{
 					slow = true;
 					break;
@@ -419,24 +444,24 @@ nocachegetattr(HeapTuple tuple,
 		 * fixed-width columns, in hope of avoiding future visits to this
 		 * routine.
 		 */
-		att[0]->attcacheoff = 0;
+		physatt[0]->attcacheoff = 0;
 
 		/* we might have set some offsets in the slow path previously */
-		while (j < natts && att[j]->attcacheoff > 0)
+		while (j < natts && physatt[j]->attcacheoff > 0)
 			j++;
 
-		off = att[j - 1]->attcacheoff + att[j - 1]->attlen;
+		off = physatt[j - 1]->attcacheoff + physatt[j - 1]->attlen;
 
 		for (; j < natts; j++)
 		{
-			if (att[j]->attlen <= 0)
+			if (physatt[j]->attlen <= 0)
 				break;
 
-			off = att_align_nominal(off, att[j]->attalign);
+			off = att_align_nominal(off, physatt[j]->attalign);
 
-			att[j]->attcacheoff = off;
+			physatt[j]->attcacheoff = off;
 
-			off += att[j]->attlen;
+			off += physatt[j]->attlen;
 		}
 
 		Assert(j > attnum);
@@ -457,20 +482,21 @@ nocachegetattr(HeapTuple tuple,
 		 * then advance over the attr based on its length.  Nulls have no
 		 * storage and no alignment padding either.  We can use/set
 		 * attcacheoff until we reach either a null or a var-width attribute.
+		 * "i" is an index into the attphysnum-ordered array here.
 		 */
 		off = 0;
 		for (i = 0;; i++)		/* loop exit is at "break" */
 		{
-			if (HeapTupleHasNulls(tuple) && att_isnull(i, bp))
+			if (HeapTupleHasNulls(tuple) && att_isnull(physatt[i]->attnum - 1, bp))
 			{
 				usecache = false;
 				continue;		/* this cannot be the target att */
 			}
 
 			/* If we know the next offset, we can skip the rest */
-			if (usecache && att[i]->attcacheoff >= 0)
-				off = att[i]->attcacheoff;
-			else if (att[i]->attlen == -1)
+			if (usecache && physatt[i]->attcacheoff >= 0)
+				off = physatt[i]->attcacheoff;
+			else if (physatt[i]->attlen == -1)
 			{
 				/*
 				 * We can only cache the offset for a varlena attribute if the
@@ -479,11 +505,11 @@ nocachegetattr(HeapTuple tuple,
 				 * either an aligned or unaligned value.
 				 */
 				if (usecache &&
-					off == att_align_nominal(off, att[i]->attalign))
-					att[i]->attcacheoff = off;
+					off == att_align_nominal(off, physatt[i]->attalign))
+					physatt[i]->attcacheoff = off;
 				else
 				{
-					off = att_align_pointer(off, att[i]->attalign, -1,
+					off = att_align_pointer(off, physatt[i]->attalign, -1,
 											tp + off);
 					usecache = false;
 				}
@@ -491,18 +517,19 @@ nocachegetattr(HeapTuple tuple,
 			else
 			{
 				/* not varlena, so safe to use att_align_nominal */
-				off = att_align_nominal(off, att[i]->attalign);
+				off = att_align_nominal(off, physatt[i]->attalign);
 
 				if (usecache)
-					att[i]->attcacheoff = off;
+					physatt[i]->attcacheoff = off;
 			}
 
-			if (i == attnum)
+			/* if this this is our attribute, we're done */
+			if (physatt[i]->attnum - 1 == attnum)
 				break;
 
-			off = att_addlength_pointer(off, att[i]->attlen, tp + off);
+			off = att_addlength_pointer(off, physatt[i]->attlen, tp + off);
 
-			if (usecache && att[i]->attlen <= 0)
+			if (usecache && physatt[i]->attlen <= 0)
 				usecache = false;
 		}
 	}
@@ -657,6 +684,8 @@ heap_copy_tuple_as_datum(HeapTuple tuple, TupleDesc tupleDesc)
  *		construct a tuple from the given values[] and isnull[] arrays,
  *		which are of the length indicated by tupleDescriptor->natts
  *
+ *		The input arrays must always be in attnum order.
+ *
  * The result is allocated in the current memory context.
  */
 HeapTuple
@@ -889,7 +918,8 @@ heap_modifytuple(HeapTuple tuple,
 /*
  * heap_deform_tuple
  *		Given a tuple, extract data into values/isnull arrays; this is
- *		the inverse of heap_form_tuple.
+ *		the inverse of heap_form_tuple.  Like that routine, the output
+ *		arrays are sorted in attnum order.
  *
  *		Storage for the values/isnull arrays is provided by the caller;
  *		it should be sized according to tupleDesc->natts not
@@ -909,10 +939,10 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 {
 	HeapTupleHeader tup = tuple->t_data;
 	bool		hasnulls = HeapTupleHasNulls(tuple);
-	Form_pg_attribute *att = tupleDesc->attrs;
+	Form_pg_attribute *att = TupleDescGetPhysSortedAttrs(tupleDesc);
 	int			tdesc_natts = tupleDesc->natts;
 	int			natts;			/* number of atts to extract */
-	int			attnum;
+	int			i;
 	char	   *tp;				/* ptr to tuple data */
 	long		off;			/* offset in tuple data */
 	bits8	   *bp = tup->t_bits;		/* ptr to null bitmap in tuple */
@@ -931,9 +961,10 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 
 	off = 0;
 
-	for (attnum = 0; attnum < natts; attnum++)
+	for (i = 0; i < natts; i++)
 	{
-		Form_pg_attribute thisatt = att[attnum];
+		Form_pg_attribute thisatt = att[i];
+		int		attnum = thisatt->attnum - 1;
 
 		if (hasnulls && att_isnull(attnum, bp))
 		{
@@ -983,13 +1014,14 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 	}
 
 	/*
-	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as null
+	 * Read remaining attributes as nulls
+	 *
+	 * XXX think this through ...
 	 */
-	for (; attnum < tdesc_natts; attnum++)
+	for (; i < tdesc_natts; i++)
 	{
-		values[attnum] = (Datum) 0;
-		isnull[attnum] = true;
+		values[i] = (Datum) 0;
+		isnull[i] = true;
 	}
 }
 
@@ -1042,10 +1074,9 @@ heap_deformtuple(HeapTuple tuple,
  *		This is essentially an incremental version of heap_deform_tuple:
  *		on each call we extract attributes up to the one needed, without
  *		re-computing information about previously extracted attributes.
- *		slot->tts_nvalid is the number of attributes already extracted.
  */
 static void
-slot_deform_tuple(TupleTableSlot *slot, int natts)
+slot_deform_tuple(TupleTableSlot *slot, AttrNumber natts)
 {
 	HeapTuple	tuple = slot->tts_tuple;
 	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
@@ -1054,6 +1085,8 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 	HeapTupleHeader tup = tuple->t_data;
 	bool		hasnulls = HeapTupleHasNulls(tuple);
 	Form_pg_attribute *att = tupleDesc->attrs;
+	Form_pg_attribute *physatt = TupleDescGetPhysSortedAttrs(tupleDesc);
+	int			maxphysnum;
 	int			attnum;
 	char	   *tp;				/* ptr to tuple data */
 	long		off;			/* offset in tuple data */
@@ -1064,15 +1097,16 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 	 * Check whether the first call for this tuple, and initialize or restore
 	 * loop state.
 	 */
-	attnum = slot->tts_nvalid;
-	if (attnum == 0)
+	if (slot->tts_nphysvalid == 0)
 	{
+		Assert(slot->tts_nvalid == 0);
 		/* Start from the first attribute */
 		off = 0;
 		slow = false;
 	}
 	else
 	{
+		Assert(slot->tts_nvalid != 0);
 		/* Restore state from previous execution */
 		off = slot->tts_off;
 		slow = slot->tts_slow;
@@ -1080,19 +1114,74 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 
 	tp = (char *) tup + tup->t_hoff;
 
-	for (; attnum < natts; attnum++)
+	/*
+	 * Scan the attribute array to determine the maximum physical position that
+	 * we need to extract.  Start from the position next to the one that was
+	 * last extracted.
+	 */
+	maxphysnum = slot->tts_nphysvalid;
+	for (attnum = slot->tts_nvalid + 1; attnum <= natts; attnum++)
+	{
+		if (att[attnum - 1]->attphysnum > maxphysnum)
+			maxphysnum = tupleDesc->attrs[attnum - 1]->attphysnum;
+	}
+
+	/*
+	 * Now walk the physical-order attribute array to decode up the point so
+	 * determined, starting from the element one past the one we already
+	 * extracted.
+	 */
+	for (attnum = slot->tts_nphysvalid + 1; attnum <= maxphysnum; attnum++)
 	{
-		Form_pg_attribute thisatt = att[attnum];
+		int	i;
+		Form_pg_attribute thisatt;
+		int			thisattnum = -1;
 
-		if (hasnulls && att_isnull(attnum, bp))
+		/*
+		 * We need to find all physical attributes between the one we fetched
+		 * previously (tts_nphysvalid) and the one we need now (maxphysnum).
+		 * There might be 'holes' when requesting virtual tuples (e.g. a single
+		 * attribute with attphysnum = 100) so we'll walk through physatt to
+		 * find the proper attribute. If we don't find it we skip to the next
+		 * physattnum.
+		 *
+		 * FIXME This is a bit ugly, because TupleDescGetPhysSortedAttrs does
+		 *       not expect holes, so the whole idea of direct indexing into
+		 *       physatt is not working here. The current solution is rather
+		 *       straight-forward and probably not very efficient - simply
+		 *       skip those physnum values not present in the tuple descriptor.
+		 *
+		 *       There are a few ways to fix this: e.g. building the physatt
+		 *       in an 'expanded' form, including missing attributes, which
+		 *       would allow direct indexing (but what will happen at the
+		 *       places that don't expect this?).
+		 */
+
+		for (i = 0; i < tupleDesc->natts; i++)
 		{
-			values[attnum] = (Datum) 0;
-			isnull[attnum] = true;
+			if (physatt[i]->attphysnum == attnum)
+			{
+				thisatt = physatt[i];
+				thisattnum = thisatt->attnum - 1;
+				break;
+			}
+		}
+
+		/* skip attphysnum not present in the virtual tuple */
+		if (thisattnum == -1)
+			continue;
+
+		Assert(thisatt->attphysnum == attnum);
+
+		if (hasnulls && att_isnull(thisattnum, bp))
+		{
+			values[thisattnum] = (Datum) 0;
+			isnull[thisattnum] = true;
 			slow = true;		/* can't use attcacheoff anymore */
 			continue;
 		}
 
-		isnull[attnum] = false;
+		isnull[thisattnum] = false;
 
 		if (!slow && thisatt->attcacheoff >= 0)
 			off = thisatt->attcacheoff;
@@ -1123,7 +1212,7 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 				thisatt->attcacheoff = off;
 		}
 
-		values[attnum] = fetchatt(thisatt, tp + off);
+		values[thisattnum] = fetchatt(thisatt, tp + off);
 
 		off = att_addlength_pointer(off, thisatt->attlen, tp + off);
 
@@ -1132,9 +1221,16 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
 	}
 
 	/*
+	 * XXX could we scan further and move tts_nvalid a bit higher, without
+	 * decoding further?  Scanning in physical order might have extracted more
+	 * attributes than what was requested.
+	 */
+
+	/*
 	 * Save state for next execution
 	 */
-	slot->tts_nvalid = attnum;
+	slot->tts_nphysvalid = maxphysnum;
+	slot->tts_nvalid = natts;
 	slot->tts_off = off;
 	slot->tts_slow = slow;
 }
@@ -1152,7 +1248,7 @@ slot_deform_tuple(TupleTableSlot *slot, int natts)
  *		when the physical tuple is longer than the tupdesc.
  */
 Datum
-slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
+slot_getattr(TupleTableSlot *slot, AttrNumber attnum, bool *isnull)
 {
 	HeapTuple	tuple = slot->tts_tuple;
 	TupleDesc	tupleDesc = slot->tts_tupleDescriptor;
@@ -1170,6 +1266,9 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 		return heap_getsysattr(tuple, attnum, tupleDesc, isnull);
 	}
 
+	/* This only gets attributes by attnum, never by physnum or lognum. */
+	Assert(tupleDesc->attrs[attnum - 1]->attnum == attnum);
+
 	/*
 	 * fast path if desired attribute already cached
 	 */
@@ -1250,7 +1349,8 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 void
 slot_getallattrs(TupleTableSlot *slot)
 {
-	int			tdesc_natts = slot->tts_tupleDescriptor->natts;
+	TupleDesc	tupdesc = slot->tts_tupleDescriptor;
+	int			tdesc_natts = tupdesc->natts;
 	int			attnum;
 	HeapTuple	tuple;
 
@@ -1277,6 +1377,8 @@ slot_getallattrs(TupleTableSlot *slot)
 	/*
 	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
 	 * rest as null
+	 *
+	 * FIXME -- needs actual thought
 	 */
 	for (; attnum < tdesc_natts; attnum++)
 	{
@@ -1314,16 +1416,21 @@ slot_getsomeattrs(TupleTableSlot *slot, int attnum)
 		elog(ERROR, "cannot extract attribute from empty tuple slot");
 
 	/*
-	 * load up any slots available from physical tuple
+	 * make sure we don't try to fetch anything past the end of the physical tuple
 	 */
 	attno = HeapTupleHeaderGetNatts(tuple->t_data);
 	attno = Min(attno, attnum);
 
+	/*
+	 * load up any slots available from physical tuple
+	 */
 	slot_deform_tuple(slot, attno);
 
 	/*
 	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
 	 * rest as null
+	 *
+	 * FIXME -- this needs some actual thought
 	 */
 	for (; attno < attnum; attno++)
 	{
@@ -1392,7 +1499,8 @@ heap_freetuple(HeapTuple htup)
 /*
  * heap_form_minimal_tuple
  *		construct a MinimalTuple from the given values[] and isnull[] arrays,
- *		which are of the length indicated by tupleDescriptor->natts
+ *		which are of the length indicated by tupleDescriptor->natts.  The input
+ *		arrays must always be sorted in attnum order.
  *
  * This is exactly like heap_form_tuple() except that the result is a
  * "minimal" tuple lacking a HeapTupleData header as well as room for system
diff --git a/src/backend/access/common/printtup.c b/src/backend/access/common/printtup.c
index baed981..7482812 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;
@@ -248,6 +252,8 @@ SendRowDescriptionMessage(TupleDesc typeinfo, List *targetlist, int16 *formats)
 
 /*
  * Get the lookup info that printtup() needs
+ *
+ * The resulting array is indexed by attnum.
  */
 static void
 printtup_prepare_info(DR_printtup *myState, TupleDesc typeinfo, int numAttrs)
@@ -327,7 +333,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 the correct output order. (XXX which is ...)
 	 */
 	for (i = 0; i < natts; ++i)
 	{
@@ -430,7 +437,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 +525,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);
@@ -533,6 +542,7 @@ debugtup(TupleTableSlot *slot, DestReceiver *self)
 {
 	TupleDesc	typeinfo = slot->tts_tupleDescriptor;
 	int			natts = typeinfo->natts;
+	Form_pg_attribute attrib;
 	int			i;
 	Datum		attr;
 	char	   *value;
@@ -540,17 +550,21 @@ 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)
 	{
+		attrib = typeinfo->attrs[i];
 		attr = slot_getattr(slot, i + 1, &isnull);
 		if (isnull)
 			continue;
-		getTypeOutputInfo(typeinfo->attrs[i]->atttypid,
-						  &typoutput, &typisvarlena);
+		getTypeOutputInfo(attrib->atttypid, &typoutput, &typisvarlena);
 
 		value = OidOutputFunctionCall(typoutput, attr);
 
-		printatt((unsigned) i + 1, typeinfo->attrs[i], value);
+		printatt((unsigned) i + 1, attrib, value);
 	}
 	printf("\t----\n");
 }
@@ -612,7 +626,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 41d71c8..924f4af 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,8 @@ CreateTemplateTupleDesc(int natts, bool hasoid)
 	 * Initialize other fields of the tupdesc.
 	 */
 	desc->natts = natts;
+	desc->logattrs = NULL;
+	desc->physattrs = NULL;
 	desc->constr = NULL;
 	desc->tdtypeid = RECORDOID;
 	desc->tdtypmod = -1;
@@ -120,6 +123,8 @@ 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->physattrs = NULL;
 	desc->constr = NULL;
 	desc->tdtypeid = RECORDOID;
 	desc->tdtypmod = -1;
@@ -154,6 +159,9 @@ CreateTupleDescCopy(TupleDesc tupdesc)
 	desc->tdtypeid = tupdesc->tdtypeid;
 	desc->tdtypmod = tupdesc->tdtypmod;
 
+	Assert(desc->logattrs == NULL);
+	Assert(desc->physattrs == NULL);
+
 	return desc;
 }
 
@@ -251,11 +259,16 @@ 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 */
 	dst->attrs[dstAttno - 1]->attnotnull = false;
 	dst->attrs[dstAttno - 1]->atthasdef = false;
+
+	/* Reset the new entry physical and logical position too */
+	dst->attrs[dstAttno - 1]->attphysnum = dstAttno;
+	dst->attrs[dstAttno - 1]->attlognum = dstAttno;
 }
 
 /*
@@ -301,6 +314,11 @@ FreeTupleDesc(TupleDesc tupdesc)
 		pfree(tupdesc->constr);
 	}
 
+	if (tupdesc->logattrs)
+		pfree(tupdesc->logattrs);
+	if (tupdesc->physattrs)
+		pfree(tupdesc->physattrs);
+
 	pfree(tupdesc);
 }
 
@@ -345,7 +363,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 +404,12 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (attr1->attlen != attr2->attlen)
 			return false;
+#if 0
+		if (attr1->attphysnum != attr2->attphysnum)
+			return false;
+		if (attr1->attlognum != attr2->attlognum)
+			return false;
+#endif
 		if (attr1->attndims != attr2->attndims)
 			return false;
 		if (attr1->atttypmod != attr2->atttypmod)
@@ -529,6 +553,8 @@ TupleDescInitEntry(TupleDesc desc,
 	att->atttypmod = typmod;
 
 	att->attnum = attributeNumber;
+	att->attphysnum = attributeNumber;
+	att->attlognum = attributeNumber;
 	att->attndims = attdim;
 
 	att->attnotnull = false;
@@ -574,6 +600,24 @@ TupleDescInitEntryCollation(TupleDesc desc,
 	desc->attrs[attributeNumber - 1]->attcollation = collationid;
 }
 
+/*
+ * Assign a nondefault attphysnum to a previously initialized tuple descriptor
+ * entry.
+ */
+void
+TupleDescInitEntryPhysicalPosition(TupleDesc desc,
+								   AttrNumber attributeNumber,
+								   AttrNumber attphysnum)
+{
+	/*
+	 * sanity checks
+	 */
+	AssertArg(PointerIsValid(desc));
+	AssertArg(attributeNumber >= 1);
+	AssertArg(attributeNumber <= desc->natts);
+
+	desc->attrs[attributeNumber - 1]->attphysnum = attphysnum;
+}
 
 /*
  * BuildDescForRelation
@@ -666,6 +710,9 @@ BuildDescForRelation(List *schema)
 		desc->constr = NULL;
 	}
 
+	Assert(desc->logattrs == NULL);
+	Assert(desc->physattrs == NULL);
+
 	return desc;
 }
 
@@ -726,5 +773,78 @@ BuildDescFromLists(List *names, List *types, List *typmods, List *collations)
 		TupleDescInitEntryCollation(desc, attnum, attcollation);
 	}
 
+	Assert(desc->logattrs == NULL);
+	Assert(desc->physattrs == 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;
+}
+
+static int
+cmpphysnum(const void *attr1, const void *attr2)
+{
+	Form_pg_attribute	att1 = *(Form_pg_attribute *) attr1;
+	Form_pg_attribute	att2 = *(Form_pg_attribute *) attr2;
+
+	if (att1->attphysnum < att2->attphysnum)
+		return -1;
+	if (att1->attphysnum > att2->attphysnum)
+		return 1;
+	return 0;
+}
+
+static inline Form_pg_attribute *
+tupdescSortAttrs(TupleDesc desc,
+				 int (*cmpfn)(const void *, const void *))
+{
+	Form_pg_attribute *attrs;
+	Size	size = sizeof(Form_pg_attribute) * desc->natts;
+
+	/*
+	 * The attribute arrays must be allocated in the same memcxt as the tupdesc
+	 * they belong to, so that they aren't reset ahead of time.
+	 */
+	attrs = MemoryContextAlloc(GetMemoryChunkContext(desc), size);
+	memcpy(attrs, desc->attrs, size);
+	qsort(attrs, desc->natts, sizeof(Form_pg_attribute), cmpfn);
+
+	return attrs;
+}
+
+/*
+ * Return the array of attrs sorted by logical position
+ */
+Form_pg_attribute *
+TupleDescGetLogSortedAttrs(TupleDesc desc)
+{
+	if (desc->logattrs == NULL)
+		desc->logattrs = tupdescSortAttrs(desc, cmplognum);
+
+	return desc->logattrs;
+}
+
+/*
+ * Return the array of attrs sorted by physical position
+ */
+Form_pg_attribute *
+TupleDescGetPhysSortedAttrs(TupleDesc desc)
+{
+	if (desc->physattrs == NULL)
+		desc->physattrs = tupdescSortAttrs(desc, cmpphysnum);
+
+	return desc->physattrs;
+}
diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c
index 8464e87..d2d547b 100644
--- a/src/backend/access/heap/tuptoaster.c
+++ b/src/backend/access/heap/tuptoaster.c
@@ -690,8 +690,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) > maxDataLen)
 	{
 		int			biggest_attno = -1;
 		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
@@ -780,8 +780,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) > maxDataLen &&
 		   rel->rd_rel->reltoastrelid != InvalidOid)
 	{
 		int			biggest_attno = -1;
@@ -831,8 +831,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) > maxDataLen)
 	{
 		int			biggest_attno = -1;
 		int32		biggest_size = MAXALIGN(TOAST_POINTER_SIZE);
@@ -894,8 +894,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) > maxDataLen &&
 		   rel->rd_rel->reltoastrelid != InvalidOid)
 	{
 		int			biggest_attno = -1;
@@ -969,8 +969,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);
 		new_tuple_len = new_header_len + new_data_len;
 
 		/*
@@ -1202,8 +1202,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);
 	new_tuple_len = new_header_len + new_data_len;
 
 	new_data = (HeapTupleHeader) palloc0(new_tuple_len);
diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c
index ad49964..5f1f288 100644
--- a/src/backend/bootstrap/bootstrap.c
+++ b/src/backend/bootstrap/bootstrap.c
@@ -658,7 +658,9 @@ DefineAttr(char *name, char *type, int attnum, int nullness)
 
 	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 a5c78ee..fcc12ab 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -217,6 +217,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');
 
@@ -254,6 +256,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 17f7266..43466c4 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 f85ed93..5df7302 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 92ff632..fc60a6a 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 */
@@ -4319,7 +4319,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 = TupleDescGetLogSortedAttrs(tupDesc);
 		int			attr_count = tupDesc->natts;
 		int			i;
 
@@ -4327,7 +4327,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 f5d5b63..a646b8c 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -1484,7 +1484,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
@@ -1541,6 +1542,7 @@ MergeAttributes(List *schema, List *supers, char relpersistence,
 			parentsWithOids++;
 
 		tupleDesc = RelationGetDescr(relation);
+		parent_attrs = TupleDescGetLogSortedAttrs(tupleDesc);
 		constr = tupleDesc->constr;
 
 		/*
@@ -1551,10 +1553,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;
@@ -4726,6 +4735,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 fec76d4..89b850d 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -624,6 +624,9 @@ ExecEvalScalarVar(ExprState *exprstate, ExprContext *econtext,
 
 		attr = slot_tupdesc->attrs[attnum - 1];
 
+		/* The attnums must match (Var vs. the attribute). */
+		Assert(attr->attnum == attnum);
+
 		/* can't check type if dropped, since atttypid is probably 0 */
 		if (!attr->attisdropped)
 		{
@@ -830,6 +833,7 @@ ExecEvalWholeRowVar(WholeRowVarExprState *wrvstate, ExprContext *econtext,
 									  slot_tupdesc->natts,
 									  var_tupdesc->natts)));
 
+		/* FIXME -- this should probably consider attributes in logical order */
 		for (i = 0; i < var_tupdesc->natts; i++)
 		{
 			Form_pg_attribute vattr = var_tupdesc->attrs[i];
@@ -1029,6 +1033,7 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,
 	/* Check to see if any dropped attributes are non-null */
 	for (i = 0; i < var_tupdesc->natts; i++)
 	{
+		/* XXX should probably consider attributes in logical order */
 		Form_pg_attribute vattr = var_tupdesc->attrs[i];
 		Form_pg_attribute sattr = tupleDesc->attrs[i];
 
@@ -1182,6 +1187,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,
@@ -1618,6 +1626,7 @@ tupledesc_match(TupleDesc dst_tupdesc, TupleDesc src_tupdesc)
 
 	for (i = 0; i < dst_tupdesc->natts; i++)
 	{
+		/* XXX should consider attributes in logical order? */
 		Form_pg_attribute dattr = dst_tupdesc->attrs[i];
 		Form_pg_attribute sattr = src_tupdesc->attrs[i];
 
@@ -3258,6 +3267,7 @@ ExecEvalRow(RowExprState *rstate,
 	int			natts;
 	ListCell   *arg;
 	int			i;
+	Form_pg_attribute *attrs;
 
 	/* Set default values for result flags: non-null, not a set result */
 	*isNull = false;
@@ -3272,13 +3282,18 @@ ExecEvalRow(RowExprState *rstate,
 	/* preset to nulls in case rowtype has some later-added columns */
 	memset(isnull, true, natts * sizeof(bool));
 
-	/* Evaluate field values */
+	/*
+	 * Evaluate field values.  Note the incoming expr array is sorted in
+	 * logical order.
+	 */
+	attrs = TupleDescGetLogSortedAttrs(rstate->tupdesc);
 	i = 0;
 	foreach(arg, rstate->args)
 	{
 		ExprState  *e = (ExprState *) lfirst(arg);
+		int			attnum = attrs[i]->attnum - 1;
 
-		values[i] = ExecEvalExpr(e, econtext, &isnull[i], NULL);
+		values[attnum] = ExecEvalExpr(e, econtext, &isnull[attnum], NULL);
 		i++;
 	}
 
@@ -4028,6 +4043,7 @@ ExecEvalFieldSelect(FieldSelectState *fstate,
 	TupleDesc	tupDesc;
 	Form_pg_attribute attr;
 	HeapTupleData tmptup;
+	Form_pg_attribute *attrs;
 
 	tupDatum = ExecEvalExpr(fstate->arg, econtext, isNull, isDone);
 
@@ -4055,7 +4071,9 @@ 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 = TupleDescGetLogSortedAttrs(tupDesc);
+	attrs = tupDesc->attrs;
+	attr = attrs[fieldnum - 1];
 
 	/* Check for dropped column, and force a NULL result if so */
 	if (attr->attisdropped)
@@ -4078,7 +4096,7 @@ ExecEvalFieldSelect(FieldSelectState *fstate,
 	tmptup.t_data = tuple;
 
 	result = heap_getattr(&tmptup,
-						  fieldnum,
+						  attr->attnum,
 						  tupDesc,
 						  isNull);
 	return result;
@@ -4104,6 +4122,7 @@ ExecEvalFieldStore(FieldStoreState *fstate,
 	bool	   *isnull;
 	Datum		save_datum;
 	bool		save_isNull;
+	Form_pg_attribute *attrs;
 	ListCell   *l1,
 			   *l2;
 
@@ -4115,6 +4134,8 @@ ExecEvalFieldStore(FieldStoreState *fstate,
 	/* Lookup tupdesc if first time through or after rescan */
 	tupDesc = get_cached_rowtype(fstore->resulttype, -1,
 								 &fstate->argdesc, econtext);
+//	attrs = TupleDescGetLogSortedAttrs(tupDesc);
+	attrs = tupDesc->attrs;
 
 	/* Allocate workspace */
 	values = (Datum *) palloc(tupDesc->natts * sizeof(Datum));
@@ -4153,8 +4174,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
@@ -4165,13 +4188,13 @@ 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;
@@ -4818,12 +4841,13 @@ ExecInitExpr(Expr *node, PlanState *parent)
 					rstate->tupdesc = lookup_rowtype_tupdesc_copy(rowexpr->row_typeid, -1);
 				}
 				/* In either case, adopt RowExpr's column aliases */
+				/* XXX this is problematic ... names should be assigned in logical order */
 				ExecTypeSetColNames(rstate->tupdesc, rowexpr->colnames);
 				/* Bless the tupdesc in case it's now of type RECORD */
 				BlessTupleDesc(rstate->tupdesc);
 				/* Set up evaluation, skipping any deleted columns */
 				Assert(list_length(rowexpr->args) <= rstate->tupdesc->natts);
-				attrs = rstate->tupdesc->attrs;
+				attrs = TupleDescGetLogSortedAttrs(rstate->tupdesc);
 				i = 0;
 				foreach(l, rowexpr->args)
 				{
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c
index 3f0d809..d2721fe 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 = TupleDescGetPhysSortedAttrs(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)
@@ -286,7 +287,7 @@ tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, TupleDesc tupdesc
 		/* if these Asserts fail, planner messed up */
 		Assert(var->varno == varno);
 		Assert(var->varlevelsup == 0);
-		if (var->varattno != attrno)
+		if (var->varattno != att_tup->attnum)
 			return false;		/* out of order */
 		if (att_tup->attisdropped)
 			return false;		/* table contains dropped columns */
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 753754d..7092f2b 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -120,6 +120,7 @@ MakeTupleTableSlot(void)
 	slot->tts_mcxt = CurrentMemoryContext;
 	slot->tts_buffer = InvalidBuffer;
 	slot->tts_nvalid = 0;
+	slot->tts_nphysvalid = 0;
 	slot->tts_values = NULL;
 	slot->tts_isnull = NULL;
 	slot->tts_mintuple = NULL;
@@ -353,6 +354,7 @@ ExecStoreTuple(HeapTuple tuple,
 
 	/* Mark extracted state invalid */
 	slot->tts_nvalid = 0;
+	slot->tts_nphysvalid = 0;
 
 	/*
 	 * If tuple is on a disk page, keep the page pinned as long as we hold a
@@ -426,6 +428,7 @@ ExecStoreMinimalTuple(MinimalTuple mtup,
 
 	/* Mark extracted state invalid */
 	slot->tts_nvalid = 0;
+	slot->tts_nphysvalid = 0;
 
 	return slot;
 }
@@ -472,6 +475,7 @@ ExecClearTuple(TupleTableSlot *slot)	/* slot in which to store tuple */
 	 */
 	slot->tts_isempty = true;
 	slot->tts_nvalid = 0;
+	slot->tts_nphysvalid = 0;
 
 	return slot;
 }
@@ -499,6 +503,7 @@ ExecStoreVirtualTuple(TupleTableSlot *slot)
 
 	slot->tts_isempty = false;
 	slot->tts_nvalid = slot->tts_tupleDescriptor->natts;
+	slot->tts_nphysvalid = slot->tts_tupleDescriptor->natts;
 
 	return slot;
 }
@@ -595,11 +600,12 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
 		return minimal_tuple_from_heap_tuple(slot->tts_tuple);
 
 	/*
-	 * Otherwise we need to build a tuple from the Datum array.
+	 * Otherwise we need to build a tuple from the Datum array.  The
+	 * arrays in the slot are in physical order, so we need to re-sort
+	 * them in attnum order to pass them to heap_form_minimal_tuple.
 	 */
 	return heap_form_minimal_tuple(slot->tts_tupleDescriptor,
-								   slot->tts_values,
-								   slot->tts_isnull);
+								   slot->tts_values, slot->tts_isnull);
 }
 
 /* --------------------------------
@@ -771,6 +777,7 @@ ExecMaterializeSlot(TupleTableSlot *slot)
 	 * that we have not pfree'd tts_mintuple, if there is one.)
 	 */
 	slot->tts_nvalid = 0;
+	slot->tts_nphysvalid = 0;
 
 	/*
 	 * On the same principle of not depending on previous remote storage,
@@ -925,6 +932,7 @@ ExecTypeFromTLInternal(List *targetList, bool hasoid, bool skipjunk)
 
 		if (skipjunk && tle->resjunk)
 			continue;
+
 		TupleDescInitEntry(typeInfo,
 						   cur_resno,
 						   tle->resname,
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 6c3eff7..952fb4f 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 */
@@ -1716,11 +1717,13 @@ check_sql_fn_retval(Oid func_id, Oid rettype, List *queryTreeList,
 
 		/*
 		 * Verify that the targetlist matches the return tuple type. We scan
-		 * the non-deleted attributes to ensure that they match the datatypes
+		 * the non-deleted attributes, in logical ordering, to ensure that
+		 * they match the datatypes
 		 * of the non-resjunk columns.  For deleted attributes, insert NULL
 		 * result columns if the caller asked for that.
 		 */
 		tupnatts = tupdesc->natts;
+		attrs = TupleDescGetLogSortedAttrs(tupdesc);
 		tuplogcols = 0;			/* we'll count nondeleted cols as we go */
 		colindex = 0;
 		newtlist = NIL;			/* these are only used if modifyTargetList */
@@ -1749,7 +1752,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 +1809,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/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index 83d562e..73df8af 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -146,6 +146,7 @@ ExecGroup(GroupState *node)
 			 * Compare with first tuple and see if this tuple is of the same
 			 * group.  If so, ignore it and keep scanning.
 			 */
+			/* FIXME -- here, the grpColIdx seems to cause trouble */
 			if (!execTuplesMatch(firsttupleslot, outerslot,
 								 numCols, grpColIdx,
 								 node->eqfunctions,
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 5282a4f..3570020 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1084,6 +1084,7 @@ _copyVar(const Var *from)
 
 	COPY_SCALAR_FIELD(varno);
 	COPY_SCALAR_FIELD(varattno);
+	COPY_SCALAR_FIELD(varphysno);
 	COPY_SCALAR_FIELD(vartype);
 	COPY_SCALAR_FIELD(vartypmod);
 	COPY_SCALAR_FIELD(varcollid);
@@ -1792,6 +1793,7 @@ _copyTargetEntry(const TargetEntry *from)
 	COPY_SCALAR_FIELD(ressortgroupref);
 	COPY_SCALAR_FIELD(resorigtbl);
 	COPY_SCALAR_FIELD(resorigcol);
+	COPY_SCALAR_FIELD(resorigphyscol);
 	COPY_SCALAR_FIELD(resjunk);
 
 	return newnode;
@@ -2009,6 +2011,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/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index fe509b0..f3b47e5 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -141,6 +141,7 @@ _equalVar(const Var *a, const Var *b)
 {
 	COMPARE_SCALAR_FIELD(varno);
 	COMPARE_SCALAR_FIELD(varattno);
+	COMPARE_SCALAR_FIELD(varphysno);
 	COMPARE_SCALAR_FIELD(vartype);
 	COMPARE_SCALAR_FIELD(vartypmod);
 	COMPARE_SCALAR_FIELD(varcollid);
@@ -691,6 +692,7 @@ _equalTargetEntry(const TargetEntry *a, const TargetEntry *b)
 	COMPARE_SCALAR_FIELD(ressortgroupref);
 	COMPARE_SCALAR_FIELD(resorigtbl);
 	COMPARE_SCALAR_FIELD(resorigcol);
+	COMPARE_SCALAR_FIELD(resorigphyscol);
 	COMPARE_SCALAR_FIELD(resjunk);
 
 	return true;
diff --git a/src/backend/nodes/makefuncs.c b/src/backend/nodes/makefuncs.c
index 6fdf44d..0c54920 100644
--- a/src/backend/nodes/makefuncs.c
+++ b/src/backend/nodes/makefuncs.c
@@ -90,6 +90,8 @@ makeVar(Index varno,
 
 	/* Likewise, we just set location to "unknown" here */
 	var->location = -1;
+	/* Likewise, we just set physical position to invalid */
+	var->varphysno = InvalidAttrNumber;
 
 	return var;
 }
@@ -250,6 +252,7 @@ makeTargetEntry(Expr *expr,
 	tle->ressortgroupref = 0;
 	tle->resorigtbl = InvalidOid;
 	tle->resorigcol = 0;
+	tle->resorigphyscol = 0;
 
 	tle->resjunk = resjunk;
 
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2f417fe..90e44cb 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"
@@ -917,6 +918,7 @@ _outVar(StringInfo str, const Var *node)
 
 	WRITE_UINT_FIELD(varno);
 	WRITE_INT_FIELD(varattno);
+	WRITE_INT_FIELD(varphysno);
 	WRITE_OID_FIELD(vartype);
 	WRITE_INT_FIELD(vartypmod);
 	WRITE_OID_FIELD(varcollid);
@@ -1439,10 +1441,27 @@ _outTargetEntry(StringInfo str, const TargetEntry *node)
 	WRITE_UINT_FIELD(ressortgroupref);
 	WRITE_OID_FIELD(resorigtbl);
 	WRITE_INT_FIELD(resorigcol);
+	WRITE_INT_FIELD(resorigphyscol);
 	WRITE_BOOL_FIELD(resjunk);
 }
 
 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");
@@ -2425,6 +2444,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);
@@ -3106,6 +3126,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 563209c..d82e533 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -429,6 +429,7 @@ _readVar(void)
 
 	READ_UINT_FIELD(varno);
 	READ_INT_FIELD(varattno);
+	READ_INT_FIELD(varphysno);
 	READ_OID_FIELD(vartype);
 	READ_INT_FIELD(vartypmod);
 	READ_OID_FIELD(varcollid);
@@ -1143,6 +1144,7 @@ _readTargetEntry(void)
 	READ_UINT_FIELD(ressortgroupref);
 	READ_OID_FIELD(resorigtbl);
 	READ_INT_FIELD(resorigcol);
+	READ_INT_FIELD(resorigphyscol);
 	READ_BOOL_FIELD(resjunk);
 
 	READ_DONE();
@@ -1218,6 +1220,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/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index ec828cd..d392ade 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -78,8 +78,9 @@ typedef struct
 	(((con)->consttype == REGCLASSOID || (con)->consttype == OIDOID) && \
 	 !(con)->constisnull)
 
-#define fix_scan_list(root, lst, rtoffset) \
+/* #define fix_scan_list(root, lst, rtoffset) \
 	((List *) fix_scan_expr(root, (Node *) (lst), rtoffset))
+	*/
 
 static void add_rtes_to_flat_rtable(PlannerInfo *root, bool recursing);
 static void flatten_unplanned_rtes(PlannerGlobal *glob, RangeTblEntry *rte);
@@ -156,13 +157,16 @@ static bool extract_query_dependencies_walker(Node *node,
  * 3. We adjust Vars in upper plan nodes to refer to the outputs of their
  * subplans.
  *
- * 4. PARAM_MULTIEXPR Params are replaced by regular PARAM_EXEC Params,
+ * 4. We correct attphysnum annotations in scan tuple descriptors to match
+ * the real values.
+ *
+ * 5. PARAM_MULTIEXPR Params are replaced by regular PARAM_EXEC Params,
  * now that we have finished planning all MULTIEXPR subplans.
  *
- * 5. We compute regproc OIDs for operators (ie, we look up the function
+ * 6. We compute regproc OIDs for operators (ie, we look up the function
  * that implements each op).
  *
- * 6. We create lists of specific objects that the plan depends on.
+ * 7. We create lists of specific objects that the plan depends on.
  * This will be used by plancache.c to drive invalidation of cached plans.
  * Relation dependencies are represented by OIDs, and everything else by
  * PlanInvalItems (this distinction is motivated by the shared-inval APIs).
@@ -418,6 +422,89 @@ add_rte_to_flat_rtable(PlannerGlobal *glob, RangeTblEntry *rte)
 		glob->relationOids = lappend_oid(glob->relationOids, newrte->relid);
 }
 
+
+static Node *fix_physno_mutator(Node *node, void *context);
+
+static List *
+fix_scan_list(PlannerInfo *root, List *targetlist, int rtoffset)
+{
+	Node  *node;
+
+	node = fix_physno_mutator((Node *) targetlist, root);
+	return (List *) fix_scan_expr(root, node, rtoffset);
+}
+
+#include "parser/parsetree.h"
+#include "utils/rel.h"
+#include "access/heapam.h"
+static Node *
+fix_physno_mutator(Node *node, void *context)
+{
+	PlannerInfo *root = (PlannerInfo *) context;
+
+	if (node == NULL)
+		return NULL;
+
+	if (IsA(node, Var))
+	{
+		/* do the transformation here */
+		/*
+		 * FIXME --- there must be a way to do this properly .. perhaps save the
+		 * attphysnum array in the RTE struct?
+		 */
+		Var		*var = (Var *) node;
+		RangeTblEntry *rte;
+		Relation	rel;
+
+		/* varphysno is equal to varattno by default */
+		var->varphysno = var->varattno;
+
+		if (var->varattno > 0 && !IS_SPECIAL_VARNO(var->varno))
+		{
+			rte = rt_fetch(var->varno, root->parse->rtable);
+
+			/* if it's an actual relation, use find the proper attphysnum */
+			if (rte->rtekind == RTE_RELATION)
+			{
+				rel = relation_open(rte->relid, NoLock);
+
+				/*
+				 * First Var in this relation, cache the attphysnums.
+				 *
+				 * FIXME This caches all the physnums at once, as it's simpler
+				 */
+				if (rte->physnums == NULL)
+				{
+					AttrNumber attnum;
+
+					/* will be initialized to InvalidAttrNumber (0), which is OK */
+					rte->physnums = (AttrNumber*)palloc0(rel->rd_att->natts * sizeof(AttrNumber));
+
+					for (attnum = 1; attnum <= rel->rd_att->natts; attnum++)
+					{
+						/* must not be already set (duplicate attphysnum) */
+						Assert(rte->physnums[rel->rd_att->attrs[attnum-1]->attnum-1] == 0);
+						rte->physnums[rel->rd_att->attrs[attnum-1]->attnum-1]
+										= rel->rd_att->attrs[attnum-1]->attphysnum;
+					}
+				}
+
+				/* lookup the varphysno in the cache */
+				var->varphysno = rte->physnums[var->varattno-1];
+
+				/* make sure we actually found it */
+				Assert(var->varphysno != InvalidAttrNumber);
+
+				relation_close(rel, NoLock);
+			}
+		}
+	}
+
+	return expression_tree_mutator(node, fix_physno_mutator, (void *) context);
+}
+
+
+
 /*
  * set_plan_refs: recurse through the Plan nodes of a single subquery level
  */
@@ -613,6 +700,7 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 			 */
 			set_dummy_tlist_references(plan, rtoffset);
 
+
 			/*
 			 * Since these plan types don't check quals either, we should not
 			 * find any qual expression attached to them.
@@ -1066,6 +1154,7 @@ copyVar(Var *var)
 	return newvar;
 }
 
+
 /*
  * fix_expr_common
  *		Do generic set_plan_references processing on an expression node
@@ -1172,6 +1261,7 @@ fix_param_node(PlannerInfo *root, Param *p)
  *		Do set_plan_references processing on a scan-level expression
  *
  * This consists of incrementing all Vars' varnos by rtoffset,
+ * setting all Vars' varphysnum to their correct values,
  * replacing PARAM_MULTIEXPR Params, expanding PlaceHolderVars,
  * looking up operator opcode info for OpExpr and related nodes,
  * and adding OIDs from regclass Const nodes into root->glob->relationOids.
@@ -1206,6 +1296,7 @@ fix_scan_expr(PlannerInfo *root, Node *node, int rtoffset)
 	}
 }
 
+
 static Node *
 fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
 {
@@ -1214,7 +1305,7 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
 	if (IsA(node, Var))
 	{
 		Var		   *var = copyVar((Var *) node);
-
+	
 		Assert(var->varlevelsup == 0);
 
 		/*
@@ -1227,6 +1318,7 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
 			var->varno += context->rtoffset;
 		if (var->varnoold > 0)
 			var->varnoold += context->rtoffset;
+
 		return (Node *) var;
 	}
 	if (IsA(node, Param))
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 8a0199b..7ccfb2a 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 8a6c3cc..384c599 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/clauses.c b/src/backend/optimizer/util/clauses.c
index 84d58ae..af10116 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2216,6 +2216,8 @@ CommuteRowCompareExpr(RowCompareExpr *clause)
  * is still what it was when the expression was parsed.  This is needed to
  * guard against improper simplification after ALTER COLUMN TYPE.  (XXX we
  * may well need to make similar checks elsewhere?)
+ *
+ * FIXME do we need to do something about the fieldnum here?
  */
 static bool
 rowtype_field_matches(Oid rowtypeid, int fieldnum,
@@ -3253,6 +3255,7 @@ eval_const_expressions_mutator(Node *node,
 							return fld;
 					}
 				}
+				/* FIXME  does this need change? */
 				newfselect = makeNode(FieldSelect);
 				newfselect->arg = (Expr *) arg;
 				newfselect->fieldnum = fselect->fieldnum;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 313a5c1..de5ce38 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -858,17 +858,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 = TupleDescGetLogSortedAttrs(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)
 				{
@@ -918,7 +920,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 a68f2e8..7049a33 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 8d90b50..1766afa 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -723,7 +723,7 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
  *
  * *top_rti: receives the rangetable index of top_rte.  (Ditto.)
  *
- * *namespace: receives a List of ParseNamespaceItems for the RTEs exposed
+ * *namespace: receives a List of ParseNamespaceItem for the RTEs exposed
  * as table/column names by this item.  (The lateral_only flags in these items
  * are indeterminate and should be explicitly set by the caller before use.)
  */
@@ -880,9 +880,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 a4e494b..2bfcb24 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 = TupleDescGetLogSortedAttrs(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 a200804..9acf5b7 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 = TupleDescGetLogSortedAttrs(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 8d4f79f..1312a37 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.
@@ -527,6 +533,11 @@ GetCTEForRTE(ParseState *pstate, RangeTblEntry *rte, int rtelevelsup)
  *
  * Side effect: if we find a match, mark the RTE as requiring read access
  * for the column.
+ *
+ * XXX the coding of this routine seems to make it impossible to have
+ * attlognums using a different numbering scheme from attnums, which would be a
+ * handy tool to detect incorrect usage of either array.  Can we fix that,
+ * and are there other places that suffer from the same problem?
  */
 Node *
 scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,
@@ -561,6 +572,13 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte, char *colname,
 						 errmsg("column reference \"%s\" is ambiguous",
 								colname),
 						 parser_errposition(pstate, location)));
+			/*
+			 * If the RTE has lognums, the eref->colnames array is sorted in
+			 * logical order; in that case we need to map the attnum we have
+			 * (which is a logical attnum) to the identity one.
+			 */
+			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 +848,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 +879,11 @@ buildRelationAliases(TupleDesc tupdesc, Alias *alias, Alias *eref)
 		numaliases = 0;
 	}
 
+	attrs = TupleDescGetLogSortedAttrs(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 +908,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 +1058,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 +1118,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 +1450,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 +1815,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 +1839,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 +1906,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 +2159,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 +2168,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 +2185,15 @@ 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;
+
+	attrs = (logical_sort ? TupleDescGetLogSortedAttrs(tupdesc) :
+			 tupdesc->attrs);
 
 	if (colnames)
 	{
@@ -2171,9 +2207,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)
 		{
@@ -2221,6 +2258,8 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 							  attr->atttypid, attr->atttypmod,
 							  attr->attcollation,
 							  sublevels_up);
+
+			varnode->varphysno = InvalidAttrNumber;
 			varnode->location = location;
 
 			*colvars = lappend(*colvars, varnode);
@@ -2240,7 +2279,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 +2287,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);
 
 	/*
@@ -2301,7 +2340,10 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 	 */
 	if (rte->alias &&
 		attnum > 0 && attnum <= list_length(rte->alias->colnames))
+	{
+		/* FIXME change attnum to lognum! */
 		return strVal(list_nth(rte->alias->colnames, attnum - 1));
+	}
 
 	/*
 	 * If the RTE is a relation, go to the system catalogs not the
@@ -2408,6 +2450,7 @@ get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
 
 							Assert(tupdesc);
 							Assert(attnum <= tupdesc->natts);
+							/* FIXME map using lognums?? */
 							att_tup = tupdesc->attrs[attnum - 1];
 
 							/*
@@ -2604,6 +2647,7 @@ get_rte_attribute_is_dropped(RangeTblEntry *rte, AttrNumber attnum)
 
 							Assert(tupdesc);
 							Assert(attnum - atts_done <= tupdesc->natts);
+							/* FIXME -- map using lognums? */
 							att_tup = tupdesc->attrs[attnum - atts_done - 1];
 							return att_tup->attisdropped;
 						}
@@ -2696,7 +2740,7 @@ attnameAttNum(Relation rd, const char *attname, bool sysColOK)
 		Form_pg_attribute att = rd->rd_att->attrs[i];
 
 		if (namestrcmp(&(att->attname), attname) == 0 && !att->attisdropped)
-			return i + 1;
+			return att->attnum;
 	}
 
 	if (sysColOK)
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3724330..9edc822 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 = TupleDescGetLogSortedAttrs(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 = TupleDescGetLogSortedAttrs(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)
@@ -1352,7 +1355,7 @@ ExpandRowReference(ParseState *pstate, Node *expr,
 
 		fselect = makeNode(FieldSelect);
 		fselect->arg = (Expr *) copyObject(expr);
-		fselect->fieldnum = i + 1;
+		fselect->fieldnum = att->attnum;
 		fselect->resulttype = att->atttypid;
 		fselect->resulttypmod = att->atttypmod;
 		/* save attribute's collation for parse_collate.c */
@@ -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 df45708..df659ad 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1327,7 +1327,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 a65e18d..4df1dd9 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 = TupleDescGetLogSortedAttrs(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 = TupleDescGetLogSortedAttrs(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 = TupleDescGetLogSortedAttrs(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 = TupleDescGetLogSortedAttrs(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/include/access/htup_details.h b/src/include/access/htup_details.h
index 0a673cd..c431925 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -740,14 +740,15 @@ 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);
+						 Datum *values, bool *isnull);
 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);
+						   Datum *values, bool *isnull);
+
 
 /* these three are deprecated versions of the three above: */
 extern HeapTuple heap_formtuple(TupleDesc tupleDescriptor,
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index 91b0034..bb88ce4 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -60,6 +60,14 @@ typedef struct tupleConstr
  * row type, or a value >= 0 to allow the rowtype to be looked up in the
  * typcache.c type cache.
  *
+ * For descriptors coming out of catalogued relations, it is possible to obtain
+ * an array of attributes sorted by attlognum and attphysnum.  The attlognum
+ * one helps *-expansion, among other things; the attphysnum one is useful for
+ * encoding and decoding tuples to and from the on-disk representation.  The
+ * arrays are initially set to NULL, and are only populated on first access;
+ * those wanting to access it should always do it through
+ * TupleDescGetLogSortedAttrs / TupleDescGetPhysSortedAttrs.
+ *
  * 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 +81,8 @@ 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 */
+	Form_pg_attribute *physattrs;	/* array of attributes sorted by attphysnum */
 	TupleConstr *constr;		/* constraints, or NULL if none */
 	Oid			tdtypeid;		/* composite type ID for tuple type */
 	int32		tdtypmod;		/* typmod for tuple type */
@@ -123,8 +133,16 @@ extern void TupleDescInitEntryCollation(TupleDesc desc,
 							AttrNumber attributeNumber,
 							Oid collationid);
 
+extern void TupleDescInitEntryPhysicalPosition(TupleDesc desc,
+								   AttrNumber attributeNumber,
+								   AttrNumber attphysnum);
+
 extern TupleDesc BuildDescForRelation(List *schema);
 
 extern TupleDesc BuildDescFromLists(List *names, List *types, List *typmods, List *collations);
 
+extern Form_pg_attribute *TupleDescGetLogSortedAttrs(TupleDesc desc);
+
+extern Form_pg_attribute *TupleDescGetPhysSortedAttrs(TupleDesc desc);
+
 #endif   /* TUPDESC_H */
diff --git a/src/include/access/tupmacs.h b/src/include/access/tupmacs.h
index 2f84fee..2dfe913 100644
--- a/src/include/access/tupmacs.h
+++ b/src/include/access/tupmacs.h
@@ -17,6 +17,8 @@
 
 /*
  * check to see if the ATT'th bit of an array of 8-bit bytes is set.
+ *
+ * Note that the index into the nulls array is attnum-1.
  */
 #define att_isnull(ATT, BITS) (!((BITS)[(ATT) >> 3] & (1 << ((ATT) & 0x07))))
 
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 87a3462..ba10a01 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" and any other query where the column
+	 * ordering is user-visible.
 	 */
 	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 8b4c35c..c5f0411 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/executor/tuptable.h b/src/include/executor/tuptable.h
index 48f84bf..9abc3f4 100644
--- a/src/include/executor/tuptable.h
+++ b/src/include/executor/tuptable.h
@@ -89,10 +89,21 @@
  * buffer page.)
  *
  * tts_nvalid indicates the number of valid columns in the tts_values/isnull
+ * arrays.  When the slot is holding a "virtual" tuple this must be equal to the
+ * descriptor's natts.  When the slot is holding a physical tuple this is equal
+ * to the latest column to which we have fully extracted, that is, there are no
+ * "holes" at the left of this column.  Since the disposition of attributes in
+ * the physical storage might not match the ordering of the attributes in the
+ * tuple descriptor, we keep a separate tts_nphysvalid counter which determines
+ * the point up to which we have physically extracted the values.
+ *
+ * tts_nvalid indicates the number of valid columns in the tts_values/isnull
  * arrays.  When the slot is holding a "virtual" tuple this must be equal
  * to the descriptor's natts.  When the slot is holding a physical tuple
  * this is equal to the number of columns we have extracted (we always
- * extract columns from left to right, so there are no holes).
+ * extract columns from left to right, so there are no holes).  Note that since
+ * the tts_values/tts_isnull arrays follow physical ordering, tts_nvalid
+ * is an attphysnum.
  *
  * tts_values/tts_isnull are allocated when a descriptor is assigned to the
  * slot; they are of length equal to the descriptor's natts.
@@ -122,6 +133,7 @@ typedef struct TupleTableSlot
 	MemoryContext tts_mcxt;		/* slot itself is in this context */
 	Buffer		tts_buffer;		/* tuple's buffer, or InvalidBuffer */
 	int			tts_nvalid;		/* # of valid values in tts_values */
+	int			tts_nphysvalid;	/* # of values actually decoded */
 	Datum	   *tts_values;		/* current per-attribute values */
 	bool	   *tts_isnull;		/* current per-attribute isnull flags */
 	MinimalTuple tts_mintuple;	/* minimal tuple, or NULL if none */
@@ -165,9 +177,11 @@ extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot,
 			 TupleTableSlot *srcslot);
 
 /* in access/common/heaptuple.c */
-extern Datum slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull);
+extern Datum slot_getattr(TupleTableSlot *slot, AttrNumber attnum, bool *isnull);
 extern void slot_getallattrs(TupleTableSlot *slot);
 extern void slot_getsomeattrs(TupleTableSlot *slot, int attnum);
 extern bool slot_attisnull(TupleTableSlot *slot, int attnum);
+extern void slot_sort_datumarrays(TupleTableSlot *slot, Datum **values,
+					  bool **isnull);
 
 #endif   /* TUPTABLE_H */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ac13302..1f9a453 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -754,10 +754,12 @@ 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 */
+	AttrNumber *physnums;		/* mapping attnum => attphysnum (NIL) */
 
 	/*
 	 * Fields valid for a subquery RTE (else NULL):
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index dbc5a35..910141e 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -146,8 +146,9 @@ 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 */
+	AttrNumber	varphysno;		/* physical position of column in table */
 	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 */
@@ -1212,6 +1213,7 @@ typedef struct TargetEntry
 								 * clause */
 	Oid			resorigtbl;		/* OID of column's source table */
 	AttrNumber	resorigcol;		/* column's number in source table */
+	AttrNumber	resorigphyscol;	/* column's physical position in source table */
 	bool		resjunk;		/* set to true to eliminate the attribute from
 								 * final target list */
 } TargetEntry;
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index c886335..9c85509 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/expected/create_table.out b/src/test/regress/expected/create_table.out
index 35451d5..3815510 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -31,9 +31,9 @@ CREATE TABLE onek (
 	string4		name
 );
 CREATE TABLE tenk1 (
-	unique1		int4,
-	unique2		int4,
 	two			int4,
+	unique2		int4,
+	unique1		int4,
 	four		int4,
 	ten			int4,
 	twenty		int4,
@@ -48,6 +48,8 @@ CREATE TABLE tenk1 (
 	stringu2	name,
 	string4		name
 ) WITH OIDS;
+UPDATE pg_attribute SET attlognum = 1 where attrelid = 'tenk1'::regclass and attname = 'unique1';
+UPDATE pg_attribute SET attlognum = 3 where attrelid = 'tenk1'::regclass and attname = 'two';
 CREATE TABLE tenk2 (
 	unique1 	int4,
 	unique2 	int4,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e0ae2f2..973d3be 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 7f762bd..711b767 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..db808b4
--- /dev/null
+++ b/src/test/regress/sql/col_order.sql
@@ -0,0 +1,99 @@
+drop table if exists foo, bar, baz cascade;
+
+create table foo (
+	a int,
+	b int,
+	c int,
+	d int,
+	e int);
+update pg_attribute set attphysnum = 4 where attname = 'b' and attrelid = 'foo'::regclass;
+update pg_attribute set attphysnum = 2 where attname = 'd' and attrelid = 'foo'::regclass;
+insert into foo values (1, 2, 3, 4, 5);
+select * from foo;
+-- \quit
+drop table foo;
+
+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;
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index 08029a9..a6feccd 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -35,9 +35,9 @@ CREATE TABLE onek (
 );
 
 CREATE TABLE tenk1 (
-	unique1		int4,
-	unique2		int4,
 	two			int4,
+	unique2		int4,
+	unique1		int4,
 	four		int4,
 	ten			int4,
 	twenty		int4,
@@ -53,6 +53,9 @@ CREATE TABLE tenk1 (
 	string4		name
 ) WITH OIDS;
 
+UPDATE pg_attribute SET attlognum = 1 where attrelid = 'tenk1'::regclass and attname = 'unique1';
+UPDATE pg_attribute SET attlognum = 3 where attrelid = 'tenk1'::regclass and attname = 'two';
+
 CREATE TABLE tenk2 (
 	unique1 	int4,
 	unique2 	int4,
#!/usr/bin/env python

import argparse
import datetime
import psycopg2
import psycopg2.extras

from random import shuffle
from time import sleep

def get_attributes(conn, relname):
	'fetch attributes for a relation (excluding system ones)'

	cur = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
	cur.execute('SELECT attname FROM pg_attribute WHERE attrelid = %(relname)s::regclass AND attnum > 0', {'relname' : relname})

	attrs = cur.fetchall()
	cur.close()

	return [a['attname'] for a in attrs]


def shuffle_attrs(conn, relname, attrs):
	'shuffle the lognums (randomly)'

	shuffle(attrs)

	cur = conn.cursor()

	# conn.begin()

	for lognum in range(len(attrs)):
		cur.execute('UPDATE pg_attribute SET attlognum = %(lognum)s WHERE attrelid = %(relname)s::regclass AND attname = %(name)s', {'lognum' : (lognum+1), 'relname' : relname, 'name' : attrs[lognum]})

	conn.commit()

	cur.close()

	return attrs


def fetch_attrs(conn, tables):

	rels = {}
	for t in tables:
		rels.update({t : get_attributes(conn, t)})

	return rels


def parse_args():
	''
	parser = argparse.ArgumentParser(description='attlognum randomizer')
	parser.add_argument('-t', '--tables', metavar='T', nargs='+', required=True, help='name of table to randomize')
	parser.add_argument('-q', '--query',  metavar='QUERY', required=False, nargs='*', type=str, help='query to execute')

	parser.add_argument('--host', dest='host', default='localhost', help='hostname of the database')
	parser.add_argument('--db',   dest='dbname', default='test', help='name of the database')

	parser.add_argument('-i', '--iterations',  metavar='COUNT', type=int, default=100, help='number of iterations')
	parser.add_argument('-T', '--time',  metavar='SECONDS', type=int, default=0, help='number of seconds')

	parser.add_argument('--init-script', type=str, help='initialization script (create tables etc.)')
	parser.add_argument('--test-script', type=str, help='test script (may be multiple queries)')

	return parser.parse_args()

if __name__ == '__main__':
	
	args = parse_args()

	iter_count = 1
	start_time = datetime.datetime.now()

	while True:

		print "========== iteration",iter_count,"=========="

		# recreate the database (drop if exists)
		conn = psycopg2.connect('host=%s dbname=postgres' % (args.host,))
		conn.autocommit = True
		cur = conn.cursor()
		cur.execute('DROP DATABASE IF EXISTS %s' % (args.dbname,))
		cur.execute('CREATE DATABASE %s' % (args.dbname,))
		cur.close()
		conn.close()

		# now, run the queries (if supplied), or the test script
		try:

			# connect to the proper database
			conn = psycopg2.connect('host=%s dbname=%s' % (args.host, args.dbname))
			conn.autocommit = True

			# run init script (if supplied)
			if args.init_script:
				with open(args.init_script, 'r') as init_script:
					data = init_script.read()
					cur = conn.cursor()
					cur.execute(data)
					cur.close()

			# fetch attributes for the tables
			rels = fetch_attrs(conn, args.tables)

			# randomize the attlognums
			for t in rels:
				attrs = shuffle_attrs(conn, t, rels[t])
				print t,':',attrs

			cur = conn.cursor()

			if args.query:
				for q in args.query:
					print "running query:",q
					cur.execute(q)

			if args.test_script:
				with open(args.test_script, 'r') as test_script:
					data = test_script.read()
					cur = conn.cursor()
					cur.execute(data)
					cur.close()

			cur.close()

		except Exception as ex:
			print "FAILED: ", str(ex)
			# sleep for a few seconds, in case we need a recovery
			sleep(10)

		finally:
			conn.close()

		iter_count += 1
		duration = (datetime.datetime.now() - start_time).total_seconds()

		if (args.iterations > 0) and (iter_count > args.iterations):
			break

		if (args.time > 0) and (duration > args.time):
			break

Attachment: attlognum-init.sql
Description: application/sql

Attachment: attlognum-test.sql
Description: application/sql

-- 
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