diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 8b68fb7..07533f6 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2531,7 +2531,7 @@ AddRelationNewConstraints(Relation rel,
 	TupleConstr *oldconstr;
 	int			numoldchecks;
 	ParseState *pstate;
-	RangeTblEntry *rte;
+	ParseNamespaceItem *nsitem;
 	int			numchecks;
 	List	   *checknames;
 	ListCell   *cell;
@@ -2554,13 +2554,13 @@ AddRelationNewConstraints(Relation rel,
 	 */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
-	rte = addRangeTableEntryForRelation(pstate,
-										rel,
-										AccessShareLock,
-										NULL,
-										false,
-										true);
-	addRTEtoQuery(pstate, rte, true, true, true);
+	nsitem = addRangeTableEntryForRelation(pstate,
+										   rel,
+										   AccessShareLock,
+										   NULL,
+										   false,
+										   true);
+	addNSItemtoQuery(pstate, nsitem, true, true, true);
 
 	/*
 	 * Process column default expressions.
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 42a147b..04d5bde 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -882,6 +882,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 	if (stmt->relation)
 	{
 		LOCKMODE	lockmode = is_from ? RowExclusiveLock : AccessShareLock;
+		ParseNamespaceItem *nsitem;
 		RangeTblEntry *rte;
 		TupleDesc	tupDesc;
 		List	   *attnums;
@@ -894,14 +895,15 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
 
 		relid = RelationGetRelid(rel);
 
-		rte = addRangeTableEntryForRelation(pstate, rel, lockmode,
-											NULL, false, false);
+		nsitem = addRangeTableEntryForRelation(pstate, rel, lockmode,
+											   NULL, false, false);
+		rte = nsitem->p_rte;
 		rte->requiredPerms = (is_from ? ACL_INSERT : ACL_SELECT);
 
 		if (stmt->whereClause)
 		{
-			/* add rte to column namespace  */
-			addRTEtoQuery(pstate, rte, false, true, true);
+			/* add nsitem to column namespace  */
+			addNSItemtoQuery(pstate, nsitem, false, true, true);
 
 			/* Transform the raw expression tree */
 			whereClause = transformExpr(pstate, stmt->whereClause, EXPR_KIND_COPY_WHERE);
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 36093dc..1e538a5 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -568,9 +568,9 @@ RemoveRoleFromObjectPolicy(Oid roleid, Oid classid, Oid policy_id)
 			qual_expr = stringToNode(qual_value);
 
 			/* Add this rel to the parsestate's rangetable, for dependencies */
-			addRangeTableEntryForRelation(qual_pstate, rel,
-										  AccessShareLock,
-										  NULL, false, false);
+			(void) addRangeTableEntryForRelation(qual_pstate, rel,
+												 AccessShareLock,
+												 NULL, false, false);
 
 			qual_parse_rtable = qual_pstate->p_rtable;
 			free_parsestate(qual_pstate);
@@ -592,9 +592,9 @@ RemoveRoleFromObjectPolicy(Oid roleid, Oid classid, Oid policy_id)
 			with_check_qual = stringToNode(with_check_value);
 
 			/* Add this rel to the parsestate's rangetable, for dependencies */
-			addRangeTableEntryForRelation(with_check_pstate, rel,
-										  AccessShareLock,
-										  NULL, false, false);
+			(void) addRangeTableEntryForRelation(with_check_pstate, rel,
+												 AccessShareLock,
+												 NULL, false, false);
 
 			with_check_parse_rtable = with_check_pstate->p_rtable;
 			free_parsestate(with_check_pstate);
@@ -699,7 +699,7 @@ CreatePolicy(CreatePolicyStmt *stmt)
 	ArrayType  *role_ids;
 	ParseState *qual_pstate;
 	ParseState *with_check_pstate;
-	RangeTblEntry *rte;
+	ParseNamespaceItem *nsitem;
 	Node	   *qual;
 	Node	   *with_check_qual;
 	ScanKeyData skey[2];
@@ -755,16 +755,16 @@ CreatePolicy(CreatePolicyStmt *stmt)
 	target_table = relation_open(table_id, NoLock);
 
 	/* Add for the regular security quals */
-	rte = addRangeTableEntryForRelation(qual_pstate, target_table,
-										AccessShareLock,
-										NULL, false, false);
-	addRTEtoQuery(qual_pstate, rte, false, true, true);
+	nsitem = addRangeTableEntryForRelation(qual_pstate, target_table,
+										   AccessShareLock,
+										   NULL, false, false);
+	addNSItemtoQuery(qual_pstate, nsitem, false, true, true);
 
 	/* Add for the with-check quals */
-	rte = addRangeTableEntryForRelation(with_check_pstate, target_table,
-										AccessShareLock,
-										NULL, false, false);
-	addRTEtoQuery(with_check_pstate, rte, false, true, true);
+	nsitem = addRangeTableEntryForRelation(with_check_pstate, target_table,
+										   AccessShareLock,
+										   NULL, false, false);
+	addNSItemtoQuery(with_check_pstate, nsitem, false, true, true);
 
 	qual = transformWhereClause(qual_pstate,
 								copyObject(stmt->qual),
@@ -933,14 +933,14 @@ AlterPolicy(AlterPolicyStmt *stmt)
 	/* Parse the using policy clause */
 	if (stmt->qual)
 	{
-		RangeTblEntry *rte;
+		ParseNamespaceItem *nsitem;
 		ParseState *qual_pstate = make_parsestate(NULL);
 
-		rte = addRangeTableEntryForRelation(qual_pstate, target_table,
-											AccessShareLock,
-											NULL, false, false);
+		nsitem = addRangeTableEntryForRelation(qual_pstate, target_table,
+											   AccessShareLock,
+											   NULL, false, false);
 
-		addRTEtoQuery(qual_pstate, rte, false, true, true);
+		addNSItemtoQuery(qual_pstate, nsitem, false, true, true);
 
 		qual = transformWhereClause(qual_pstate, copyObject(stmt->qual),
 									EXPR_KIND_POLICY,
@@ -956,14 +956,14 @@ AlterPolicy(AlterPolicyStmt *stmt)
 	/* Parse the with-check policy clause */
 	if (stmt->with_check)
 	{
-		RangeTblEntry *rte;
+		ParseNamespaceItem *nsitem;
 		ParseState *with_check_pstate = make_parsestate(NULL);
 
-		rte = addRangeTableEntryForRelation(with_check_pstate, target_table,
-											AccessShareLock,
-											NULL, false, false);
+		nsitem = addRangeTableEntryForRelation(with_check_pstate, target_table,
+											   AccessShareLock,
+											   NULL, false, false);
 
-		addRTEtoQuery(with_check_pstate, rte, false, true, true);
+		addNSItemtoQuery(with_check_pstate, nsitem, false, true, true);
 
 		with_check_qual = transformWhereClause(with_check_pstate,
 											   copyObject(stmt->with_check),
@@ -1107,9 +1107,9 @@ AlterPolicy(AlterPolicyStmt *stmt)
 			qual = stringToNode(qual_value);
 
 			/* Add this rel to the parsestate's rangetable, for dependencies */
-			addRangeTableEntryForRelation(qual_pstate, target_table,
-										  AccessShareLock,
-										  NULL, false, false);
+			(void) addRangeTableEntryForRelation(qual_pstate, target_table,
+												 AccessShareLock,
+												 NULL, false, false);
 
 			qual_parse_rtable = qual_pstate->p_rtable;
 			free_parsestate(qual_pstate);
@@ -1149,9 +1149,10 @@ AlterPolicy(AlterPolicyStmt *stmt)
 			with_check_qual = stringToNode(with_check_value);
 
 			/* Add this rel to the parsestate's rangetable, for dependencies */
-			addRangeTableEntryForRelation(with_check_pstate, target_table,
-										  AccessShareLock,
-										  NULL, false, false);
+			(void) addRangeTableEntryForRelation(with_check_pstate,
+												 target_table,
+												 AccessShareLock,
+												 NULL, false, false);
 
 			with_check_parse_rtable = with_check_pstate->p_rtable;
 			free_parsestate(with_check_pstate);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5b882f8..e21cdd7 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -918,7 +918,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 					defaultPartOid;
 		Relation	parent,
 					defaultRel = NULL;
-		RangeTblEntry *rte;
+		ParseNamespaceItem *nsitem;
 
 		/* Already have strong enough lock on the parent */
 		parent = table_open(parentId, NoLock);
@@ -962,13 +962,14 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 		pstate->p_sourcetext = queryString;
 
 		/*
-		 * Add an RTE containing this relation, so that transformExpr called
-		 * on partition bound expressions is able to report errors using a
-		 * proper context.
+		 * Add an nsitem containing this relation, so that transformExpr
+		 * called on partition bound expressions is able to report errors
+		 * using a proper context.
 		 */
-		rte = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
-											NULL, false, false);
-		addRTEtoQuery(pstate, rte, false, true, true);
+		nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
+											   NULL, false, false);
+		addNSItemtoQuery(pstate, nsitem, false, true, true);
+
 		bound = transformPartitionBound(pstate, parent, stmt->partbound);
 
 		/*
@@ -14970,7 +14971,7 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
 {
 	PartitionSpec *newspec;
 	ParseState *pstate;
-	RangeTblEntry *rte;
+	ParseNamespaceItem *nsitem;
 	ListCell   *l;
 
 	newspec = makeNode(PartitionSpec);
@@ -15004,9 +15005,9 @@ transformPartitionSpec(Relation rel, PartitionSpec *partspec, char *strategy)
 	 * rangetable entry.  We need a ParseState for transformExpr.
 	 */
 	pstate = make_parsestate(NULL);
-	rte = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
-										NULL, false, true);
-	addRTEtoQuery(pstate, rte, true, true, true);
+	nsitem = addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
+										   NULL, false, true);
+	addNSItemtoQuery(pstate, nsitem, true, true, true);
 
 	/* take care of any partition expressions */
 	foreach(l, partspec->partParams)
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 36093a2..18eda58 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -565,7 +565,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 	if (!whenClause && stmt->whenClause)
 	{
 		ParseState *pstate;
-		RangeTblEntry *rte;
+		ParseNamespaceItem *nsitem;
 		List	   *varList;
 		ListCell   *lc;
 
@@ -574,20 +574,20 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
 		pstate->p_sourcetext = queryString;
 
 		/*
-		 * Set up RTEs for OLD and NEW references.
+		 * Set up nsitems for OLD and NEW references.
 		 *
 		 * 'OLD' must always have varno equal to 1 and 'NEW' equal to 2.
 		 */
-		rte = addRangeTableEntryForRelation(pstate, rel,
-											AccessShareLock,
-											makeAlias("old", NIL),
-											false, false);
-		addRTEtoQuery(pstate, rte, false, true, true);
-		rte = addRangeTableEntryForRelation(pstate, rel,
-											AccessShareLock,
-											makeAlias("new", NIL),
-											false, false);
-		addRTEtoQuery(pstate, rte, false, true, true);
+		nsitem = addRangeTableEntryForRelation(pstate, rel,
+											   AccessShareLock,
+											   makeAlias("old", NIL),
+											   false, false);
+		addNSItemtoQuery(pstate, nsitem, false, true, true);
+		nsitem = addRangeTableEntryForRelation(pstate, rel,
+											   AccessShareLock,
+											   makeAlias("new", NIL),
+											   false, false);
+		addNSItemtoQuery(pstate, nsitem, false, true, true);
 
 		/* Transform expression.  Copy to be sure we don't modify original */
 		whenClause = transformWhereClause(pstate,
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 9b51480..5fc02b0 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -341,6 +341,7 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 {
 	Relation	viewRel;
 	List	   *new_rt;
+	ParseNamespaceItem *nsitem;
 	RangeTblEntry *rt_entry1,
 			   *rt_entry2;
 	ParseState *pstate;
@@ -365,14 +366,17 @@ UpdateRangeTableOfViewParse(Oid viewOid, Query *viewParse)
 	 * Create the 2 new range table entries and form the new range table...
 	 * OLD first, then NEW....
 	 */
-	rt_entry1 = addRangeTableEntryForRelation(pstate, viewRel,
-											  AccessShareLock,
-											  makeAlias("old", NIL),
-											  false, false);
-	rt_entry2 = addRangeTableEntryForRelation(pstate, viewRel,
-											  AccessShareLock,
-											  makeAlias("new", NIL),
-											  false, false);
+	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
+										   AccessShareLock,
+										   makeAlias("old", NIL),
+										   false, false);
+	rt_entry1 = nsitem->p_rte;
+	nsitem = addRangeTableEntryForRelation(pstate, viewRel,
+										   AccessShareLock,
+										   makeAlias("new", NIL),
+										   false, false);
+	rt_entry2 = nsitem->p_rte;
+
 	/* Must override addRangeTableEntry's default access-check flags */
 	rt_entry1->requiredPerms = 0;
 	rt_entry2->requiredPerms = 0;
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 48b62a5..615309a 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -1217,6 +1217,7 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	Query	   *subselect = (Query *) sublink->subselect;
 	Relids		upper_varnos;
 	int			rtindex;
+	ParseNamespaceItem *nsitem;
 	RangeTblEntry *rte;
 	RangeTblRef *rtr;
 	List	   *subquery_vars;
@@ -1264,11 +1265,12 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	 * below). Therefore this is a lot easier than what pull_up_subqueries has
 	 * to go through.
 	 */
-	rte = addRangeTableEntryForSubquery(pstate,
-										subselect,
-										makeAlias("ANY_subquery", NIL),
-										false,
-										false);
+	nsitem = addRangeTableEntryForSubquery(pstate,
+										   subselect,
+										   makeAlias("ANY_subquery", NIL),
+										   false,
+										   false);
+	rte = nsitem->p_rte;
 	parse->rtable = lappend(parse->rtable, rte);
 	rtindex = list_length(parse->rtable);
 
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 0656279..2057bc4 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -415,9 +415,8 @@ transformDeleteStmt(ParseState *pstate, DeleteStmt *stmt)
 										 stmt->relation->inh,
 										 true,
 										 ACL_DELETE);
-
-	/* grab the namespace item made by setTargetTable */
-	nsitem = (ParseNamespaceItem *) llast(pstate->p_namespace);
+	nsitem = pstate->p_target_nsitem;
+	Assert(nsitem->p_rtindex == qry->resultRelation);
 
 	/* there's no DISTINCT in DELETE */
 	qry->distinctClause = NIL;
@@ -476,8 +475,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	List	   *sub_namespace;
 	List	   *icolumns;
 	List	   *attrnos;
+	ParseNamespaceItem *nsitem;
 	RangeTblEntry *rte;
-	RangeTblRef *rtr;
 	ListCell   *icols;
 	ListCell   *attnos;
 	ListCell   *lc;
@@ -613,16 +612,12 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		 * Make the source be a subquery in the INSERT's rangetable, and add
 		 * it to the INSERT's joinlist.
 		 */
-		rte = addRangeTableEntryForSubquery(pstate,
-											selectQuery,
-											makeAlias("*SELECT*", NIL),
-											false,
-											false);
-		rtr = makeNode(RangeTblRef);
-		/* assume new rte is at end */
-		rtr->rtindex = list_length(pstate->p_rtable);
-		Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
-		pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
+		nsitem = addRangeTableEntryForSubquery(pstate,
+											   selectQuery,
+											   makeAlias("*SELECT*", NIL),
+											   false,
+											   false);
+		addNSItemtoQuery(pstate, nsitem, true, false, false);
 
 		/*----------
 		 * Generate an expression list for the INSERT that selects all the
@@ -652,7 +647,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 				expr = tle->expr;
 			else
 			{
-				Var		   *var = makeVarFromTargetEntry(rtr->rtindex, tle);
+				Var		   *var = makeVarFromTargetEntry(nsitem->p_rtindex, tle);
 
 				var->location = exprLocation((Node *) tle->expr);
 				expr = (Expr *) var;
@@ -774,19 +769,15 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		/*
 		 * Generate the VALUES RTE
 		 */
-		rte = addRangeTableEntryForValues(pstate, exprsLists,
-										  coltypes, coltypmods, colcollations,
-										  NULL, lateral, true);
-		rtr = makeNode(RangeTblRef);
-		/* assume new rte is at end */
-		rtr->rtindex = list_length(pstate->p_rtable);
-		Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
-		pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
+		nsitem = addRangeTableEntryForValues(pstate, exprsLists,
+											 coltypes, coltypmods, colcollations,
+											 NULL, lateral, true);
+		addNSItemtoQuery(pstate, nsitem, true, false, false);
 
 		/*
 		 * Generate list of Vars referencing the RTE
 		 */
-		expandRTE(rte, rtr->rtindex, 0, -1, false, NULL, &exprList);
+		exprList = expandNSItemVars(nsitem, 0, -1, NULL);
 
 		/*
 		 * Re-apply any indirection on the target column specs to the Vars
@@ -829,7 +820,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	 * Generate query's target list using the computed list of expressions.
 	 * Also, mark all the target columns as needing insert permissions.
 	 */
-	rte = pstate->p_target_rangetblentry;
+	rte = pstate->p_target_nsitem->p_rte;
 	qry->targetList = NIL;
 	Assert(list_length(exprList) <= list_length(icolumns));
 	forthree(lc, exprList, icols, icolumns, attnos, attrnos)
@@ -863,8 +854,8 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 	if (stmt->returningList)
 	{
 		pstate->p_namespace = NIL;
-		addRTEtoQuery(pstate, pstate->p_target_rangetblentry,
-					  false, true, true);
+		addNSItemtoQuery(pstate, pstate->p_target_nsitem,
+						 false, true, true);
 		qry->returningList = transformReturningList(pstate,
 													stmt->returningList);
 	}
@@ -999,7 +990,6 @@ transformOnConflictClause(ParseState *pstate,
 	Oid			arbiterConstraint;
 	List	   *onConflictSet = NIL;
 	Node	   *onConflictWhere = NULL;
-	RangeTblEntry *exclRte = NULL;
 	int			exclRelIndex = 0;
 	List	   *exclRelTlist = NIL;
 	OnConflictExpr *result;
@@ -1012,6 +1002,8 @@ transformOnConflictClause(ParseState *pstate,
 	if (onConflictClause->action == ONCONFLICT_UPDATE)
 	{
 		Relation	targetrel = pstate->p_target_relation;
+		ParseNamespaceItem *exclNSItem;
+		RangeTblEntry *exclRte;
 
 		/*
 		 * All INSERT expressions have been parsed, get ready for potentially
@@ -1025,17 +1017,18 @@ transformOnConflictClause(ParseState *pstate,
 		 * relation, and no permission checks are required on it.  (We'll
 		 * check the actual target relation, instead.)
 		 */
-		exclRte = addRangeTableEntryForRelation(pstate,
-												targetrel,
-												RowExclusiveLock,
-												makeAlias("excluded", NIL),
-												false, false);
+		exclNSItem = addRangeTableEntryForRelation(pstate,
+												   targetrel,
+												   RowExclusiveLock,
+												   makeAlias("excluded", NIL),
+												   false, false);
+		exclRte = exclNSItem->p_rte;
+		exclRelIndex = exclNSItem->p_rtindex;
+
 		exclRte->relkind = RELKIND_COMPOSITE_TYPE;
 		exclRte->requiredPerms = 0;
 		/* other permissions fields in exclRte are already empty */
 
-		exclRelIndex = list_length(pstate->p_rtable);
-
 		/* Create EXCLUDED rel's targetlist for use by EXPLAIN */
 		exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel,
 														 exclRelIndex);
@@ -1044,9 +1037,9 @@ transformOnConflictClause(ParseState *pstate,
 		 * Add EXCLUDED and the target RTE to the namespace, so that they can
 		 * be used in the UPDATE subexpressions.
 		 */
-		addRTEtoQuery(pstate, exclRte, false, true, true);
-		addRTEtoQuery(pstate, pstate->p_target_rangetblentry,
-					  false, true, true);
+		addNSItemtoQuery(pstate, exclNSItem, false, true, true);
+		addNSItemtoQuery(pstate, pstate->p_target_nsitem,
+						 false, true, true);
 
 		/*
 		 * Now transform the UPDATE subexpressions.
@@ -1343,7 +1336,6 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	List	  **colexprs = NULL;
 	int			sublist_length = -1;
 	bool		lateral = false;
-	RangeTblEntry *rte;
 	ParseNamespaceItem *nsitem;
 	ListCell   *lc;
 	ListCell   *lc2;
@@ -1511,14 +1503,10 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	/*
 	 * Generate the VALUES RTE
 	 */
-	rte = addRangeTableEntryForValues(pstate, exprsLists,
-									  coltypes, coltypmods, colcollations,
-									  NULL, lateral, true);
-	addRTEtoQuery(pstate, rte, true, true, true);
-
-	/* grab the namespace item made by addRTEtoQuery */
-	nsitem = (ParseNamespaceItem *) llast(pstate->p_namespace);
-	Assert(rte == nsitem->p_rte);
+	nsitem = addRangeTableEntryForValues(pstate, exprsLists,
+										 coltypes, coltypmods, colcollations,
+										 NULL, lateral, true);
+	addNSItemtoQuery(pstate, nsitem, true, true, true);
 
 	/*
 	 * Generate a targetlist as though expanding "*"
@@ -1593,7 +1581,9 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 			   *targetnames,
 			   *sv_namespace;
 	int			sv_rtable_length;
-	RangeTblEntry *jrte;
+	ParseNamespaceItem *jnsitem;
+	ParseNamespaceColumn *sortnscolumns;
+	int			sortcolindex;
 	int			tllen;
 
 	qry->commandType = CMD_SELECT;
@@ -1686,6 +1676,9 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	qry->targetList = NIL;
 	targetvars = NIL;
 	targetnames = NIL;
+	sortnscolumns = (ParseNamespaceColumn *)
+		palloc0(list_length(sostmt->colTypes) * sizeof(ParseNamespaceColumn));
+	sortcolindex = 0;
 
 	forfour(lct, sostmt->colTypes,
 			lcm, sostmt->colTypmods,
@@ -1716,6 +1709,14 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 		qry->targetList = lappend(qry->targetList, tle);
 		targetvars = lappend(targetvars, var);
 		targetnames = lappend(targetnames, makeString(colName));
+		sortnscolumns[sortcolindex].p_varno = leftmostRTI;
+		sortnscolumns[sortcolindex].p_varattno = lefttle->resno;
+		sortnscolumns[sortcolindex].p_vartype = colType;
+		sortnscolumns[sortcolindex].p_vartypmod = colTypmod;
+		sortnscolumns[sortcolindex].p_varcollid = colCollation;
+		sortnscolumns[sortcolindex].p_varnosyn = leftmostRTI;
+		sortnscolumns[sortcolindex].p_varattnosyn = lefttle->resno;
+		sortcolindex++;
 	}
 
 	/*
@@ -1730,18 +1731,19 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 	 */
 	sv_rtable_length = list_length(pstate->p_rtable);
 
-	jrte = addRangeTableEntryForJoin(pstate,
-									 targetnames,
-									 JOIN_INNER,
-									 targetvars,
-									 NULL,
-									 false);
+	jnsitem = addRangeTableEntryForJoin(pstate,
+										targetnames,
+										sortnscolumns,
+										JOIN_INNER,
+										targetvars,
+										NULL,
+										false);
 
 	sv_namespace = pstate->p_namespace;
 	pstate->p_namespace = NIL;
 
-	/* add jrte to column namespace only */
-	addRTEtoQuery(pstate, jrte, false, false, true);
+	/* add jnsitem to column namespace only */
+	addNSItemtoQuery(pstate, jnsitem, false, false, true);
 
 	/*
 	 * For now, we don't support resjunk sort clauses on the output of a
@@ -1757,7 +1759,7 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 										  EXPR_KIND_ORDER_BY,
 										  false /* allow SQL92 rules */ );
 
-	/* restore namespace, remove jrte from rtable */
+	/* restore namespace, remove join RTE from rtable */
 	pstate->p_namespace = sv_namespace;
 	pstate->p_rtable = list_truncate(pstate->p_rtable, sv_rtable_length);
 
@@ -1869,7 +1871,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		/* Process leaf SELECT */
 		Query	   *selectQuery;
 		char		selectName[32];
-		RangeTblEntry *rte PG_USED_FOR_ASSERTS_ONLY;
+		ParseNamespaceItem *nsitem;
 		RangeTblRef *rtr;
 		ListCell   *tl;
 
@@ -1926,19 +1928,17 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		 */
 		snprintf(selectName, sizeof(selectName), "*SELECT* %d",
 				 list_length(pstate->p_rtable) + 1);
-		rte = addRangeTableEntryForSubquery(pstate,
-											selectQuery,
-											makeAlias(selectName, NIL),
-											false,
-											false);
+		nsitem = addRangeTableEntryForSubquery(pstate,
+											   selectQuery,
+											   makeAlias(selectName, NIL),
+											   false,
+											   false);
 
 		/*
 		 * Return a RangeTblRef to replace the SelectStmt in the set-op tree.
 		 */
 		rtr = makeNode(RangeTblRef);
-		/* assume new rte is at end */
-		rtr->rtindex = list_length(pstate->p_rtable);
-		Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
+		rtr->rtindex = nsitem->p_rtindex;
 		return (Node *) rtr;
 	}
 	else
@@ -2236,9 +2236,8 @@ transformUpdateStmt(ParseState *pstate, UpdateStmt *stmt)
 										 stmt->relation->inh,
 										 true,
 										 ACL_UPDATE);
-
-	/* grab the namespace item made by setTargetTable */
-	nsitem = (ParseNamespaceItem *) llast(pstate->p_namespace);
+	nsitem = pstate->p_target_nsitem;
+	Assert(nsitem->p_rtindex == qry->resultRelation);
 
 	/* subqueries in FROM cannot access the result relation */
 	nsitem->p_lateral_only = true;
@@ -2297,7 +2296,7 @@ transformUpdateTargetList(ParseState *pstate, List *origTlist)
 		pstate->p_next_resno = RelationGetNumberOfAttributes(pstate->p_target_relation) + 1;
 
 	/* Prepare non-junk columns for assignment to target table */
-	target_rte = pstate->p_target_rangetblentry;
+	target_rte = pstate->p_target_nsitem->p_rte;
 	orig_tl = list_head(origTlist);
 
 	foreach(tl, tlist)
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index ebbba2d..80d27f5 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -51,37 +51,33 @@
 #include "utils/rel.h"
 #include "utils/syscache.h"
 
-/* Convenience macro for the most common makeNamespaceItem() case */
-#define makeDefaultNSItem(rte, rti) \
-	makeNamespaceItem(rte, rti, true, true, false, true)
 
 static void extractRemainingColumns(List *common_colnames,
 									List *src_colnames, List *src_colvars,
-									List **res_colnames, List **res_colvars);
+									ParseNamespaceColumn *src_nscolumns,
+									List **res_colnames, List **res_colvars,
+									ParseNamespaceColumn *res_nscolumns);
 static Node *transformJoinUsingClause(ParseState *pstate,
 									  RangeTblEntry *leftRTE, RangeTblEntry *rightRTE,
 									  List *leftVars, List *rightVars);
 static Node *transformJoinOnClause(ParseState *pstate, JoinExpr *j,
 								   List *namespace);
-static RangeTblEntry *getRTEForSpecialRelationTypes(ParseState *pstate,
-													RangeVar *rv);
-static RangeTblEntry *transformTableEntry(ParseState *pstate, RangeVar *r);
-static RangeTblEntry *transformRangeSubselect(ParseState *pstate,
-											  RangeSubselect *r);
-static RangeTblEntry *transformRangeFunction(ParseState *pstate,
-											 RangeFunction *r);
-static RangeTblEntry *transformRangeTableFunc(ParseState *pstate,
-											  RangeTableFunc *t);
+static ParseNamespaceItem *transformTableEntry(ParseState *pstate, RangeVar *r);
+static ParseNamespaceItem *transformRangeSubselect(ParseState *pstate,
+												   RangeSubselect *r);
+static ParseNamespaceItem *transformRangeFunction(ParseState *pstate,
+												  RangeFunction *r);
+static ParseNamespaceItem *transformRangeTableFunc(ParseState *pstate,
+												   RangeTableFunc *t);
 static TableSampleClause *transformRangeTableSample(ParseState *pstate,
 													RangeTableSample *rts);
+static ParseNamespaceItem *getNSItemForSpecialRelationTypes(ParseState *pstate,
+															RangeVar *rv);
 static Node *transformFromClauseItem(ParseState *pstate, Node *n,
-									 RangeTblEntry **top_rte, int *top_rti,
+									 ParseNamespaceItem **top_nsitem,
 									 List **namespace);
 static Node *buildMergedJoinVar(ParseState *pstate, JoinType jointype,
 								Var *l_colvar, Var *r_colvar);
-static ParseNamespaceItem *makeNamespaceItem(RangeTblEntry *rte, int rtindex,
-											 bool rel_visible, bool cols_visible,
-											 bool lateral_only, bool lateral_ok);
 static void setNamespaceColumnVisibility(List *namespace, bool cols_visible);
 static void setNamespaceLateralState(List *namespace,
 									 bool lateral_only, bool lateral_ok);
@@ -130,13 +126,11 @@ transformFromClause(ParseState *pstate, List *frmList)
 	foreach(fl, frmList)
 	{
 		Node	   *n = lfirst(fl);
-		RangeTblEntry *rte;
-		int			rtindex;
+		ParseNamespaceItem *nsitem;
 		List	   *namespace;
 
 		n = transformFromClauseItem(pstate, n,
-									&rte,
-									&rtindex,
+									&nsitem,
 									&namespace);
 
 		checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace);
@@ -183,8 +177,7 @@ int
 setTargetTable(ParseState *pstate, RangeVar *relation,
 			   bool inh, bool alsoSource, AclMode requiredPerms)
 {
-	RangeTblEntry *rte;
-	int			rtindex;
+	ParseNamespaceItem *nsitem;
 
 	/*
 	 * ENRs hide tables of the same name, so we need to check for them first.
@@ -212,19 +205,14 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 												RowExclusiveLock);
 
 	/*
-	 * Now build an RTE.
+	 * Now build an RTE and a ParseNamespaceItem.
 	 */
-	rte = addRangeTableEntryForRelation(pstate, pstate->p_target_relation,
-										RowExclusiveLock,
-										relation->alias, inh, false);
+	nsitem = addRangeTableEntryForRelation(pstate, pstate->p_target_relation,
+										   RowExclusiveLock,
+										   relation->alias, inh, false);
 
-	/* assume new rte is at end */
-	rtindex = list_length(pstate->p_rtable);
-	Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
-
-	/* remember the RTE as being the query target */
-	pstate->p_target_rangetblentry = rte;
-	pstate->p_target_rtindex = rtindex;
+	/* remember the RTE/nsitem as being the query target */
+	pstate->p_target_nsitem = nsitem;
 
 	/*
 	 * Override addRangeTableEntry's default ACL_SELECT permissions check, and
@@ -235,28 +223,30 @@ setTargetTable(ParseState *pstate, RangeVar *relation,
 	 * analysis, we will add the ACL_SELECT bit back again; see
 	 * markVarForSelectPriv and its callers.
 	 */
-	rte->requiredPerms = requiredPerms;
+	nsitem->p_rte->requiredPerms = requiredPerms;
 
 	/*
 	 * If UPDATE/DELETE, add table to joinlist and namespace.
-	 *
-	 * Note: some callers know that they can find the new ParseNamespaceItem
-	 * at the end of the pstate->p_namespace list.  This is a bit ugly but not
-	 * worth complicating this function's signature for.
 	 */
 	if (alsoSource)
-		addRTEtoQuery(pstate, rte, true, true, true);
+		addNSItemtoQuery(pstate, nsitem, true, true, true);
 
-	return rtindex;
+	return nsitem->p_rtindex;
 }
 
 /*
  * Extract all not-in-common columns from column lists of a source table
+ *
+ * We hand back new lists in *res_colnames and *res_colvars.  But
+ * res_nscolumns points to a caller-allocated array that we fill in
+ * the next few entries in.
  */
 static void
 extractRemainingColumns(List *common_colnames,
 						List *src_colnames, List *src_colvars,
-						List **res_colnames, List **res_colvars)
+						ParseNamespaceColumn *src_nscolumns,
+						List **res_colnames, List **res_colvars,
+						ParseNamespaceColumn *res_nscolumns)
 {
 	List	   *new_colnames = NIL;
 	List	   *new_colvars = NIL;
@@ -271,6 +261,14 @@ extractRemainingColumns(List *common_colnames,
 		bool		match = false;
 		ListCell   *cnames;
 
+		/*
+		 * Ignore any dropped columns in the src_nscolumns input.  (The list
+		 * inputs won't contain entries for dropped columns.)
+		 */
+		while (src_nscolumns->p_varno == 0)
+			src_nscolumns++;
+
+		/* is current src column already accounted for in common_colnames? */
 		foreach(cnames, common_colnames)
 		{
 			char	   *ccolname = strVal(lfirst(cnames));
@@ -284,9 +282,15 @@ extractRemainingColumns(List *common_colnames,
 
 		if (!match)
 		{
+			/* Nope, so emit it as next output column */
 			new_colnames = lappend(new_colnames, lfirst(lnames));
 			new_colvars = lappend(new_colvars, lfirst(lvars));
+			/* Copy the input relation's nscolumn data for this column */
+			*res_nscolumns = *src_nscolumns;
+			res_nscolumns++;
 		}
+
+		src_nscolumns++;
 	}
 
 	*res_colnames = new_colnames;
@@ -388,25 +392,20 @@ transformJoinOnClause(ParseState *pstate, JoinExpr *j, List *namespace)
 /*
  * transformTableEntry --- transform a RangeVar (simple relation reference)
  */
-static RangeTblEntry *
+static ParseNamespaceItem *
 transformTableEntry(ParseState *pstate, RangeVar *r)
 {
-	RangeTblEntry *rte;
-
-	/* We need only build a range table entry */
-	rte = addRangeTableEntry(pstate, r, r->alias, r->inh, true);
-
-	return rte;
+	/* addRangeTableEntry does all the work */
+	return addRangeTableEntry(pstate, r, r->alias, r->inh, true);
 }
 
 /*
  * transformRangeSubselect --- transform a sub-SELECT appearing in FROM
  */
-static RangeTblEntry *
+static ParseNamespaceItem *
 transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 {
 	Query	   *query;
-	RangeTblEntry *rte;
 
 	/*
 	 * We require user to supply an alias for a subselect, per SQL92. To relax
@@ -454,29 +453,26 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 		elog(ERROR, "unexpected non-SELECT command in subquery in FROM");
 
 	/*
-	 * OK, build an RTE for the subquery.
+	 * OK, build an RTE and nsitem for the subquery.
 	 */
-	rte = addRangeTableEntryForSubquery(pstate,
-										query,
-										r->alias,
-										r->lateral,
-										true);
-
-	return rte;
+	return addRangeTableEntryForSubquery(pstate,
+										 query,
+										 r->alias,
+										 r->lateral,
+										 true);
 }
 
 
 /*
  * transformRangeFunction --- transform a function call appearing in FROM
  */
-static RangeTblEntry *
+static ParseNamespaceItem *
 transformRangeFunction(ParseState *pstate, RangeFunction *r)
 {
 	List	   *funcexprs = NIL;
 	List	   *funcnames = NIL;
 	List	   *coldeflists = NIL;
 	bool		is_lateral;
-	RangeTblEntry *rte;
 	ListCell   *lc;
 
 	/*
@@ -677,13 +673,11 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
 	is_lateral = r->lateral || contain_vars_of_level((Node *) funcexprs, 0);
 
 	/*
-	 * OK, build an RTE for the function.
+	 * OK, build an RTE and nsitem for the function.
 	 */
-	rte = addRangeTableEntryForFunction(pstate,
-										funcnames, funcexprs, coldeflists,
-										r, is_lateral, true);
-
-	return rte;
+	return addRangeTableEntryForFunction(pstate,
+										 funcnames, funcexprs, coldeflists,
+										 r, is_lateral, true);
 }
 
 /*
@@ -694,13 +688,12 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
  * row-generating expression, the column-generating expressions, and the
  * default value expressions.
  */
-static RangeTblEntry *
+static ParseNamespaceItem *
 transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf)
 {
 	TableFunc  *tf = makeNode(TableFunc);
 	const char *constructName;
 	Oid			docType;
-	RangeTblEntry *rte;
 	bool		is_lateral;
 	ListCell   *col;
 	char	  **names;
@@ -903,10 +896,8 @@ transformRangeTableFunc(ParseState *pstate, RangeTableFunc *rtf)
 	 */
 	is_lateral = rtf->lateral || contain_vars_of_level((Node *) tf, 0);
 
-	rte = addRangeTableEntryForTableFunc(pstate,
-										 tf, rtf->alias, is_lateral, true);
-
-	return rte;
+	return addRangeTableEntryForTableFunc(pstate,
+										  tf, rtf->alias, is_lateral, true);
 }
 
 /*
@@ -1013,17 +1004,17 @@ transformRangeTableSample(ParseState *pstate, RangeTableSample *rts)
 }
 
 /*
- * getRTEForSpecialRelationTypes
+ * getNSItemForSpecialRelationTypes
  *
  * If given RangeVar refers to a CTE or an EphemeralNamedRelation,
- * build and return an appropriate RTE, otherwise return NULL
+ * build and return an appropriate ParseNamespaceItem, otherwise return NULL
  */
-static RangeTblEntry *
-getRTEForSpecialRelationTypes(ParseState *pstate, RangeVar *rv)
+static ParseNamespaceItem *
+getNSItemForSpecialRelationTypes(ParseState *pstate, RangeVar *rv)
 {
+	ParseNamespaceItem *nsitem;
 	CommonTableExpr *cte;
 	Index		levelsup;
-	RangeTblEntry *rte;
 
 	/*
 	 * if it is a qualified name, it can't be a CTE or tuplestore reference
@@ -1033,13 +1024,13 @@ getRTEForSpecialRelationTypes(ParseState *pstate, RangeVar *rv)
 
 	cte = scanNameSpaceForCTE(pstate, rv->relname, &levelsup);
 	if (cte)
-		rte = addRangeTableEntryForCTE(pstate, cte, levelsup, rv, true);
+		nsitem = addRangeTableEntryForCTE(pstate, cte, levelsup, rv, true);
 	else if (scanNameSpaceForENR(pstate, rv->relname))
-		rte = addRangeTableEntryForENR(pstate, rv, true);
+		nsitem = addRangeTableEntryForENR(pstate, rv, true);
 	else
-		rte = NULL;
+		nsitem = NULL;
 
-	return rte;
+	return nsitem;
 }
 
 /*
@@ -1053,11 +1044,9 @@ getRTEForSpecialRelationTypes(ParseState *pstate, RangeVar *rv)
  * The function return value is the node to add to the jointree (a
  * RangeTblRef or JoinExpr).  Additional output parameters are:
  *
- * *top_rte: receives the RTE corresponding to the jointree item.
- * (We could extract this from the function return node, but it saves cycles
- * to pass it back separately.)
- *
- * *top_rti: receives the rangetable index of top_rte.  (Ditto.)
+ * *top_nsitem: receives the ParseNamespaceItem directly corresponding to the
+ * jointree item.  (This is only used during internal recursion, not by
+ * outside callers.)
  *
  * *namespace: receives a List of ParseNamespaceItems for the RTEs exposed
  * as table/column names by this item.  (The lateral_only flags in these items
@@ -1065,7 +1054,7 @@ getRTEForSpecialRelationTypes(ParseState *pstate, RangeVar *rv)
  */
 static Node *
 transformFromClauseItem(ParseState *pstate, Node *n,
-						RangeTblEntry **top_rte, int *top_rti,
+						ParseNamespaceItem **top_nsitem,
 						List **namespace)
 {
 	if (IsA(n, RangeVar))
@@ -1073,78 +1062,58 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		/* Plain relation reference, or perhaps a CTE reference */
 		RangeVar   *rv = (RangeVar *) n;
 		RangeTblRef *rtr;
-		RangeTblEntry *rte;
-		int			rtindex;
+		ParseNamespaceItem *nsitem;
 
 		/* Check if it's a CTE or tuplestore reference */
-		rte = getRTEForSpecialRelationTypes(pstate, rv);
+		nsitem = getNSItemForSpecialRelationTypes(pstate, rv);
 
 		/* if not found above, must be a table reference */
-		if (!rte)
-			rte = transformTableEntry(pstate, rv);
-
-		/* assume new rte is at end */
-		rtindex = list_length(pstate->p_rtable);
-		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
-		*top_rte = rte;
-		*top_rti = rtindex;
-		*namespace = list_make1(makeDefaultNSItem(rte, rtindex));
+		if (!nsitem)
+			nsitem = transformTableEntry(pstate, rv);
+
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
 		rtr = makeNode(RangeTblRef);
-		rtr->rtindex = rtindex;
+		rtr->rtindex = nsitem->p_rtindex;
 		return (Node *) rtr;
 	}
 	else if (IsA(n, RangeSubselect))
 	{
 		/* sub-SELECT is like a plain relation */
 		RangeTblRef *rtr;
-		RangeTblEntry *rte;
-		int			rtindex;
-
-		rte = transformRangeSubselect(pstate, (RangeSubselect *) n);
-		/* assume new rte is at end */
-		rtindex = list_length(pstate->p_rtable);
-		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
-		*top_rte = rte;
-		*top_rti = rtindex;
-		*namespace = list_make1(makeDefaultNSItem(rte, rtindex));
+		ParseNamespaceItem *nsitem;
+
+		nsitem = transformRangeSubselect(pstate, (RangeSubselect *) n);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
 		rtr = makeNode(RangeTblRef);
-		rtr->rtindex = rtindex;
+		rtr->rtindex = nsitem->p_rtindex;
 		return (Node *) rtr;
 	}
 	else if (IsA(n, RangeFunction))
 	{
 		/* function is like a plain relation */
 		RangeTblRef *rtr;
-		RangeTblEntry *rte;
-		int			rtindex;
-
-		rte = transformRangeFunction(pstate, (RangeFunction *) n);
-		/* assume new rte is at end */
-		rtindex = list_length(pstate->p_rtable);
-		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
-		*top_rte = rte;
-		*top_rti = rtindex;
-		*namespace = list_make1(makeDefaultNSItem(rte, rtindex));
+		ParseNamespaceItem *nsitem;
+
+		nsitem = transformRangeFunction(pstate, (RangeFunction *) n);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
 		rtr = makeNode(RangeTblRef);
-		rtr->rtindex = rtindex;
+		rtr->rtindex = nsitem->p_rtindex;
 		return (Node *) rtr;
 	}
 	else if (IsA(n, RangeTableFunc))
 	{
 		/* table function is like a plain relation */
 		RangeTblRef *rtr;
-		RangeTblEntry *rte;
-		int			rtindex;
-
-		rte = transformRangeTableFunc(pstate, (RangeTableFunc *) n);
-		/* assume new rte is at end */
-		rtindex = list_length(pstate->p_rtable);
-		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
-		*top_rte = rte;
-		*top_rti = rtindex;
-		*namespace = list_make1(makeDefaultNSItem(rte, rtindex));
+		ParseNamespaceItem *nsitem;
+
+		nsitem = transformRangeTableFunc(pstate, (RangeTableFunc *) n);
+		*top_nsitem = nsitem;
+		*namespace = list_make1(nsitem);
 		rtr = makeNode(RangeTblRef);
-		rtr->rtindex = rtindex;
+		rtr->rtindex = nsitem->p_rtindex;
 		return (Node *) rtr;
 	}
 	else if (IsA(n, RangeTableSample))
@@ -1152,19 +1121,17 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		/* TABLESAMPLE clause (wrapping some other valid FROM node) */
 		RangeTableSample *rts = (RangeTableSample *) n;
 		Node	   *rel;
-		RangeTblRef *rtr;
 		RangeTblEntry *rte;
 
 		/* Recursively transform the contained relation */
 		rel = transformFromClauseItem(pstate, rts->relation,
-									  top_rte, top_rti, namespace);
-		/* Currently, grammar could only return a RangeVar as contained rel */
-		rtr = castNode(RangeTblRef, rel);
-		rte = rt_fetch(rtr->rtindex, pstate->p_rtable);
+									  top_nsitem, namespace);
+		rte = (*top_nsitem)->p_rte;
 		/* We only support this on plain relations and matviews */
-		if (rte->relkind != RELKIND_RELATION &&
-			rte->relkind != RELKIND_MATVIEW &&
-			rte->relkind != RELKIND_PARTITIONED_TABLE)
+		if (rte->rtekind != RTE_RELATION ||
+			(rte->relkind != RELKIND_RELATION &&
+			 rte->relkind != RELKIND_MATVIEW &&
+			 rte->relkind != RELKIND_PARTITIONED_TABLE))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("TABLESAMPLE clause can only be applied to tables and materialized views"),
@@ -1172,16 +1139,15 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 
 		/* Transform TABLESAMPLE details and attach to the RTE */
 		rte->tablesample = transformRangeTableSample(pstate, rts);
-		return (Node *) rtr;
+		return rel;
 	}
 	else if (IsA(n, JoinExpr))
 	{
 		/* A newfangled join expression */
 		JoinExpr   *j = (JoinExpr *) n;
-		RangeTblEntry *l_rte;
-		RangeTblEntry *r_rte;
-		int			l_rtindex;
-		int			r_rtindex;
+		ParseNamespaceItem *nsitem;
+		ParseNamespaceItem *l_nsitem;
+		ParseNamespaceItem *r_nsitem;
 		List	   *l_namespace,
 				   *r_namespace,
 				   *my_namespace,
@@ -1191,9 +1157,10 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 				   *l_colvars,
 				   *r_colvars,
 				   *res_colvars;
+		ParseNamespaceColumn *res_nscolumns;
+		int			res_colindex;
 		bool		lateral_ok;
 		int			sv_namespace_length;
-		RangeTblEntry *rte;
 		int			k;
 
 		/*
@@ -1201,8 +1168,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		 * it in this order for correct visibility of LATERAL references.
 		 */
 		j->larg = transformFromClauseItem(pstate, j->larg,
-										  &l_rte,
-										  &l_rtindex,
+										  &l_nsitem,
 										  &l_namespace);
 
 		/*
@@ -1225,8 +1191,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 
 		/* And now we can process the RHS */
 		j->rarg = transformFromClauseItem(pstate, j->rarg,
-										  &r_rte,
-										  &r_rtindex,
+										  &r_nsitem,
 										  &r_namespace);
 
 		/* Remove the left-side RTEs from the namespace list again */
@@ -1248,12 +1213,10 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		/*
 		 * Extract column name and var lists from both subtrees
 		 *
-		 * Note: expandRTE returns new lists, safe for me to modify
+		 * Note: expandNSItemVars returns new lists, safe for me to modify
 		 */
-		expandRTE(l_rte, l_rtindex, 0, -1, false,
-				  &l_colnames, &l_colvars);
-		expandRTE(r_rte, r_rtindex, 0, -1, false,
-				  &r_colnames, &r_colvars);
+		l_colvars = expandNSItemVars(l_nsitem, 0, -1, &l_colnames);
+		r_colvars = expandNSItemVars(r_nsitem, 0, -1, &r_colnames);
 
 		/*
 		 * Natural join does not explicitly specify columns; must generate
@@ -1302,6 +1265,12 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		res_colnames = NIL;
 		res_colvars = NIL;
 
+		/* this may be larger than needed */
+		res_nscolumns = (ParseNamespaceColumn *)
+			palloc0((list_length(l_colnames) + list_length(r_colnames)) *
+					sizeof(ParseNamespaceColumn));
+		res_colindex = 0;
+
 		if (j->usingClause)
 		{
 			/*
@@ -1325,6 +1294,8 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 				int			r_index = -1;
 				Var		   *l_colvar,
 						   *r_colvar;
+				Node	   *u_colvar;
+				ParseNamespaceColumn *res_nscolumn;
 
 				/* Check for USING(foo,foo) */
 				foreach(col, res_colnames)
@@ -1390,16 +1361,58 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 				r_usingvars = lappend(r_usingvars, r_colvar);
 
 				res_colnames = lappend(res_colnames, lfirst(ucol));
-				res_colvars = lappend(res_colvars,
-									  buildMergedJoinVar(pstate,
-														 j->jointype,
-														 l_colvar,
-														 r_colvar));
+				u_colvar = buildMergedJoinVar(pstate,
+											  j->jointype,
+											  l_colvar,
+											  r_colvar);
+				res_colvars = lappend(res_colvars, u_colvar);
+				res_nscolumn = res_nscolumns + res_colindex;
+				res_colindex++;
+				if (u_colvar == (Node *) l_colvar)
+				{
+					/* Merged column is equivalent to left input */
+					res_nscolumn->p_varno = l_colvar->varno;
+					res_nscolumn->p_varattno = l_colvar->varattno;
+					res_nscolumn->p_vartype = l_colvar->vartype;
+					res_nscolumn->p_vartypmod = l_colvar->vartypmod;
+					res_nscolumn->p_varcollid = l_colvar->varcollid;
+					/* XXX these are not quite right, but doesn't matter yet */
+					res_nscolumn->p_varnosyn = l_colvar->varno;
+					res_nscolumn->p_varattnosyn = l_colvar->varattno;
+				}
+				else if (u_colvar == (Node *) r_colvar)
+				{
+					/* Merged column is equivalent to right input */
+					res_nscolumn->p_varno = r_colvar->varno;
+					res_nscolumn->p_varattno = r_colvar->varattno;
+					res_nscolumn->p_vartype = r_colvar->vartype;
+					res_nscolumn->p_vartypmod = r_colvar->vartypmod;
+					res_nscolumn->p_varcollid = r_colvar->varcollid;
+					/* XXX these are not quite right, but doesn't matter yet */
+					res_nscolumn->p_varnosyn = r_colvar->varno;
+					res_nscolumn->p_varattnosyn = r_colvar->varattno;
+				}
+				else
+				{
+					/*
+					 * Merged column is not semantically equivalent to either
+					 * input, so it needs to be referenced as the join output
+					 * column.  We don't know the join's varno yet, so we'll
+					 * replace these zeroes below.
+					 */
+					res_nscolumn->p_varno = 0;
+					res_nscolumn->p_varattno = res_colindex;
+					res_nscolumn->p_vartype = exprType(u_colvar);
+					res_nscolumn->p_vartypmod = exprTypmod(u_colvar);
+					res_nscolumn->p_varcollid = exprCollation(u_colvar);
+					res_nscolumn->p_varnosyn = 0;
+					res_nscolumn->p_varattnosyn = res_colindex;
+				}
 			}
 
 			j->quals = transformJoinUsingClause(pstate,
-												l_rte,
-												r_rte,
+												l_nsitem->p_rte,
+												r_nsitem->p_rte,
 												l_usingvars,
 												r_usingvars);
 		}
@@ -1416,10 +1429,16 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		/* Add remaining columns from each side to the output columns */
 		extractRemainingColumns(res_colnames,
 								l_colnames, l_colvars,
-								&l_colnames, &l_colvars);
+								l_nsitem->p_nscolumns,
+								&l_colnames, &l_colvars,
+								res_nscolumns + res_colindex);
+		res_colindex += list_length(l_colvars);
 		extractRemainingColumns(res_colnames,
 								r_colnames, r_colvars,
-								&r_colnames, &r_colvars);
+								r_nsitem->p_nscolumns,
+								&r_colnames, &r_colvars,
+								res_nscolumns + res_colindex);
+		res_colindex += list_length(r_colvars);
 		res_colnames = list_concat(res_colnames, l_colnames);
 		res_colvars = list_concat(res_colvars, l_colvars);
 		res_colnames = list_concat(res_colnames, r_colnames);
