diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index c67f7f35436827a0bfc0038976b88b66214fa6c3..f0aa9042e02b6ddd10c8fdf8174b41b606b260a0 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.159 2001/02/12 20:07:21 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/catalog/heap.c,v 1.160 2001/02/14 21:34:59 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -1533,7 +1533,6 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, char *adbin,
 				 bool updatePgAttribute)
 {
 	Node	   *expr;
-	RangeTblEntry *rte;
 	char	   *adsrc;
 	Relation	adrel;
 	Relation	idescs[Num_pg_attrdef_indices];
@@ -1551,16 +1550,12 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, char *adbin,
 	expr = stringToNode(adbin);
 
 	/*
-	 * deparse_expression needs a RangeTblEntry list, so make one
+	 * deparse it
 	 */
-	rte = makeNode(RangeTblEntry);
-	rte->relname = RelationGetRelationName(rel);
-	rte->relid = RelationGetRelid(rel);
-	rte->eref = makeNode(Attr);
-	rte->eref->relname = RelationGetRelationName(rel);
-	rte->inh = false;
-	rte->inFromCl = true;
-	adsrc = deparse_expression(expr, makeList1(makeList1(rte)), false);
+	adsrc = deparse_expression(expr,
+							   deparse_context_for(RelationGetRelationName(rel),
+												   RelationGetRelid(rel)),
+							   false);
 
 	values[Anum_pg_attrdef_adrelid - 1] = RelationGetRelid(rel);
 	values[Anum_pg_attrdef_adnum - 1] = attnum;
@@ -1619,7 +1614,6 @@ static void
 StoreRelCheck(Relation rel, char *ccname, char *ccbin)
 {
 	Node	   *expr;
-	RangeTblEntry *rte;
 	char	   *ccsrc;
 	Relation	rcrel;
 	Relation	idescs[Num_pg_relcheck_indices];
@@ -1634,16 +1628,12 @@ StoreRelCheck(Relation rel, char *ccname, char *ccbin)
 	expr = (Node *) make_ands_explicit((List *) expr);
 
 	/*
-	 * deparse_expression needs a RangeTblEntry list, so make one
+	 * deparse it
 	 */
-	rte = makeNode(RangeTblEntry);
-	rte->relname = RelationGetRelationName(rel);
-	rte->relid = RelationGetRelid(rel);
-	rte->eref = makeNode(Attr);
-	rte->eref->relname = RelationGetRelationName(rel);
-	rte->inh = false;
-	rte->inFromCl = true;
-	ccsrc = deparse_expression(expr, makeList1(makeList1(rte)), false);
+	ccsrc = deparse_expression(expr,
+							   deparse_context_for(RelationGetRelationName(rel),
+												   RelationGetRelid(rel)),
+							   false);
 
 	values[Anum_pg_relcheck_rcrelid - 1] = RelationGetRelid(rel);
 	values[Anum_pg_relcheck_rcname - 1] = DirectFunctionCall1(namein,
@@ -1764,9 +1754,8 @@ AddRelationRawConstraints(Relation rel,
 	 * sole rangetable entry.  We need a ParseState for transformExpr.
 	 */
 	pstate = make_parsestate(NULL);
-	makeRangeTable(pstate, NULL);
 	rte = addRangeTableEntry(pstate, relname, NULL, false, true);
-	addRTEtoJoinList(pstate, rte);
+	addRTEtoQuery(pstate, rte, true, true);
 
 	/*
 	 * Process column default expressions.
diff --git a/src/backend/commands/command.c b/src/backend/commands/command.c
index 30695a7a90bd4cace0ec01611233ef165baef987..8808a03f1acb41361d687a487c40ac5d0b31ae08 100644
--- a/src/backend/commands/command.c
+++ b/src/backend/commands/command.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.120 2001/01/29 00:39:20 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.121 2001/02/14 21:35:00 tgl Exp $
  *
  * NOTES
  *	  The PerformAddAttribute() code, like most of the relation
@@ -1136,10 +1136,9 @@ AlterTableAddConstraint(char *relationName,
 					 * the expression we can pass to ExecQual
 					 */
 					pstate = make_parsestate(NULL);
-					makeRangeTable(pstate, NULL);
 					rte = addRangeTableEntry(pstate, relationName, NULL,
 											 false, true);
-					addRTEtoJoinList(pstate, rte);
+					addRTEtoQuery(pstate, rte, true, true);
 
 					/* Convert the A_EXPR in raw_expr into an EXPR */
 					expr = transformExpr(pstate, constr->raw_expr,
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index b1ccda71bf709301c571628d91e214f6f434e61a..11ceae19a689ae02b3a03b2ec684bb31e6fbdc9f 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Id: analyze.c,v 1.178 2001/01/27 07:23:48 tgl Exp $
+ *	$Id: analyze.c,v 1.179 2001/02/14 21:35:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -257,11 +257,10 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 
 	qry->commandType = CMD_DELETE;
 
-	/* set up a range table */
-	lockTargetTable(pstate, stmt->relname);
-	makeRangeTable(pstate, NIL);
-	setTargetTable(pstate, stmt->relname,
-				   interpretInhOption(stmt->inhOpt), true);
+	/* set up range table with just the result rel */
+	qry->resultRelation = setTargetTable(pstate, stmt->relname,
+										 interpretInhOption(stmt->inhOpt),
+										 true);
 
 	qry->distinctClause = NIL;
 
@@ -271,7 +270,6 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
-	qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasAggs = pstate->p_hasAggs;
@@ -289,6 +287,8 @@ static Query *
 transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 {
 	Query	   *qry = makeNode(Query);
+	List	   *sub_rtable;
+	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
 	List	   *attnos;
@@ -300,11 +300,35 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	pstate->p_is_insert = true;
 
 	/*
-	 * Must get write lock on target table before scanning SELECT,
+	 * If a non-nil rangetable/namespace was passed in, and we are doing
+	 * INSERT/SELECT, arrange to pass the rangetable/namespace down to the
+	 * SELECT.  This can only happen if we are inside a CREATE RULE,
+	 * and in that case we want the rule's OLD and NEW rtable entries to
+	 * appear as part of the SELECT's rtable, not as outer references for
+	 * it.  (Kluge!)  The SELECT's joinlist is not affected however.
+	 * We must do this before adding the target table to the INSERT's rtable.
+	 */
+	if (stmt->selectStmt)
+	{
+		sub_rtable = pstate->p_rtable;
+		pstate->p_rtable = NIL;
+		sub_namespace = pstate->p_namespace;
+		pstate->p_namespace = NIL;
+	}
+	else
+	{
+		sub_rtable = NIL;		/* not used, but keep compiler quiet */
+		sub_namespace = NIL;
+	}
+
+	/*
+	 * Must get write lock on INSERT target table before scanning SELECT,
 	 * else we will grab the wrong kind of initial lock if the target
-	 * table is also mentioned in the SELECT part.
+	 * table is also mentioned in the SELECT part.  Note that the target
+	 * table is not added to the joinlist or namespace.
 	 */
-	lockTargetTable(pstate, stmt->relname);
+	qry->resultRelation = setTargetTable(pstate, stmt->relname,
+										 false, false);
 
 	/*
 	 * Is it INSERT ... SELECT or INSERT ... VALUES?
@@ -323,15 +347,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * otherwise the behavior of SELECT within INSERT might be different
 		 * from a stand-alone SELECT. (Indeed, Postgres up through 6.5 had
 		 * bugs of just that nature...)
-		 *
-		 * If a non-nil rangetable was passed in, pass it down to the SELECT.
-		 * This can only happen if we are inside a CREATE RULE, and in that
-		 * case we want the rule's OLD and NEW rtable entries to appear as
-		 * part of the SELECT's rtable, not as outer references for it.
 		 */
-		sub_pstate->p_rtable = pstate->p_rtable;
-		pstate->p_rtable = NIL;
+		sub_pstate->p_rtable = sub_rtable;
+		sub_pstate->p_namespace = sub_namespace;
+
 		selectQuery = transformStmt(sub_pstate, stmt->selectStmt);
+
 		release_pstate_resources(sub_pstate);
 		pfree(sub_pstate);
 
@@ -341,7 +362,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 			elog(ERROR, "INSERT ... SELECT may not specify INTO");
 		/*
 		 * Make the source be a subquery in the INSERT's rangetable,
-		 * and add it to the joinlist.
+		 * and add it to the INSERT's joinlist.
 		 */
 		rte = addRangeTableEntryForSubquery(pstate,
 											selectQuery,
@@ -400,13 +421,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	/*
 	 * Now we are done with SELECT-like processing, and can get on with
 	 * transforming the target list to match the INSERT target columns.
-	 *
-	 * In particular, it's time to add the INSERT target to the rangetable.
-	 * (We didn't want it there until now since it shouldn't be visible in
-	 * the SELECT part.)  Note that the INSERT target is NOT added to the
-	 * joinlist, since we don't want to join over it.
 	 */
-	setTargetTable(pstate, stmt->relname, false, false);
 
 	/* Prepare to assign non-conflicting resnos to resjunk attributes */
 	if (pstate->p_last_resno <= pstate->p_target_relation->rd_rel->relnatts)
@@ -495,7 +510,6 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	/* done building the range table and jointree */
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, NULL);
-	qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasAggs = pstate->p_hasAggs;
@@ -1565,27 +1579,27 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
 	oldrte->checkForRead = false;
 	newrte->checkForRead = false;
 	/*
-	 * They must be in the joinlist too for lookup purposes, but only add
+	 * They must be in the namespace too for lookup purposes, but only add
 	 * the one(s) that are relevant for the current kind of rule.  In an
 	 * UPDATE rule, quals must refer to OLD.field or NEW.field to be
 	 * unambiguous, but there's no need to be so picky for INSERT & DELETE.
 	 * (Note we marked the RTEs "inFromCl = true" above to allow unqualified
-	 * references to their fields.)
+	 * references to their fields.)  We do not add them to the joinlist.
 	 */
 	switch (stmt->event)
 	{
 		case CMD_SELECT:
-			addRTEtoJoinList(pstate, oldrte);
+			addRTEtoQuery(pstate, oldrte, false, true);
 			break;
 		case CMD_UPDATE:
-			addRTEtoJoinList(pstate, oldrte);
-			addRTEtoJoinList(pstate, newrte);
+			addRTEtoQuery(pstate, oldrte, false, true);
+			addRTEtoQuery(pstate, newrte, false, true);
 			break;
 		case CMD_INSERT:
-			addRTEtoJoinList(pstate, newrte);
+			addRTEtoQuery(pstate, newrte, false, true);
 			break;
 		case CMD_DELETE:
-			addRTEtoJoinList(pstate, oldrte);
+			addRTEtoQuery(pstate, oldrte, false, true);
 			break;
 		default:
 			elog(ERROR, "transformRuleStmt: unexpected event type %d",
@@ -1638,8 +1652,9 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
 			 * Set up OLD/NEW in the rtable for this statement.  The entries
 			 * are marked not inFromCl because we don't want them to be
 			 * referred to by unqualified field names nor "*" in the rule
-			 * actions.  We don't need to add them to the joinlist for
-			 * qualified-name lookup, either (see qualifiedNameToVar()).
+			 * actions.  We must add them to the namespace, however, or they
+			 * won't be accessible at all.  We decide later whether to put
+			 * them in the joinlist.
 			 */
 			oldrte = addRangeTableEntry(sub_pstate, stmt->object->relname,
 										makeAttr("*OLD*", NULL),
@@ -1649,6 +1664,8 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
 										false, false);
 			oldrte->checkForRead = false;
 			newrte->checkForRead = false;
+			addRTEtoQuery(sub_pstate, oldrte, false, true);
+			addRTEtoQuery(sub_pstate, newrte, false, true);
 
 			/* Transform the rule action statement */
 			top_subqry = transformStmt(sub_pstate, lfirst(actions));
@@ -1712,10 +1729,10 @@ transformRuleStmt(ParseState *pstate, RuleStmt *stmt)
 			 */
 			if (has_old || (has_new && stmt->event == CMD_UPDATE))
 			{
-				/* hack so we can use addRTEtoJoinList() */
+				/* hack so we can use addRTEtoQuery() */
 				sub_pstate->p_rtable = sub_qry->rtable;
 				sub_pstate->p_joinlist = sub_qry->jointree->fromlist;
-				addRTEtoJoinList(sub_pstate, oldrte);
+				addRTEtoQuery(sub_pstate, oldrte, true, false);
 				sub_qry->jointree->fromlist = sub_pstate->p_joinlist;
 			}
 
@@ -1779,8 +1796,8 @@ transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
 	/* make FOR UPDATE clause available to addRangeTableEntry */
 	pstate->p_forUpdate = stmt->forUpdate;
 
-	/* set up a range table */
-	makeRangeTable(pstate, stmt->fromClause);
+	/* process the FROM clause */
+	transformFromClause(pstate, stmt->fromClause);
 
 	/* transform targetlist and WHERE */
 	qry->targetList = transformTargetList(pstate, stmt->targetList);
@@ -2055,7 +2072,6 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
 	if (isLeaf)
 	{
 		/* Process leaf SELECT */
-		List   *save_rtable;
 		List   *selectList;
 		Query  *selectQuery;
 		char	selectName[32];
@@ -2063,16 +2079,13 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt)
 		RangeTblRef *rtr;
 
 		/*
-		 * Transform SelectStmt into a Query.  We do not want any previously
-		 * transformed leaf queries to be visible in the outer context of
-		 * this sub-query, so temporarily make the top-level pstate have an
-		 * empty rtable.  (We needn't do the same with the joinlist because
-		 * we aren't entering anything in the top-level joinlist.)
+		 * Transform SelectStmt into a Query.
+		 *
+		 * Note: previously transformed sub-queries don't affect the parsing
+		 * of this sub-query, because they are not in the toplevel pstate's
+		 * namespace list.
 		 */
-		save_rtable = pstate->p_rtable;
-		pstate->p_rtable = NIL;
 		selectList = parse_analyze((Node *) stmt, pstate);
-		pstate->p_rtable = save_rtable;
 
 		Assert(length(selectList) == 1);
 		selectQuery = (Query *) lfirst(selectList);
@@ -2202,19 +2215,15 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 	qry->commandType = CMD_UPDATE;
 	pstate->p_is_update = true;
 
+	qry->resultRelation = setTargetTable(pstate, stmt->relname,
+										 interpretInhOption(stmt->inhOpt),
+										 true);
+
 	/*
 	 * the FROM clause is non-standard SQL syntax. We used to be able to
 	 * do this with REPLACE in POSTQUEL so we keep the feature.
-	 *
-	 * Note: it's critical here that we process FROM before adding the
-	 * target table to the rtable --- otherwise, if the target is also
-	 * used in FROM, we'd fail to notice that it should be marked
-	 * checkForRead as well as checkForWrite.  See setTargetTable().
 	 */
-	lockTargetTable(pstate, stmt->relname);
-	makeRangeTable(pstate, stmt->fromClause);
-	setTargetTable(pstate, stmt->relname,
-				   interpretInhOption(stmt->inhOpt), true);
+	transformFromClause(pstate, stmt->fromClause);
 
 	qry->targetList = transformTargetList(pstate, stmt->targetList);
 
@@ -2222,7 +2231,6 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 
 	qry->rtable = pstate->p_rtable;
 	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
-	qry->resultRelation = refnameRangeTablePosn(pstate, stmt->relname, NULL);
 
 	qry->hasSubLinks = pstate->p_hasSubLinks;
 	qry->hasAggs = pstate->p_hasAggs;
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 0f3f9d23028aafa7df49e42a416976cad188ff4c..266b6da75b56e543d45b6137398fc9df6754a880 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.75 2001/01/24 19:43:01 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_clause.c,v 1.76 2001/02/14 21:35:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -58,26 +58,30 @@ static bool exprIsInSortList(Node *expr, List *sortList, List *targetList);
 
 
 /*
- * makeRangeTable -
- *	  Build the initial range table from the FROM clause.
+ * transformFromClause -
+ *	  Process the FROM clause and add items to the query's range table,
+ *	  joinlist, and namespace.
  *
- * The range table constructed here may grow as we transform the expressions
- * in the query's quals and target list. (Note that this happens because in
- * POSTQUEL, we allow references to relations not specified in the
- * from-clause.  PostgreSQL keeps this extension to standard SQL.)
+ * Note: we assume that pstate's p_rtable, p_joinlist, and p_namespace lists
+ * were initialized to NIL when the pstate was created.  We will add onto
+ * any entries already present --- this is needed for rule processing, as
+ * well as for UPDATE and DELETE.
  *
- * Note: we assume that pstate's p_rtable and p_joinlist lists were
- * initialized to NIL when the pstate was created.  We will add onto
- * any entries already present --- this is needed for rule processing!
+ * The range table may grow still further when we transform the expressions
+ * in the query's quals and target list. (This is possible because in
+ * POSTQUEL, we allowed references to relations not specified in the
+ * from-clause.  PostgreSQL keeps this extension to standard SQL.)
  */
 void
-makeRangeTable(ParseState *pstate, List *frmList)
+transformFromClause(ParseState *pstate, List *frmList)
 {
 	List	   *fl;
 
 	/*
 	 * The grammar will have produced a list of RangeVars, RangeSubselects,
-	 * and/or JoinExprs. Transform each one, and then add it to the joinlist.
+	 * and/or JoinExprs. Transform each one (possibly adding entries to the
+	 * rtable), check for duplicate refnames, and then add it to the joinlist
+	 * and namespace.
 	 */
 	foreach(fl, frmList)
 	{
@@ -85,27 +89,41 @@ makeRangeTable(ParseState *pstate, List *frmList)
 		List	   *containedRels;
 
 		n = transformFromClauseItem(pstate, n, &containedRels);
+		checkNameSpaceConflicts(pstate, (Node *) pstate->p_namespace, n);
 		pstate->p_joinlist = lappend(pstate->p_joinlist, n);
+		pstate->p_namespace = lappend(pstate->p_namespace, n);
 	}
 }
 
 /*
- * lockTargetTable
- *	  Find the target relation of INSERT/UPDATE/DELETE and acquire write
- *	  lock on it.  This must be done before building the range table,
- *	  in case the target is also mentioned as a source relation --- we
- *	  want to be sure to grab the write lock before any read lock.
+ * setTargetTable
+ *	  Add the target relation of INSERT/UPDATE/DELETE to the range table,
+ *	  and make the special links to it in the ParseState.
+ *
+ *	  We also open the target relation and acquire a write lock on it.
+ *	  This must be done before processing the FROM list, in case the target
+ *	  is also mentioned as a source relation --- we want to be sure to grab
+ *	  the write lock before any read lock.
  *
- * The ParseState's link to the target relcache entry is also set here.
+ *	  If alsoSource is true, add the target to the query's joinlist and
+ *	  namespace.  For INSERT, we don't want the target to be joined to;
+ *	  it's a destination of tuples, not a source.	For UPDATE/DELETE,
+ *	  we do need to scan or join the target.  (NOTE: we do not bother
+ *	  to check for namespace conflict; we assume that the namespace was
+ *	  initially empty in these cases.)
+ *
+ *	  Returns the rangetable index of the target relation.
  */
-void
-lockTargetTable(ParseState *pstate, char *relname)
+int
+setTargetTable(ParseState *pstate, char *relname,
+			   bool inh, bool alsoSource)
 {
+	RangeTblEntry *rte;
+	int			rtindex;
+
 	/* Close old target; this could only happen for multi-action rules */
 	if (pstate->p_target_relation != NULL)
 		heap_close(pstate->p_target_relation, NoLock);
-	pstate->p_target_relation = NULL;
-	pstate->p_target_rangetblentry = NULL; /* setTargetTable will set this */
 
 	/*
 	 * Open target rel and grab suitable lock (which we will hold till
@@ -115,62 +133,36 @@ lockTargetTable(ParseState *pstate, char *relname)
 	 * but *not* release the lock.
 	 */
 	pstate->p_target_relation = heap_openr(relname, RowExclusiveLock);
-}
 
-/*
- * setTargetTable
- *	  Add the target relation of INSERT/UPDATE/DELETE to the range table,
- *	  and make the special links to it in the ParseState.
- *
- *	  inJoinSet says whether to add the target to the join list.
- *	  For INSERT, we don't want the target to be joined to; it's a
- *	  destination of tuples, not a source.	For UPDATE/DELETE, we do
- *	  need to scan or join the target.
- */
-void
-setTargetTable(ParseState *pstate, char *relname, bool inh, bool inJoinSet)
-{
-	RangeTblEntry *rte;
+	/*
+	 * Now build an RTE.
+	 */
+	rte = addRangeTableEntry(pstate, relname, NULL, inh, false);
+	pstate->p_target_rangetblentry = rte;
 
-	/* look for relname only at current nesting level... */
-	if (refnameRangeTablePosn(pstate, relname, NULL) == 0)
-	{
-		rte = addRangeTableEntry(pstate, relname, NULL, inh, false);
-		/*
-		 * Since the rel wasn't in the rangetable already, it's not being
-		 * read; override addRangeTableEntry's default checkForRead.
-		 *
-		 * If we find an explicit reference to the rel later during
-		 * parse analysis, scanRTEForColumn will change checkForRead
-		 * to 'true' again.  That can't happen for INSERT but it is
-		 * possible for UPDATE and DELETE.
-		 */
-		rte->checkForRead = false;
-	}
-	else
-	{
-		rte = refnameRangeTableEntry(pstate, relname);
-		/*
-		 * Since the rel was in the rangetable already, it's being read
-		 * as well as written.  Therefore, leave checkForRead true.
-		 *
-		 * Force inh to the desired setting for the target (XXX is this
-		 * reasonable?  It's *necessary* that INSERT target not be marked
-		 * inheritable, but otherwise not too clear what to do if conflict?)
-		 */
-		rte->inh = inh;
-	}
+	/* assume new rte is at end */
+	rtindex = length(pstate->p_rtable);
+	Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
 
-	/* Mark target table as requiring write access. */
+	/*
+	 * Override addRangeTableEntry's default checkForRead, and instead
+	 * mark target table as requiring write access.
+	 *
+	 * If we find an explicit reference to the rel later during
+	 * parse analysis, scanRTEForColumn will change checkForRead
+	 * to 'true' again.  That can't happen for INSERT but it is
+	 * possible for UPDATE and DELETE.
+	 */
+	rte->checkForRead = false;
 	rte->checkForWrite = true;
 
-	if (inJoinSet)
-		addRTEtoJoinList(pstate, rte);
-
-	/* lockTargetTable should have been called earlier */
-	Assert(pstate->p_target_relation != NULL);
+	/*
+	 * If UPDATE/DELETE, add table to joinlist and namespace.
+	 */
+	if (alsoSource)
+		addRTEtoQuery(pstate, rte, true, true);
 
-	pstate->p_target_rangetblentry = rte;
+	return rtindex;
 }
 
 /*
@@ -313,22 +305,21 @@ transformJoinOnClause(ParseState *pstate, JoinExpr *j,
 					  List *containedRels)
 {
 	Node	   *result;
-	List	   *sv_joinlist;
+	List	   *save_namespace;
 	List	   *clause_varnos,
 			   *l;
 
 	/*
-	 * This is a tad tricky, for two reasons.  First, at the point where
-	 * we're called, the two subtrees of the JOIN node aren't yet part of
-	 * the pstate's joinlist, which means that transformExpr() won't resolve
-	 * unqualified references to their columns correctly.  We fix this in a
-	 * slightly klugy way: temporarily make the pstate's joinlist consist of
-	 * just those two subtrees (which creates exactly the namespace the ON
-	 * clause should see).  This is OK only because the ON clause can't
-	 * legally alter the joinlist by causing relation refs to be added.
+	 * This is a tad tricky, for two reasons.  First, the namespace that
+	 * the join expression should see is just the two subtrees of the JOIN
+	 * plus any outer references from upper pstate levels.  So, temporarily
+	 * set this pstate's namespace accordingly.  (We need not check for
+	 * refname conflicts, because transformFromClauseItem() already did.)
+	 * NOTE: this code is OK only because the ON clause can't legally alter
+	 * the namespace by causing implicit relation refs to be added.
 	 */
-	sv_joinlist = pstate->p_joinlist;
-	pstate->p_joinlist = makeList2(j->larg, j->rarg);
+	save_namespace = pstate->p_namespace;
+	pstate->p_namespace = makeList2(j->larg, j->rarg);
 
 	/* This part is just like transformWhereClause() */
 	result = transformExpr(pstate, j->quals, EXPR_COLUMN_FIRST);
@@ -338,14 +329,14 @@ transformJoinOnClause(ParseState *pstate, JoinExpr *j,
 			 typeidTypeName(exprType(result)));
 	}
 
-	pstate->p_joinlist = sv_joinlist;
+	pstate->p_namespace = save_namespace;
 
 	/*
 	 * Second, we need to check that the ON condition doesn't refer to any
 	 * rels outside the input subtrees of the JOIN.  It could do that despite
-	 * our hack on the joinlist if it uses fully-qualified names.  So, grovel
+	 * our hack on the namespace if it uses fully-qualified names.  So, grovel
 	 * through the transformed clause and make sure there are no bogus
-	 * references.
+	 * references.  (Outer references are OK, and are ignored here.)
 	 */
 	clause_varnos = pull_varnos(result);
 	foreach(l, clause_varnos)
@@ -384,8 +375,8 @@ transformTableEntry(ParseState *pstate, RangeVar *r)
 							 interpretInhOption(r->inhOpt), true);
 
 	/*
-	 * We create a RangeTblRef, but we do not add it to the joinlist here.
-	 * makeRangeTable will do so, if we are at top level of the FROM clause.
+	 * We create a RangeTblRef, but we do not add it to the joinlist or
+	 * namespace; our caller must do that if appropriate.
 	 */
 	rtr = makeNode(RangeTblRef);
 	/* assume new rte is at end */
@@ -402,8 +393,7 @@ transformTableEntry(ParseState *pstate, RangeVar *r)
 static RangeTblRef *
 transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 {
-	List	   *save_rtable;
-	List	   *save_joinlist;
+	List	   *save_namespace;
 	List	   *parsetrees;
 	Query	   *query;
 	RangeTblEntry *rte;
@@ -424,15 +414,12 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 	 * does not include other FROM items).  But it does need to be able to
 	 * see any further-up parent states, so we can't just pass a null parent
 	 * pstate link.  So, temporarily make the current query level have an
-	 * empty rtable and joinlist.
+	 * empty namespace.
 	 */
-	save_rtable = pstate->p_rtable;
-	save_joinlist = pstate->p_joinlist;
-	pstate->p_rtable = NIL;
-	pstate->p_joinlist = NIL;
+	save_namespace = pstate->p_namespace;
+	pstate->p_namespace = NIL;
 	parsetrees = parse_analyze(r->subquery, pstate);
-	pstate->p_rtable = save_rtable;
-	pstate->p_joinlist = save_joinlist;
+	pstate->p_namespace = save_namespace;
 
 	/*
 	 * Check that we got something reasonable.  Some of these conditions
@@ -456,8 +443,8 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 	rte = addRangeTableEntryForSubquery(pstate, query, r->name, true);
 
 	/*
-	 * We create a RangeTblRef, but we do not add it to the joinlist here.
-	 * makeRangeTable will do so, if we are at top level of the FROM clause.
+	 * We create a RangeTblRef, but we do not add it to the joinlist or
+	 * namespace; our caller must do that if appropriate.
 	 */
 	rtr = makeNode(RangeTblRef);
 	/* assume new rte is at end */
@@ -472,7 +459,7 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
  * transformFromClauseItem -
  *	  Transform a FROM-clause item, adding any required entries to the
  *	  range table list being built in the ParseState, and return the
- *	  transformed item ready to include in the joinlist.
+ *	  transformed item ready to include in the joinlist and namespace.
  *	  This routine can recurse to handle SQL92 JOIN expressions.
  *
  *	  Aside from the primary return value (the transformed joinlist item)
@@ -525,6 +512,13 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 		 */
 		*containedRels = nconc(l_containedRels, r_containedRels);
 
+		/*
+		 * Check for conflicting refnames in left and right subtrees.  Must
+		 * do this because higher levels will assume I hand back a self-
+		 * consistent namespace subtree.
+		 */
+		checkNameSpaceConflicts(pstate, j->larg, j->rarg);
+
 		/*
 		 * Extract column name and var lists from both subtrees
 		 */
@@ -733,23 +727,9 @@ transformFromClauseItem(ParseState *pstate, Node *n, List **containedRels)
 
 		/*
 		 * Process alias (AS clause), if any.
-		 *
-		 * The given table alias must be unique in the current nesting level,
-		 * ie it cannot match any RTE refname or jointable alias.  This is
-		 * a bit painful to check because my own child joins are not yet in
-		 * the pstate's joinlist, so they have to be scanned separately.
 		 */
 		if (j->alias)
 		{
-			/* Check against previously created RTEs and joinlist entries */
-			if (refnameRangeOrJoinEntry(pstate, j->alias->relname, NULL))
-				elog(ERROR, "Table name \"%s\" specified more than once",
-					 j->alias->relname);
-			/* Check children */
-			if (scanJoinListForRefname(j->larg, j->alias->relname) ||
-				scanJoinListForRefname(j->rarg, j->alias->relname))
-				elog(ERROR, "Table name \"%s\" specified more than once",
-					 j->alias->relname);
 			/*
 			 * If a column alias list is specified, substitute the alias
 			 * names into my output-column list
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index d39f06006aa5cddb8dbd22e5db8b1ba334b5dc56..93a986d835e24b0071e5c942d2c1133a6725b00f 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.89 2001/01/24 19:43:01 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_expr.c,v 1.90 2001/02/14 21:35:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -541,7 +541,8 @@ transformIndirection(ParseState *pstate, Node *basenode, List *indirection)
 {
 	if (indirection == NIL)
 		return basenode;
-	return (Node *) transformArraySubscripts(pstate, basenode,
+	return (Node *) transformArraySubscripts(pstate,
+											 basenode, exprType(basenode),
 											 indirection, false, NULL);
 }
 
@@ -558,13 +559,14 @@ static Node *
 transformIdent(ParseState *pstate, Ident *ident, int precedence)
 {
 	Node	   *result = NULL;
+	int			sublevels_up;
 
 	/*
 	 * try to find the ident as a relation ... but not if subscripts
 	 * appear
 	 */
 	if (ident->indirection == NIL &&
-		refnameRangeTableEntry(pstate, ident->name) != NULL)
+		refnameRangeOrJoinEntry(pstate, ident->name, &sublevels_up) != NULL)
 	{
 		ident->isRel = TRUE;
 		result = (Node *) ident;
diff --git a/src/backend/parser/parse_func.c b/src/backend/parser/parse_func.c
index cb4914849b44c39e31ac7353b8e367bca902880a..e0d2b515526a4a72e1bac530136f6cfb2fcf8d45 100644
--- a/src/backend/parser/parse_func.c
+++ b/src/backend/parser/parse_func.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.98 2001/01/24 19:43:02 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_func.c,v 1.99 2001/02/14 21:35:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -427,6 +427,7 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 		{
 			RangeTblEntry *rte;
 			int			vnum;
+			Node	   *rteorjoin;
 			int			sublevels_up;
 
 			/*
@@ -434,9 +435,29 @@ ParseFuncOrColumn(ParseState *pstate, char *funcname, List *fargs,
 			 */
 			refname = ((Ident *) arg)->name;
 
-			rte = refnameRangeTableEntry(pstate, refname);
-			if (rte == NULL)
+			rteorjoin = refnameRangeOrJoinEntry(pstate, refname,
+												&sublevels_up);
+
+			if (rteorjoin == NULL)
+			{
 				rte = addImplicitRTE(pstate, refname);
+			}
+			else if (IsA(rteorjoin, RangeTblEntry))
+			{
+				rte = (RangeTblEntry *) rteorjoin;
+			}
+			else if (IsA(rteorjoin, JoinExpr))
+			{
+				elog(ERROR,
+					 "function applied to tuple is not supported for joins");
+				rte = NULL;		/* keep compiler quiet */
+			}
+			else
+			{
+				elog(ERROR, "ParseFuncOrColumn: unexpected node type %d",
+					 nodeTag(rteorjoin));
+				rte = NULL;		/* keep compiler quiet */
+			}
 
 			vnum = RTERangeTablePosn(pstate, rte, &sublevels_up);
 
diff --git a/src/backend/parser/parse_node.c b/src/backend/parser/parse_node.c
index afe8ae1b801fad662d00d2363053643715cd0a33..36e43166aa92e01969e2b420fa987dde942d714e 100644
--- a/src/backend/parser/parse_node.c
+++ b/src/backend/parser/parse_node.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.51 2001/01/24 19:43:02 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_node.c,v 1.52 2001/02/14 21:35:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -229,20 +229,22 @@ make_var(ParseState *pstate, RangeTblEntry *rte, int attrno)
  *
  * pstate		Parse state
  * arrayBase	Already-transformed expression for the array as a whole
+ *				(may be NULL if we are handling an INSERT)
+ * arrayType	OID of array's datatype
  * indirection	Untransformed list of subscripts (must not be NIL)
  * forceSlice	If true, treat subscript as array slice in all cases
  * assignFrom	NULL for array fetch, else transformed expression for source.
  */
-ArrayRef   *
+ArrayRef *
 transformArraySubscripts(ParseState *pstate,
 						 Node *arrayBase,
+						 Oid arrayType,
 						 List *indirection,
 						 bool forceSlice,
 						 Node *assignFrom)
 {
-	Oid			typearray,
-				typeelement,
-				typeresult;
+	Oid			elementType,
+				resultType;
 	HeapTuple	type_tuple_array,
 				type_tuple_element;
 	Form_pg_type type_struct_array,
@@ -254,28 +256,26 @@ transformArraySubscripts(ParseState *pstate,
 	ArrayRef   *aref;
 
 	/* Get the type tuple for the array */
-	typearray = exprType(arrayBase);
-
 	type_tuple_array = SearchSysCache(TYPEOID,
-									  ObjectIdGetDatum(typearray),
+									  ObjectIdGetDatum(arrayType),
 									  0, 0, 0);
 	if (!HeapTupleIsValid(type_tuple_array))
 		elog(ERROR, "transformArraySubscripts: Cache lookup failed for array type %u",
-			 typearray);
+			 arrayType);
 	type_struct_array = (Form_pg_type) GETSTRUCT(type_tuple_array);
 
-	typeelement = type_struct_array->typelem;
-	if (typeelement == InvalidOid)
+	elementType = type_struct_array->typelem;
+	if (elementType == InvalidOid)
 		elog(ERROR, "transformArraySubscripts: type %s is not an array",
 			 NameStr(type_struct_array->typname));
 
 	/* Get the type tuple for the array element type */
 	type_tuple_element = SearchSysCache(TYPEOID,
-										ObjectIdGetDatum(typeelement),
+										ObjectIdGetDatum(elementType),
 										0, 0, 0);
 	if (!HeapTupleIsValid(type_tuple_element))
 		elog(ERROR, "transformArraySubscripts: Cache lookup failed for array element type %u",
-			 typeelement);
+			 elementType);
 	type_struct_element = (Form_pg_type) GETSTRUCT(type_tuple_element);
 
 	/*
@@ -308,9 +308,9 @@ transformArraySubscripts(ParseState *pstate,
 	 * array type if we are fetching a slice or storing.
 	 */
 	if (isSlice || assignFrom != NULL)
-		typeresult = typearray;
+		resultType = arrayType;
 	else
-		typeresult = typeelement;
+		resultType = elementType;
 
 	/*
 	 * Transform the subscript expressions.
@@ -359,7 +359,7 @@ transformArraySubscripts(ParseState *pstate,
 	if (assignFrom != NULL)
 	{
 		Oid			typesource = exprType(assignFrom);
-		Oid			typeneeded = isSlice ? typearray : typeelement;
+		Oid			typeneeded = isSlice ? arrayType : elementType;
 
 		if (typesource != InvalidOid)
 		{
@@ -385,7 +385,7 @@ transformArraySubscripts(ParseState *pstate,
 	aref = makeNode(ArrayRef);
 	aref->refattrlength = type_struct_array->typlen;
 	aref->refelemlength = type_struct_element->typlen;
-	aref->refelemtype = typeresult;		/* XXX should save element type
+	aref->refelemtype = resultType;		/* XXX should save element type
 										 * too */
 	aref->refelembyval = type_struct_element->typbyval;
 	aref->refupperindexpr = upperIndexpr;
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 1b5d0afc71960828a49ee4a5a47b992080caecbf..d9280529c4f99774cf154f16db67ed6be2602e37 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -8,14 +8,14 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.51 2001/01/24 19:43:02 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_relation.c,v 1.52 2001/02/14 21:35:04 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
-#include <ctype.h>
-
 #include "postgres.h"
 
+#include <ctype.h>
+
 #include "access/heapam.h"
 #include "access/htup.h"
 #include "catalog/pg_type.h"
@@ -30,6 +30,8 @@
 #include "utils/lsyscache.h"
 
 
+static Node *scanNameSpaceForRefname(ParseState *pstate, Node *nsnode,
+									 char *refname);
 static Node *scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte,
 							  char *colname);
 static Node *scanJoinForColumn(JoinExpr *join, char *colname,
@@ -93,25 +95,13 @@ refnameRangeOrJoinEntry(ParseState *pstate,
 
 	while (pstate != NULL)
 	{
-		List	   *temp;
-		JoinExpr   *join;
+		Node	   *rte;
 
-		/*
-		 * Check the rangetable for RTEs; if no match, recursively scan
-		 * the joinlist for join tables.  We assume that no duplicate
-		 * entries have been made in any one nesting level.
-		 */
-		foreach(temp, pstate->p_rtable)
-		{
-			RangeTblEntry *rte = lfirst(temp);
-
-			if (strcmp(rte->eref->relname, refname) == 0)
-				return (Node *) rte;
-		}
-
-		join = scanJoinListForRefname((Node *) pstate->p_joinlist, refname);
-		if (join)
-			return (Node *) join;
+		rte = scanNameSpaceForRefname(pstate,
+									  (Node *) pstate->p_namespace,
+									  refname);
+		if (rte)
+			return rte;
 
 		pstate = pstate->parentParseState;
 		if (sublevels_up)
@@ -123,108 +113,129 @@ refnameRangeOrJoinEntry(ParseState *pstate,
 }
 
 /*
- * Recursively search a joinlist for a joinexpr with given refname
+ * Recursively search a namespace for an RTE or joinexpr with given refname.
+ *
+ * The top level of p_namespace is a list, and we recurse into any joins
+ * that are not subqueries.  It is also possible to pass an individual
+ * join subtree (useful when checking for name conflicts within a scope).
  *
- * Note that during parse analysis, we don't expect to find a FromExpr node
- * in p_joinlist; its top level is just a bare List.
+ * Note: we do not worry about the possibility of multiple matches;
+ * we assume the code that built the namespace checked for duplicates.
  */
-JoinExpr *
-scanJoinListForRefname(Node *jtnode, char *refname)
+static Node *
+scanNameSpaceForRefname(ParseState *pstate, Node *nsnode,
+						char *refname)
 {
-	JoinExpr   *result = NULL;
+	Node	   *result = NULL;
 
-	if (jtnode == NULL)
+	if (nsnode == NULL)
 		return NULL;
-	if (IsA(jtnode, List))
+	if (IsA(nsnode, RangeTblRef))
 	{
-		List	   *l;
+		int			varno = ((RangeTblRef *) nsnode)->rtindex;
+		RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
 
-		foreach(l, (List *) jtnode)
-		{
-			result = scanJoinListForRefname(lfirst(l), refname);
-			if (result)
-				break;
-		}
+		if (strcmp(rte->eref->relname, refname) == 0)
+			result = (Node *) rte;
 	}
-	else if (IsA(jtnode, RangeTblRef))
+	else if (IsA(nsnode, JoinExpr))
 	{
-		/* ignore ... */
+		JoinExpr   *j = (JoinExpr *) nsnode;
+
+		if (j->alias)
+		{
+			if (strcmp(j->alias->relname, refname) == 0)
+				return (Node *) j; /* matched a join alias */
+			/*
+			 * Tables within an aliased join are invisible from outside
+			 * the join, according to the scope rules of SQL92 (the join
+			 * is considered a subquery).  So, stop here.
+			 */
+			return NULL;
+		}
+		result = scanNameSpaceForRefname(pstate, j->larg, refname);
+		if (! result)
+			result = scanNameSpaceForRefname(pstate, j->rarg, refname);
 	}
-	else if (IsA(jtnode, JoinExpr))
+	else if (IsA(nsnode, List))
 	{
-		JoinExpr   *j = (JoinExpr *) jtnode;
+		List	   *l;
 
-		if (j->alias && strcmp(j->alias->relname, refname) == 0)
-			return j;
-		result = scanJoinListForRefname(j->larg, refname);
-		if (! result)
-			result = scanJoinListForRefname(j->rarg, refname);
+		foreach(l, (List *) nsnode)
+		{
+			result = scanNameSpaceForRefname(pstate, lfirst(l), refname);
+			if (result)
+				break;
+		}
 	}
 	else
-		elog(ERROR, "scanJoinListForRefname: unexpected node type %d",
-			 nodeTag(jtnode));
+		elog(ERROR, "scanNameSpaceForRefname: unexpected node type %d",
+			 nodeTag(nsnode));
 	return result;
 }
 
-/*
- * given refname, return a pointer to the range table entry.
- *
- * NOTE that this routine will ONLY find RTEs, not join tables.
- */
-RangeTblEntry *
-refnameRangeTableEntry(ParseState *pstate, char *refname)
+/* Convenience subroutine for checkNameSpaceConflicts */
+static void
+scanNameSpaceForConflict(ParseState *pstate, Node *nsnode,
+						 char *refname)
 {
-	List	   *temp;
-
-	while (pstate != NULL)
-	{
-		foreach(temp, pstate->p_rtable)
-		{
-			RangeTblEntry *rte = lfirst(temp);
-
-			if (strcmp(rte->eref->relname, refname) == 0)
-				return rte;
-		}
-		pstate = pstate->parentParseState;
-	}
-	return NULL;
+	if (scanNameSpaceForRefname(pstate, nsnode, refname) != NULL)
+		elog(ERROR, "Table name \"%s\" specified more than once", refname);
 }
 
 /*
- * given refname, return RT index (starting with 1) of the relation,
- * and optionally get its nesting depth (0 = current).	If sublevels_up
- * is NULL, only consider rels at the current nesting level.
- * A zero result means name not found.
+ * Recursively check for refname conflicts between two namespaces or
+ * namespace subtrees.  Raise an error if any is found.
+ *
+ * Works by recursively scanning namespace1 in the same way that
+ * scanNameSpaceForRefname does, and then looking in namespace2 for
+ * a match to each refname found in namespace1.
  *
- * NOTE that this routine will ONLY find RTEs, not join tables.
+ * Note: we assume that each given argument does not contain conflicts
+ * itself; we just want to know if the two can be merged together.
  */
-int
-refnameRangeTablePosn(ParseState *pstate, char *refname, int *sublevels_up)
+void
+checkNameSpaceConflicts(ParseState *pstate, Node *namespace1,
+						Node *namespace2)
 {
-	int			index;
-	List	   *temp;
-
-	if (sublevels_up)
-		*sublevels_up = 0;
+	if (namespace1 == NULL)
+		return;
+	if (IsA(namespace1, RangeTblRef))
+	{
+		int			varno = ((RangeTblRef *) namespace1)->rtindex;
+		RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
 
-	while (pstate != NULL)
+		scanNameSpaceForConflict(pstate, namespace2, rte->eref->relname);
+	}
+	else if (IsA(namespace1, JoinExpr))
 	{
-		index = 1;
-		foreach(temp, pstate->p_rtable)
+		JoinExpr   *j = (JoinExpr *) namespace1;
+
+		if (j->alias)
 		{
-			RangeTblEntry *rte = lfirst(temp);
+			scanNameSpaceForConflict(pstate, namespace2, j->alias->relname);
+			/*
+			 * Tables within an aliased join are invisible from outside
+			 * the join, according to the scope rules of SQL92 (the join
+			 * is considered a subquery).  So, stop here.
+			 */
+			return;
+		}
+		checkNameSpaceConflicts(pstate, j->larg, namespace2);
+		checkNameSpaceConflicts(pstate, j->rarg, namespace2);
+	}
+	else if (IsA(namespace1, List))
+	{
+		List	   *l;
 
-			if (strcmp(rte->eref->relname, refname) == 0)
-				return index;
-			index++;
+		foreach(l, (List *) namespace1)
+		{
+			checkNameSpaceConflicts(pstate, lfirst(l), namespace2);
 		}
-		pstate = pstate->parentParseState;
-		if (sublevels_up)
-			(*sublevels_up)++;
-		else
-			break;
 	}
-	return 0;
+	else
+		elog(ERROR, "checkNameSpaceConflicts: unexpected node type %d",
+			 nodeTag(namespace1));
 }
 
 /*
@@ -257,6 +268,7 @@ RTERangeTablePosn(ParseState *pstate, RangeTblEntry *rte, int *sublevels_up)
 		else
 			break;
 	}
+
 	elog(ERROR, "RTERangeTablePosn: RTE not found (internal error)");
 	return 0;					/* keep compiler quiet */
 }
@@ -369,21 +381,21 @@ colnameToVar(ParseState *pstate, char *colname)
 
 	while (pstate != NULL)
 	{
-		List	   *jt;
+		List	   *ns;
 
 		/*
-		 * We want to look only at top-level jointree items, and even for
+		 * We need to look only at top-level namespace items, and even for
 		 * those, ignore RTEs that are marked as not inFromCl and not
 		 * the query's target relation.
 		 */
-		foreach(jt, pstate->p_joinlist)
+		foreach(ns, pstate->p_namespace)
 		{
-			Node   *jtnode = (Node *) lfirst(jt);
+			Node   *nsnode = (Node *) lfirst(ns);
 			Node   *newresult = NULL;
 
-			if (IsA(jtnode, RangeTblRef))
+			if (IsA(nsnode, RangeTblRef))
 			{
-				int			varno = ((RangeTblRef *) jtnode)->rtindex;
+				int			varno = ((RangeTblRef *) nsnode)->rtindex;
 				RangeTblEntry *rte = rt_fetch(varno, pstate->p_rtable);
 
 				if (! rte->inFromCl &&
@@ -393,15 +405,15 @@ colnameToVar(ParseState *pstate, char *colname)
 				/* use orig_pstate here to get the right sublevels_up */
 				newresult = scanRTEForColumn(orig_pstate, rte, colname);
 			}
-			else if (IsA(jtnode, JoinExpr))
+			else if (IsA(nsnode, JoinExpr))
 			{
-				JoinExpr   *j = (JoinExpr *) jtnode;
+				JoinExpr   *j = (JoinExpr *) nsnode;
 
 				newresult = scanJoinForColumn(j, colname, levels_up);
 			}
 			else
 				elog(ERROR, "colnameToVar: unexpected node type %d",
-					 nodeTag(jtnode));
+					 nodeTag(nsnode));
 
 			if (newresult)
 			{
@@ -451,7 +463,7 @@ qualifiedNameToVar(ParseState *pstate, char *refname, char *colname,
 								  colname);
 	else if (IsA(rteorjoin, JoinExpr))
 		result = scanJoinForColumn((JoinExpr *) rteorjoin,
-								  colname, sublevels_up);
+								   colname, sublevels_up);
 	else
 	{
 		elog(ERROR, "qualifiedNameToVar: unexpected node type %d",
@@ -465,10 +477,11 @@ qualifiedNameToVar(ParseState *pstate, char *refname, char *colname,
 /*
  * Add an entry for a relation to the pstate's range table (p_rtable).
  *
- * If the specified refname is already present, raise error.
+ * If pstate is NULL, we just build an RTE and return it without adding it
+ * to an rtable list.
  *
- * If pstate is NULL, we just build an RTE and return it without worrying
- * about membership in an rtable list.
+ * Note: formerly this checked for refname conflicts, but that's wrong.
+ * Caller is responsible for checking for conflicts in the appropriate scope.
  */
 RangeTblEntry *
 addRangeTableEntry(ParseState *pstate,
@@ -477,27 +490,15 @@ addRangeTableEntry(ParseState *pstate,
 				   bool inh,
 				   bool inFromCl)
 {
+	RangeTblEntry *rte = makeNode(RangeTblEntry);
 	char	   *refname = alias ? alias->relname : relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
-	RangeTblEntry *rte;
 	Attr	   *eref;
 	int			maxattrs;
 	int			numaliases;
 	int			varattno;
 
-	/* Check for conflicting RTE or jointable alias (at level 0 only) */
-	if (pstate != NULL)
-	{
-		Node   *rteorjoin = refnameRangeOrJoinEntry(pstate, refname, NULL);
-
-		if (rteorjoin)
-			elog(ERROR, "Table name \"%s\" specified more than once",
-				 refname);
-	}
-
-	rte = makeNode(RangeTblEntry);
-
 	rte->relname = relname;
 	rte->alias = alias;
 	rte->subquery = NULL;
@@ -559,7 +560,8 @@ addRangeTableEntry(ParseState *pstate,
 	rte->checkAsUser = InvalidOid; /* not set-uid by default, either */
 
 	/*
-	 * Add completed RTE to range table list.
+	 * Add completed RTE to pstate's range table list, but not to join list
+	 * nor namespace --- caller must do that if appropriate.
 	 */
 	if (pstate != NULL)
 		pstate->p_rtable = lappend(pstate->p_rtable, rte);
@@ -579,25 +581,13 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 							  Attr *alias,
 							  bool inFromCl)
 {
+	RangeTblEntry *rte = makeNode(RangeTblEntry);
 	char	   *refname = alias->relname;
-	RangeTblEntry *rte;
 	Attr	   *eref;
 	int			numaliases;
 	int			varattno;
 	List	   *tlistitem;
 
-	/* Check for conflicting RTE or jointable alias (at level 0 only) */
-	if (pstate != NULL)
-	{
-		Node   *rteorjoin = refnameRangeOrJoinEntry(pstate, refname, NULL);
-
-		if (rteorjoin)
-			elog(ERROR, "Table name \"%s\" specified more than once",
-				 refname);
-	}
-
-	rte = makeNode(RangeTblEntry);
-
 	rte->relname = NULL;
 	rte->relid = InvalidOid;
 	rte->subquery = subquery;
@@ -647,7 +637,8 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->checkAsUser = InvalidOid;
 
 	/*
-	 * Add completed RTE to range table list.
+	 * Add completed RTE to pstate's range table list, but not to join list
+	 * nor namespace --- caller must do that if appropriate.
 	 */
 	if (pstate != NULL)
 		pstate->p_rtable = lappend(pstate->p_rtable, rte);
@@ -691,37 +682,30 @@ isForUpdate(ParseState *pstate, char *relname)
 }
 
 /*
- * Add the given RTE as a top-level entry in the pstate's join list,
- * unless there already is an entry for it.
+ * Add the given RTE as a top-level entry in the pstate's join list
+ * and/or name space list.  (We assume caller has checked for any
+ * namespace conflict.)
  */
 void
-addRTEtoJoinList(ParseState *pstate, RangeTblEntry *rte)
+addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
+			  bool addToJoinList, bool addToNameSpace)
 {
 	int			rtindex = RTERangeTablePosn(pstate, rte, NULL);
-	List	   *jt;
-	RangeTblRef *rtr;
-
-	foreach(jt, pstate->p_joinlist)
-	{
-		Node	   *n = (Node *) lfirst(jt);
-
-		if (IsA(n, RangeTblRef))
-		{
-			if (rtindex == ((RangeTblRef *) n)->rtindex)
-				return;			/* it's already being joined to */
-		}
-	}
+	RangeTblRef *rtr = makeNode(RangeTblRef);
 
-	/* Not present, so add it */
-	rtr = makeNode(RangeTblRef);
 	rtr->rtindex = rtindex;
-	pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
+
+	if (addToJoinList)
+		pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
+	if (addToNameSpace)
+		pstate->p_namespace = lappend(pstate->p_namespace, rtr);
 }
 
 /*
  * Add a POSTQUEL-style implicit RTE.
  *
- * We assume caller has already checked that there is no such RTE now.
+ * We assume caller has already checked that there is no RTE or join with
+ * a conflicting name.
  */
 RangeTblEntry *
 addImplicitRTE(ParseState *pstate, char *relname)
@@ -729,7 +713,7 @@ addImplicitRTE(ParseState *pstate, char *relname)
 	RangeTblEntry *rte;
 
 	rte = addRangeTableEntry(pstate, relname, NULL, false, false);
-	addRTEtoJoinList(pstate, rte);
+	addRTEtoQuery(pstate, rte, true, true);
 	warnAutoRange(pstate, relname);
 
 	return rte;
@@ -922,6 +906,11 @@ expandNamesVars(ParseState *pstate, List *names, List *vars)
  * This is unlike get_attname() because we use aliases if available.
  * In particular, it will work on an RTE for a subselect, whereas
  * get_attname() only works on real relations.
+ *
+ * XXX Actually, this is completely bogus, because refnames of RTEs are
+ * not guaranteed unique, and may not even have scope across the whole
+ * query.  Cleanest fix would be to add refname/attname to Var nodes and
+ * just print those, rather than indulging in this hack.
  * ----------
  */
 char *
@@ -1088,4 +1077,3 @@ warnAutoRange(ParseState *pstate, char *refname)
 			 pstate->parentParseState != NULL ? " in subquery" : "",
 			 refname);
 }
-
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 647b176f059548534440d918fe52968be739b1ac..6b566da7475e0399eaa69add9b17748d3a45d6bf 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.64 2001/01/24 19:43:02 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/parser/parse_target.c,v 1.65 2001/02/14 21:35:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -212,29 +212,37 @@ updateTargetListEntry(ParseState *pstate,
 	 */
 	if (indirection)
 	{
-		Attr	   *att = makeAttr(pstrdup(RelationGetRelationName(rd)),
-								   colname);
 		Node	   *arrayBase;
 		ArrayRef   *aref;
 
-		arrayBase = ParseNestedFuncOrColumn(pstate, att, EXPR_COLUMN_FIRST);
-		aref = transformArraySubscripts(pstate, arrayBase,
-										indirection,
-										pstate->p_is_insert,
-										tle->expr);
 		if (pstate->p_is_insert)
 		{
-
 			/*
 			 * The command is INSERT INTO table (arraycol[subscripts]) ...
 			 * so there is not really a source array value to work with.
 			 * Let the executor do something reasonable, if it can. Notice
-			 * that we forced transformArraySubscripts to treat the
-			 * subscripting op as an array-slice op above, so the source
-			 * data will have been coerced to array type.
+			 * that we force transformArraySubscripts to treat the
+			 * subscripting op as an array-slice op below, so the source
+			 * data will have been coerced to the array type.
+			 */
+			arrayBase = NULL;	/* signal there is no source array */
+		}
+		else
+		{
+			/*
+			 * Build a Var for the array to be updated.
 			 */
-			aref->refexpr = NULL;		/* signal there is no source array */
+			arrayBase = (Node *) make_var(pstate,
+										  pstate->p_target_rangetblentry,
+										  attrno);
 		}
+
+		aref = transformArraySubscripts(pstate,
+										arrayBase,
+										attrtype,
+										indirection,
+										pstate->p_is_insert,
+										tle->expr);
 		tle->expr = (Node *) aref;
 	}
 	else
@@ -385,22 +393,19 @@ checkInsertTargets(ParseState *pstate, List *cols, List **attrnos)
 /* ExpandAllTables()
  * Turns '*' (in the target list) into a list of targetlist entries.
  *
- * tlist entries are generated for each relation appearing in the FROM list,
- * which by now has been transformed into a joinlist.
+ * tlist entries are generated for each relation appearing at the top level
+ * of the query's namespace, except for RTEs marked not inFromCl.  (These
+ * may include NEW/OLD pseudo-entries, implicit RTEs, etc.)
  */
 static List *
 ExpandAllTables(ParseState *pstate)
 {
 	List	   *target = NIL;
-	List	   *jt;
+	List	   *ns;
 
-	/* SELECT *; */
-	if (pstate->p_joinlist == NIL)
-		elog(ERROR, "Wildcard with no tables specified not allowed");
-
-	foreach(jt, pstate->p_joinlist)
+	foreach(ns, pstate->p_namespace)
 	{
-		Node	   *n = (Node *) lfirst(jt);
+		Node	   *n = (Node *) lfirst(ns);
 
 		if (IsA(n, RangeTblRef))
 		{
@@ -431,6 +436,10 @@ ExpandAllTables(ParseState *pstate)
 				 "\n\t%s", nodeToString(n));
 	}
 
+	/* Check for SELECT *; */
+	if (target == NIL)
+		elog(ERROR, "Wildcard with no tables specified not allowed");
+
 	return target;
 }
 
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 3f9264308dd28b3443ac459f6ce78698b8b31099..872b607e87c623e021e5a9e9ec44aae3fefb4964 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3,7 +3,7 @@
  *				back to source text
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.71 2001/01/03 22:01:05 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.72 2001/02/14 21:35:05 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -35,11 +35,11 @@
  *
  **********************************************************************/
 
+#include "postgres.h"
+
 #include <unistd.h>
 #include <fcntl.h>
 
-#include "postgres.h"
-
 #include "catalog/pg_index.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_shadow.h"
@@ -52,6 +52,7 @@
 #include "parser/parse_expr.h"
 #include "parser/parsetree.h"
 #include "rewrite/rewriteManip.h"
+#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 
 
@@ -59,13 +60,30 @@
  * Local data types
  * ----------
  */
+
+/* Context info needed for invoking a recursive querytree display routine */
 typedef struct
 {
 	StringInfo	buf;			/* output buffer to append to */
-	List	   *rangetables;	/* List of List of RangeTblEntry */
+	List	   *namespaces;		/* List of deparse_namespace nodes */
 	bool		varprefix;		/* TRUE to print prefixes on Vars */
 } deparse_context;
 
+/*
+ * Each level of query context around a subtree needs a level of Var namespace.
+ * The rangetable is the list of actual RTEs, and the namespace indicates
+ * which parts of the rangetable are accessible (and under what aliases)
+ * in the expression currently being looked at.  A Var having varlevelsup=N
+ * refers to the N'th item (counting from 0) in the current context's
+ * namespaces list.
+ */
+typedef struct
+{
+	List	   *rtable;			/* List of RangeTblEntry nodes */
+	List	   *namespace;		/* List of joinlist items (RangeTblRef and
+								 * JoinExpr nodes) */
+} deparse_namespace;
+
 
 /* ----------
  * Global data
@@ -92,7 +110,7 @@ static char *query_getopclass = "SELECT * FROM pg_opclass WHERE oid = $1";
  */
 static void make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc);
 static void make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc);
-static void get_query_def(Query *query, StringInfo buf, List *parentrtables);
+static void get_query_def(Query *query, StringInfo buf, List *parentnamespace);
 static void get_select_query_def(Query *query, deparse_context *context);
 static void get_insert_query_def(Query *query, deparse_context *context);
 static void get_update_query_def(Query *query, deparse_context *context);
@@ -102,7 +120,14 @@ static void get_basic_select_query(Query *query, deparse_context *context);
 static void get_setop_query(Node *setOp, Query *query,
 							deparse_context *context, bool toplevel);
 static bool simple_distinct(List *distinctClause, List *targetList);
-static RangeTblEntry *get_rte_for_var(Var *var, deparse_context *context);
+static void get_names_for_var(Var *var, deparse_context *context,
+							  char **refname, char **attname);
+static bool get_alias_for_case(CaseExpr *caseexpr, deparse_context *context,
+							   char **refname, char **attname);
+static bool find_alias_in_namespace(Node *nsnode, Node *expr,
+									List *rangetable, int levelsup,
+									char **refname, char **attname);
+static bool phony_equal(Node *expr1, Node *expr2, int levelsup);
 static void get_rule_expr(Node *node, deparse_context *context);
 static void get_func_expr(Expr *expr, deparse_context *context);
 static void get_tle_expr(TargetEntry *tle, deparse_context *context);
@@ -599,30 +624,24 @@ pg_get_userbyid(PG_FUNCTION_ARGS)
  * expr is the node tree to be deparsed.  It must be a transformed expression
  * tree (ie, not the raw output of gram.y).
  *
- * rangetables is a List of Lists of RangeTblEntry nodes: first sublist is for
- * varlevelsup = 0, next for varlevelsup = 1, etc.	In each sublist the first
- * item is for varno = 1, next varno = 2, etc.	(Each sublist has the same
- * format as the rtable list of a parsetree or query.)
+ * dpcontext is a list of deparse_namespace nodes representing the context
+ * for interpreting Vars in the node tree.
  *
  * forceprefix is TRUE to force all Vars to be prefixed with their table names.
- * Otherwise, a prefix is printed only if there's more than one table involved
- * (and someday the code might try to print one only if there's ambiguity).
  *
  * The result is a palloc'd string.
  * ----------
  */
 char *
-deparse_expression(Node *expr, List *rangetables, bool forceprefix)
+deparse_expression(Node *expr, List *dpcontext, bool forceprefix)
 {
 	StringInfoData buf;
 	deparse_context context;
 
 	initStringInfo(&buf);
 	context.buf = &buf;
-	context.rangetables = rangetables;
-	context.varprefix = (forceprefix ||
-						 length(rangetables) != 1 ||
-						 length((List *) lfirst(rangetables)) != 1);
+	context.namespaces = dpcontext;
+	context.varprefix = forceprefix;
 
 	rulename = "";				/* in case of errors */
 
@@ -631,6 +650,43 @@ deparse_expression(Node *expr, List *rangetables, bool forceprefix)
 	return buf.data;
 }
 
+/* ----------
+ * deparse_context_for			- Build deparse context for a single relation
+ *
+ * Given the name and OID of a relation, build deparsing context for an
+ * expression referencing only that relation (as varno 1, varlevelsup 0).
+ * This is presently sufficient for the external uses of deparse_expression.
+ * ----------
+ */
+List *
+deparse_context_for(char *relname, Oid relid)
+{
+	deparse_namespace *dpns;
+	RangeTblEntry *rte;
+	RangeTblRef *rtr;
+
+	dpns = (deparse_namespace *) palloc(sizeof(deparse_namespace));
+
+	/* Build a minimal RTE for the rel */
+	rte = makeNode(RangeTblEntry);
+	rte->relname = relname;
+	rte->relid = relid;
+	rte->eref = makeNode(Attr);
+	rte->eref->relname = relname;
+	rte->inh = false;
+	rte->inFromCl = true;
+	/* Build one-element rtable */
+	dpns->rtable = makeList1(rte);
+
+	/* Build a namespace list referencing this RTE only */
+	rtr = makeNode(RangeTblRef);
+	rtr->rtindex = 1;
+	dpns->namespace = makeList1(rtr);
+
+	/* Return a one-deep namespace stack */
+	return makeList1(dpns);
+}
+
 /* ----------
  * make_ruledef			- reconstruct the CREATE RULE command
  *				  for a given pg_rewrite tuple
@@ -722,6 +778,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
 		Node	   *qual;
 		Query	   *query;
 		deparse_context context;
+		deparse_namespace dpns;
 
 		appendStringInfo(buf, " WHERE ");
 
@@ -729,8 +786,10 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
 		query = (Query *) lfirst(actions);
 
 		context.buf = buf;
-		context.rangetables = makeList1(query->rtable);
+		context.namespaces = makeList1(&dpns);
 		context.varprefix = (length(query->rtable) != 1);
+		dpns.rtable = query->rtable;
+		dpns.namespace = query->jointree ? query->jointree->fromlist : NIL;
 
 		get_rule_expr(qual, &context);
 	}
@@ -844,14 +903,17 @@ make_viewdef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc)
  * ----------
  */
 static void
-get_query_def(Query *query, StringInfo buf, List *parentrtables)
+get_query_def(Query *query, StringInfo buf, List *parentnamespace)
 {
 	deparse_context context;
+	deparse_namespace dpns;
 
 	context.buf = buf;
-	context.rangetables = lcons(query->rtable, parentrtables);
-	context.varprefix = (parentrtables != NIL ||
+	context.namespaces = lcons(&dpns, parentnamespace);
+	context.varprefix = (parentnamespace != NIL ||
 						 length(query->rtable) != 1);
+	dpns.rtable = query->rtable;
+	dpns.namespace = query->jointree ? query->jointree->fromlist : NIL;
 
 	switch (query->commandType)
 	{
@@ -1025,11 +1087,10 @@ get_basic_select_query(Query *query, deparse_context *context)
 		else
 		{
 			Var		   *var = (Var *) (tle->expr);
-			RangeTblEntry *rte;
+			char	   *refname;
 			char	   *attname;
 
-			rte = get_rte_for_var(var, context);
-			attname = get_rte_attribute_name(rte, var->varattno);
+			get_names_for_var(var, context, &refname, &attname);
 			tell_as = (strcmp(attname, tle->resdom->resname) != 0);
 		}
 
@@ -1088,7 +1149,7 @@ get_setop_query(Node *setOp, Query *query, deparse_context *context,
 		Query  *subquery = rte->subquery;
 
 		Assert(subquery != NULL);
-		get_query_def(subquery, buf, context->rangetables);
+		get_query_def(subquery, buf, context->namespaces);
 	}
 	else if (IsA(setOp, SetOperationStmt))
 	{
@@ -1336,20 +1397,277 @@ get_utility_query_def(Query *query, deparse_context *context)
 
 
 /*
- * Find the RTE referenced by a (possibly nonlocal) Var.
+ * Get the relation refname and attname for a (possibly nonlocal) Var.
+ *
+ * This is trickier than it ought to be because of the possibility of aliases
+ * and limited scope of refnames.  We have to try to return the correct alias
+ * with respect to the current namespace given by the context.
  */
-static RangeTblEntry *
-get_rte_for_var(Var *var, deparse_context *context)
+static void
+get_names_for_var(Var *var, deparse_context *context,
+				  char **refname, char **attname)
 {
-	List	   *rtlist = context->rangetables;
+	List	   *nslist = context->namespaces;
 	int			sup = var->varlevelsup;
+	deparse_namespace *dpns;
+	RangeTblEntry *rte;
+
+	/* Find appropriate nesting depth */
+	while (sup-- > 0 && nslist != NIL)
+		nslist = lnext(nslist);
+	if (nslist == NIL)
+		elog(ERROR, "get_names_for_var: bogus varlevelsup %d",
+			 var->varlevelsup);
+	dpns = (deparse_namespace *) lfirst(nslist);
+
+	/* Scan namespace to see if we can find an alias for the var */
+	if (find_alias_in_namespace((Node *) dpns->namespace, (Node *) var,
+								dpns->rtable, var->varlevelsup,
+								refname, attname))
+		return;
+
+	/*
+	 * Otherwise, fall back on the rangetable entry.  This should happen
+	 * only for uses of special RTEs like *NEW* and *OLD*, which won't
+	 * get placed in our namespace.
+	 */
+	rte = rt_fetch(var->varno, dpns->rtable);
+	*refname = rte->eref->relname;
+	*attname = get_rte_attribute_name(rte, var->varattno);
+}
+
+/*
+ * Check to see if a CASE expression matches a FULL JOIN's output expression.
+ * If so, return the refname and alias it should be expressed as.
+ */
+static bool
+get_alias_for_case(CaseExpr *caseexpr, deparse_context *context,
+				   char **refname, char **attname)
+{
+	List	   *nslist;
+	int			sup;
 
-	while (sup-- > 0)
-		rtlist = lnext(rtlist);
+	/*
+	 * This could be done more efficiently if we first groveled through the
+	 * CASE to find varlevelsup values, but it's probably not worth the
+	 * trouble.  All this code will go away someday anyway ...
+	 */
 
-	return rt_fetch(var->varno, (List *) lfirst(rtlist));
+	sup = 0;
+	foreach(nslist, context->namespaces)
+	{
+		deparse_namespace *dpns = (deparse_namespace *) lfirst(nslist);
+
+		if (find_alias_in_namespace((Node *) dpns->namespace,
+									(Node *) caseexpr,
+									dpns->rtable, sup,
+									refname, attname))
+			return true;
+		sup++;
+	}
+	return false;
 }
 
+/*
+ * Recursively scan a namespace (same representation as a jointree) to see
+ * if we can find an alias for the given expression.  If so, return the
+ * correct alias refname and attname.  The expression may be either a plain
+ * Var or a CASE expression (which may be a FULL JOIN reference).
+ */
+static bool
+find_alias_in_namespace(Node *nsnode, Node *expr,
+						List *rangetable, int levelsup,
+						char **refname, char **attname)
+{
+	if (nsnode == NULL)
+		return false;
+	if (IsA(nsnode, RangeTblRef))
+	{
+		if (IsA(expr, Var))
+		{
+			Var		   *var = (Var *) expr;
+			int			rtindex = ((RangeTblRef *) nsnode)->rtindex;
+
+			if (var->varno == rtindex && var->varlevelsup == levelsup)
+			{
+				RangeTblEntry *rte = rt_fetch(rtindex, rangetable);
+
+				*refname = rte->eref->relname;
+				*attname = get_rte_attribute_name(rte, var->varattno);
+				return true;
+			}
+		}
+	}
+	else if (IsA(nsnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) nsnode;
+
+		if (j->alias)
+		{
+			List	   *vlist;
+			List	   *nlist;
+
+			/*
+			 * Does the expr match any of the output columns of the join?
+			 *
+			 * We can't just use equal() here, because the given expr may
+			 * have nonzero levelsup, whereas the saved expression in the
+			 * JoinExpr should have zero levelsup.
+			 */
+			nlist = j->colnames;
+			foreach(vlist, j->colvars)
+			{
+				if (phony_equal(lfirst(vlist), expr, levelsup))
+				{
+					*refname = j->alias->relname;
+					*attname = strVal(lfirst(nlist));
+					return true;
+				}
+				nlist = lnext(nlist);
+			}
+			/*
+			 * Tables within an aliased join are invisible from outside
+			 * the join, according to the scope rules of SQL92 (the join
+			 * is considered a subquery).  So, stop here.
+			 */
+			return false;
+		}
+		if (find_alias_in_namespace(j->larg, expr,
+									rangetable, levelsup,
+									refname, attname))
+			return true;
+		if (find_alias_in_namespace(j->rarg, expr,
+									rangetable, levelsup,
+									refname, attname))
+			return true;
+	}
+	else if (IsA(nsnode, List))
+	{
+		List	   *l;
+
+		foreach(l, (List *) nsnode)
+		{
+			if (find_alias_in_namespace(lfirst(l), expr,
+										rangetable, levelsup,
+										refname, attname))
+				return true;
+		}
+	}
+	else
+		elog(ERROR, "find_alias_in_namespace: unexpected node type %d",
+			 nodeTag(nsnode));
+	return false;
+}
+
+/*
+ * Check for equality of two expressions, with the proviso that all Vars in
+ * expr1 should have varlevelsup = 0, while all Vars in expr2 should have
+ * varlevelsup = levelsup.
+ *
+ * In reality we only need to support equality checks on Vars and the type
+ * of CASE expression that is used for FULL JOIN outputs, so not all node
+ * types need be handled here.
+ *
+ * Otherwise, this code is a straight ripoff from equalfuncs.c.
+ */
+static bool
+phony_equal(Node *expr1, Node *expr2, int levelsup)
+{
+	if (expr1 == NULL || expr2 == NULL)
+		return (expr1 == expr2);
+	if (nodeTag(expr1) != nodeTag(expr2))
+		return false;
+	if (IsA(expr1, Var))
+	{
+		Var	   *a = (Var *) expr1;
+		Var	   *b = (Var *) expr2;
+
+		if (a->varno != b->varno)
+			return false;
+		if (a->varattno != b->varattno)
+			return false;
+		if (a->vartype != b->vartype)
+			return false;
+		if (a->vartypmod != b->vartypmod)
+			return false;
+		if (a->varlevelsup != 0 || b->varlevelsup != levelsup)
+			return false;
+		if (a->varnoold != b->varnoold)
+			return false;
+		if (a->varoattno != b->varoattno)
+			return false;
+		return true;
+	}
+	if (IsA(expr1, CaseExpr))
+	{
+		CaseExpr	   *a = (CaseExpr *) expr1;
+		CaseExpr	   *b = (CaseExpr *) expr2;
+
+		if (a->casetype != b->casetype)
+			return false;
+		if (!phony_equal(a->arg, b->arg, levelsup))
+			return false;
+		if (!phony_equal((Node *) a->args, (Node *) b->args, levelsup))
+			return false;
+		if (!phony_equal(a->defresult, b->defresult, levelsup))
+			return false;
+		return true;
+	}
+	if (IsA(expr1, CaseWhen))
+	{
+		CaseWhen	   *a = (CaseWhen *) expr1;
+		CaseWhen	   *b = (CaseWhen *) expr2;
+
+		if (!phony_equal(a->expr, b->expr, levelsup))
+			return false;
+		if (!phony_equal(a->result, b->result, levelsup))
+			return false;
+		return true;
+	}
+	if (IsA(expr1, Expr))
+	{
+		Expr	   *a = (Expr *) expr1;
+		Expr	   *b = (Expr *) expr2;
+
+		if (a->opType != b->opType)
+			return false;
+		if (!phony_equal(a->oper, b->oper, levelsup))
+			return false;
+		if (!phony_equal((Node *) a->args, (Node *) b->args, levelsup))
+			return false;
+		return true;
+	}
+	if (IsA(expr1, Func))
+	{
+		Func	   *a = (Func *) expr1;
+		Func	   *b = (Func *) expr2;
+
+		if (a->funcid != b->funcid)
+			return false;
+		if (a->functype != b->functype)
+			return false;
+		return true;
+	}
+	if (IsA(expr1, List))
+	{
+		List	   *la = (List *) expr1;
+		List	   *lb = (List *) expr2;
+		List	   *l;
+
+		if (length(la) != length(lb))
+			return false;
+		foreach(l, la)
+		{
+			if (!phony_equal(lfirst(l), lfirst(lb), levelsup))
+				return false;
+			lb = lnext(lb);
+		}
+		return true;
+	}
+	/* If we get here, there was something weird in a JOIN's colvars list */
+	elog(ERROR, "phony_equal: unexpected node type %d", nodeTag(expr1));
+	return false;
+}
 
 /* ----------
  * get_rule_expr			- Parse back an expression
@@ -1381,21 +1699,21 @@ get_rule_expr(Node *node, deparse_context *context)
 		case T_Var:
 			{
 				Var		   *var = (Var *) node;
-				RangeTblEntry *rte = get_rte_for_var(var, context);
+				char	   *refname;
+				char	   *attname;
 
+				get_names_for_var(var, context, &refname, &attname);
 				if (context->varprefix)
 				{
-					if (strcmp(rte->eref->relname, "*NEW*") == 0)
+					if (strcmp(refname, "*NEW*") == 0)
 						appendStringInfo(buf, "new.");
-					else if (strcmp(rte->eref->relname, "*OLD*") == 0)
+					else if (strcmp(refname, "*OLD*") == 0)
 						appendStringInfo(buf, "old.");
 					else
 						appendStringInfo(buf, "%s.",
-									quote_identifier(rte->eref->relname));
+										 quote_identifier(refname));
 				}
-				appendStringInfo(buf, "%s",
-						  quote_identifier(get_rte_attribute_name(rte,
-														var->varattno)));
+				appendStringInfo(buf, "%s", quote_identifier(attname));
 			}
 			break;
 
@@ -1606,6 +1924,19 @@ get_rule_expr(Node *node, deparse_context *context)
 			{
 				CaseExpr   *caseexpr = (CaseExpr *) node;
 				List	   *temp;
+				char	   *refname;
+				char	   *attname;
+
+				/* Hack for providing aliases for FULL JOIN outputs */
+				if (get_alias_for_case(caseexpr, context,
+									   &refname, &attname))
+				{
+					if (context->varprefix)
+						appendStringInfo(buf, "%s.",
+										 quote_identifier(refname));
+					appendStringInfo(buf, "%s", quote_identifier(attname));
+					break;
+				}
 
 				appendStringInfo(buf, "CASE");
 				foreach(temp, caseexpr->args)
@@ -1645,6 +1976,7 @@ get_func_expr(Expr *expr, deparse_context *context)
 {
 	StringInfo	buf = context->buf;
 	Func	   *func = (Func *) (expr->oper);
+	Oid			funcoid = func->funcid;
 	HeapTuple	proctup;
 	Form_pg_proc procStruct;
 	char	   *proname;
@@ -1652,42 +1984,37 @@ get_func_expr(Expr *expr, deparse_context *context)
 	List	   *l;
 	char	   *sep;
 
+	/*
+	 * nullvalue() and nonnullvalue() should get turned into special
+	 * syntax
+	 */
+	if (funcoid == F_NULLVALUE)
+	{
+		appendStringInfoChar(buf, '(');
+		get_rule_expr((Node *) lfirst(expr->args), context);
+		appendStringInfo(buf, " ISNULL)");
+		return;
+	}
+	if (funcoid == F_NONNULLVALUE)
+	{
+		appendStringInfoChar(buf, '(');
+		get_rule_expr((Node *) lfirst(expr->args), context);
+		appendStringInfo(buf, " NOTNULL)");
+		return;
+	}
+
 	/*
 	 * Get the functions pg_proc tuple
 	 */
 	proctup = SearchSysCache(PROCOID,
-							 ObjectIdGetDatum(func->funcid),
+							 ObjectIdGetDatum(funcoid),
 							 0, 0, 0);
 	if (!HeapTupleIsValid(proctup))
-		elog(ERROR, "cache lookup for proc %u failed", func->funcid);
+		elog(ERROR, "cache lookup for proc %u failed", funcoid);
 
 	procStruct = (Form_pg_proc) GETSTRUCT(proctup);
 	proname = NameStr(procStruct->proname);
 
-	/*
-	 * nullvalue() and nonnullvalue() should get turned into special
-	 * syntax
-	 */
-	if (procStruct->pronargs == 1 && procStruct->proargtypes[0] == InvalidOid)
-	{
-		if (strcmp(proname, "nullvalue") == 0)
-		{
-			appendStringInfoChar(buf, '(');
-			get_rule_expr((Node *) lfirst(expr->args), context);
-			appendStringInfo(buf, " ISNULL)");
-			ReleaseSysCache(proctup);
-			return;
-		}
-		if (strcmp(proname, "nonnullvalue") == 0)
-		{
-			appendStringInfoChar(buf, '(');
-			get_rule_expr((Node *) lfirst(expr->args), context);
-			appendStringInfo(buf, " NOTNULL)");
-			ReleaseSysCache(proctup);
-			return;
-		}
-	}
-
 	/*
 	 * Check to see if function is a length-coercion function for some
 	 * datatype.  If so, display the operation as a type cast.
@@ -1968,7 +2295,7 @@ get_sublink_expr(Node *node, deparse_context *context)
 	if (need_paren)
 		appendStringInfoChar(buf, '(');
 
-	get_query_def(query, buf, context->rangetables);
+	get_query_def(query, buf, context->namespaces);
 
 	if (need_paren)
 		appendStringInfo(buf, "))");
@@ -2024,6 +2351,16 @@ static void
 get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 {
 	StringInfo	buf = context->buf;
+	deparse_namespace *dpns;
+	List	   *sv_namespace;
+
+	/*
+	 * FROM-clause items have limited visibility of query's namespace.
+	 * Save and restore the outer namespace setting while we munge it.
+	 */
+	dpns = (deparse_namespace *) lfirst(context->namespaces);
+	sv_namespace = dpns->namespace;
+	dpns->namespace = NIL;
 
 	if (IsA(jtnode, RangeTblRef))
 	{
@@ -2042,7 +2379,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			/* Subquery RTE */
 			Assert(rte->subquery != NULL);
 			appendStringInfoChar(buf, '(');
-			get_query_def(rte->subquery, buf, context->rangetables);
+			get_query_def(rte->subquery, buf, context->namespaces);
 			appendStringInfoChar(buf, ')');
 		}
 		if (rte->alias != NULL)
@@ -2053,7 +2390,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			{
 				List	   *col;
 
-				appendStringInfo(buf, " (");
+				appendStringInfo(buf, "(");
 				foreach(col, rte->alias->attrs)
 				{
 					if (col != rte->alias->attrs)
@@ -2116,6 +2453,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			}
 			else if (j->quals)
 			{
+				dpns->namespace = makeList2(j->larg, j->rarg);
 				appendStringInfo(buf, " ON (");
 				get_rule_expr(j->quals, context);
 				appendStringInfoChar(buf, ')');
@@ -2131,7 +2469,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			{
 				List	   *col;
 
-				appendStringInfo(buf, " (");
+				appendStringInfo(buf, "(");
 				foreach(col, j->alias->attrs)
 				{
 					if (col != j->alias->attrs)
@@ -2146,6 +2484,8 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 	else
 		elog(ERROR, "get_from_clause_item: unexpected node type %d",
 			 nodeTag(jtnode));
+
+	dpns->namespace = sv_namespace;
 }
 
 
diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h
index 2ca40e8ceca6965b8ffa1c77ff4e59f6d3c9f850..f5cd6ea461d8f5d73b12b16833101d2f020dc9e5 100644
--- a/src/include/nodes/primnodes.h
+++ b/src/include/nodes/primnodes.h
@@ -10,7 +10,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: primnodes.h,v 1.51 2001/01/24 19:43:26 momjian Exp $
+ * $Id: primnodes.h,v 1.52 2001/02/14 21:35:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -29,7 +29,7 @@ typedef struct FunctionCache *FunctionCachePtr;
  * ----------------------------------------------------------------
  */
 
-/*
+/*--------------------
  * Resdom (Result Domain)
  *
  * Notes:
@@ -50,7 +50,7 @@ typedef struct FunctionCache *FunctionCachePtr;
  *
  * Both reskey and reskeyop are typically zero during parse/plan stages.
  * The executor does not pay any attention to ressortgroupref.
- *
+ *--------------------
  */
 typedef struct Resdom
 {
@@ -129,7 +129,6 @@ typedef struct Expr
  * list.  But varnoold/varoattno continue to hold the original values.
  * The code doesn't really need varnoold/varoattno, but they are very useful
  * for debugging and interpreting completed plans, so we keep them around.
- * ----------------
  */
 #define    INNER		65000
 #define    OUTER		65001
@@ -153,7 +152,7 @@ typedef struct Var
 	AttrNumber	varoattno;	/* original value of varattno */
 } Var;
 
-/*
+/*--------------------
  * Oper
  *
  * NOTE: in the good old days 'opno' used to be both (or either, or
@@ -169,7 +168,7 @@ typedef struct Var
  * Note also that opid is not necessarily filled in immediately on creation
  * of the node.  The planner makes sure it is valid before passing the node
  * tree to the executor, but during parsing/planning opid is typically 0.
- *
+ *--------------------
  */
 typedef struct Oper
 {
@@ -499,10 +498,14 @@ typedef struct RangeTblRef
  * are not equivalent to ON() since they also affect the output column list.
  *
  * alias is an Attr node representing the AS alias-clause attached to the
- * join expression, or NULL if no clause.  During parse analysis, colnames
- * is filled with a list of String nodes giving the column names (real or
- * alias) of the output of the join, and colvars is filled with a list of
- * expressions that can be copied to reference the output columns.
+ * join expression, or NULL if no clause.  NB: presence or absence of the
+ * alias has a critical impact on semantics, because a join with an alias
+ * restricts visibility of the tables/columns inside it.
+ *
+ * During parse analysis, colnames is filled with a list of String nodes
+ * giving the column names (real or alias) of the output of the join,
+ * and colvars is filled with a list of expressions that can be copied to
+ * reference the output columns.
  *----------
  */
 typedef struct JoinExpr
diff --git a/src/include/parser/parse_clause.h b/src/include/parser/parse_clause.h
index 8f760ca38b33ad7a515e96814810e9ef3aa57d92..31d5542efc28a8d4cfa19b3f0b08d24c0769ee87 100644
--- a/src/include/parser/parse_clause.h
+++ b/src/include/parser/parse_clause.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_clause.h,v 1.22 2001/01/24 19:43:27 momjian Exp $
+ * $Id: parse_clause.h,v 1.23 2001/02/14 21:35:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,10 +16,9 @@
 
 #include "parser/parse_node.h"
 
-extern void makeRangeTable(ParseState *pstate, List *frmList);
-extern void lockTargetTable(ParseState *pstate, char *relname);
-extern void setTargetTable(ParseState *pstate, char *relname,
-						   bool inh, bool inJoinSet);
+extern void transformFromClause(ParseState *pstate, List *frmList);
+extern int setTargetTable(ParseState *pstate, char *relname,
+						  bool inh, bool alsoSource);
 extern bool interpretInhOption(InhOption inhOpt);
 extern Node *transformWhereClause(ParseState *pstate, Node *where);
 extern List *transformGroupClause(ParseState *pstate, List *grouplist,
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index d8fcd6ee9072f0e46416092c67302414da764735..bb0229abcd17db068a169bc78eb30e06fd38828d 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_node.h,v 1.24 2001/01/24 19:43:27 momjian Exp $
+ * $Id: parse_node.h,v 1.25 2001/02/14 21:35:05 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -18,6 +18,20 @@
 
 /*
  * State information used during parse analysis
+ *
+ * p_rtable: list of RTEs that will become the rangetable of the query.
+ * Note that neither relname nor refname of these entries are necessarily
+ * unique; searching the rtable by name is a bad idea.
+ *
+ * p_joinlist: list of join items (RangeTblRef and JoinExpr nodes) that
+ * will become the fromlist of the query's top-level FromExpr node.
+ *
+ * p_namespace: list of join items that represents the current namespace
+ * for table and column lookup.  This may be just a subset of the rtable +
+ * joinlist, and/or may contain entries that are not yet added to the main
+ * joinlist.  Note that an RTE that is present in p_namespace, but does not
+ * have its inFromCl flag set, is accessible only with an explicit qualifier;
+ * lookups of unqualified column names should ignore it.
  */
 typedef struct ParseState
 {
@@ -25,6 +39,7 @@ typedef struct ParseState
 	List	   *p_rtable;		/* range table so far */
 	List	   *p_joinlist;		/* join items so far (will become
 								 * FromExpr node's fromlist) */
+	List	   *p_namespace;	/* current lookup namespace (join items) */
 	int			p_last_resno;	/* last targetlist resno assigned */
 	List	   *p_forUpdate;	/* FOR UPDATE clause, if any (see gram.y) */
 	bool		p_hasAggs;
@@ -42,6 +57,7 @@ extern Node *make_operand(char *opname, Node *tree,
 extern Var *make_var(ParseState *pstate, RangeTblEntry *rte, int attrno);
 extern ArrayRef *transformArraySubscripts(ParseState *pstate,
 						 Node *arrayBase,
+						 Oid arrayType,
 						 List *indirection,
 						 bool forceSlice,
 						 Node *assignFrom);
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index bfdf7e8c2afb2e8948a8a5c36480208c7cc233fe..274de9e88955376b5091d7040d644c38335093ea 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parse_relation.h,v 1.21 2001/01/24 19:43:27 momjian Exp $
+ * $Id: parse_relation.h,v 1.22 2001/02/14 21:35:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -19,15 +19,11 @@
 extern Node *refnameRangeOrJoinEntry(ParseState *pstate,
 									 char *refname,
 									 int *sublevels_up);
-extern RangeTblEntry *refnameRangeTableEntry(ParseState *pstate,
-											 char *refname);
-extern int refnameRangeTablePosn(ParseState *pstate,
-								 char *refname,
-								 int *sublevels_up);
+extern void checkNameSpaceConflicts(ParseState *pstate, Node *namespace1,
+									Node *namespace2);
 extern int RTERangeTablePosn(ParseState *pstate,
 							 RangeTblEntry *rte,
 							 int *sublevels_up);
-extern JoinExpr *scanJoinListForRefname(Node *jtnode, char *refname);
 extern Node *colnameToVar(ParseState *pstate, char *colname);
 extern Node *qualifiedNameToVar(ParseState *pstate, char *refname,
 								char *colname, bool implicitRTEOK);
@@ -40,7 +36,8 @@ extern RangeTblEntry *addRangeTableEntryForSubquery(ParseState *pstate,
 													Query *subquery,
 													Attr *alias,
 													bool inFromCl);
-extern void addRTEtoJoinList(ParseState *pstate, RangeTblEntry *rte);
+extern void addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
+						  bool addToJoinList, bool addToNameSpace);
 extern RangeTblEntry *addImplicitRTE(ParseState *pstate, char *relname);
 extern void expandRTE(ParseState *pstate, RangeTblEntry *rte,
 					  List **colnames, List **colvars);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 28cb478189a0026b984ab573586b10fc83c31e10..dcc923a36de0bfc8d35f184e8559e4bbeb08f593 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: builtins.h,v 1.146 2001/01/24 19:43:28 momjian Exp $
+ * $Id: builtins.h,v 1.147 2001/02/14 21:35:06 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -325,8 +325,9 @@ extern Datum pg_get_ruledef(PG_FUNCTION_ARGS);
 extern Datum pg_get_viewdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_indexdef(PG_FUNCTION_ARGS);
 extern Datum pg_get_userbyid(PG_FUNCTION_ARGS);
-extern char *deparse_expression(Node *expr, List *rangetables,
+extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix);
+extern List *deparse_context_for(char *relname, Oid relid);
 
 /* selfuncs.c */
 extern Datum eqsel(PG_FUNCTION_ARGS);
diff --git a/src/tools/backend/index.html b/src/tools/backend/index.html
index 03a5a643e0b03546df35f58b5a22b95572ab4417..cfcc6c6ac9962e3a060fd3233676646790479221 100644
--- a/src/tools/backend/index.html
+++ b/src/tools/backend/index.html
@@ -67,7 +67,7 @@ Each table referenced in the query is represented by a <A
 HREF="../../include/nodes/parsenodes.h"> RangeTableEntry,</A> and they
 are linked together to form the <I>range table</I> of the query, which
 is generated by <A HREF="../../backend/parser/parse_clause.c">
-makeRangeTable().</A>  Query.rtable holds the query's range table.<P>
+transformFromClause().</A>  Query.rtable holds the query's range table.<P>
 
 
 Certain queries, like <I>SELECT,</I> return columns of data.  Other