diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index cc26e8eb7667190af007301d059913fa4a3d734b..844ab38e83951523794403c571fb7172ce0d793c 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.148 2005/03/10 23:21:24 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.149 2005/03/26 05:53:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -287,44 +287,83 @@ static void
 rewriteTargetList(Query *parsetree, Relation target_relation)
 {
 	CmdType		commandType = parsetree->commandType;
-	List	   *tlist = parsetree->targetList;
+	TargetEntry **new_tles;
 	List	   *new_tlist = NIL;
+	List	   *junk_tlist = NIL;
+	Form_pg_attribute att_tup;
 	int			attrno,
+				next_junk_attrno,
 				numattrs;
 	ListCell   *temp;
 
 	/*
-	 * Scan the tuple description in the relation's relcache entry to make
-	 * sure we have all the user attributes in the right order.
+	 * We process the normal (non-junk) attributes by scanning the input
+	 * tlist once and transferring TLEs into an array, then scanning the
+	 * array to build an output tlist.  This avoids O(N^2) behavior for
+	 * large numbers of attributes.
+	 *
+	 * Junk attributes are tossed into a separate list during the same
+	 * tlist scan, then appended to the reconstructed tlist.
 	 */
 	numattrs = RelationGetNumberOfAttributes(target_relation);
+	new_tles = (TargetEntry **) palloc0(numattrs * sizeof(TargetEntry *));
+	next_junk_attrno = numattrs + 1;
 
-	for (attrno = 1; attrno <= numattrs; attrno++)
+	foreach(temp, parsetree->targetList)
 	{
-		Form_pg_attribute att_tup = target_relation->rd_att->attrs[attrno - 1];
-		TargetEntry *new_tle = NULL;
+		TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
+		Resdom	   *resdom = old_tle->resdom;
 
-		/* We can ignore deleted attributes */
-		if (att_tup->attisdropped)
-			continue;
+		if (!resdom->resjunk)
+		{
+			/* Normal attr: stash it into new_tles[] */
+			attrno = resdom->resno;
+			if (attrno < 1 || attrno > numattrs)
+				elog(ERROR, "bogus resno %d in targetlist", attrno);
+			att_tup = target_relation->rd_att->attrs[attrno - 1];
+
+			/* We can (and must) ignore deleted attributes */
+			if (att_tup->attisdropped)
+				continue;
 
-		/*
-		 * Look for targetlist entries matching this attr.
-		 *
-		 * Junk attributes are not candidates to be matched.
-		 */
-		foreach(temp, tlist)
+			/* Merge with any prior assignment to same attribute */
+			new_tles[attrno - 1] =
+				process_matched_tle(old_tle,
+									new_tles[attrno - 1],
+									NameStr(att_tup->attname));
+		}
+		else
 		{
-			TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
-			Resdom	   *resdom = old_tle->resdom;
+			/*
+			 * Copy all resjunk tlist entries to junk_tlist, and
+			 * assign them resnos above the last real resno.
+			 *
+			 * Typical junk entries include ORDER BY or GROUP BY expressions
+			 * (are these actually possible in an INSERT or UPDATE?), system
+			 * attribute references, etc.
+			 */
 
-			if (!resdom->resjunk && resdom->resno == attrno)
+			/* Get the resno right, but don't copy unnecessarily */
+			if (resdom->resno != next_junk_attrno)
 			{
-				new_tle = process_matched_tle(old_tle, new_tle,
-											  NameStr(att_tup->attname));
-				/* keep scanning to detect multiple assignments to attr */
+				resdom = (Resdom *) copyObject((Node *) resdom);
+				resdom->resno = next_junk_attrno;
+				old_tle = makeTargetEntry(resdom, old_tle->expr);
 			}
+			junk_tlist = lappend(junk_tlist, old_tle);
+			next_junk_attrno++;
 		}
+	}
+
+	for (attrno = 1; attrno <= numattrs; attrno++)
+	{
+		TargetEntry *new_tle = new_tles[attrno - 1];
+
+		att_tup = target_relation->rd_att->attrs[attrno - 1];
+
+		/* We can (and must) ignore deleted attributes */
+		if (att_tup->attisdropped)
+			continue;
 
 		/*
 		 * Handle the two cases where we need to insert a default
@@ -332,7 +371,7 @@ rewriteTargetList(Query *parsetree, Relation target_relation)
 		 * column, or the tlist entry is a DEFAULT placeholder node.
 		 */
 		if ((new_tle == NULL && commandType == CMD_INSERT) ||
-		  (new_tle && new_tle->expr && IsA(new_tle->expr, SetToDefault)))
+			(new_tle && new_tle->expr && IsA(new_tle->expr, SetToDefault)))
 		{
 			Node	   *new_expr;
 
@@ -380,40 +419,9 @@ rewriteTargetList(Query *parsetree, Relation target_relation)
 			new_tlist = lappend(new_tlist, new_tle);
 	}
 
-	/*
-	 * Copy all resjunk tlist entries to the end of the new tlist, and
-	 * assign them resnos above the last real resno.
-	 *
-	 * Typical junk entries include ORDER BY or GROUP BY expressions (are
-	 * these actually possible in an INSERT or UPDATE?), system attribute
-	 * references, etc.
-	 */
-	foreach(temp, tlist)
-	{
-		TargetEntry *old_tle = (TargetEntry *) lfirst(temp);
-		Resdom	   *resdom = old_tle->resdom;
-
-		if (resdom->resjunk)
-		{
-			/* Get the resno right, but don't copy unnecessarily */
-			if (resdom->resno != attrno)
-			{
-				resdom = (Resdom *) copyObject((Node *) resdom);
-				resdom->resno = attrno;
-				old_tle = makeTargetEntry(resdom, old_tle->expr);
-			}
-			new_tlist = lappend(new_tlist, old_tle);
-			attrno++;
-		}
-		else
-		{
-			/* Let's just make sure we processed all the non-junk items */
-			if (resdom->resno < 1 || resdom->resno > numattrs)
-				elog(ERROR, "bogus resno %d in targetlist", resdom->resno);
-		}
-	}
+	pfree(new_tles);
 
-	parsetree->targetList = new_tlist;
+	parsetree->targetList = list_concat(new_tlist, junk_tlist);
 }