@@ -1441,21 +1460,41 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		}
 
 		/*
-		 * Now build an RTE for the result of the join
+		 * Now build an RTE and nsitem for the result of the join.
+		 * res_nscolumns isn't totally done yet, but that's OK because
+		 * addRangeTableEntryForJoin doesn't examine it, only store a pointer.
 		 */
-		rte = addRangeTableEntryForJoin(pstate,
-										res_colnames,
-										j->jointype,
-										res_colvars,
-										j->alias,
-										true);
+		nsitem = addRangeTableEntryForJoin(pstate,
+										   res_colnames,
+										   res_nscolumns,
+										   j->jointype,
+										   res_colvars,
+										   j->alias,
+										   true);
 
-		/* assume new rte is at end */
-		j->rtindex = list_length(pstate->p_rtable);
-		Assert(rte == rt_fetch(j->rtindex, pstate->p_rtable));
+		j->rtindex = nsitem->p_rtindex;
 
-		*top_rte = rte;
-		*top_rti = j->rtindex;
+		/*
+		 * Now that we know the join RTE's rangetable index, we can fix up the
+		 * res_nscolumns data in places where it should contain that.
+		 */
+		Assert(res_colindex == list_length(nsitem->p_rte->eref->colnames));
+		for (k = 0; k < res_colindex; k++)
+		{
+			ParseNamespaceColumn *nscol = res_nscolumns + k;
+
+			/* fill in join RTI for merged columns */
+			if (nscol->p_varno == 0)
+				nscol->p_varno = j->rtindex;
+			if (nscol->p_varnosyn == 0)
+				nscol->p_varnosyn = j->rtindex;
+			/* if join has an alias, it syntactically hides all inputs */
+			if (j->alias)
+			{
+				nscol->p_varnosyn = j->rtindex;
+				nscol->p_varattnosyn = k + 1;
+			}
+		}
 
 		/* make a matching link to the JoinExpr for later use */
 		for (k = list_length(pstate->p_joinexprs) + 1; k < j->rtindex; k++)
