diff --git a/src/backend/optimizer/prep/preptlist.c b/src/backend/optimizer/prep/preptlist.c
index 95e5ddbc9db923277571ed8a09aceabd7763c9b9..f3158050d9f924872856a92ea008222cdc9eb29b 100644
--- a/src/backend/optimizer/prep/preptlist.c
+++ b/src/backend/optimizer/prep/preptlist.c
@@ -3,40 +3,40 @@
  * preptlist.c
  *	  Routines to preprocess the parse tree target list
  *
- * Copyright (c) 1994, Regents of the University of California
+ * This module takes care of altering the query targetlist as needed for
+ * INSERT, UPDATE, and DELETE queries.  For INSERT and UPDATE queries,
+ * the targetlist must contain an entry for each attribute of the target
+ * relation in the correct order.  For both UPDATE and DELETE queries,
+ * we need a junk targetlist entry holding the CTID attribute --- the
+ * executor relies on this to find the tuple to be replaced/deleted.
+ *
  *
+ * Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.31 1999/08/22 20:14:51 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/prep/preptlist.c,v 1.32 1999/10/30 23:06:32 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
+
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "catalog/pg_type.h"
 #include "nodes/makefuncs.h"
-#include "optimizer/clauses.h"
 #include "optimizer/prep.h"
 #include "parser/parsetree.h"
 #include "utils/lsyscache.h"
-#include "utils/syscache.h"
 
-static List *expand_targetlist(List *tlist, Oid relid, int command_type,
-				  Index result_relation);
-static List *replace_matching_resname(List *new_tlist,
-						 List *old_tlist);
-static List *new_relation_targetlist(Oid relid, Index rt_index,
-						NodeTag node_type);
+
+static List *expand_targetlist(List *tlist, int command_type,
+							   Index result_relation, List *range_table);
 
 
 /*
  * preprocess_targetlist
  *	  Driver for preprocessing the parse tree targetlist.
  *
- *	  1. Deal with appends and replaces by filling missing attributes
- *		 in the target list.
- *	  2. Reset operator OIDs to the appropriate regproc ids.
- *
  *	  Returns the new targetlist.
  */
 List *