@@ -1483,13 +1522,13 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		 * The join RTE itself is always made visible for unqualified column
 		 * names.  It's visible as a relation name only if it has an alias.
 		 */
-		*namespace = lappend(my_namespace,
-							 makeNamespaceItem(rte,
-											   j->rtindex,
-											   (j->alias != NULL),
-											   true,
-											   false,
-											   true));
+		nsitem->p_rel_visible = (j->alias != NULL);
+		nsitem->p_cols_visible = true;
+		nsitem->p_lateral_only = false;
+		nsitem->p_lateral_ok = true;
+
+		*top_nsitem = nsitem;
+		*namespace = lappend(my_namespace, nsitem);
 
 		return (Node *) j;
 	}
@@ -1618,27 +1657,6 @@ buildMergedJoinVar(ParseState *pstate, JoinType jointype,
 }
 
 /*
- * makeNamespaceItem -
- *	  Convenience subroutine to construct a ParseNamespaceItem.
- */
-static ParseNamespaceItem *
-makeNamespaceItem(RangeTblEntry *rte, int rtindex,
-				  bool rel_visible, bool cols_visible,
-				  bool lateral_only, bool lateral_ok)
-{
-	ParseNamespaceItem *nsitem;
-
-	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
-	nsitem->p_rte = rte;
-	nsitem->p_rtindex = rtindex;
-	nsitem->p_rel_visible = rel_visible;
-	nsitem->p_cols_visible = cols_visible;
-	nsitem->p_lateral_only = lateral_only;
-	nsitem->p_lateral_ok = lateral_ok;
-	return nsitem;
-}
-
-/*
  * setNamespaceColumnVisibility -
  *	  Convenience subroutine to update cols_visible flags in a namespace list.
  */