@@ -45,53 +45,49 @@ preprocess_targetlist(List *tlist,
 					  Index result_relation,
 					  List *range_table)
 {
-	Oid			relid = InvalidOid;
-	List	   *expanded_tlist;
-	List	   *t_list;
-
-	if (result_relation >= 1 && command_type != CMD_SELECT)
-		relid = getrelid(result_relation, range_table);
-
 	/*
 	 * for heap_formtuple to work, the targetlist must match the exact
-	 * order of the attributes. We also need to fill in the missing
-	 * attributes here.							-ay 10/94
+	 * order of the attributes. We also need to fill in any missing
+	 * attributes.							-ay 10/94
 	 */
-	expanded_tlist = expand_targetlist(tlist, relid, command_type, result_relation);
-
-	t_list = copyObject(expanded_tlist);
-
-	/* ------------------
-	 *	for "replace" or "delete" queries, add ctid of the result
-	 *	relation into the target list so that the ctid can get
-	 *	propogate through the execution and in the end ExecReplace()
-	 *	will find the right tuple to replace or delete.  This
-	 *	extra field will be removed in ExecReplace().
-	 *	For convinient, we append this extra field to the end of
-	 *	the target list.
-	 * ------------------
+	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+		tlist = expand_targetlist(tlist, command_type,
+								  result_relation, range_table);
+
+	/*
+	 *	for "update" and "delete" queries, add ctid of the result
+	 *	relation into the target list so that the ctid will propagate
+	 *	through execution and ExecutePlan() will be able to identify
+	 *	the right tuple to replace or delete.  This extra field is
+	 *	marked "junk" so that it is not stored back into the tuple.
 	 */
 	if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
 	{
-		TargetEntry *ctid;
 		Resdom	   *resdom;
 		Var		   *var;
 
-		resdom = makeResdom(length(t_list) + 1,
+		resdom = makeResdom(length(tlist) + 1,
 							TIDOID,
 							-1,
-							"ctid",
+							pstrdup("ctid"),
 							0,
 							0,
 							true);
 
-		var = makeVar(result_relation, -1, TIDOID, -1, 0);
+		var = makeVar(result_relation, SelfItemPointerAttributeNumber,
+					  TIDOID, -1, 0);
+
+		/* For an UPDATE, expand_targetlist already created a fresh tlist.
+		 * For DELETE, better do a listCopy so that we don't destructively
+		 * modify the original tlist (is this really necessary?).
+		 */
+		if (command_type == CMD_DELETE)
+			tlist = listCopy(tlist);
 
-		ctid = makeTargetEntry(resdom, (Node *) var);
-		t_list = lappend(t_list, ctid);
+		tlist = lappend(tlist, makeTargetEntry(resdom, (Node *) var));
 	}
 
-	return t_list;
+	return tlist;
 }
 
 /*****************************************************************************
@@ -103,201 +99,98 @@ preprocess_targetlist(List *tlist,
 /*
  * expand_targetlist
  *	  Given a target list as generated by the parser and a result relation,
- *	  add targetlist entries for the attributes which have not been used.
- *
- *	  XXX This code is only supposed to work with unnested relations.
- *
- *	  'tlist' is the original target list
- *	  'relid' is the relid of the result relation
- *	  'command' is the update command
- *
- * Returns the expanded target list, sorted in resno order.
+ *	  add targetlist entries for any missing attributes, and order the
+ *	  non-junk attributes in proper field order.
  */
 static List *
-expand_targetlist(List *tlist,
-				  Oid relid,
-				  int command_type,
-				  Index result_relation)
-{
-	NodeTag		node_type = T_Invalid;
-
-	switch (command_type)
-	{
-		case CMD_INSERT:
-			node_type = (NodeTag) T_Const;
-			break;
-		case CMD_UPDATE:
-			node_type = (NodeTag) T_Var;
-			break;
-	}
-
-	if (node_type != T_Invalid)
-	{
-		List	   *ntlist = new_relation_targetlist(relid,
-													 result_relation,
-													 node_type);
-
-		return replace_matching_resname(ntlist, tlist);
-	}
-	else
-		return tlist;
-
-}
-
-
-static List *
-replace_matching_resname(List *new_tlist, List *old_tlist)
+expand_targetlist(List *tlist, int command_type,
+				  Index result_relation, List *range_table)
 {
-	List	   *temp,
-			   *i;
-	List	   *t_list = NIL;
-
-	foreach(i, new_tlist)
-	{
-		TargetEntry *new_tle = (TargetEntry *) lfirst(i);
-		TargetEntry *matching_old_tl = NULL;
-
-		foreach(temp, old_tlist)
-		{
-			TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
+	int			old_tlist_len = length(tlist);
+	List	   *new_tlist = NIL;
+	bool	   *tlistentry_used;
+	Relation	rel;
+	int			attrno,
+				numattrs,
+				old_tlist_index;
+	List	   *temp;
 
-			if (!strcmp(old_tle->resdom->resname,
-						new_tle->resdom->resname))
-			{
-				matching_old_tl = old_tle;
-				break;
-			}
-		}
-
-		if (matching_old_tl)
-		{
-			/* XXX safe to modify old resdom? */
-			matching_old_tl->resdom->resno = new_tle->resdom->resno;
-			t_list = lappend(t_list, matching_old_tl);
-		}
-		else
-			t_list = lappend(t_list, new_tle);
-	}
+	/*
+	 * Keep a map of which tlist items we have transferred to new list.
+	 * +1 here keeps palloc from complaining if old_tlist_len=0.
+	 */
+	tlistentry_used = (bool *) palloc((old_tlist_len+1) * sizeof(bool));
+	memset(tlistentry_used, 0, (old_tlist_len+1) * sizeof(bool));
 
 	/*
-	 * It is possible that 'old_tlist' has some negative attributes (i.e.
-	 * negative resnos). This only happens if this is a replace/append
-	 * command and we explicitly specify a system attribute. Of course
-	 * this is not a very good idea if this is a user query, but on the
-	 * other hand the rule manager uses this mechanism to replace rule
-	 * locks.
-	 *
-	 * So, copy all these entries to the end of the target list and set their
-	 * 'resjunk' value to true to show that these are special attributes
-	 * and have to be treated specially by the executor!
+	 * Scan the tuple description in the relation's relcache entry to make
+	 * sure we have all the user attributes in the right order.
 	 */
-	foreach(temp, old_tlist)
-	{
-		TargetEntry *old_tle,
-				   *new_tl;
+	rel = heap_open(getrelid(result_relation, range_table), AccessShareLock);
 
-		old_tle = lfirst(temp);
-		if (old_tle->resdom->resno < 0)
-		{
-			Resdom	   *newresdom;
+	numattrs = RelationGetNumberOfAttributes(rel);
+
+	for (attrno = 1; attrno <= numattrs; attrno++)
+	{
+		Form_pg_attribute att_tup = rel->rd_att->attrs[attrno-1];
+		char		   *attrname = att_tup->attname.data;
+		TargetEntry	   *new_tle = NULL;
 
-			newresdom = (Resdom *) copyObject((Node *) old_tle->resdom);
-			newresdom->resno = length(t_list) + 1;
-			newresdom->resjunk = true;
-			new_tl = makeTargetEntry(newresdom, old_tle->expr);
-			t_list = lappend(t_list, new_tl);
-		}
 		/*
-		 * Also it is possible that the parser or rewriter added some junk
-		 * attributes to hold ORDER/GROUP BY expressions which are not part of
-		 * the result attributes. We can simply identify them by looking
-		 * at the ressortgroupref in the TLE's resdom, which is a unique
-		 * number telling which TLE belongs to which Sort/GroupClause.
+		 * We match targetlist entries to attributes using the resname.
 		 */
-		else if (old_tle->resdom->ressortgroupref > 0)
+		old_tlist_index = 0;
+		foreach(temp, tlist)
 		{
-			bool		already_there = false;
+			TargetEntry	   *old_tle = (TargetEntry *) lfirst(temp);
+			Resdom		   *resdom = old_tle->resdom;
 
-			/*
-			 * Check if the tle is already in the new list
-			 */
-			foreach(i, t_list)
+			if (! tlistentry_used[old_tlist_index] &&
+				strcmp(resdom->resname, attrname) == 0 &&
+				! resdom->resjunk)
 			{
-				TargetEntry *new_tle = (TargetEntry *) lfirst(i);
-
-				if (new_tle->resdom->ressortgroupref ==
-					old_tle->resdom->ressortgroupref)
+				/*
+				 * We can recycle the old TLE+resdom if right resno; else
+				 * make a new one to avoid modifying the old tlist structure.
+				 * (Is preserving old tlist actually necessary?)
+				 */
+				if (resdom->resno == attrno)
 				{
-					already_there = true;
-					break;
+					new_tle = old_tle;
 				}
+				else
+				{
+					resdom = (Resdom *) copyObject((Node *) resdom);
+					resdom->resno = attrno;
+					new_tle = makeTargetEntry(resdom, old_tle->expr);
+				}
+				tlistentry_used[old_tlist_index] = true;
+				break;
 			}
-
-			/*
-			 * If not, add it and make sure it is now a junk attribute
-			 */
-			if (!already_there)
-			{
-				Resdom	   *newresdom;
-
-				newresdom = (Resdom *) copyObject((Node *) old_tle->resdom);
-				newresdom->resno = length(t_list) + 1;
-				newresdom->resjunk = true;
-				new_tl = makeTargetEntry(newresdom, old_tle->expr);
-				t_list = lappend(t_list, new_tl);
-			}
+			old_tlist_index++;
 		}
-	}
 
-	return t_list;
-}
-
-/*
- * new_relation_targetlist
- *	  Generate a targetlist for the relation with relation OID 'relid'
- *	  and rangetable index 'rt_index'.
- *
- *	  Returns the new targetlist.
- */
-static List *
-new_relation_targetlist(Oid relid, Index rt_index, NodeTag node_type)
-{
-	List	   *t_list = NIL;
-	int			natts = get_relnatts(relid);
-	AttrNumber	attno;
-
-	for (attno = 1; attno <= natts; attno++)
-	{
-		HeapTuple	tp;
-		Form_pg_attribute att_tup;
-		char	   *attname;
-		Oid			atttype;
-		int32		atttypmod;
-		bool		attisset;
-
-		tp = SearchSysCacheTuple(ATTNUM,
-								 ObjectIdGetDatum(relid),
-								 UInt16GetDatum(attno),
-								 0, 0);
-		if (! HeapTupleIsValid(tp))
+		if (new_tle == NULL)
 		{
-			elog(ERROR, "new_relation_targetlist: no attribute tuple %u %d",
-				 relid, attno);
-		}
-		att_tup = (Form_pg_attribute) GETSTRUCT(tp);
-		attname = pstrdup(att_tup->attname.data);
-		atttype = att_tup->atttypid;
-		atttypmod = att_tup->atttypmod;
-		attisset = att_tup->attisset;
+			/*
+			 * Didn't find a matching tlist entry, so make one.
+			 *
+			 * For INSERT, generate a constant of the default value for
+			 * the attribute type, or NULL if no default value.
+			 *
+			 * For UPDATE, generate a Var reference to the existing value
+			 * of the attribute, so that it gets copied to the new tuple.
+			 */
+			Oid			atttype = att_tup->atttypid;
+			int32		atttypmod = att_tup->atttypmod;
 
-		switch (node_type)
-		{
-			case T_Const:		/* INSERT command */
+			switch (command_type)
+			{
+				case CMD_INSERT:
 				{
 					Datum		typedefault = get_typdefault(atttype);
 					int			typlen;
 					Const	   *temp_const;
-					TargetEntry *temp_tle;
 
 					if (typedefault == PointerGetDatum(NULL))
 						typlen = 0;
@@ -308,7 +201,7 @@ new_relation_targetlist(Oid relid, Index rt_index, NodeTag node_type)
 						 * any set attribute is the size of the OID used to
 						 * represent it.
 						 */
-						if (attisset)
+						if (att_tup->attisset)
 							typlen = get_typlen(OIDOID);
 						else
 							typlen = get_typlen(atttype);
@@ -322,40 +215,69 @@ new_relation_targetlist(Oid relid, Index rt_index, NodeTag node_type)
 										   false, /* not a set */
 										   false);
 
-					temp_tle = makeTargetEntry(makeResdom(attno,
-														  atttype,
-														  -1,
-														  attname,
-														  0,
-														  (Oid) 0,
-														  false),
-											   (Node *) temp_const);
-					t_list = lappend(t_list, temp_tle);
+					new_tle = makeTargetEntry(makeResdom(attrno,
+														 atttype,
+														 -1,
+														 pstrdup(attrname),
+														 0,
+														 (Oid) 0,
+														 false),
+											  (Node *) temp_const);
 					break;
 				}
-			case T_Var:			/* UPDATE command */
+				case CMD_UPDATE:
 				{
 					Var		   *temp_var;
-					TargetEntry *temp_tle;
 
-					temp_var = makeVar(rt_index, attno, atttype,
+					temp_var = makeVar(result_relation, attrno, atttype,
 									   atttypmod, 0);
 
-					temp_tle = makeTargetEntry(makeResdom(attno,
-														  atttype,
-														  atttypmod,
-														  attname,
-														  0,
-														  (Oid) 0,
-														  false),
-											   (Node *) temp_var);
-					t_list = lappend(t_list, temp_tle);
+					new_tle = makeTargetEntry(makeResdom(attrno,
+														 atttype,
+														 atttypmod,
+														 pstrdup(attrname),
+														 0,
+														 (Oid) 0,
+														 false),
+											  (Node *) temp_var);
 					break;
 				}
-			default:			/* do nothing */
-				break;
+				default:
+					elog(ERROR, "expand_targetlist: unexpected command_type");
+					break;
+			}
+		}
+
+		new_tlist = lappend(new_tlist, new_tle);
+	}
+
+	/*
+	 * Copy all unprocessed tlist entries to the end of the new tlist,
+	 * making sure they are marked resjunk = true.  Typical junk entries
+	 * include ORDER BY or GROUP BY expressions (are these actually possible
+	 * in an INSERT or UPDATE?), system attribute references, etc.
+	 */
+	old_tlist_index = 0;
+	foreach(temp, tlist)
+	{
+		TargetEntry	   *old_tle = (TargetEntry *) lfirst(temp);
+
+		if (! tlistentry_used[old_tlist_index])
+		{
+			Resdom		   *resdom;
+
+			resdom = (Resdom *) copyObject((Node *) old_tle->resdom);
+			resdom->resno = attrno++;
+			resdom->resjunk = true;
+			new_tlist = lappend(new_tlist,
+								makeTargetEntry(resdom, old_tle->expr));
 		}
+		old_tlist_index++;
 	}
 
-	return t_list;
+	heap_close(rel, AccessShareLock);
+
+	pfree(tlistentry_used);
+
+	return new_tlist;
 }