@@ -3163,8 +3181,8 @@ transformOnConflictArbiter(ParseState *pstate,
 		 */
 		save_namespace = pstate->p_namespace;
 		pstate->p_namespace = NIL;
-		addRTEtoQuery(pstate, pstate->p_target_rangetblentry,
-					  false, false, true);
+		addNSItemtoQuery(pstate, pstate->p_target_nsitem,
+						 false, false, true);
 
 		if (infer->indexElems)
 			*arbiterExpr = resolve_unique_index_expr(pstate, infer,
@@ -3189,7 +3207,7 @@ transformOnConflictArbiter(ParseState *pstate,
 		if (infer->conname)
 		{
 			Oid			relid = RelationGetRelid(pstate->p_target_relation);
-			RangeTblEntry *rte = pstate->p_target_rangetblentry;
+			RangeTblEntry *rte = pstate->p_target_nsitem->p_rte;
 			Bitmapset  *conattnos;
 
 			conattnos = get_relation_constraint_attnos(relid, infer->conname,
diff --git a/src/backend/parser/parse_coerce.c b/src/backend/parser/parse_coerce.c
index a6c51a9..484298d 100644
--- a/src/backend/parser/parse_coerce.c
+++ b/src/backend/parser/parse_coerce.c
@@ -1010,11 +1010,10 @@ coerce_record_to_complex(ParseState *pstate, Node *node,
 		int			rtindex = ((Var *) node)->varno;
 		int			sublevels_up = ((Var *) node)->varlevelsup;
 		int			vlocation = ((Var *) node)->location;
-		RangeTblEntry *rte;
+		ParseNamespaceItem *nsitem;
 
-		rte = GetRTEByRangeTablePosn(pstate, rtindex, sublevels_up);
-		expandRTE(rte, rtindex, sublevels_up, vlocation, false,
-				  NULL, &args);
+		nsitem = GetNSItemByRangeTablePosn(pstate, rtindex, sublevels_up);
+		args = expandNSItemVars(nsitem, sublevels_up, vlocation, NULL);
 	}
 	else
 		ereport(ERROR,
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 25e92de..06511df 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -2656,8 +2656,8 @@ static Node *
 transformCurrentOfExpr(ParseState *pstate, CurrentOfExpr *cexpr)
 {
 	/* CURRENT OF can only appear at top level of UPDATE/DELETE */
-	Assert(pstate->p_target_rtindex > 0);
-	cexpr->cvarno = pstate->p_target_rtindex;
+	Assert(pstate->p_target_nsitem != NULL);
+	cexpr->cvarno = pstate->p_target_nsitem->p_rtindex;
 
 	/*
 	 * Check to see if the cursor name matches a parameter of type REFCURSOR.
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 4888311..113bb51 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -472,7 +472,8 @@ check_lateral_ref_ok(ParseState *pstate, ParseNamespaceItem *nsitem,
 				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
 				 errmsg("invalid reference to FROM-clause entry for table \"%s\"",
 						refname),
-				 (rte == pstate->p_target_rangetblentry) ?
+				 (pstate->p_target_nsitem != NULL &&
+				  rte == pstate->p_target_nsitem->p_rte) ?
 				 errhint("There is an entry for table \"%s\", but it cannot be referenced from this part of the query.",
 						 refname) :
 				 errdetail("The combining JOIN type must be INNER or LEFT for a LATERAL reference."),
@@ -669,9 +670,6 @@ scanNSItemForColumn(ParseState *pstate, ParseNamespaceItem *nsitem,
 	RangeTblEntry *rte = nsitem->p_rte;
 	int			attnum;
 	Var		   *var;
-	Oid			vartypeid;
-	int32		vartypmod;
-	Oid			varcollid;
 
 	/*
 	 * Scan the RTE's column names (or aliases) for a match.  Complain if
@@ -703,11 +701,39 @@ scanNSItemForColumn(ParseState *pstate, ParseNamespaceItem *nsitem,
 				 parser_errposition(pstate, location)));
 
 	/* Found a valid match, so build a Var */
-	get_rte_attribute_type(rte, attnum,
-						   &vartypeid, &vartypmod, &varcollid);
-	var = makeVar(nsitem->p_rtindex, attnum,
-				  vartypeid, vartypmod, varcollid,
-				  sublevels_up);
+	if (attnum > InvalidAttrNumber)
+	{
+		/* Get attribute data from the ParseNamespaceColumn array */
+		ParseNamespaceColumn *nscol = &nsitem->p_nscolumns[attnum - 1];
+
+		/* Complain if dropped column.  See notes in scanRTEForColumn. */
+		if (nscol->p_varno == 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					 errmsg("column \"%s\" of relation \"%s\" does not exist",
+							colname,
+							rte->eref->aliasname)));
+
+		var = makeVar(nsitem->p_rtindex,
+					  attnum,
+					  nscol->p_vartype,
+					  nscol->p_vartypmod,
+					  nscol->p_varcollid,
+					  sublevels_up);
+	}
+	else
+	{
+		/* System column, so use predetermined type data */
+		const FormData_pg_attribute *sysatt;
+
+		sysatt = SystemAttributeDefinition(attnum);
+		var = makeVar(nsitem->p_rtindex,
+					  attnum,
+					  sysatt->atttypid,
+					  sysatt->atttypmod,
+					  sysatt->attcollation,
+					  sublevels_up);
+	}
 	var->location = location;
 
 	/* Require read access to the column */
@@ -753,11 +779,9 @@ scanRTEForColumn(ParseState *pstate, RangeTblEntry *rte,
 	 * don't bother to test for that case here.
 	 *
 	 * Should this somehow go wrong and we try to access a dropped column,
-	 * we'll still catch it by virtue of the checks in
-	 * get_rte_attribute_type(), which is called by scanNSItemForColumn().
-	 * That routine has to do a cache lookup anyway, so the check there is
-	 * cheap.  Callers interested in finding match with shortest distance need
-	 * to defend against this directly, though.
+	 * we'll still catch it by virtue of the check in scanNSItemForColumn().
+	 * Callers interested in finding match with shortest distance need to
+	 * defend against this directly, though.
 	 */
 	foreach(c, rte->eref->colnames)
 	{
@@ -1201,6 +1225,121 @@ chooseScalarFunctionAlias(Node *funcexpr, char *funcname,
 }
 
 /*
+ * buildNSItemFromTupleDesc
+ *		Build a ParseNamespaceItem, given a tupdesc describing the columns.
+ *
+ * rte: the new RangeTblEntry for the rel
+ * rtindex: its index in the rangetable list
+ * tupdesc: the physical column information
+ */
+static ParseNamespaceItem *
+buildNSItemFromTupleDesc(RangeTblEntry *rte, Index rtindex, TupleDesc tupdesc)
+{
+	ParseNamespaceItem *nsitem;
+	ParseNamespaceColumn *nscolumns;
+	int			maxattrs = tupdesc->natts;
+	int			varattno;
+
+	/* colnames must have the same number of entries as the nsitem */
+	Assert(maxattrs == list_length(rte->eref->colnames));
+
+	/* extract per-column data from the tupdesc */
+	nscolumns = (ParseNamespaceColumn *)
+		palloc0(maxattrs * sizeof(ParseNamespaceColumn));
+
+	for (varattno = 0; varattno < maxattrs; varattno++)
+	{
+		Form_pg_attribute attr = TupleDescAttr(tupdesc, varattno);
+
+		/* For a dropped column, just leave the entry as zeroes */
+		if (attr->attisdropped)
+			continue;
+
+		nscolumns[varattno].p_varno = rtindex;
+		nscolumns[varattno].p_varattno = varattno + 1;
+		nscolumns[varattno].p_vartype = attr->atttypid;
+		nscolumns[varattno].p_vartypmod = attr->atttypmod;
+		nscolumns[varattno].p_varcollid = attr->attcollation;
+		nscolumns[varattno].p_varnosyn = rtindex;
+		nscolumns[varattno].p_varattnosyn = varattno + 1;
+	}
+
+	/* ... and build the nsitem */
+	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
+	nsitem->p_rte = rte;
+	nsitem->p_rtindex = rtindex;
+	nsitem->p_nscolumns = nscolumns;
+	/* set default visibility flags; might get changed later */
+	nsitem->p_rel_visible = true;
+	nsitem->p_cols_visible = true;
+	nsitem->p_lateral_only = false;
+	nsitem->p_lateral_ok = true;
+
+	return nsitem;
+}
+
+/*
+ * buildNSItemFromLists
+ *		Build a ParseNamespaceItem, given column type information in lists.
+ *
+ * rte: the new RangeTblEntry for the rel
+ * rtindex: its index in the rangetable list
+ * coltypes: per-column datatype OIDs
+ * coltypmods: per-column type modifiers
+ * colcollation: per-column collation OIDs
+ */
+static ParseNamespaceItem *
+buildNSItemFromLists(RangeTblEntry *rte, Index rtindex,
+					 List *coltypes, List *coltypmods, List *colcollations)
+{
+	ParseNamespaceItem *nsitem;
+	ParseNamespaceColumn *nscolumns;
+	int			maxattrs = list_length(coltypes);
+	int			varattno;
+	ListCell   *lct;
+	ListCell   *lcm;
+	ListCell   *lcc;
+
+	/* colnames must have the same number of entries as the nsitem */
+	Assert(maxattrs == list_length(rte->eref->colnames));
+
+	Assert(maxattrs == list_length(coltypmods));
+	Assert(maxattrs == list_length(colcollations));
+
+	/* extract per-column data from the lists */
+	nscolumns = (ParseNamespaceColumn *)
+		palloc0(maxattrs * sizeof(ParseNamespaceColumn));
+
+	varattno = 0;
+	forthree(lct, coltypes,
+			 lcm, coltypmods,
+			 lcc, colcollations)
+	{
+		nscolumns[varattno].p_varno = rtindex;
+		nscolumns[varattno].p_varattno = varattno + 1;
+		nscolumns[varattno].p_vartype = lfirst_oid(lct);
+		nscolumns[varattno].p_vartypmod = lfirst_int(lcm);
+		nscolumns[varattno].p_varcollid = lfirst_oid(lcc);
+		nscolumns[varattno].p_varnosyn = rtindex;
+		nscolumns[varattno].p_varattnosyn = varattno + 1;
+		varattno++;
+	}
+
+	/* ... and build the nsitem */
+	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
+	nsitem->p_rte = rte;
+	nsitem->p_rtindex = rtindex;
+	nsitem->p_nscolumns = nscolumns;
+	/* set default visibility flags; might get changed later */
+	nsitem->p_rel_visible = true;
+	nsitem->p_cols_visible = true;
+	nsitem->p_lateral_only = false;
+	nsitem->p_lateral_ok = true;
+
+	return nsitem;
+}
+
+/*
  * Open a table during parse analysis
  *
  * This is essentially just the same as table_openrv(), except that it caters
@@ -1255,11 +1394,15 @@ parserOpenTable(ParseState *pstate, const RangeVar *relation, int lockmode)
 
 /*
  * Add an entry for a relation to the pstate's range table (p_rtable).
+ * Then, construct and return a ParseNamespaceItem for the new RTE.
+ *
+ * We do not link the ParseNamespaceItem into the pstate here; it's the
+ * caller's job to do that in the appropriate way.
  *
  * Note: formerly this checked for refname conflicts, but that's wrong.
  * Caller is responsible for checking for conflicts in the appropriate scope.
  */
-RangeTblEntry *
+ParseNamespaceItem *
 addRangeTableEntry(ParseState *pstate,
 				   RangeVar *relation,
 				   Alias *alias,
@@ -1270,6 +1413,7 @@ addRangeTableEntry(ParseState *pstate,
 	char	   *refname = alias ? alias->aliasname : relation->relname;
 	LOCKMODE	lockmode;
 	Relation	rel;
+	ParseNamespaceItem *nsitem;
 
 	Assert(pstate != NULL);
 
@@ -1302,13 +1446,6 @@ addRangeTableEntry(ParseState *pstate,
 	buildRelationAliases(rel->rd_att, alias, rte->eref);
 
 	/*
-	 * Drop the rel refcount, but keep the access lock till end of transaction
-	 * so that the table can't be deleted or have its schema modified
-	 * underneath us.
-	 */
-	table_close(rel, NoLock);
-
-	/*
 	 * Set flags and access permissions.
 	 *
 	 * The initial default on access checks is always check-for-READ-access,
@@ -1326,16 +1463,32 @@ addRangeTableEntry(ParseState *pstate,
 	rte->extraUpdatedCols = NULL;
 
 	/*
-	 * Add completed RTE to pstate's range table list, but not to join list
-	 * nor namespace --- caller must do that if appropriate.
+	 * Add completed RTE to pstate's range table list, so that we know its
+	 * index.  But we don't add it to the join list --- caller must do that if
+	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
 
-	return rte;
+	/*
+	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
+	 * list --- caller must do that if appropriate.
+	 */
+	nsitem = buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+									  rel->rd_att);
+
+	/*
+	 * Drop the rel refcount, but keep the access lock till end of transaction
+	 * so that the table can't be deleted or have its schema modified
+	 * underneath us.
+	 */
+	table_close(rel, NoLock);
+
+	return nsitem;
 }
 
 /*
  * Add an entry for a relation to the pstate's range table (p_rtable).
+ * Then, construct and return a ParseNamespaceItem for the new RTE.
  *
  * This is just like addRangeTableEntry() except that it makes an RTE
  * given an already-open relation instead of a RangeVar reference.
@@ -1349,7 +1502,7 @@ addRangeTableEntry(ParseState *pstate,
  * would require importing storage/lock.h into parse_relation.h.  Since
  * LOCKMODE is typedef'd as int anyway, that seems like overkill.
  */
-RangeTblEntry *
+ParseNamespaceItem *
 addRangeTableEntryForRelation(ParseState *pstate,
 							  Relation rel,
 							  int lockmode,
@@ -1398,21 +1551,28 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	rte->extraUpdatedCols = NULL;
 
 	/*
-	 * Add completed RTE to pstate's range table list, but not to join list
-	 * nor namespace --- caller must do that if appropriate.
+	 * Add completed RTE to pstate's range table list, so that we know its
+	 * index.  But we don't add it to the join list --- caller must do that if
+	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
 
-	return rte;
+	/*
+	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
+	 * list --- caller must do that if appropriate.
+	 */
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+									rel->rd_att);
 }
 
 /*
  * Add an entry for a subquery to the pstate's range table (p_rtable).
+ * Then, construct and return a ParseNamespaceItem for the new RTE.
  *
  * This is just like addRangeTableEntry() except that it makes a subquery RTE.
  * Note that an alias clause *must* be supplied.
  */
-RangeTblEntry *
+ParseNamespaceItem *
 addRangeTableEntryForSubquery(ParseState *pstate,
 							  Query *subquery,
 							  Alias *alias,
@@ -1423,6 +1583,9 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	char	   *refname = alias->aliasname;
 	Alias	   *eref;
 	int			numaliases;
+	List	   *coltypes,
+			   *coltypmods,
+			   *colcollations;
 	int			varattno;
 	ListCell   *tlistitem;
 
@@ -1435,7 +1598,8 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	eref = copyObject(alias);
 	numaliases = list_length(eref->colnames);
 
-	/* fill in any unspecified alias columns */
+	/* fill in any unspecified alias columns, and extract column type info */
+	coltypes = coltypmods = colcollations = NIL;
 	varattno = 0;
 	foreach(tlistitem, subquery->targetList)
 	{
@@ -1452,6 +1616,12 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 			attrname = pstrdup(te->resname);
 			eref->colnames = lappend(eref->colnames, makeString(attrname));
 		}
+		coltypes = lappend_oid(coltypes,
+							   exprType((Node *) te->expr));
+		coltypmods = lappend_int(coltypmods,
+								 exprTypmod((Node *) te->expr));
+		colcollations = lappend_oid(colcollations,
+									exprCollation((Node *) te->expr));
 	}
 	if (varattno < numaliases)
 		ereport(ERROR,
@@ -1478,21 +1648,27 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 	rte->extraUpdatedCols = NULL;
 
 	/*
-	 * Add completed RTE to pstate's range table list, but not to join list
-	 * nor namespace --- caller must do that if appropriate.
+	 * Add completed RTE to pstate's range table list, so that we know its
+	 * index.  But we don't add it to the join list --- caller must do that if
+	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
 
-	return rte;
+	/*
+	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
+	 * list --- caller must do that if appropriate.
+	 */
+	return buildNSItemFromLists(rte, list_length(pstate->p_rtable),
+								coltypes, coltypmods, colcollations);
 }
 
 /*
  * Add an entry for a function (or functions) to the pstate's range table
- * (p_rtable).
+ * (p_rtable).  Then, construct and return a ParseNamespaceItem for the new RTE.
  *
  * This is just like addRangeTableEntry() except that it makes a function RTE.
  */
-RangeTblEntry *
+ParseNamespaceItem *
 addRangeTableEntryForFunction(ParseState *pstate,
 							  List *funcnames,
 							  List *funcexprs,
@@ -1742,20 +1918,27 @@ addRangeTableEntryForFunction(ParseState *pstate,
 	rte->extraUpdatedCols = NULL;
 
 	/*
-	 * Add completed RTE to pstate's range table list, but not to join list
-	 * nor namespace --- caller must do that if appropriate.
+	 * Add completed RTE to pstate's range table list, so that we know its
+	 * index.  But we don't add it to the join list --- caller must do that if
+	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
 
-	return rte;
+	/*
+	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
+	 * list --- caller must do that if appropriate.
+	 */
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+									tupdesc);
 }
 
 /*
  * Add an entry for a table function to the pstate's range table (p_rtable).
+ * Then, construct and return a ParseNamespaceItem for the new RTE.
  *
  * This is much like addRangeTableEntry() except that it makes a tablefunc RTE.
  */
-RangeTblEntry *
+ParseNamespaceItem *
 addRangeTableEntryForTableFunc(ParseState *pstate,
 							   TableFunc *tf,
 							   Alias *alias,
@@ -1806,20 +1989,28 @@ addRangeTableEntryForTableFunc(ParseState *pstate,
 	rte->extraUpdatedCols = NULL;
 
 	/*
-	 * Add completed RTE to pstate's range table list, but not to join list
-	 * nor namespace --- caller must do that if appropriate.
+	 * Add completed RTE to pstate's range table list, so that we know its
+	 * index.  But we don't add it to the join list --- caller must do that if
+	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
 
-	return rte;
+	/*
+	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
+	 * list --- caller must do that if appropriate.
+	 */
+	return buildNSItemFromLists(rte, list_length(pstate->p_rtable),
+								rte->coltypes, rte->coltypmods,
+								rte->colcollations);
 }
 
 /*
  * Add an entry for a VALUES list to the pstate's range table (p_rtable).
+ * Then, construct and return a ParseNamespaceItem for the new RTE.
  *
  * This is much like addRangeTableEntry() except that it makes a values RTE.
  */
-RangeTblEntry *
+ParseNamespaceItem *
 addRangeTableEntryForValues(ParseState *pstate,
 							List *exprs,
 							List *coltypes,
@@ -1885,22 +2076,33 @@ addRangeTableEntryForValues(ParseState *pstate,
 	rte->extraUpdatedCols = NULL;
 
 	/*
-	 * Add completed RTE to pstate's range table list, but not to join list
-	 * nor namespace --- caller must do that if appropriate.
+	 * Add completed RTE to pstate's range table list, so that we know its
+	 * index.  But we don't add it to the join list --- caller must do that if
+	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
 
-	return rte;
+	/*
+	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
+	 * list --- caller must do that if appropriate.
+	 */
+	return buildNSItemFromLists(rte, list_length(pstate->p_rtable),
+								rte->coltypes, rte->coltypmods,
+								rte->colcollations);
 }
 
 /*
  * Add an entry for a join to the pstate's range table (p_rtable).
+ * Then, construct and return a ParseNamespaceItem for the new RTE.
  *
  * This is much like addRangeTableEntry() except that it makes a join RTE.
+ * Also, it's more convenient for the caller to construct the
+ * ParseNamespaceColumn array, so we pass that in.
  */
-RangeTblEntry *
+ParseNamespaceItem *
 addRangeTableEntryForJoin(ParseState *pstate,
 						  List *colnames,
+						  ParseNamespaceColumn *nscolumns,
 						  JoinType jointype,
 						  List *aliasvars,
 						  Alias *alias,
@@ -1909,6 +2111,7 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
 	Alias	   *eref;
 	int			numaliases;
+	ParseNamespaceItem *nsitem;
 
 	Assert(pstate != NULL);
 
@@ -1956,20 +2159,36 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	rte->extraUpdatedCols = NULL;
 
 	/*
-	 * Add completed RTE to pstate's range table list, but not to join list
-	 * nor namespace --- caller must do that if appropriate.
+	 * Add completed RTE to pstate's range table list, so that we know its
+	 * index.  But we don't add it to the join list --- caller must do that if
+	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
 
-	return rte;
+	/*
+	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
+	 * list --- caller must do that if appropriate.
+	 */
+	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
+	nsitem->p_rte = rte;
+	nsitem->p_rtindex = list_length(pstate->p_rtable);
+	nsitem->p_nscolumns = nscolumns;
+	/* set default visibility flags; might get changed later */
+	nsitem->p_rel_visible = true;
+	nsitem->p_cols_visible = true;
+	nsitem->p_lateral_only = false;
+	nsitem->p_lateral_ok = true;
+
+	return nsitem;
 }
 
 /*
  * Add an entry for a CTE reference to the pstate's range table (p_rtable).
+ * Then, construct and return a ParseNamespaceItem for the new RTE.
  *
  * This is much like addRangeTableEntry() except that it makes a CTE RTE.
  */
-RangeTblEntry *
+ParseNamespaceItem *
 addRangeTableEntryForCTE(ParseState *pstate,
 						 CommonTableExpr *cte,
 						 Index levelsup,
@@ -2059,17 +2278,25 @@ addRangeTableEntryForCTE(ParseState *pstate,
 	rte->extraUpdatedCols = NULL;
 
 	/*
-	 * Add completed RTE to pstate's range table list, but not to join list
-	 * nor namespace --- caller must do that if appropriate.
+	 * Add completed RTE to pstate's range table list, so that we know its
+	 * index.  But we don't add it to the join list --- caller must do that if
+	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
 
-	return rte;
+	/*
+	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
+	 * list --- caller must do that if appropriate.
+	 */
+	return buildNSItemFromLists(rte, list_length(pstate->p_rtable),
+								rte->coltypes, rte->coltypmods,
+								rte->colcollations);
 }
 
 /*
  * Add an entry for an ephemeral named relation reference to the pstate's
  * range table (p_rtable).
+ * Then, construct and return a ParseNamespaceItem for the new RTE.
  *
  * It is expected that the RangeVar, which up until now is only known to be an
  * ephemeral named relation, will (in conjunction with the QueryEnvironment in
@@ -2079,7 +2306,7 @@ addRangeTableEntryForCTE(ParseState *pstate,
  * This is much like addRangeTableEntry() except that it makes an RTE for an
  * ephemeral named relation.
  */
-RangeTblEntry *
+ParseNamespaceItem *
 addRangeTableEntryForENR(ParseState *pstate,
 						 RangeVar *rv,
 						 bool inFromCl)
@@ -2164,12 +2391,18 @@ addRangeTableEntryForENR(ParseState *pstate,
 	rte->selectedCols = NULL;
 
 	/*
-	 * Add completed RTE to pstate's range table list, but not to join list
-	 * nor namespace --- caller must do that if appropriate.
+	 * Add completed RTE to pstate's range table list, so that we know its
+	 * index.  But we don't add it to the join list --- caller must do that if
+	 * appropriate.
 	 */
 	pstate->p_rtable = lappend(pstate->p_rtable, rte);
 
-	return rte;
+	/*
+	 * Build a ParseNamespaceItem, but don't add it to the pstate's namespace
+	 * list --- caller must do that if appropriate.
+	 */
+	return buildNSItemFromTupleDesc(rte, list_length(pstate->p_rtable),
+									tupdesc);
 }
 
 
@@ -2221,49 +2454,26 @@ isLockedRefname(ParseState *pstate, const char *refname)
 }
 
 /*
- * Add the given RTE as a top-level entry in the pstate's join list
+ * Add the given nsitem/RTE as a top-level entry in the pstate's join list
  * and/or namespace list.  (We assume caller has checked for any
- * namespace conflicts.)  The RTE is always marked as unconditionally
+ * namespace conflicts.)  The nsitem is always marked as unconditionally
  * visible, that is, not LATERAL-only.
- *
- * Note: some callers know that they can find the new ParseNamespaceItem
- * at the end of the pstate->p_namespace list.  This is a bit ugly but not
- * worth complicating this function's signature for.
  */
 void
-addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
-			  bool addToJoinList,
-			  bool addToRelNameSpace, bool addToVarNameSpace)
+addNSItemtoQuery(ParseState *pstate, ParseNamespaceItem *nsitem,
+				 bool addToJoinList,
+				 bool addToRelNameSpace, bool addToVarNameSpace)
 {
-	int			rtindex;
-
-	/*
-	 * Most callers have just added the RTE to the rangetable, so it's likely
-	 * to be the last entry.  Hence, it's a good idea to search the rangetable
-	 * back-to-front.
-	 */
-	for (rtindex = list_length(pstate->p_rtable); rtindex > 0; rtindex--)
-	{
-		if (rte == rt_fetch(rtindex, pstate->p_rtable))
-			break;
-	}
-	if (rtindex <= 0)
-		elog(ERROR, "RTE not found (internal error)");
-
 	if (addToJoinList)
 	{
 		RangeTblRef *rtr = makeNode(RangeTblRef);
 
-		rtr->rtindex = rtindex;
+		rtr->rtindex = nsitem->p_rtindex;
 		pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
 	}
 	if (addToRelNameSpace || addToVarNameSpace)
 	{
-		ParseNamespaceItem *nsitem;
-
-		nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
-		nsitem->p_rte = rte;
-		nsitem->p_rtindex = rtindex;
+		/* Set the new nsitem's visibility flags correctly */
 		nsitem->p_rel_visible = addToRelNameSpace;
 		nsitem->p_cols_visible = addToVarNameSpace;
 		nsitem->p_lateral_only = false;
@@ -2721,6 +2931,61 @@ expandTupleDesc(TupleDesc tupdesc, Alias *eref, int count, int offset,
 }
 
 /*
+ * expandNSItemVars
+ *	  Produce a list of Vars, and optionally a list of column names,
+ *	  for the non-dropped columns of the nsitem.
+ *
+ * The emitted Vars are marked with the given sublevels_up and location.
+ *
+ * If colnames isn't NULL, a list of String items for the columns is stored
+ * there; note that it's just a subset of the RTE's eref list, and hence
+ * the list elements mustn't be modified.
+ */
+List *
+expandNSItemVars(ParseNamespaceItem *nsitem,
+				 int sublevels_up, int location,
+				 List **colnames)
+{
+	List	   *result = NIL;
+	int			colindex;
+	ListCell   *lc;
+
+	if (colnames)
+		*colnames = NIL;
+	colindex = 0;
+	foreach(lc, nsitem->p_rte->eref->colnames)
+	{
+		Value	   *colnameval = (Value *) lfirst(lc);
+		const char *colname = strVal(colnameval);
+		ParseNamespaceColumn *nscol = nsitem->p_nscolumns + colindex;
+
+		if (colname[0])
+		{
+			Var		   *var;
+
+			Assert(nscol->p_varno > 0);
+			var = makeVar(nsitem->p_rtindex,
+						  colindex + 1,
+						  nscol->p_vartype,
+						  nscol->p_vartypmod,
+						  nscol->p_varcollid,
+						  sublevels_up);
+			var->location = location;
+			result = lappend(result, var);
+			if (colnames)
+				*colnames = lappend(*colnames, colnameval);
+		}
+		else
+		{
+			/* dropped column, ignore */
+			Assert(nscol->p_varno == 0);
+		}
+		colindex++;
+	}
+	return result;
+}
+
+/*
  * expandNSItemAttrs -
  *	  Workhorse for "*" expansion: produce a list of targetentries
  *	  for the attributes of the nsitem
@@ -2739,8 +3004,7 @@ expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 			   *var;
 	List	   *te_list = NIL;
 
-	expandRTE(rte, nsitem->p_rtindex, sublevels_up, location, false,
-			  &names, &vars);
+	vars = expandNSItemVars(nsitem, sublevels_up, location, &names);
 
 	/*
 	 * Require read access to the table.  This is normally redundant with the
@@ -2816,204 +3080,6 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
 }
 
 /*
- * get_rte_attribute_type
- *		Get attribute type/typmod/collation information from a RangeTblEntry
- */
-void
-get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
-					   Oid *vartype, int32 *vartypmod, Oid *varcollid)
-{
-	switch (rte->rtekind)
-	{
-		case RTE_RELATION:
-			{
-				/* Plain relation RTE --- get the attribute's type info */
-				HeapTuple	tp;
-				Form_pg_attribute att_tup;
-
-				tp = SearchSysCache2(ATTNUM,
-									 ObjectIdGetDatum(rte->relid),
-									 Int16GetDatum(attnum));
-				if (!HeapTupleIsValid(tp))	/* shouldn't happen */
-					elog(ERROR, "cache lookup failed for attribute %d of relation %u",
-						 attnum, rte->relid);
-				att_tup = (Form_pg_attribute) GETSTRUCT(tp);
-
-				/*
-				 * If dropped column, pretend it ain't there.  See notes in
-				 * scanRTEForColumn.
-				 */
-				if (att_tup->attisdropped)
-					ereport(ERROR,
-							(errcode(ERRCODE_UNDEFINED_COLUMN),
-							 errmsg("column \"%s\" of relation \"%s\" does not exist",
-									NameStr(att_tup->attname),
-									get_rel_name(rte->relid))));
-				*vartype = att_tup->atttypid;
-				*vartypmod = att_tup->atttypmod;
-				*varcollid = att_tup->attcollation;
-				ReleaseSysCache(tp);
-			}
-			break;
-		case RTE_SUBQUERY:
-			{
-				/* Subselect RTE --- get type info from subselect's tlist */
-				TargetEntry *te = get_tle_by_resno(rte->subquery->targetList,
-												   attnum);
-
-				if (te == NULL || te->resjunk)
-					elog(ERROR, "subquery %s does not have attribute %d",
-						 rte->eref->aliasname, attnum);
-				*vartype = exprType((Node *) te->expr);
-				*vartypmod = exprTypmod((Node *) te->expr);
-				*varcollid = exprCollation((Node *) te->expr);
-			}
-			break;
-		case RTE_FUNCTION:
-			{
-				/* Function RTE */
-				ListCell   *lc;
-				int			atts_done = 0;
-
-				/* Identify which function covers the requested column */
-				foreach(lc, rte->functions)
-				{
-					RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
-
-					if (attnum > atts_done &&
-						attnum <= atts_done + rtfunc->funccolcount)
-					{
-						TypeFuncClass functypclass;
-						Oid			funcrettype;
-						TupleDesc	tupdesc;
-
-						attnum -= atts_done;	/* now relative to this func */
-						functypclass = get_expr_result_type(rtfunc->funcexpr,
-															&funcrettype,
-															&tupdesc);
-
-						if (functypclass == TYPEFUNC_COMPOSITE ||
-							functypclass == TYPEFUNC_COMPOSITE_DOMAIN)
-						{
-							/* Composite data type, e.g. a table's row type */
-							Form_pg_attribute att_tup;
-
-							Assert(tupdesc);
-							Assert(attnum <= tupdesc->natts);
-							att_tup = TupleDescAttr(tupdesc, attnum - 1);
-
-							/*
-							 * If dropped column, pretend it ain't there.  See
-							 * notes in scanRTEForColumn.
-							 */
-							if (att_tup->attisdropped)
-								ereport(ERROR,
-										(errcode(ERRCODE_UNDEFINED_COLUMN),
-										 errmsg("column \"%s\" of relation \"%s\" does not exist",
-												NameStr(att_tup->attname),
-												rte->eref->aliasname)));
-							*vartype = att_tup->atttypid;
-							*vartypmod = att_tup->atttypmod;
-							*varcollid = att_tup->attcollation;
-						}
-						else if (functypclass == TYPEFUNC_SCALAR)
-						{
-							/* Base data type, i.e. scalar */
-							*vartype = funcrettype;
-							*vartypmod = -1;
-							*varcollid = exprCollation(rtfunc->funcexpr);
-						}
-						else if (functypclass == TYPEFUNC_RECORD)
-						{
-							*vartype = list_nth_oid(rtfunc->funccoltypes,
-													attnum - 1);
-							*vartypmod = list_nth_int(rtfunc->funccoltypmods,
-													  attnum - 1);
-							*varcollid = list_nth_oid(rtfunc->funccolcollations,
-													  attnum - 1);
-						}
-						else
-						{
-							/*
-							 * addRangeTableEntryForFunction should've caught
-							 * this
-							 */
-							elog(ERROR, "function in FROM has unsupported return type");
-						}
-						return;
-					}
-					atts_done += rtfunc->funccolcount;
-				}
-
-				/* If we get here, must be looking for the ordinality column */
-				if (rte->funcordinality && attnum == atts_done + 1)
-				{
-					*vartype = INT8OID;
-					*vartypmod = -1;
-					*varcollid = InvalidOid;
-					return;
-				}
-
-				/* this probably can't happen ... */
-				ereport(ERROR,
-						(errcode(ERRCODE_UNDEFINED_COLUMN),
-						 errmsg("column %d of relation \"%s\" does not exist",
-								attnum,
-								rte->eref->aliasname)));
-			}
-			break;
-		case RTE_JOIN:
-			{
-				/*
-				 * Join RTE --- get type info from join RTE's alias variable
-				 */
-				Node	   *aliasvar;
-
-				Assert(attnum > 0 && attnum <= list_length(rte->joinaliasvars));
-				aliasvar = (Node *) list_nth(rte->joinaliasvars, attnum - 1);
-				Assert(aliasvar != NULL);
-				*vartype = exprType(aliasvar);
-				*vartypmod = exprTypmod(aliasvar);
-				*varcollid = exprCollation(aliasvar);
-			}
-			break;
-		case RTE_TABLEFUNC:
-		case RTE_VALUES:
-		case RTE_CTE:
-		case RTE_NAMEDTUPLESTORE:
-			{
-				/*
-				 * tablefunc, VALUES, CTE, or ENR RTE --- get type info from
-				 * lists in the RTE
-				 */
-				Assert(attnum > 0 && attnum <= list_length(rte->coltypes));
-				*vartype = list_nth_oid(rte->coltypes, attnum - 1);
-				*vartypmod = list_nth_int(rte->coltypmods, attnum - 1);
-				*varcollid = list_nth_oid(rte->colcollations, attnum - 1);
-
-				/* For ENR, better check for dropped column */
-				if (!OidIsValid(*vartype))
-					ereport(ERROR,
-							(errcode(ERRCODE_UNDEFINED_COLUMN),
-							 errmsg("column %d of relation \"%s\" does not exist",
-									attnum,
-									rte->eref->aliasname)));
-			}
-			break;
-		case RTE_RESULT:
-			/* this probably can't happen ... */
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("column %d of relation \"%s\" does not exist",
-							attnum,
-							rte->eref->aliasname)));
-			break;
-		default:
-			elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
-	}
-}
-
-/*
  * get_rte_attribute_is_dropped
  *		Check whether attempted attribute ref is to a dropped column
  */
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 46fe6c6..967808b 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -551,7 +551,7 @@ transformAssignedExpr(ParseState *pstate,
 			 */
 			Var		   *var;
 
-			var = makeVar(pstate->p_target_rtindex, attrno,
+			var = makeVar(pstate->p_target_nsitem->p_rtindex, attrno,
 						  attrtype, attrtypmod, attrcollation, 0);
 			var->location = location;
 
@@ -1359,8 +1359,7 @@ ExpandSingleTable(ParseState *pstate, ParseNamespaceItem *nsitem,
 		List	   *vars;
 		ListCell   *l;
 
-		expandRTE(rte, nsitem->p_rtindex, sublevels_up, location, false,
-				  NULL, &vars);
+		vars = expandNSItemVars(nsitem, sublevels_up, location, NULL);
 
 		/*
 		 * Require read access to the table.  This is normally redundant with
@@ -1496,6 +1495,12 @@ expandRecordVariable(ParseState *pstate, Var *var, int levelsup)
 	Assert(IsA(var, Var));
 	Assert(var->vartype == RECORDOID);
 
+	/*
+	 * Note: it's tempting to use GetNSItemByRangeTablePosn here so that we
+	 * can use expandNSItemVars instead of expandRTE; but that does not work
+	 * for some of the recursion cases below, where we have consed up a
+	 * ParseState that lacks p_namespace data.
+	 */
 	netlevelsup = var->varlevelsup + levelsup;
 	rte = GetRTEByRangeTablePosn(pstate, var->varno, netlevelsup);
 	attnum = var->varattno;
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index 45bb31e..b783cb8 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -2598,7 +2598,7 @@ IndexStmt *
 transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
 {
 	ParseState *pstate;
-	RangeTblEntry *rte;
+	ParseNamespaceItem *nsitem;
 	ListCell   *l;
 	Relation	rel;
 
@@ -2622,12 +2622,12 @@ transformIndexStmt(Oid relid, IndexStmt *stmt, const char *queryString)
 	 * relation, but we still need to open it.
 	 */
 	rel = relation_open(relid, NoLock);
-	rte = addRangeTableEntryForRelation(pstate, rel,
-										AccessShareLock,
-										NULL, false, true);
+	nsitem = addRangeTableEntryForRelation(pstate, rel,
+										   AccessShareLock,
+										   NULL, false, true);
 
 	/* no to join list, yes to namespaces */
-	addRTEtoQuery(pstate, rte, false, true, true);
+	addNSItemtoQuery(pstate, nsitem, false, true, true);
 
 	/* take care of the where clause */
 	if (stmt->whereClause)
@@ -2707,8 +2707,8 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 {
 	Relation	rel;
 	ParseState *pstate;
-	RangeTblEntry *oldrte;
-	RangeTblEntry *newrte;
+	ParseNamespaceItem *oldnsitem;
+	ParseNamespaceItem *newnsitem;
 
 	/*
 	 * To avoid deadlock, make sure the first thing we do is grab
@@ -2729,20 +2729,20 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 
 	/*
 	 * NOTE: 'OLD' must always have a varno equal to 1 and 'NEW' equal to 2.
-	 * Set up their RTEs in the main pstate for use in parsing the rule
-	 * qualification.
+	 * Set up their ParseNamespaceItems in the main pstate for use in parsing
+	 * the rule qualification.
 	 */
-	oldrte = addRangeTableEntryForRelation(pstate, rel,
-										   AccessShareLock,
-										   makeAlias("old", NIL),
-										   false, false);
-	newrte = addRangeTableEntryForRelation(pstate, rel,
-										   AccessShareLock,
-										   makeAlias("new", NIL),
-										   false, false);
+	oldnsitem = addRangeTableEntryForRelation(pstate, rel,
+											  AccessShareLock,
+											  makeAlias("old", NIL),
+											  false, false);
+	newnsitem = addRangeTableEntryForRelation(pstate, rel,
+											  AccessShareLock,
+											  makeAlias("new", NIL),
+											  false, false);
 	/* Must override addRangeTableEntry's default access-check flags */
-	oldrte->requiredPerms = 0;
-	newrte->requiredPerms = 0;
+	oldnsitem->p_rte->requiredPerms = 0;
+	newnsitem->p_rte->requiredPerms = 0;
 
 	/*
 	 * They must be in the namespace too for lookup purposes, but only add the
@@ -2754,17 +2754,17 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 	switch (stmt->event)
 	{
 		case CMD_SELECT:
-			addRTEtoQuery(pstate, oldrte, false, true, true);
+			addNSItemtoQuery(pstate, oldnsitem, false, true, true);
 			break;
 		case CMD_UPDATE:
-			addRTEtoQuery(pstate, oldrte, false, true, true);
-			addRTEtoQuery(pstate, newrte, false, true, true);
+			addNSItemtoQuery(pstate, oldnsitem, false, true, true);
+			addNSItemtoQuery(pstate, newnsitem, false, true, true);
 			break;
 		case CMD_INSERT:
-			addRTEtoQuery(pstate, newrte, false, true, true);
+			addNSItemtoQuery(pstate, newnsitem, false, true, true);
 			break;
 		case CMD_DELETE:
-			addRTEtoQuery(pstate, oldrte, false, true, true);
+			addNSItemtoQuery(pstate, oldnsitem, false, true, true);
 			break;
 		default:
 			elog(ERROR, "unrecognized event type: %d",
@@ -2832,18 +2832,18 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 			 * nor "*" in the rule actions.  We decide later whether to put
 			 * them in the joinlist.
 			 */
-			oldrte = addRangeTableEntryForRelation(sub_pstate, rel,
-												   AccessShareLock,
-												   makeAlias("old", NIL),
-												   false, false);
-			newrte = addRangeTableEntryForRelation(sub_pstate, rel,
-												   AccessShareLock,
-												   makeAlias("new", NIL),
-												   false, false);
-			oldrte->requiredPerms = 0;
-			newrte->requiredPerms = 0;
-			addRTEtoQuery(sub_pstate, oldrte, false, true, false);
-			addRTEtoQuery(sub_pstate, newrte, false, true, false);
+			oldnsitem = addRangeTableEntryForRelation(sub_pstate, rel,
+													  AccessShareLock,
+													  makeAlias("old", NIL),
+													  false, false);
+			newnsitem = addRangeTableEntryForRelation(sub_pstate, rel,
+													  AccessShareLock,
+													  makeAlias("new", NIL),
+													  false, false);
+			oldnsitem->p_rte->requiredPerms = 0;
+			newnsitem->p_rte->requiredPerms = 0;
+			addNSItemtoQuery(sub_pstate, oldnsitem, false, true, false);
+			addNSItemtoQuery(sub_pstate, newnsitem, false, true, false);
 
 			/* Transform the rule action statement */
 			top_subqry = transformStmt(sub_pstate,
@@ -2967,6 +2967,8 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 			 */
 			if (has_old || (has_new && stmt->event == CMD_UPDATE))
 			{
+				RangeTblRef *rtr;
+
 				/*
 				 * If sub_qry is a setop, manipulating its jointree will do no
 				 * good at all, because the jointree is dummy. (This should be
@@ -2976,11 +2978,11 @@ transformRuleStmt(RuleStmt *stmt, const char *queryString,
 					ereport(ERROR,
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 							 errmsg("conditional UNION/INTERSECT/EXCEPT statements are not implemented")));
-				/* hack so we can use addRTEtoQuery() */
-				sub_pstate->p_rtable = sub_qry->rtable;
-				sub_pstate->p_joinlist = sub_qry->jointree->fromlist;
-				addRTEtoQuery(sub_pstate, oldrte, true, false, false);
-				sub_qry->jointree->fromlist = sub_pstate->p_joinlist;
+				/* hackishly add OLD to the already-built FROM clause */
+				rtr = makeNode(RangeTblRef);
+				rtr->rtindex = oldnsitem->p_rtindex;
+				sub_qry->jointree->fromlist =
+					lappend(sub_qry->jointree->fromlist, rtr);
 			}
 
 			newactions = lappend(newactions, top_subqry);
@@ -3025,7 +3027,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	List	   *newcmds = NIL;
 	bool		skipValidation = true;
 	AlterTableCmd *newcmd;
-	RangeTblEntry *rte;
+	ParseNamespaceItem *nsitem;
 
 	/*
 	 * We must not scribble on the passed-in AlterTableStmt, so copy it. (This
@@ -3040,13 +3042,13 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
 	/* Set up pstate */
 	pstate = make_parsestate(NULL);
 	pstate->p_sourcetext = queryString;
-	rte = addRangeTableEntryForRelation(pstate,
-										rel,
-										AccessShareLock,
-										NULL,
-										false,
-										true);
-	addRTEtoQuery(pstate, rte, false, true, true);
+	nsitem = addRangeTableEntryForRelation(pstate,
+										   rel,
+										   AccessShareLock,
+										   NULL,
+										   false,
+										   true);
+	addNSItemtoQuery(pstate, nsitem, false, true, true);
 
 	/* Set up CreateStmtContext */
 	cxt.pstate = pstate;
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index e01d18c..8f4eb8b 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -777,8 +777,8 @@ copy_table(Relation rel)
 	copybuf = makeStringInfo();
 
 	pstate = make_parsestate(NULL);
-	addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
-								  NULL, false, false);
+	(void) addRangeTableEntryForRelation(pstate, rel, AccessShareLock,
+										 NULL, false, false);
 
 	attnamelist = make_copy_attnamelist(relmapentry);
 	cstate = BeginCopyFrom(pstate, rel, NULL, false, copy_read_data, attnamelist, NIL);
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 334efba..54fa806 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -3227,6 +3227,7 @@ rewriteTargetView(Query *parsetree, Relation view)
 	{
 		Index		old_exclRelIndex,
 					new_exclRelIndex;
+		ParseNamespaceItem *new_exclNSItem;
 		RangeTblEntry *new_exclRte;
 		List	   *tmp_tlist;
 
@@ -3261,11 +3262,12 @@ rewriteTargetView(Query *parsetree, Relation view)
 		 */
 		old_exclRelIndex = parsetree->onConflict->exclRelIndex;
 
-		new_exclRte = addRangeTableEntryForRelation(make_parsestate(NULL),
-													base_rel,
-													RowExclusiveLock,
-													makeAlias("excluded", NIL),
-													false, false);
+		new_exclNSItem = addRangeTableEntryForRelation(make_parsestate(NULL),
+													   base_rel,
+													   RowExclusiveLock,
+													   makeAlias("excluded", NIL),
+													   false, false);
+		new_exclRte = new_exclNSItem->p_rte;
 		new_exclRte->relkind = RELKIND_COMPOSITE_TYPE;
 		new_exclRte->requiredPerms = 0;
 		/* other permissions fields in new_exclRte are already empty */
diff --git a/src/include/parser/parse_node.h b/src/include/parser/parse_node.h
index 674acc5..93e7c09 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -19,6 +19,11 @@
 #include "utils/relcache.h"
 
 
+/* Forward references for some structs declared below */
+typedef struct ParseState ParseState;
+typedef struct ParseNamespaceItem ParseNamespaceItem;
+typedef struct ParseNamespaceColumn ParseNamespaceColumn;
+
 /*
  * Expression kinds distinguished by transformExpr().  Many of these are not
  * semantically distinct so far as expression transformation goes; rather,
@@ -79,8 +84,6 @@ typedef enum ParseExprKind
 /*
  * Function signatures for parser hooks
  */
-typedef struct ParseState ParseState;
-
 typedef Node *(*PreParseColumnRefHook) (ParseState *pstate, ColumnRef *cref);
 typedef Node *(*PostParseColumnRefHook) (ParseState *pstate, ColumnRef *cref, Node *var);
 typedef Node *(*ParseParamRefHook) (ParseState *pstate, ParamRef *pref);
@@ -132,9 +135,7 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  *
  * p_target_relation: target relation, if query is INSERT, UPDATE, or DELETE.
  *
- * p_target_rangetblentry: target relation's entry in the rtable list.
- *
- * p_target_rtindex: target relation's index in the rtable list.
+ * p_target_nsitem: target relation's ParseNamespaceItem.
  *
  * p_is_insert: true to process assignment expressions like INSERT, false
  * to process them like UPDATE.  (Note this can change intra-statement, for
@@ -174,7 +175,7 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  */
 struct ParseState
 {
-	struct ParseState *parentParseState;	/* stack link */
+	ParseState *parentParseState;	/* stack link */
 	const char *p_sourcetext;	/* source text, or NULL if not available */
 	List	   *p_rtable;		/* range table so far */
 	List	   *p_joinexprs;	/* JoinExprs for RTE_JOIN p_rtable entries */
@@ -187,8 +188,7 @@ struct ParseState
 	List	   *p_future_ctes;	/* common table exprs not yet in namespace */
 	CommonTableExpr *p_parent_cte;	/* this query's containing CTE */
 	Relation	p_target_relation;	/* INSERT/UPDATE/DELETE target rel */
-	RangeTblEntry *p_target_rangetblentry;	/* target rel's RTE, or NULL */
-	int			p_target_rtindex;	/* target rel's RT index, or 0 */
+	ParseNamespaceItem *p_target_nsitem;	/* target rel's NSItem, or NULL */
 	bool		p_is_insert;	/* process assignment like INSERT not UPDATE */
 	List	   *p_windowdefs;	/* raw representations of window clauses */
 	ParseExprKind p_expr_kind;	/* what kind of expression we're parsing */
@@ -225,6 +225,10 @@ struct ParseState
 /*
  * An element of a namespace list.
  *
+ * The p_nscolumns array contains info showing how to construct Vars
+ * referencing corresponding elements of the RTE's colnames list.  If the RTE
+ * contains dropped columns, the corresponding array elements are all-zeroes.
+ *
  * Namespace items with p_rel_visible set define which RTEs are accessible by
  * qualified names, while those with p_cols_visible set define which RTEs are
  * accessible by unqualified names.  These sets are different because a JOIN
@@ -249,15 +253,46 @@ struct ParseState
  * are more complicated than "must have different alias names", so in practice
  * code searching a namespace list has to check for ambiguous references.
  */
-typedef struct ParseNamespaceItem
+struct ParseNamespaceItem
 {
 	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
 	int			p_rtindex;		/* The relation's index in the rangetable */
+	/* array of same length as p_rte->eref->colnames: */
+	ParseNamespaceColumn *p_nscolumns;	/* per-column data */
 	bool		p_rel_visible;	/* Relation name is visible? */
 	bool		p_cols_visible; /* Column names visible as unqualified refs? */
 	bool		p_lateral_only; /* Is only visible to LATERAL expressions? */
 	bool		p_lateral_ok;	/* If so, does join type allow use? */
-} ParseNamespaceItem;
+};
+
+/*
+ * Data about one column of a ParseNamespaceItem.
+ *
+ * We track the info needed to construct a Var referencing the column
+ * (but only for user-defined columns; system column references and
+ * whole-row references are handled separately).
+ *
+ * p_varno and p_varattno identify the semantic referent, which is a
+ * base-relation column unless the reference is to a join USING column that
+ * isn't semantically equivalent to either join input column (because it is a
+ * FULL join or the input column requires a type coercion).  In those cases
+ * p_varno and p_varattno refer to the JOIN RTE.
+ *
+ * p_varnosyn and p_varattnosyn are either identical to p_varno/p_varattno,
+ * or they specify the column's position in an aliased JOIN RTE that hides
+ * the semantic referent RTE's refname.  (That could be either the JOIN RTE
+ * in which this ParseNamespaceColumn entry exists, or some lower join level.)
+ */
+struct ParseNamespaceColumn
+{
+	Index		p_varno;		/* rangetable index */
+	AttrNumber	p_varattno;		/* attribute number of the column */
+	Oid			p_vartype;		/* pg_type OID */
+	int32		p_vartypmod;	/* type modifier value */
+	Oid			p_varcollid;	/* OID of collation, or InvalidOid */
+	Index		p_varnosyn;		/* rangetable index of syntactic referent */
+	AttrNumber	p_varattnosyn;	/* attribute number of syntactic referent */
+};
 
 /* Support for parser_errposition_callback function */
 typedef struct ParseCallbackState
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index b09a71e..cb44fd4 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -45,66 +45,70 @@ extern void markVarForSelectPriv(ParseState *pstate, Var *var,
 								 RangeTblEntry *rte);
 extern Relation parserOpenTable(ParseState *pstate, const RangeVar *relation,
 								int lockmode);
-extern RangeTblEntry *addRangeTableEntry(ParseState *pstate,
-										 RangeVar *relation,
-										 Alias *alias,
-										 bool inh,
-										 bool inFromCl);
-extern RangeTblEntry *addRangeTableEntryForRelation(ParseState *pstate,
-													Relation rel,
-													int lockmode,
-													Alias *alias,
-													bool inh,
-													bool inFromCl);
-extern RangeTblEntry *addRangeTableEntryForSubquery(ParseState *pstate,
-													Query *subquery,
-													Alias *alias,
-													bool lateral,
-													bool inFromCl);
-extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate,
-													List *funcnames,
-													List *funcexprs,
-													List *coldeflists,
-													RangeFunction *rangefunc,
-													bool lateral,
-													bool inFromCl);
-extern RangeTblEntry *addRangeTableEntryForValues(ParseState *pstate,
-												  List *exprs,
-												  List *coltypes,
-												  List *coltypmods,
-												  List *colcollations,
-												  Alias *alias,
-												  bool lateral,
-												  bool inFromCl);
-extern RangeTblEntry *addRangeTableEntryForTableFunc(ParseState *pstate,
-													 TableFunc *tf,
+extern ParseNamespaceItem *addRangeTableEntry(ParseState *pstate,
+											  RangeVar *relation,
+											  Alias *alias,
+											  bool inh,
+											  bool inFromCl);
+extern ParseNamespaceItem *addRangeTableEntryForRelation(ParseState *pstate,
+														 Relation rel,
+														 int lockmode,
+														 Alias *alias,
+														 bool inh,
+														 bool inFromCl);
+extern ParseNamespaceItem *addRangeTableEntryForSubquery(ParseState *pstate,
+														 Query *subquery,
+														 Alias *alias,
+														 bool lateral,
+														 bool inFromCl);
+extern ParseNamespaceItem *addRangeTableEntryForFunction(ParseState *pstate,
+														 List *funcnames,
+														 List *funcexprs,
+														 List *coldeflists,
+														 RangeFunction *rangefunc,
+														 bool lateral,
+														 bool inFromCl);
+extern ParseNamespaceItem *addRangeTableEntryForValues(ParseState *pstate,
+													   List *exprs,
+													   List *coltypes,
+													   List *coltypmods,
+													   List *colcollations,
+													   Alias *alias,
+													   bool lateral,
+													   bool inFromCl);
+extern ParseNamespaceItem *addRangeTableEntryForTableFunc(ParseState *pstate,
+														  TableFunc *tf,
+														  Alias *alias,
+														  bool lateral,
+														  bool inFromCl);
+extern ParseNamespaceItem *addRangeTableEntryForJoin(ParseState *pstate,
+													 List *colnames,
+													 ParseNamespaceColumn *nscolumns,
+													 JoinType jointype,
+													 List *aliasvars,
 													 Alias *alias,
-													 bool lateral,
 													 bool inFromCl);
-extern RangeTblEntry *addRangeTableEntryForJoin(ParseState *pstate,
-												List *colnames,
-												JoinType jointype,
-												List *aliasvars,
-												Alias *alias,
-												bool inFromCl);
-extern RangeTblEntry *addRangeTableEntryForCTE(ParseState *pstate,
-											   CommonTableExpr *cte,
-											   Index levelsup,
-											   RangeVar *rv,
-											   bool inFromCl);
-extern RangeTblEntry *addRangeTableEntryForENR(ParseState *pstate,
-											   RangeVar *rv,
-											   bool inFromCl);
+extern ParseNamespaceItem *addRangeTableEntryForCTE(ParseState *pstate,
+													CommonTableExpr *cte,
+													Index levelsup,
+													RangeVar *rv,
+													bool inFromCl);
+extern ParseNamespaceItem *addRangeTableEntryForENR(ParseState *pstate,
+													RangeVar *rv,
+													bool inFromCl);
 extern bool isLockedRefname(ParseState *pstate, const char *refname);
-extern void addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
-						  bool addToJoinList,
-						  bool addToRelNameSpace, bool addToVarNameSpace);
+extern void addNSItemtoQuery(ParseState *pstate, ParseNamespaceItem *nsitem,
+							 bool addToJoinList,
+							 bool addToRelNameSpace, bool addToVarNameSpace);
 extern void errorMissingRTE(ParseState *pstate, RangeVar *relation) pg_attribute_noreturn();
 extern void errorMissingColumn(ParseState *pstate,
 							   const char *relname, const char *colname, int location) pg_attribute_noreturn();
 extern void expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 					  int location, bool include_dropped,
 					  List **colnames, List **colvars);
+extern List *expandNSItemVars(ParseNamespaceItem *nsitem,
+							  int sublevels_up, int location,
+							  List **colnames);
 extern List *expandNSItemAttrs(ParseState *pstate, ParseNamespaceItem *nsitem,
 							   int sublevels_up, int location);
 extern int	attnameAttNum(Relation rd, const char *attname, bool sysColOK);
diff --git a/src/include/parser/parsetree.h b/src/include/parser/parsetree.h
index cf47e10..a2c3832 100644
--- a/src/include/parser/parsetree.h
+++ b/src/include/parser/parsetree.h
@@ -38,15 +38,7 @@
 extern char *get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum);
 
 /*
- * Given an RTE and an attribute number, return the appropriate
- * type and typemod info for that attribute of that RTE.
- */
-extern void get_rte_attribute_type(RangeTblEntry *rte, AttrNumber attnum,
-								   Oid *vartype, int32 *vartypmod, Oid *varcollid);
-
-/*
- * Check whether an attribute of an RTE has been dropped (note that
- * get_rte_attribute_type will fail on such an attr)
+ * Check whether an attribute of an RTE has been dropped
  */
 extern bool get_rte_attribute_is_dropped(RangeTblEntry *rte,
 										 AttrNumber attnum);
diff --git a/src/test/modules/test_rls_hooks/test_rls_hooks.c b/src/test/modules/test_rls_hooks/test_rls_hooks.c
index f576ecb..66df39d 100644
--- a/src/test/modules/test_rls_hooks/test_rls_hooks.c
+++ b/src/test/modules/test_rls_hooks/test_rls_hooks.c
@@ -70,7 +70,7 @@ test_rls_hooks_permissive(CmdType cmdtype, Relation relation)
 	Node	   *e;
 	ColumnRef  *c;
 	ParseState *qual_pstate;
-	RangeTblEntry *rte;
+	ParseNamespaceItem *nsitem;
 
 	if (strcmp(RelationGetRelationName(relation), "rls_test_permissive") != 0 &&
 		strcmp(RelationGetRelationName(relation), "rls_test_both") != 0)
@@ -78,9 +78,10 @@ test_rls_hooks_permissive(CmdType cmdtype, Relation relation)
 
 	qual_pstate = make_parsestate(NULL);
 
-	rte = addRangeTableEntryForRelation(qual_pstate, relation, AccessShareLock,
-										NULL, false, false);
-	addRTEtoQuery(qual_pstate, rte, false, true, true);
+	nsitem = addRangeTableEntryForRelation(qual_pstate,
+										   relation, AccessShareLock,
+										   NULL, false, false);
+	addNSItemtoQuery(qual_pstate, nsitem, false, true, true);
 
 	role = ObjectIdGetDatum(ACL_ID_PUBLIC);
 
@@ -134,8 +135,7 @@ test_rls_hooks_restrictive(CmdType cmdtype, Relation relation)
 	Node	   *e;
 	ColumnRef  *c;
 	ParseState *qual_pstate;
-	RangeTblEntry *rte;
-
+	ParseNamespaceItem *nsitem;
 
 	if (strcmp(RelationGetRelationName(relation), "rls_test_restrictive") != 0 &&
 		strcmp(RelationGetRelationName(relation), "rls_test_both") != 0)
@@ -143,9 +143,10 @@ test_rls_hooks_restrictive(CmdType cmdtype, Relation relation)
 
 	qual_pstate = make_parsestate(NULL);
 
-	rte = addRangeTableEntryForRelation(qual_pstate, relation, AccessShareLock,
-										NULL, false, false);
-	addRTEtoQuery(qual_pstate, rte, false, true, true);
+	nsitem = addRangeTableEntryForRelation(qual_pstate,
+										   relation, AccessShareLock,
+										   NULL, false, false);
+	addNSItemtoQuery(qual_pstate, nsitem, false, true, true);
 
 	role = ObjectIdGetDatum(ACL_ID_PUBLIC);
 
