diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 33252a8e205abc3726ffb32c5615ef6fa64e28a8..323f3a11ae961db082a472ee8a8125916b4e2b22 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -653,9 +653,9 @@ elapsed_time(instr_time *starttime)
  *	  Prescan the planstate tree to identify which RTEs are referenced
  *
  * Adds the relid of each referenced RTE to *rels_used.  The result controls
- * which RTEs are assigned aliases by select_rtable_names_for_explain.	This
- * ensures that we don't confusingly assign un-suffixed aliases to RTEs that
- * never appear in the EXPLAIN output (such as inheritance parents).
+ * which RTEs are assigned aliases by select_rtable_names_for_explain.
+ * This ensures that we don't confusingly assign un-suffixed aliases to RTEs
+ * that never appear in the EXPLAIN output (such as inheritance parents).
  */
 static void
 ExplainPreScanNode(PlanState *planstate, Bitmapset **rels_used)
@@ -1954,6 +1954,8 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 
 	rte = rt_fetch(rti, es->rtable);
 	refname = (char *) list_nth(es->rtable_names, rti - 1);
+	if (refname == NULL)
+		refname = rte->eref->aliasname;
 
 	switch (nodeTag(plan))
 	{
@@ -2026,8 +2028,7 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 							 quote_identifier(objectname));
 		else if (objectname != NULL)
 			appendStringInfo(es->str, " %s", quote_identifier(objectname));
-		if (refname != NULL &&
-			(objectname == NULL || strcmp(refname, objectname) != 0))
+		if (objectname == NULL || strcmp(refname, objectname) != 0)
 			appendStringInfo(es->str, " %s", quote_identifier(refname));
 	}
 	else
@@ -2036,8 +2037,7 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
 			ExplainPropertyText(objecttag, objectname, es);
 		if (namespace != NULL)
 			ExplainPropertyText("Schema", namespace, es);
-		if (refname != NULL)
-			ExplainPropertyText("Alias", refname, es);
+		ExplainPropertyText("Alias", refname, es);
 	}
 }
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index aca670095b9812f9e0d74523665b57099877e058..e859379cb95cb74ad6578889fb13ed60c2b06c9d 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -888,9 +888,7 @@ DefineDomain(CreateDomainStmt *stmt)
 						 */
 						defaultValue =
 							deparse_expression(defaultExpr,
-											   deparse_context_for(domainName,
-																 InvalidOid),
-											   false, false);
+											   NIL, false, false);
 						defaultValueBin = nodeToString(defaultExpr);
 					}
 				}
@@ -2143,9 +2141,7 @@ AlterDomainDefault(List *names, Node *defaultRaw)
 			 * easier for pg_dump).
 			 */
 			defaultValue = deparse_expression(defaultExpr,
-								deparse_context_for(NameStr(typTup->typname),
-													InvalidOid),
-											  false, false);
+											  NIL, false, false);
 
 			/*
 			 * Form an updated tuple with the new default and write it back.
@@ -2941,14 +2937,9 @@ domainAddConstraint(Oid domainOid, Oid domainNamespace, Oid baseTypeOid,
 
 	/*
 	 * Deparse it to produce text for consrc.
-	 *
-	 * Since VARNOs aren't allowed in domain constraints, relation context
-	 * isn't required as anything other than a shell.
 	 */
 	ccsrc = deparse_expression(expr,
-							   deparse_context_for(domainName,
-												   InvalidOid),
-							   false, false);
+							   NIL, false, false);
 
 	/*
 	 * Store the constraint in pg_constraint
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index f58418e0ceb257a59862096f52bfb03f8b327655..a387030059dfbf5e20836270d68630eda8d5f359 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -106,9 +106,16 @@ typedef struct
  * the current context's namespaces list.
  *
  * The rangetable is the list of actual RTEs from the query tree, and the
- * cte list is the list of actual CTEs.  rtable_names holds the alias name
- * to be used for each RTE (either a C string, or NULL for nameless RTEs
- * such as unnamed joins).
+ * cte list is the list of actual CTEs.
+ *
+ * rtable_names holds the alias name to be used for each RTE (either a C
+ * string, or NULL for nameless RTEs such as unnamed joins).
+ * rtable_columns holds the column alias names to be used for each RTE.
+ *
+ * In some cases we need to make names of merged JOIN USING columns unique
+ * across the whole query, not only per-RTE.  If so, unique_using is TRUE
+ * and using_names is a list of C strings representing names already assigned
+ * to USING columns.
  *
  * When deparsing plan trees, there is always just a single item in the
  * deparse_namespace list (since a plan tree never contains Vars with
@@ -124,7 +131,11 @@ typedef struct
 {
 	List	   *rtable;			/* List of RangeTblEntry nodes */
 	List	   *rtable_names;	/* Parallel list of names for RTEs */
+	List	   *rtable_columns; /* Parallel list of deparse_columns structs */
 	List	   *ctes;			/* List of CommonTableExpr nodes */
+	/* Workspace for column alias assignment: */
+	bool		unique_using;	/* Are we making USING names globally unique */
+	List	   *using_names;	/* List of assigned names for USING columns */
 	/* Remaining fields are used only when deparsing a Plan tree: */
 	PlanState  *planstate;		/* immediate parent of current expression */
 	List	   *ancestors;		/* ancestors of planstate */
@@ -135,6 +146,111 @@ typedef struct
 	List	   *index_tlist;	/* referent for INDEX_VAR Vars */
 } deparse_namespace;
 
+/*
+ * Per-relation data about column alias names.
+ *
+ * Selecting aliases is unreasonably complicated because of the need to dump
+ * rules/views whose underlying tables may have had columns added, deleted, or
+ * renamed since the query was parsed.	We must nonetheless print the rule/view
+ * in a form that can be reloaded and will produce the same results as before.
+ *
+ * For each RTE used in the query, we must assign column aliases that are
+ * unique within that RTE.	SQL does not require this of the original query,
+ * but due to factors such as *-expansion we need to be able to uniquely
+ * reference every column in a decompiled query.  As long as we qualify all
+ * column references, per-RTE uniqueness is sufficient for that.
+ *
+ * However, we can't ensure per-column name uniqueness for unnamed join RTEs,
+ * since they just inherit column names from their input RTEs, and we can't
+ * rename the columns at the join level.  Most of the time this isn't an issue
+ * because we don't need to reference the join's output columns as such; we
+ * can reference the input columns instead.  That approach fails for merged
+ * FULL JOIN USING columns, however, so when we have one of those in an
+ * unnamed join, we have to make that column's alias globally unique across
+ * the whole query to ensure it can be referenced unambiguously.
+ *
+ * Another problem is that a JOIN USING clause requires the columns to be
+ * merged to have the same aliases in both input RTEs.	To handle that, we do
+ * USING-column alias assignment in a recursive traversal of the query's
+ * jointree.  When descending through a JOIN with USING, we preassign the
+ * USING column names to the child columns, overriding other rules for column
+ * alias assignment.
+ *
+ * Another problem is that if a JOIN's input tables have had columns added or
+ * deleted since the query was parsed, we must generate a column alias list
+ * for the join that matches the current set of input columns --- otherwise, a
+ * change in the number of columns in the left input would throw off matching
+ * of aliases to columns of the right input.  Thus, positions in the printable
+ * column alias list are not necessarily one-for-one with varattnos of the
+ * JOIN, so we need a separate new_colnames[] array for printing purposes.
+ */
+typedef struct
+{
+	/*
+	 * colnames is an array containing column aliases to use for columns that
+	 * existed when the query was parsed.  Dropped columns have NULL entries.
+	 * This array can be directly indexed by varattno to get a Var's name.
+	 *
+	 * Non-NULL entries are guaranteed unique within the RTE, *except* when
+	 * this is for an unnamed JOIN RTE.  In that case we merely copy up names
+	 * from the two input RTEs.
+	 *
+	 * During the recursive descent in set_using_names(), forcible assignment
+	 * of a child RTE's column name is represented by pre-setting that element
+	 * of the child's colnames array.  So at that stage, NULL entries in this
+	 * array just mean that no name has been preassigned, not necessarily that
+	 * the column is dropped.
+	 */
+	int			num_cols;		/* length of colnames[] array */
+	char	  **colnames;		/* array of C strings and NULLs */
+
+	/*
+	 * new_colnames is an array containing column aliases to use for columns
+	 * that would exist if the query was re-parsed against the current
+	 * definitions of its base tables.	This is what to print as the column
+	 * alias list for the RTE.	This array does not include dropped columns,
+	 * but it will include columns added since original parsing.  Indexes in
+	 * it therefore have little to do with current varattno values.  As above,
+	 * entries are unique unless this is for an unnamed JOIN RTE.  (In such an
+	 * RTE, we never actually print this array, but we must compute it anyway
+	 * for possible use in computing column names of upper joins.) The
+	 * parallel array is_new_col marks which of these columns are new since
+	 * original parsing.  Entries with is_new_col false must match the
+	 * non-NULL colnames entries one-for-one.
+	 */
+	int			num_new_cols;	/* length of new_colnames[] array */
+	char	  **new_colnames;	/* array of C strings */
+	bool	   *is_new_col;		/* array of bool flags */
+
+	/* This flag tells whether we should actually print a column alias list */
+	bool		printaliases;
+
+	/*
+	 * If this struct is for a JOIN RTE, we fill these fields during the
+	 * set_using_names() pass to describe its relationship to its child RTEs.
+	 *
+	 * leftattnos and rightattnos are arrays with one entry per existing
+	 * output column of the join (hence, indexable by join varattno).  For a
+	 * simple reference to a column of the left child, leftattnos[i] is the
+	 * child RTE's attno and rightattnos[i] is zero; and conversely for a
+	 * column of the right child.  But for merged columns produced by JOIN
+	 * USING/NATURAL JOIN, both leftattnos[i] and rightattnos[i] are nonzero.
+	 *
+	 * If it's a JOIN USING, usingNames holds the alias names selected for the
+	 * merged columns (these might be different from the original USING list,
+	 * if we had to modify names to achieve uniqueness).
+	 */
+	int			leftrti;		/* rangetable index of left child */
+	int			rightrti;		/* rangetable index of right child */
+	int		   *leftattnos;		/* left-child varattnos of join cols, or 0 */
+	int		   *rightattnos;	/* right-child varattnos of join cols, or 0 */
+	List	   *usingNames;		/* names assigned to merged columns */
+} deparse_columns;
+
+/* This macro is analogous to rt_fetch(), but for deparse_columns structs */
+#define deparse_columns_fetch(rangetable_index, dpns) \
+	((deparse_columns *) list_nth((dpns)->rtable_columns, (rangetable_index)-1))
+
 
 /* ----------
  * Global data
@@ -181,6 +297,25 @@ static void set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
 				 Bitmapset *rels_used);
 static bool refname_is_unique(char *refname, deparse_namespace *dpns,
 				  List *parent_namespaces);
+static void set_deparse_for_query(deparse_namespace *dpns, Query *query,
+					  List *parent_namespaces);
+static void set_simple_column_names(deparse_namespace *dpns);
+static bool has_unnamed_full_join_using(Node *jtnode);
+static void set_using_names(deparse_namespace *dpns, Node *jtnode);
+static void set_relation_column_names(deparse_namespace *dpns,
+						  RangeTblEntry *rte,
+						  deparse_columns *colinfo);
+static void set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
+					  deparse_columns *colinfo);
+static bool colname_is_unique(char *colname, deparse_namespace *dpns,
+				  deparse_columns *colinfo);
+static char *make_colname_unique(char *colname, deparse_namespace *dpns,
+					deparse_columns *colinfo);
+static void expand_colnames_array_to(deparse_columns *colinfo, int n);
+static void identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
+					  deparse_columns *colinfo);
+static void flatten_join_using_qual(Node *qual,
+						List **leftvars, List **rightvars);
 static char *get_rtable_name(int rtindex, deparse_context *context);
 static void set_deparse_planstate(deparse_namespace *dpns, PlanState *ps);
 static void push_child_plan(deparse_namespace *dpns, PlanState *ps,
@@ -249,9 +384,9 @@ static void get_from_clause(Query *query, const char *prefix,
 				deparse_context *context);
 static void get_from_clause_item(Node *jtnode, Query *query,
 					 deparse_context *context);
-static void get_from_clause_alias(Alias *alias, RangeTblEntry *rte,
+static void get_column_alias_list(deparse_columns *colinfo,
 					  deparse_context *context);
-static void get_from_clause_coldeflist(List *names,
+static void get_from_clause_coldeflist(deparse_columns *colinfo,
 						   List *types, List *typmods, List *collations,
 						   deparse_context *context);
 static void get_opclass_name(Oid opclass, Oid actual_datatype,
@@ -704,6 +839,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 		dpns.rtable = list_make2(oldrte, newrte);
 		dpns.ctes = NIL;
 		set_rtable_names(&dpns, NIL, NULL);
+		set_simple_column_names(&dpns);
 
 		/* Set up context with one-deep namespace stack */
 		context.buf = &buf;
@@ -2133,13 +2269,14 @@ deparse_expression(Node *expr, List *dpcontext,
  * tree (ie, not the raw output of gram.y).
  *
  * dpcontext is a list of deparse_namespace nodes representing the context
- * for interpreting Vars in the node tree.
+ * for interpreting Vars in the node tree.	It can be NIL if no Vars are
+ * expected.
  *
  * forceprefix is TRUE to force all Vars to be prefixed with their table names.
  *
  * showimplicit is TRUE to force all implicit casts to be shown explicitly.
  *
- * tries to pretty up the output according to prettyFlags and startIndent.
+ * Tries to pretty up the output according to prettyFlags and startIndent.
  *
  * The result is a palloc'd string.
  * ----------
@@ -2198,6 +2335,7 @@ deparse_context_for(const char *aliasname, Oid relid)
 	dpns->rtable = list_make1(rte);
 	dpns->ctes = NIL;
 	set_rtable_names(dpns, NIL, NULL);
+	set_simple_column_names(dpns);
 
 	/* Return a one-deep namespace stack */
 	return list_make1(dpns);
@@ -2241,6 +2379,13 @@ deparse_context_for_planstate(Node *planstate, List *ancestors,
 	dpns->rtable_names = rtable_names;
 	dpns->ctes = NIL;
 
+	/*
+	 * Set up column name aliases.	We will get rather bogus results for join
+	 * RTEs, but that doesn't matter because plan trees don't contain any join
+	 * alias Vars.
+	 */
+	set_simple_column_names(dpns);
+
 	/* Set our attention on the specific plan node passed in */
 	set_deparse_planstate(dpns, (PlanState *) planstate);
 	dpns->ancestors = ancestors;
@@ -2252,8 +2397,8 @@ deparse_context_for_planstate(Node *planstate, List *ancestors,
 /*
  * select_rtable_names_for_explain	- Select RTE aliases for EXPLAIN
  *
- * Determine the aliases we'll use during an EXPLAIN operation.  This is
- * just a frontend to set_rtable_names.  We have to expose the aliases
+ * Determine the relation aliases we'll use during an EXPLAIN operation.
+ * This is just a frontend to set_rtable_names.  We have to expose the aliases
  * to EXPLAIN because EXPLAIN needs to know the right alias names to print.
  */
 List *
@@ -2265,12 +2410,13 @@ select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used)
 	dpns.rtable = rtable;
 	dpns.ctes = NIL;
 	set_rtable_names(&dpns, NIL, rels_used);
+	/* We needn't bother computing column aliases yet */
 
 	return dpns.rtable_names;
 }
 
 /*
- * set_rtable_names: select RTE aliases to be used in printing variables
+ * set_rtable_names: select RTE aliases to be used in printing a query
  *
  * We fill in dpns->rtable_names with a list of names that is one-for-one with
  * the already-filled dpns->rtable list.  Each RTE name is unique among those
@@ -2278,6 +2424,9 @@ select_rtable_names_for_explain(List *rtable, Bitmapset *rels_used)
  * parent_namespaces.
  *
  * If rels_used isn't NULL, only RTE indexes listed in it are given aliases.
+ *
+ * Note that this function is only concerned with relation names, not column
+ * names.
  */
 static void
 set_rtable_names(deparse_namespace *dpns, List *parent_namespaces,
@@ -2371,6 +2520,1002 @@ refname_is_unique(char *refname, deparse_namespace *dpns,
 	return true;
 }
 
+/*
+ * set_deparse_for_query: set up deparse_namespace for deparsing a Query tree
+ *
+ * For convenience, this is defined to initialize the deparse_namespace struct
+ * from scratch.
+ */
+static void
+set_deparse_for_query(deparse_namespace *dpns, Query *query,
+					  List *parent_namespaces)
+{
+	ListCell   *lc;
+	ListCell   *lc2;
+
+	/* Initialize *dpns and fill rtable/ctes links */
+	memset(dpns, 0, sizeof(deparse_namespace));
+	dpns->rtable = query->rtable;
+	dpns->ctes = query->cteList;
+
+	/* Assign a unique relation alias to each RTE */
+	set_rtable_names(dpns, parent_namespaces, NULL);
+
+	/* Initialize dpns->rtable_columns to contain zeroed structs */
+	dpns->rtable_columns = NIL;
+	while (list_length(dpns->rtable_columns) < list_length(dpns->rtable))
+		dpns->rtable_columns = lappend(dpns->rtable_columns,
+									   palloc0(sizeof(deparse_columns)));
+
+	/* Detect whether global uniqueness of USING names is needed */
+	dpns->unique_using = has_unnamed_full_join_using((Node *) query->jointree);
+
+	/*
+	 * Select names for columns merged by USING, via a recursive pass over the
+	 * query jointree.
+	 */
+	set_using_names(dpns, (Node *) query->jointree);
+
+	/*
+	 * Now assign remaining column aliases for each RTE.  We do this in a
+	 * linear scan of the rtable, so as to process RTEs whether or not they
+	 * are in the jointree (we mustn't miss NEW.*, INSERT target relations,
+	 * etc).  JOIN RTEs must be processed after their children, but this is
+	 * okay because they appear later in the rtable list than their children
+	 * (cf Asserts in identify_join_columns()).
+	 */
+	forboth(lc, dpns->rtable, lc2, dpns->rtable_columns)
+	{
+		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+		deparse_columns *colinfo = (deparse_columns *) lfirst(lc2);
+
+		if (rte->rtekind == RTE_JOIN)
+			set_join_column_names(dpns, rte, colinfo);
+		else
+			set_relation_column_names(dpns, rte, colinfo);
+	}
+}
+
+/*
+ * set_simple_column_names: fill in column aliases for non-query situations
+ *
+ * This handles EXPLAIN and cases where we only have relation RTEs.  Without
+ * a join tree, we can't do anything smart about join RTEs, but we don't
+ * need to (note that EXPLAIN should never see join alias Vars anyway).
+ * If we do hit a join RTE we'll just process it like a non-table base RTE.
+ */
+static void
+set_simple_column_names(deparse_namespace *dpns)
+{
+	ListCell   *lc;
+	ListCell   *lc2;
+
+	/* Initialize dpns->rtable_columns to contain zeroed structs */
+	dpns->rtable_columns = NIL;
+	while (list_length(dpns->rtable_columns) < list_length(dpns->rtable))
+		dpns->rtable_columns = lappend(dpns->rtable_columns,
+									   palloc0(sizeof(deparse_columns)));
+
+	/* Assign unique column aliases within each RTE */
+	forboth(lc, dpns->rtable, lc2, dpns->rtable_columns)
+	{
+		RangeTblEntry *rte = (RangeTblEntry *) lfirst(lc);
+		deparse_columns *colinfo = (deparse_columns *) lfirst(lc2);
+
+		set_relation_column_names(dpns, rte, colinfo);
+	}
+}
+
+/*
+ * has_unnamed_full_join_using: search jointree for unnamed FULL JOIN USING
+ *
+ * Merged columns of a FULL JOIN USING act differently from either of the
+ * input columns, so they have to be referenced as columns of the JOIN not
+ * as columns of either input.	And this is problematic if the join is
+ * unnamed (alias-less): we cannot qualify the column's name with an RTE
+ * name, since there is none.  (Forcibly assigning an alias to the join is
+ * not a solution, since that will prevent legal references to tables below
+ * the join.)  To ensure that every column in the query is unambiguously
+ * referenceable, we must assign such merged columns names that are globally
+ * unique across the whole query, aliasing other columns out of the way as
+ * necessary.
+ *
+ * Because the ensuing re-aliasing is fairly damaging to the readability of
+ * the query, we don't do this unless we have to.  So, we must pre-scan
+ * the join tree to see if we have to, before starting set_using_names().
+ */
+static bool
+has_unnamed_full_join_using(Node *jtnode)
+{
+	if (IsA(jtnode, RangeTblRef))
+	{
+		/* nothing to do here */
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *lc;
+
+		foreach(lc, f->fromlist)
+		{
+			if (has_unnamed_full_join_using((Node *) lfirst(lc)))
+				return true;
+		}
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+
+		/* Is it an unnamed FULL JOIN with USING? */
+		if (j->alias == NULL &&
+			j->jointype == JOIN_FULL &&
+			j->usingClause)
+			return true;
+
+		/* Nope, but inspect children */
+		if (has_unnamed_full_join_using(j->larg))
+			return true;
+		if (has_unnamed_full_join_using(j->rarg))
+			return true;
+	}
+	else
+		elog(ERROR, "unrecognized node type: %d",
+			 (int) nodeTag(jtnode));
+	return false;
+}
+
+/*
+ * set_using_names: select column aliases to be used for merged USING columns
+ *
+ * We do this during a recursive descent of the query jointree.
+ * dpns->unique_using must already be set to determine the global strategy.
+ *
+ * Column alias info is saved in the dpns->rtable_columns list, which is
+ * assumed to be filled with pre-zeroed deparse_columns structs.
+ */
+static void
+set_using_names(deparse_namespace *dpns, Node *jtnode)
+{
+	if (IsA(jtnode, RangeTblRef))
+	{
+		/* nothing to do now */
+	}
+	else if (IsA(jtnode, FromExpr))
+	{
+		FromExpr   *f = (FromExpr *) jtnode;
+		ListCell   *lc;
+
+		foreach(lc, f->fromlist)
+			set_using_names(dpns, (Node *) lfirst(lc));
+	}
+	else if (IsA(jtnode, JoinExpr))
+	{
+		JoinExpr   *j = (JoinExpr *) jtnode;
+		RangeTblEntry *rte = rt_fetch(j->rtindex, dpns->rtable);
+		deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns);
+		int		   *leftattnos;
+		int		   *rightattnos;
+		deparse_columns *leftcolinfo;
+		deparse_columns *rightcolinfo;
+		int			i;
+		ListCell   *lc;
+
+		/* Get info about the shape of the join */
+		identify_join_columns(j, rte, colinfo);
+		leftattnos = colinfo->leftattnos;
+		rightattnos = colinfo->rightattnos;
+
+		/* Look up the not-yet-filled-in child deparse_columns structs */
+		leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns);
+		rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns);
+
+		/*
+		 * If this join is unnamed, then we cannot substitute new aliases at
+		 * this level, so any name requirements pushed down to here must be
+		 * pushed down again to the children.
+		 */
+		if (rte->alias == NULL)
+		{
+			for (i = 0; i < colinfo->num_cols; i++)
+			{
+				char	   *colname = colinfo->colnames[i];
+
+				if (colname == NULL)
+					continue;
+
+				/* Push down to left column, unless it's a system column */
+				if (leftattnos[i] > 0)
+				{
+					expand_colnames_array_to(leftcolinfo, leftattnos[i]);
+					leftcolinfo->colnames[leftattnos[i] - 1] = colname;
+				}
+
+				/* Same on the righthand side */
+				if (rightattnos[i] > 0)
+				{
+					expand_colnames_array_to(rightcolinfo, rightattnos[i]);
+					rightcolinfo->colnames[rightattnos[i] - 1] = colname;
+				}
+			}
+		}
+
+		/*
+		 * If there's a USING clause, select the USING column names and push
+		 * those names down to the children.  We have two strategies:
+		 *
+		 * If dpns->unique_using is TRUE, we force all USING names to be
+		 * unique across the whole query level.  In principle we'd only need
+		 * the names of USING columns in unnamed full joins to be globally
+		 * unique, but to safely assign all USING names in a single pass, we
+		 * have to enforce the same uniqueness rule for all of them.  However,
+		 * if a USING column's name has been pushed down from the parent, we
+		 * should use it as-is rather than making a uniqueness adjustment.
+		 * This is necessary when we're at an unnamed join, and it creates no
+		 * risk of ambiguity.  Also, if there's a user-written output alias
+		 * for a merged column, we prefer to use that rather than the input
+		 * name; this simplifies the logic and seems likely to lead to less
+		 * aliasing overall.
+		 *
+		 * If dpns->unique_using is FALSE, we only need USING names to be
+		 * unique within their own join RTE.  We still need to honor
+		 * pushed-down names, though.
+		 *
+		 * Though significantly different in results, these two strategies are
+		 * implemented by the same code, with only the difference of whether
+		 * to put assigned names into dpns->using_names.
+		 */
+		if (j->usingClause)
+		{
+			/* USING names must correspond to the first join output columns */
+			expand_colnames_array_to(colinfo, list_length(j->usingClause));
+			i = 0;
+			foreach(lc, j->usingClause)
+			{
+				char	   *colname = strVal(lfirst(lc));
+
+				/* Assert it's a merged column */
+				Assert(leftattnos[i] != 0 && rightattnos[i] != 0);
+
+				/* Adopt passed-down name if any, else select unique name */
+				if (colinfo->colnames[i] != NULL)
+					colname = colinfo->colnames[i];
+				else
+				{
+					/* Prefer user-written output alias if any */
+					if (rte->alias && i < list_length(rte->alias->colnames))
+						colname = strVal(list_nth(rte->alias->colnames, i));
+					/* Make it appropriately unique */
+					colname = make_colname_unique(colname, dpns, colinfo);
+					if (dpns->unique_using)
+						dpns->using_names = lappend(dpns->using_names,
+													colname);
+					/* Save it as output column name, too */
+					colinfo->colnames[i] = colname;
+				}
+
+				/* Remember selected names for use later */
+				colinfo->usingNames = lappend(colinfo->usingNames, colname);
+
+				/* Push down to left column, unless it's a system column */
+				if (leftattnos[i] > 0)
+				{
+					expand_colnames_array_to(leftcolinfo, leftattnos[i]);
+					leftcolinfo->colnames[leftattnos[i] - 1] = colname;
+				}
+
+				/* Same on the righthand side */
+				if (rightattnos[i] > 0)
+				{
+					expand_colnames_array_to(rightcolinfo, rightattnos[i]);
+					rightcolinfo->colnames[rightattnos[i] - 1] = colname;
+				}
+
+				i++;
+			}
+		}
+
+		/* Now recursively assign USING column names in children */
+		set_using_names(dpns, j->larg);
+		set_using_names(dpns, j->rarg);
+	}
+	else
+		elog(ERROR, "unrecognized node type: %d",
+			 (int) nodeTag(jtnode));
+}
+
+/*
+ * set_relation_column_names: select column aliases for a non-join RTE
+ *
+ * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed.
+ * If any colnames entries are already filled in, those override local
+ * choices.
+ */
+static void
+set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
+						  deparse_columns *colinfo)
+{
+	int			ncolumns;
+	char	  **real_colnames;
+	bool		changed_any;
+	int			noldcolumns;
+	int			i;
+	int			j;
+
+	/*
+	 * Extract the RTE's "real" column names.  This is comparable to
+	 * get_rte_attribute_name, except that it's important to disregard dropped
+	 * columns.  We put NULL into the array for a dropped column.
+	 */
+	if (rte->rtekind == RTE_RELATION)
+	{
+		/* Relation --- look to the system catalogs for up-to-date info */
+		Relation	rel;
+		TupleDesc	tupdesc;
+
+		rel = relation_open(rte->relid, AccessShareLock);
+		tupdesc = RelationGetDescr(rel);
+
+		ncolumns = tupdesc->natts;
+		real_colnames = (char **) palloc(ncolumns * sizeof(char *));
+
+		for (i = 0; i < ncolumns; i++)
+		{
+			if (tupdesc->attrs[i]->attisdropped)
+				real_colnames[i] = NULL;
+			else
+				real_colnames[i] = pstrdup(NameStr(tupdesc->attrs[i]->attname));
+		}
+		relation_close(rel, AccessShareLock);
+	}
+	else
+	{
+		/* Otherwise use the column names from eref */
+		ListCell   *lc;
+
+		ncolumns = list_length(rte->eref->colnames);
+		real_colnames = (char **) palloc(ncolumns * sizeof(char *));
+
+		i = 0;
+		foreach(lc, rte->eref->colnames)
+		{
+			real_colnames[i] = strVal(lfirst(lc));
+			i++;
+		}
+	}
+
+	/*
+	 * Ensure colinfo->colnames has a slot for each column.  (It could be long
+	 * enough already, if we pushed down a name for the last column.)  Note:
+	 * it's possible that there are now more columns than there were when the
+	 * query was parsed, ie colnames could be longer than rte->eref->colnames.
+	 * We must assign unique aliases to the new columns too, else there could
+	 * be unresolved conflicts when the view/rule is reloaded.
+	 */
+	expand_colnames_array_to(colinfo, ncolumns);
+	Assert(colinfo->num_cols == ncolumns);
+
+	/*
+	 * Make sufficiently large new_colnames and is_new_col arrays, too.
+	 *
+	 * Note: because we leave colinfo->num_new_cols zero until after the loop,
+	 * colname_is_unique will not consult that array, which is fine because it
+	 * would only be duplicate effort.
+	 */
+	colinfo->new_colnames = (char **) palloc(ncolumns * sizeof(char *));
+	colinfo->is_new_col = (bool *) palloc(ncolumns * sizeof(bool));
+
+	/*
+	 * Scan the columns, select a unique alias for each one, and store it in
+	 * colinfo->colnames and colinfo->new_colnames.  The former array has NULL
+	 * entries for dropped columns, the latter omits them.	Also mark
+	 * new_colnames entries as to whether they are new since parse time; this
+	 * is the case for entries beyond the length of rte->eref->colnames.
+	 */
+	noldcolumns = list_length(rte->eref->colnames);
+	changed_any = false;
+	j = 0;
+	for (i = 0; i < ncolumns; i++)
+	{
+		char	   *real_colname = real_colnames[i];
+		char	   *colname = colinfo->colnames[i];
+
+		/* Skip dropped columns */
+		if (real_colname == NULL)
+		{
+			Assert(colname == NULL);	/* colnames[i] is already NULL */
+			continue;
+		}
+
+		/* If alias already assigned, that's what to use */
+		if (colname == NULL)
+		{
+			/* If user wrote an alias, prefer that over real column name */
+			if (rte->alias && i < list_length(rte->alias->colnames))
+				colname = strVal(list_nth(rte->alias->colnames, i));
+			else
+				colname = real_colname;
+
+			/* Unique-ify and insert into colinfo */
+			colname = make_colname_unique(colname, dpns, colinfo);
+
+			colinfo->colnames[i] = colname;
+		}
+
+		/* Put names of non-dropped columns in new_colnames[] too */
+		colinfo->new_colnames[j] = colname;
+		/* And mark them as new or not */
+		colinfo->is_new_col[j] = (i >= noldcolumns);
+		j++;
+
+		/* Remember if any assigned aliases differ from "real" name */
+		if (!changed_any && strcmp(colname, real_colname) != 0)
+			changed_any = true;
+	}
+
+	/*
+	 * Set correct length for new_colnames[] array.  (Note: if columns have
+	 * been added, colinfo->num_cols includes them, which is not really quite
+	 * right but is harmless, since any new columns must be at the end where
+	 * they won't affect varattnos of pre-existing columns.)
+	 */
+	colinfo->num_new_cols = j;
+
+	/*
+	 * For a relation RTE, we need only print the alias column names if any
+	 * are different from the underlying "real" names.	For a function RTE,
+	 * always emit a complete column alias list; this is to protect against
+	 * possible instability of the default column names (eg, from altering
+	 * parameter names).  For other RTE types, print if we changed anything OR
+	 * if there were user-written column aliases (since the latter would be
+	 * part of the underlying "reality").
+	 */
+	if (rte->rtekind == RTE_RELATION)
+		colinfo->printaliases = changed_any;
+	else if (rte->rtekind == RTE_FUNCTION)
+		colinfo->printaliases = true;
+	else if (rte->alias && rte->alias->colnames != NIL)
+		colinfo->printaliases = true;
+	else
+		colinfo->printaliases = changed_any;
+}
+
+/*
+ * set_join_column_names: select column aliases for a join RTE
+ *
+ * Column alias info is saved in *colinfo, which is assumed to be pre-zeroed.
+ * If any colnames entries are already filled in, those override local
+ * choices.  Also, names for USING columns were already chosen by
+ * set_using_names().  We further expect that column alias selection has been
+ * completed for both input RTEs.
+ */
+static void
+set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
+					  deparse_columns *colinfo)
+{
+	deparse_columns *leftcolinfo;
+	deparse_columns *rightcolinfo;
+	bool		changed_any;
+	int			noldcolumns;
+	int			nnewcolumns;
+	Bitmapset  *leftmerged = NULL;
+	Bitmapset  *rightmerged = NULL;
+	int			i;
+	int			j;
+	int			ic;
+	int			jc;
+
+	/* Look up the previously-filled-in child deparse_columns structs */
+	leftcolinfo = deparse_columns_fetch(colinfo->leftrti, dpns);
+	rightcolinfo = deparse_columns_fetch(colinfo->rightrti, dpns);
+
+	/*
+	 * Ensure colinfo->colnames has a slot for each column.  (It could be long
+	 * enough already, if we pushed down a name for the last column.)  Note:
+	 * it's possible that one or both inputs now have more columns than there
+	 * were when the query was parsed, but we'll deal with that below.  We
+	 * only need entries in colnames for pre-existing columns.
+	 */
+	noldcolumns = list_length(rte->eref->colnames);
+	expand_colnames_array_to(colinfo, noldcolumns);
+	Assert(colinfo->num_cols == noldcolumns);
+
+	/*
+	 * Scan the join output columns, select an alias for each one, and store
+	 * it in colinfo->colnames.  If there are USING columns, set_using_names()
+	 * already selected their names, so we can start the loop at the first
+	 * non-merged column.
+	 */
+	changed_any = false;
+	for (i = list_length(colinfo->usingNames); i < noldcolumns; i++)
+	{
+		char	   *colname = colinfo->colnames[i];
+		char	   *real_colname;
+
+		/* Get the child column name */
+		if (colinfo->leftattnos[i] > 0)
+			real_colname = leftcolinfo->colnames[colinfo->leftattnos[i] - 1];
+		else if (colinfo->rightattnos[i] > 0)
+			real_colname = rightcolinfo->colnames[colinfo->rightattnos[i] - 1];
+		else
+		{
+			/* We're joining system columns --- use eref name */
+			real_colname = (char *) list_nth(rte->eref->colnames, i);
+		}
+
+		/* Ignore dropped columns (only possible for non-merged column) */
+		if (real_colname == NULL)
+		{
+			Assert(colname == NULL);
+			continue;
+		}
+
+		/* In an unnamed join, just report child column names as-is */
+		if (rte->alias == NULL)
+		{
+			colinfo->colnames[i] = real_colname;
+			continue;
+		}
+
+		/* If alias already assigned, that's what to use */
+		if (colname == NULL)
+		{
+			/* If user wrote an alias, prefer that over real column name */
+			if (rte->alias && i < list_length(rte->alias->colnames))
+				colname = strVal(list_nth(rte->alias->colnames, i));
+			else
+				colname = real_colname;
+
+			/* Unique-ify and insert into colinfo */
+			colname = make_colname_unique(colname, dpns, colinfo);
+
+			colinfo->colnames[i] = colname;
+		}
+
+		/* Remember if any assigned aliases differ from "real" name */
+		if (!changed_any && strcmp(colname, real_colname) != 0)
+			changed_any = true;
+	}
+
+	/*
+	 * Calculate number of columns the join would have if it were re-parsed
+	 * now, and create storage for the new_colnames and is_new_col arrays.
+	 *
+	 * Note: colname_is_unique will be consulting new_colnames[] during the
+	 * loops below, so its not-yet-filled entries must be zeroes.
+	 */
+	nnewcolumns = leftcolinfo->num_new_cols + rightcolinfo->num_new_cols -
+		list_length(colinfo->usingNames);
+	colinfo->num_new_cols = nnewcolumns;
+	colinfo->new_colnames = (char **) palloc0(nnewcolumns * sizeof(char *));
+	colinfo->is_new_col = (bool *) palloc0(nnewcolumns * sizeof(bool));
+
+	/*
+	 * Generating the new_colnames array is a bit tricky since any new columns
+	 * added since parse time must be inserted in the right places.  This code
+	 * must match the parser, which will order a join's columns as merged
+	 * columns first (in USING-clause order), then non-merged columns from the
+	 * left input (in attnum order), then non-merged columns from the right
+	 * input (ditto).  If one of the inputs is itself a join, its columns will
+	 * be ordered according to the same rule, which means newly-added columns
+	 * might not be at the end.  We can figure out what's what by consulting
+	 * the leftattnos and rightattnos arrays plus the input is_new_col arrays.
+	 *
+	 * In these loops, i indexes leftattnos/rightattnos (so it's join varattno
+	 * less one), j indexes new_colnames/is_new_col, and ic/jc have similar
+	 * meanings for the current child RTE.
+	 */
+
+	/* Handle merged columns; they are first and can't be new */
+	i = j = 0;
+	while (i < noldcolumns &&
+		   colinfo->leftattnos[i] != 0 &&
+		   colinfo->rightattnos[i] != 0)
+	{
+		/* column name is already determined and known unique */
+		colinfo->new_colnames[j] = colinfo->colnames[i];
+		colinfo->is_new_col[j] = false;
+
+		/* build bitmapsets of child attnums of merged columns */
+		if (colinfo->leftattnos[i] > 0)
+			leftmerged = bms_add_member(leftmerged, colinfo->leftattnos[i]);
+		if (colinfo->rightattnos[i] > 0)
+			rightmerged = bms_add_member(rightmerged, colinfo->rightattnos[i]);
+
+		i++, j++;
+	}
+
+	/* Handle non-merged left-child columns */
+	ic = 0;
+	for (jc = 0; jc < leftcolinfo->num_new_cols; jc++)
+	{
+		char	   *child_colname = leftcolinfo->new_colnames[jc];
+
+		if (!leftcolinfo->is_new_col[jc])
+		{
+			/* Advance ic to next non-dropped old column of left child */
+			while (ic < leftcolinfo->num_cols &&
+				   leftcolinfo->colnames[ic] == NULL)
+				ic++;
+			Assert(ic < leftcolinfo->num_cols);
+			ic++;
+			/* If it is a merged column, we already processed it */
+			if (bms_is_member(ic, leftmerged))
+				continue;
+			/* Else, advance i to the corresponding existing join column */
+			while (i < colinfo->num_cols &&
+				   colinfo->colnames[i] == NULL)
+				i++;
+			Assert(i < colinfo->num_cols);
+			Assert(ic == colinfo->leftattnos[i]);
+			/* Use the already-assigned name of this column */
+			colinfo->new_colnames[j] = colinfo->colnames[i];
+			i++;
+		}
+		else
+		{
+			/*
+			 * Unique-ify the new child column name and assign, unless we're
+			 * in an unnamed join, in which case just copy
+			 */
+			if (rte->alias != NULL)
+			{
+				colinfo->new_colnames[j] =
+					make_colname_unique(child_colname, dpns, colinfo);
+				if (!changed_any &&
+					strcmp(colinfo->new_colnames[j], child_colname) != 0)
+					changed_any = true;
+			}
+			else
+				colinfo->new_colnames[j] = child_colname;
+		}
+
+		colinfo->is_new_col[j] = leftcolinfo->is_new_col[jc];
+		j++;
+	}
+
+	/* Handle non-merged right-child columns in exactly the same way */
+	ic = 0;
+	for (jc = 0; jc < rightcolinfo->num_new_cols; jc++)
+	{
+		char	   *child_colname = rightcolinfo->new_colnames[jc];
+
+		if (!rightcolinfo->is_new_col[jc])
+		{
+			/* Advance ic to next non-dropped old column of right child */
+			while (ic < rightcolinfo->num_cols &&
+				   rightcolinfo->colnames[ic] == NULL)
+				ic++;
+			Assert(ic < rightcolinfo->num_cols);
+			ic++;
+			/* If it is a merged column, we already processed it */
+			if (bms_is_member(ic, rightmerged))
+				continue;
+			/* Else, advance i to the corresponding existing join column */
+			while (i < colinfo->num_cols &&
+				   colinfo->colnames[i] == NULL)
+				i++;
+			Assert(i < colinfo->num_cols);
+			Assert(ic == colinfo->rightattnos[i]);
+			/* Use the already-assigned name of this column */
+			colinfo->new_colnames[j] = colinfo->colnames[i];
+			i++;
+		}
+		else
+		{
+			/*
+			 * Unique-ify the new child column name and assign, unless we're
+			 * in an unnamed join, in which case just copy
+			 */
+			if (rte->alias != NULL)
+			{
+				colinfo->new_colnames[j] =
+					make_colname_unique(child_colname, dpns, colinfo);
+				if (!changed_any &&
+					strcmp(colinfo->new_colnames[j], child_colname) != 0)
+					changed_any = true;
+			}
+			else
+				colinfo->new_colnames[j] = child_colname;
+		}
+
+		colinfo->is_new_col[j] = rightcolinfo->is_new_col[jc];
+		j++;
+	}
+
+	/* Assert we processed the right number of columns */
+#ifdef USE_ASSERT_CHECKING
+	while (i < colinfo->num_cols && colinfo->colnames[i] == NULL)
+		i++;
+	Assert(i == colinfo->num_cols);
+	Assert(j == nnewcolumns);
+#endif
+
+	/*
+	 * For a named join, print column aliases if we changed any from the child
+	 * names.  Unnamed joins cannot print aliases.
+	 */
+	if (rte->alias != NULL)
+		colinfo->printaliases = changed_any;
+	else
+		colinfo->printaliases = false;
+}
+
+/*
+ * colname_is_unique: is colname distinct from already-chosen column names?
+ *
+ * dpns is query-wide info, colinfo is for the column's RTE
+ */
+static bool
+colname_is_unique(char *colname, deparse_namespace *dpns,
+				  deparse_columns *colinfo)
+{
+	int			i;
+	ListCell   *lc;
+
+	/* Check against already-assigned column aliases within RTE */
+	for (i = 0; i < colinfo->num_cols; i++)
+	{
+		char	   *oldname = colinfo->colnames[i];
+
+		if (oldname && strcmp(oldname, colname) == 0)
+			return false;
+	}
+
+	/*
+	 * If we're building a new_colnames array, check that too (this will be
+	 * partially but not completely redundant with the previous checks)
+	 */
+	for (i = 0; i < colinfo->num_new_cols; i++)
+	{
+		char	   *oldname = colinfo->new_colnames[i];
+
+		if (oldname && strcmp(oldname, colname) == 0)
+			return false;
+	}
+
+	/* Also check against USING-column names that must be globally unique */
+	foreach(lc, dpns->using_names)
+	{
+		char	   *oldname = (char *) lfirst(lc);
+
+		if (strcmp(oldname, colname) == 0)
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * make_colname_unique: modify colname if necessary to make it unique
+ *
+ * dpns is query-wide info, colinfo is for the column's RTE
+ */
+static char *
+make_colname_unique(char *colname, deparse_namespace *dpns,
+					deparse_columns *colinfo)
+{
+	/*
+	 * If the selected name isn't unique, append digits to make it so
+	 */
+	if (!colname_is_unique(colname, dpns, colinfo))
+	{
+		char	   *modname = (char *) palloc(strlen(colname) + 32);
+		int			i = 0;
+
+		do
+		{
+			sprintf(modname, "%s_%d", colname, ++i);
+		} while (!colname_is_unique(modname, dpns, colinfo));
+		colname = modname;
+	}
+	return colname;
+}
+
+/*
+ * expand_colnames_array_to: make colinfo->colnames at least n items long
+ *
+ * Any added array entries are initialized to zero.
+ */
+static void
+expand_colnames_array_to(deparse_columns *colinfo, int n)
+{
+	if (n > colinfo->num_cols)
+	{
+		if (colinfo->colnames == NULL)
+			colinfo->colnames = (char **) palloc0(n * sizeof(char *));
+		else
+		{
+			colinfo->colnames = (char **) repalloc(colinfo->colnames,
+												   n * sizeof(char *));
+			memset(colinfo->colnames + colinfo->num_cols, 0,
+				   (n - colinfo->num_cols) * sizeof(char *));
+		}
+		colinfo->num_cols = n;
+	}
+}
+
+/*
+ * identify_join_columns: figure out where columns of a join come from
+ *
+ * Fills the join-specific fields of the colinfo struct, except for
+ * usingNames which is filled later.
+ */
+static void
+identify_join_columns(JoinExpr *j, RangeTblEntry *jrte,
+					  deparse_columns *colinfo)
+{
+	int			numjoincols;
+	int			i;
+	ListCell   *lc;
+
+	/* Extract left/right child RT indexes */
+	if (IsA(j->larg, RangeTblRef))
+		colinfo->leftrti = ((RangeTblRef *) j->larg)->rtindex;
+	else if (IsA(j->larg, JoinExpr))
+		colinfo->leftrti = ((JoinExpr *) j->larg)->rtindex;
+	else
+		elog(ERROR, "unrecognized node type in jointree: %d",
+			 (int) nodeTag(j->larg));
+	if (IsA(j->rarg, RangeTblRef))
+		colinfo->rightrti = ((RangeTblRef *) j->rarg)->rtindex;
+	else if (IsA(j->rarg, JoinExpr))
+		colinfo->rightrti = ((JoinExpr *) j->rarg)->rtindex;
+	else
+		elog(ERROR, "unrecognized node type in jointree: %d",
+			 (int) nodeTag(j->rarg));
+
+	/* Assert children will be processed earlier than join in second pass */
+	Assert(colinfo->leftrti < j->rtindex);
+	Assert(colinfo->rightrti < j->rtindex);
+
+	/* Initialize result arrays with zeroes */
+	numjoincols = list_length(jrte->joinaliasvars);
+	Assert(numjoincols == list_length(jrte->eref->colnames));
+	colinfo->leftattnos = (int *) palloc0(numjoincols * sizeof(int));
+	colinfo->rightattnos = (int *) palloc0(numjoincols * sizeof(int));
+
+	/* Scan the joinaliasvars list to identify simple column references */
+	i = 0;
+	foreach(lc, jrte->joinaliasvars)
+	{
+		Var		   *aliasvar = (Var *) lfirst(lc);
+
+		if (IsA(aliasvar, Var))
+		{
+			Assert(aliasvar->varlevelsup == 0);
+			Assert(aliasvar->varattno != 0);
+			if (aliasvar->varno == colinfo->leftrti)
+				colinfo->leftattnos[i] = aliasvar->varattno;
+			else if (aliasvar->varno == colinfo->rightrti)
+				colinfo->rightattnos[i] = aliasvar->varattno;
+			else
+				elog(ERROR, "unexpected varno %d in JOIN RTE",
+					 aliasvar->varno);
+		}
+		else if (IsA(aliasvar, CoalesceExpr))
+		{
+			/*
+			 * It's a merged column in FULL JOIN USING.  Ignore it for now and
+			 * let the code below identify the merged columns.
+			 */
+		}
+		else
+		{
+			/*
+			 * Although NULL constants can appear in joinaliasvars lists
+			 * during planning, we shouldn't see any here, since the Query
+			 * tree hasn't been through AcquireRewriteLocks().
+			 */
+			elog(ERROR, "unrecognized node type in join alias vars: %d",
+				 (int) nodeTag(aliasvar));
+		}
+
+		i++;
+	}
+
+	/*
+	 * If there's a USING clause, deconstruct the join quals to identify the
+	 * merged columns.	This is a tad painful but if we cannot rely on the
+	 * column names, there is no other representation of which columns were
+	 * joined by USING.  (Unless the join type is FULL, we can't tell from the
+	 * joinaliasvars list which columns are merged.)  Note: we assume that the
+	 * merged columns are the first output column(s) of the join.
+	 */
+	if (j->usingClause)
+	{
+		List	   *leftvars = NIL;
+		List	   *rightvars = NIL;
+		ListCell   *lc2;
+
+		/* Extract left- and right-side Vars from the qual expression */
+		flatten_join_using_qual(j->quals, &leftvars, &rightvars);
+		Assert(list_length(leftvars) == list_length(j->usingClause));
+		Assert(list_length(rightvars) == list_length(j->usingClause));
+
+		/* Mark the output columns accordingly */
+		i = 0;
+		forboth(lc, leftvars, lc2, rightvars)
+		{
+			Var		   *leftvar = (Var *) lfirst(lc);
+			Var		   *rightvar = (Var *) lfirst(lc2);
+
+			Assert(leftvar->varlevelsup == 0);
+			Assert(leftvar->varattno != 0);
+			if (leftvar->varno != colinfo->leftrti)
+				elog(ERROR, "unexpected varno %d in JOIN USING qual",
+					 leftvar->varno);
+			colinfo->leftattnos[i] = leftvar->varattno;
+
+			Assert(rightvar->varlevelsup == 0);
+			Assert(rightvar->varattno != 0);
+			if (rightvar->varno != colinfo->rightrti)
+				elog(ERROR, "unexpected varno %d in JOIN USING qual",
+					 rightvar->varno);
+			colinfo->rightattnos[i] = rightvar->varattno;
+
+			i++;
+		}
+	}
+}
+
+/*
+ * flatten_join_using_qual: extract Vars being joined from a JOIN/USING qual
+ *
+ * We assume that transformJoinUsingClause won't have produced anything except
+ * AND nodes, equality operator nodes, and possibly implicit coercions, and
+ * that the AND node inputs match left-to-right with the original USING list.
+ *
+ * Caller must initialize the result lists to NIL.
+ */
+static void
+flatten_join_using_qual(Node *qual, List **leftvars, List **rightvars)
+{
+	if (IsA(qual, BoolExpr))
+	{
+		/* Handle AND nodes by recursion */
+		BoolExpr   *b = (BoolExpr *) qual;
+		ListCell   *lc;
+
+		Assert(b->boolop == AND_EXPR);
+		foreach(lc, b->args)
+		{
+			flatten_join_using_qual((Node *) lfirst(lc),
+									leftvars, rightvars);
+		}
+	}
+	else if (IsA(qual, OpExpr))
+	{
+		/* Otherwise we should have an equality operator */
+		OpExpr	   *op = (OpExpr *) qual;
+		Var		   *var;
+
+		if (list_length(op->args) != 2)
+			elog(ERROR, "unexpected unary operator in JOIN/USING qual");
+		/* Arguments should be Vars with perhaps implicit coercions */
+		var = (Var *) strip_implicit_coercions((Node *) linitial(op->args));
+		if (!IsA(var, Var))
+			elog(ERROR, "unexpected node type in JOIN/USING qual: %d",
+				 (int) nodeTag(var));
+		*leftvars = lappend(*leftvars, var);
+		var = (Var *) strip_implicit_coercions((Node *) lsecond(op->args));
+		if (!IsA(var, Var))
+			elog(ERROR, "unexpected node type in JOIN/USING qual: %d",
+				 (int) nodeTag(var));
+		*rightvars = lappend(*rightvars, var);
+	}
+	else
+	{
+		/* Perhaps we have an implicit coercion to boolean? */
+		Node	   *q = strip_implicit_coercions(qual);
+
+		if (q != qual)
+			flatten_join_using_qual(q, leftvars, rightvars);
+		else
+			elog(ERROR, "unexpected node type in JOIN/USING qual: %d",
+				 (int) nodeTag(qual));
+	}
+}
+
 /*
  * get_rtable_name: convenience function to get a previously assigned RTE alias
  *
@@ -2684,10 +3829,7 @@ make_ruledef(StringInfo buf, HeapTuple ruletup, TupleDesc rulettc,
 		context.wrapColumn = WRAP_COLUMN_DEFAULT;
 		context.indentLevel = PRETTYINDENT_STD;
 
-		memset(&dpns, 0, sizeof(dpns));
-		dpns.rtable = query->rtable;
-		dpns.ctes = query->cteList;
-		set_rtable_names(&dpns, NIL, NULL);
+		set_deparse_for_query(&dpns, query, NIL);
 
 		get_rule_expr(qual, &context, false);
 	}
@@ -2835,10 +3977,7 @@ get_query_def(Query *query, StringInfo buf, List *parentnamespace,
 	context.wrapColumn = wrapColumn;
 	context.indentLevel = startIndent;
 
-	memset(&dpns, 0, sizeof(dpns));
-	dpns.rtable = query->rtable;
-	dpns.ctes = query->cteList;
-	set_rtable_names(&dpns, parentnamespace, NULL);
+	set_deparse_for_query(&dpns, query, parentnamespace);
 
 	switch (query->commandType)
 	{
@@ -4010,7 +5149,7 @@ get_utility_query_def(Query *query, deparse_context *context)
  * name in the query.
  *
  * Returns the attname of the Var, or NULL if the Var has no attname (because
- * it is a whole-row Var).
+ * it is a whole-row Var or a subplan output reference).
  */
 static char *
 get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
@@ -4020,6 +5159,7 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 	AttrNumber	attnum;
 	int			netlevelsup;
 	deparse_namespace *dpns;
+	deparse_columns *colinfo;
 	char	   *refname;
 	char	   *attname;
 
@@ -4034,12 +5174,14 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 	/*
 	 * Try to find the relevant RTE in this rtable.  In a plan tree, it's
 	 * likely that varno is OUTER_VAR or INNER_VAR, in which case we must dig
-	 * down into the subplans, or INDEX_VAR, which is resolved similarly.
+	 * down into the subplans, or INDEX_VAR, which is resolved similarly. Also
+	 * find the aliases previously assigned for this RTE.
 	 */
 	if (var->varno >= 1 && var->varno <= list_length(dpns->rtable))
 	{
 		rte = rt_fetch(var->varno, dpns->rtable);
 		refname = (char *) list_nth(dpns->rtable_names, var->varno - 1);
+		colinfo = deparse_columns_fetch(var->varno, dpns);
 		attnum = var->varattno;
 	}
 	else if (var->varno == OUTER_VAR && dpns->outer_tlist)
@@ -4162,11 +5304,11 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 	/*
 	 * If it's an unnamed join, look at the expansion of the alias variable.
 	 * If it's a simple reference to one of the input vars, then recursively
-	 * print the name of that var instead.	(This allows correct decompiling
-	 * of cases where there are identically named columns on both sides of the
-	 * join.)  When it's not a simple reference, we have to just print the
-	 * unqualified variable name (this can only happen with columns that were
-	 * merged by USING or NATURAL clauses).
+	 * print the name of that var instead.  When it's not a simple reference,
+	 * we have to just print the unqualified join column name.  (This can only
+	 * happen with columns that were merged by USING or NATURAL clauses in a
+	 * FULL JOIN; we took pains previously to make the unqualified column name
+	 * unique in such cases.)
 	 *
 	 * This wouldn't work in decompiling plan trees, because we don't store
 	 * joinaliasvars lists after planning; but a plan tree should never
@@ -4198,8 +5340,18 @@ get_variable(Var *var, int levelsup, bool istoplevel, deparse_context *context)
 
 	if (attnum == InvalidAttrNumber)
 		attname = NULL;
+	else if (attnum > 0)
+	{
+		/* Get column name to use from the colinfo struct */
+		Assert(attnum <= colinfo->num_cols);
+		attname = colinfo->colnames[attnum - 1];
+		Assert(attname != NULL);
+	}
 	else
+	{
+		/* System column - name is fixed, get it from the catalog */
 		attname = get_rte_attribute_name(rte, attnum);
+	}
 
 	if (refname && (context->varprefix || attname == NULL))
 	{
@@ -4429,10 +5581,8 @@ get_name_for_var_field(Var *var, int fieldno,
 						deparse_namespace mydpns;
 						const char *result;
 
-						memset(&mydpns, 0, sizeof(mydpns));
-						mydpns.rtable = rte->subquery->rtable;
-						mydpns.ctes = rte->subquery->cteList;
-						set_rtable_names(&mydpns, context->namespaces, NULL);
+						set_deparse_for_query(&mydpns, rte->subquery,
+											  context->namespaces);
 
 						context->namespaces = lcons(&mydpns,
 													context->namespaces);
@@ -4547,10 +5697,8 @@ get_name_for_var_field(Var *var, int fieldno,
 						deparse_namespace mydpns;
 						const char *result;
 
-						memset(&mydpns, 0, sizeof(mydpns));
-						mydpns.rtable = ctequery->rtable;
-						mydpns.ctes = ctequery->cteList;
-						set_rtable_names(&mydpns, context->namespaces, NULL);
+						set_deparse_for_query(&mydpns, ctequery,
+											  context->namespaces);
 
 						new_nslist = list_copy_tail(context->namespaces,
 													ctelevelsup);
@@ -6751,12 +7899,14 @@ static void
 get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 {
 	StringInfo	buf = context->buf;
+	deparse_namespace *dpns = (deparse_namespace *) linitial(context->namespaces);
 
 	if (IsA(jtnode, RangeTblRef))
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 		RangeTblEntry *rte = rt_fetch(varno, query->rtable);
 		char	   *refname = get_rtable_name(varno, context);
+		deparse_columns *colinfo = deparse_columns_fetch(varno, dpns);
 		bool		printalias;
 
 		if (rte->lateral)
@@ -6803,6 +7953,11 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			/* Always print alias if user provided one */
 			printalias = true;
 		}
+		else if (colinfo->printaliases)
+		{
+			/* Always print alias if we need to print column aliases */
+			printalias = true;
+		}
 		else if (rte->rtekind == RTE_RELATION)
 		{
 			/*
@@ -6816,9 +7971,10 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 		else if (rte->rtekind == RTE_FUNCTION)
 		{
 			/*
-			 * For a function RTE, always print alias.  This covers possible
+			 * For a function RTE, always print alias.	This covers possible
 			 * renaming of the function and/or instability of the
 			 * FigureColname rules for things that aren't simple functions.
+			 * Also note we'd need to force it anyway for the RECORD case.
 			 */
 			printalias = true;
 		}
@@ -6836,42 +7992,25 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 			appendStringInfo(buf, " %s", quote_identifier(refname));
 
 		/* Print the column definitions or aliases, if needed */
-		if (rte->rtekind == RTE_FUNCTION)
+		if (rte->rtekind == RTE_FUNCTION && rte->funccoltypes != NIL)
 		{
-			if (rte->funccoltypes != NIL)
-			{
-				/* Function returning RECORD, reconstruct the columndefs */
-				if (!printalias)
-					appendStringInfo(buf, " AS ");
-				get_from_clause_coldeflist(rte->eref->colnames,
-										   rte->funccoltypes,
-										   rte->funccoltypmods,
-										   rte->funccolcollations,
-										   context);
-			}
-			else
-			{
-				/*
-				 * For a function RTE, always emit a complete column alias
-				 * list; this is to protect against possible instability of
-				 * the default column names (eg, from altering parameter
-				 * names).
-				 */
-				get_from_clause_alias(rte->eref, rte, context);
-			}
+			/* Function returning RECORD, reconstruct the columndefs */
+			get_from_clause_coldeflist(colinfo,
+									   rte->funccoltypes,
+									   rte->funccoltypmods,
+									   rte->funccolcollations,
+									   context);
 		}
 		else
 		{
-			/*
-			 * For non-function RTEs, just report whatever the user originally
-			 * gave as column aliases.
-			 */
-			get_from_clause_alias(rte->alias, rte, context);
+			/* Else print column aliases as needed */
+			get_column_alias_list(colinfo, context);
 		}
 	}
 	else if (IsA(jtnode, JoinExpr))
 	{
 		JoinExpr   *j = (JoinExpr *) jtnode;
+		deparse_columns *colinfo = deparse_columns_fetch(j->rtindex, dpns);
 		bool		need_paren_on_right;
 
 		need_paren_on_right = PRETTY_PAREN(context) &&
@@ -6883,70 +8022,36 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 
 		get_from_clause_item(j->larg, query, context);
 
-		if (j->isNatural)
-		{
-			if (!PRETTY_INDENT(context))
-				appendStringInfoChar(buf, ' ');
-			switch (j->jointype)
-			{
-				case JOIN_INNER:
-					appendContextKeyword(context, "NATURAL JOIN ",
-										 -PRETTYINDENT_JOIN,
-										 PRETTYINDENT_JOIN, 0);
-					break;
-				case JOIN_LEFT:
-					appendContextKeyword(context, "NATURAL LEFT JOIN ",
-										 -PRETTYINDENT_JOIN,
-										 PRETTYINDENT_JOIN, 0);
-					break;
-				case JOIN_FULL:
-					appendContextKeyword(context, "NATURAL FULL JOIN ",
-										 -PRETTYINDENT_JOIN,
-										 PRETTYINDENT_JOIN, 0);
-					break;
-				case JOIN_RIGHT:
-					appendContextKeyword(context, "NATURAL RIGHT JOIN ",
-										 -PRETTYINDENT_JOIN,
-										 PRETTYINDENT_JOIN, 0);
-					break;
-				default:
-					elog(ERROR, "unrecognized join type: %d",
-						 (int) j->jointype);
-			}
-		}
-		else
+		switch (j->jointype)
 		{
-			switch (j->jointype)
-			{
-				case JOIN_INNER:
-					if (j->quals)
-						appendContextKeyword(context, " JOIN ",
-											 -PRETTYINDENT_JOIN,
-											 PRETTYINDENT_JOIN, 2);
-					else
-						appendContextKeyword(context, " CROSS JOIN ",
-											 -PRETTYINDENT_JOIN,
-											 PRETTYINDENT_JOIN, 1);
-					break;
-				case JOIN_LEFT:
-					appendContextKeyword(context, " LEFT JOIN ",
+			case JOIN_INNER:
+				if (j->quals)
+					appendContextKeyword(context, " JOIN ",
 										 -PRETTYINDENT_JOIN,
 										 PRETTYINDENT_JOIN, 2);
-					break;
-				case JOIN_FULL:
-					appendContextKeyword(context, " FULL JOIN ",
-										 -PRETTYINDENT_JOIN,
-										 PRETTYINDENT_JOIN, 2);
-					break;
-				case JOIN_RIGHT:
-					appendContextKeyword(context, " RIGHT JOIN ",
+				else
+					appendContextKeyword(context, " CROSS JOIN ",
 										 -PRETTYINDENT_JOIN,
-										 PRETTYINDENT_JOIN, 2);
-					break;
-				default:
-					elog(ERROR, "unrecognized join type: %d",
-						 (int) j->jointype);
-			}
+										 PRETTYINDENT_JOIN, 1);
+				break;
+			case JOIN_LEFT:
+				appendContextKeyword(context, " LEFT JOIN ",
+									 -PRETTYINDENT_JOIN,
+									 PRETTYINDENT_JOIN, 2);
+				break;
+			case JOIN_FULL:
+				appendContextKeyword(context, " FULL JOIN ",
+									 -PRETTYINDENT_JOIN,
+									 PRETTYINDENT_JOIN, 2);
+				break;
+			case JOIN_RIGHT:
+				appendContextKeyword(context, " RIGHT JOIN ",
+									 -PRETTYINDENT_JOIN,
+									 PRETTYINDENT_JOIN, 2);
+				break;
+			default:
+				elog(ERROR, "unrecognized join type: %d",
+					 (int) j->jointype);
 		}
 
 		if (need_paren_on_right)
@@ -6957,32 +8062,35 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 
 		context->indentLevel -= PRETTYINDENT_JOIN_ON;
 
-		if (!j->isNatural)
+		if (j->usingClause)
 		{
-			if (j->usingClause)
-			{
-				ListCell   *col;
+			ListCell   *lc;
+			bool		first = true;
 
-				appendStringInfo(buf, " USING (");
-				foreach(col, j->usingClause)
-				{
-					if (col != list_head(j->usingClause))
-						appendStringInfo(buf, ", ");
-					appendStringInfoString(buf,
-									  quote_identifier(strVal(lfirst(col))));
-				}
-				appendStringInfoChar(buf, ')');
-			}
-			else if (j->quals)
+			appendStringInfo(buf, " USING (");
+			/* Use the assigned names, not what's in usingClause */
+			foreach(lc, colinfo->usingNames)
 			{
-				appendStringInfo(buf, " ON ");
-				if (!PRETTY_PAREN(context))
-					appendStringInfoChar(buf, '(');
-				get_rule_expr(j->quals, context, false);
-				if (!PRETTY_PAREN(context))
-					appendStringInfoChar(buf, ')');
+				char	   *colname = (char *) lfirst(lc);
+
+				if (first)
+					first = false;
+				else
+					appendStringInfo(buf, ", ");
+				appendStringInfoString(buf, quote_identifier(colname));
 			}
+			appendStringInfoChar(buf, ')');
+		}
+		else if (j->quals)
+		{
+			appendStringInfo(buf, " ON ");
+			if (!PRETTY_PAREN(context))
+				appendStringInfoChar(buf, '(');
+			get_rule_expr(j->quals, context, false);
+			if (!PRETTY_PAREN(context))
+				appendStringInfoChar(buf, ')');
 		}
+
 		if (!PRETTY_PAREN(context) || j->alias != NULL)
 			appendStringInfoChar(buf, ')');
 
@@ -6991,9 +8099,7 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 		{
 			appendStringInfo(buf, " %s",
 							 quote_identifier(j->alias->aliasname));
-			get_from_clause_alias(j->alias,
-								  rt_fetch(j->rtindex, query->rtable),
-								  context);
+			get_column_alias_list(colinfo, context);
 		}
 	}
 	else
@@ -7002,28 +8108,25 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 }
 
 /*
- * get_from_clause_alias - reproduce column alias list
+ * get_column_alias_list - print column alias list for an RTE
  *
- * This is tricky because we must ignore dropped columns.
+ * Caller must already have printed the relation's alias name.
  */
 static void
-get_from_clause_alias(Alias *alias, RangeTblEntry *rte,
-					  deparse_context *context)
+get_column_alias_list(deparse_columns *colinfo, deparse_context *context)
 {
 	StringInfo	buf = context->buf;
-	ListCell   *col;
-	AttrNumber	attnum;
+	int			i;
 	bool		first = true;
 
-	if (alias == NULL || alias->colnames == NIL)
-		return;					/* definitely nothing to do */
+	/* Don't print aliases if not needed */
+	if (!colinfo->printaliases)
+		return;
 
-	attnum = 0;
-	foreach(col, alias->colnames)
+	for (i = 0; i < colinfo->num_new_cols; i++)
 	{
-		attnum++;
-		if (get_rte_attribute_is_dropped(rte, attnum))
-			continue;
+		char	   *colname = colinfo->new_colnames[i];
+
 		if (first)
 		{
 			appendStringInfoChar(buf, '(');
@@ -7031,8 +8134,7 @@ get_from_clause_alias(Alias *alias, RangeTblEntry *rte,
 		}
 		else
 			appendStringInfo(buf, ", ");
-		appendStringInfoString(buf,
-							   quote_identifier(strVal(lfirst(col))));
+		appendStringInfoString(buf, quote_identifier(colname));
 	}
 	if (!first)
 		appendStringInfoChar(buf, ')');
@@ -7045,7 +8147,7 @@ get_from_clause_alias(Alias *alias, RangeTblEntry *rte,
  * responsible for ensuring that an alias or AS is present before it.
  */
 static void
-get_from_clause_coldeflist(List *names,
+get_from_clause_coldeflist(deparse_columns *colinfo,
 						   List *types, List *typmods, List *collations,
 						   deparse_context *context)
 {
@@ -7053,27 +8155,19 @@ get_from_clause_coldeflist(List *names,
 	ListCell   *l1;
 	ListCell   *l2;
 	ListCell   *l3;
-	ListCell   *l4;
-	int			i = 0;
+	int			i;
 
 	appendStringInfoChar(buf, '(');
 
-	l2 = list_head(types);
-	l3 = list_head(typmods);
-	l4 = list_head(collations);
-	foreach(l1, names)
-	{
-		char	   *attname = strVal(lfirst(l1));
-		Oid			atttypid;
-		int32		atttypmod;
-		Oid			attcollation;
-
-		atttypid = lfirst_oid(l2);
-		l2 = lnext(l2);
-		atttypmod = lfirst_int(l3);
-		l3 = lnext(l3);
-		attcollation = lfirst_oid(l4);
-		l4 = lnext(l4);
+	i = 0;
+	forthree(l1, types, l2, typmods, l3, collations)
+	{
+		char	   *attname = colinfo->colnames[i];
+		Oid			atttypid = lfirst_oid(l1);
+		int32		atttypmod = lfirst_int(l2);
+		Oid			attcollation = lfirst_oid(l3);
+
+		Assert(attname);		/* shouldn't be any dropped columns here */
 
 		if (i > 0)
 			appendStringInfo(buf, ", ");
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index d37c88234a279cd48286bbaadb7b6f42104a51ef..5e235bb98f1dffea062b01c9fb84e0a0f933ae4e 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -288,7 +288,7 @@ SELECT relname, relkind, reloptions FROM pg_class
  mysecview4 | v       | {security_barrier=false}
 (4 rows)
 
--- Test view decompilation in the face of renaming conflicts
+-- Test view decompilation in the face of relation renaming conflicts
 CREATE TABLE tt1 (f1 int, f2 int, f3 text);
 CREATE TABLE tx1 (x1 int, x2 int, x3 text);
 CREATE TABLE temp_view_test.tt1 (y1 int, f2 int, f3 text);
@@ -648,57 +648,379 @@ View definition:
            FROM temp_view_test.tx1 tx1_1
           WHERE tx1.y1 = tx1_1.f1));
 
+-- Test view decompilation in the face of column addition/deletion/renaming
+create table tt2 (a int, b int, c int);
+create table tt3 (ax int8, b int2, c numeric);
+create table tt4 (ay int, b int, q int);
+create view v1 as select * from tt2 natural join tt3;
+create view v1a as select * from (tt2 natural join tt3) j;
+create view v2 as select * from tt2 join tt3 using (b,c) join tt4 using (b);
+create view v2a as select * from (tt2 join tt3 using (b,c) join tt4 using (b)) j;
+create view v3 as select * from tt2 join tt3 using (b,c) full join tt4 using (b);
+select pg_get_viewdef('v1', true);
+           pg_get_viewdef            
+-------------------------------------
+  SELECT tt2.b, tt3.c, tt2.a, tt3.ax+
+    FROM tt2                        +
+    JOIN tt3 USING (b, c);
+(1 row)
+
+select pg_get_viewdef('v1a', true);
+        pg_get_viewdef        
+------------------------------
+  SELECT j.b, j.c, j.a, j.ax +
+    FROM (tt2                +
+    JOIN tt3 USING (b, c)) j;
+(1 row)
+
+select pg_get_viewdef('v2', true);
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT tt2.b, tt3.c, tt2.a, tt3.ax, tt4.ay, tt4.q+
+    FROM tt2                                       +
+    JOIN tt3 USING (b, c)                          +
+    JOIN tt4 USING (b);
+(1 row)
+
+select pg_get_viewdef('v2a', true);
+             pg_get_viewdef             
+----------------------------------------
+  SELECT j.b, j.c, j.a, j.ax, j.ay, j.q+
+    FROM (tt2                          +
+    JOIN tt3 USING (b, c)              +
+    JOIN tt4 USING (b)) j;
+(1 row)
+
+select pg_get_viewdef('v3', true);
+                 pg_get_viewdef                 
+------------------------------------------------
+  SELECT b, tt3.c, tt2.a, tt3.ax, tt4.ay, tt4.q+
+    FROM tt2                                   +
+    JOIN tt3 USING (b, c)                      +
+    FULL JOIN tt4 USING (b);
+(1 row)
+
+alter table tt2 add column d int;
+alter table tt2 add column e int;
+select pg_get_viewdef('v1', true);
+           pg_get_viewdef            
+-------------------------------------
+  SELECT tt2.b, tt3.c, tt2.a, tt3.ax+
+    FROM tt2                        +
+    JOIN tt3 USING (b, c);
+(1 row)
+
+select pg_get_viewdef('v1a', true);
+        pg_get_viewdef        
+------------------------------
+  SELECT j.b, j.c, j.a, j.ax +
+    FROM (tt2                +
+    JOIN tt3 USING (b, c)) j;
+(1 row)
+
+select pg_get_viewdef('v2', true);
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT tt2.b, tt3.c, tt2.a, tt3.ax, tt4.ay, tt4.q+
+    FROM tt2                                       +
+    JOIN tt3 USING (b, c)                          +
+    JOIN tt4 USING (b);
+(1 row)
+
+select pg_get_viewdef('v2a', true);
+             pg_get_viewdef             
+----------------------------------------
+  SELECT j.b, j.c, j.a, j.ax, j.ay, j.q+
+    FROM (tt2                          +
+    JOIN tt3 USING (b, c)              +
+    JOIN tt4 USING (b)) j;
+(1 row)
+
+select pg_get_viewdef('v3', true);
+                 pg_get_viewdef                 
+------------------------------------------------
+  SELECT b, tt3.c, tt2.a, tt3.ax, tt4.ay, tt4.q+
+    FROM tt2                                   +
+    JOIN tt3 USING (b, c)                      +
+    FULL JOIN tt4 USING (b);
+(1 row)
+
+alter table tt3 rename c to d;
+select pg_get_viewdef('v1', true);
+             pg_get_viewdef              
+-----------------------------------------
+  SELECT tt2.b, tt3.c, tt2.a, tt3.ax    +
+    FROM tt2                            +
+    JOIN tt3 tt3(ax, b, c) USING (b, c);
+(1 row)
+
+select pg_get_viewdef('v1a', true);
+               pg_get_viewdef               
+--------------------------------------------
+  SELECT j.b, j.c, j.a, j.ax               +
+    FROM (tt2                              +
+    JOIN tt3 tt3(ax, b, c) USING (b, c)) j;
+(1 row)
+
+select pg_get_viewdef('v2', true);
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT tt2.b, tt3.c, tt2.a, tt3.ax, tt4.ay, tt4.q+
+    FROM tt2                                       +
+    JOIN tt3 tt3(ax, b, c) USING (b, c)            +
+    JOIN tt4 USING (b);
+(1 row)
+
+select pg_get_viewdef('v2a', true);
+             pg_get_viewdef             
+----------------------------------------
+  SELECT j.b, j.c, j.a, j.ax, j.ay, j.q+
+    FROM (tt2                          +
+    JOIN tt3 tt3(ax, b, c) USING (b, c)+
+    JOIN tt4 USING (b)) j;
+(1 row)
+
+select pg_get_viewdef('v3', true);
+                 pg_get_viewdef                 
+------------------------------------------------
+  SELECT b, tt3.c, tt2.a, tt3.ax, tt4.ay, tt4.q+
+    FROM tt2                                   +
+    JOIN tt3 tt3(ax, b, c) USING (b, c)        +
+    FULL JOIN tt4 USING (b);
+(1 row)
+
+alter table tt3 add column c int;
+alter table tt3 add column e int;
+select pg_get_viewdef('v1', true);
+                 pg_get_viewdef                  
+-------------------------------------------------
+  SELECT tt2.b, tt3.c, tt2.a, tt3.ax            +
+    FROM tt2                                    +
+    JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c);
+(1 row)
+
+select pg_get_viewdef('v1a', true);
+                                 pg_get_viewdef                                  
+---------------------------------------------------------------------------------
+  SELECT j.b, j.c, j.a, j.ax                                                    +
+    FROM (tt2                                                                   +
+    JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)) j(b, c, a, d, e, ax, c_1, e_1);
+(1 row)
+
+select pg_get_viewdef('v2', true);
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT tt2.b, tt3.c, tt2.a, tt3.ax, tt4.ay, tt4.q+
+    FROM tt2                                       +
+    JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)    +
+    JOIN tt4 USING (b);
+(1 row)
+
+select pg_get_viewdef('v2a', true);
+                        pg_get_viewdef                         
+---------------------------------------------------------------
+  SELECT j.b, j.c, j.a, j.ax, j.ay, j.q                       +
+    FROM (tt2                                                 +
+    JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)               +
+    JOIN tt4 USING (b)) j(b, c, a, d, e, ax, c_1, e_1, ay, q);
+(1 row)
+
+select pg_get_viewdef('v3', true);
+                 pg_get_viewdef                 
+------------------------------------------------
+  SELECT b, tt3.c, tt2.a, tt3.ax, tt4.ay, tt4.q+
+    FROM tt2                                   +
+    JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)+
+    FULL JOIN tt4 USING (b);
+(1 row)
+
+alter table tt2 drop column d;
+select pg_get_viewdef('v1', true);
+                 pg_get_viewdef                  
+-------------------------------------------------
+  SELECT tt2.b, tt3.c, tt2.a, tt3.ax            +
+    FROM tt2                                    +
+    JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c);
+(1 row)
+
+select pg_get_viewdef('v1a', true);
+                                pg_get_viewdef                                
+------------------------------------------------------------------------------
+  SELECT j.b, j.c, j.a, j.ax                                                 +
+    FROM (tt2                                                                +
+    JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)) j(b, c, a, e, ax, c_1, e_1);
+(1 row)
+
+select pg_get_viewdef('v2', true);
+                   pg_get_viewdef                   
+----------------------------------------------------
+  SELECT tt2.b, tt3.c, tt2.a, tt3.ax, tt4.ay, tt4.q+
+    FROM tt2                                       +
+    JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)    +
+    JOIN tt4 USING (b);
+(1 row)
+
+select pg_get_viewdef('v2a', true);
+                       pg_get_viewdef                       
+------------------------------------------------------------
+  SELECT j.b, j.c, j.a, j.ax, j.ay, j.q                    +
+    FROM (tt2                                              +
+    JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)            +
+    JOIN tt4 USING (b)) j(b, c, a, e, ax, c_1, e_1, ay, q);
+(1 row)
+
+select pg_get_viewdef('v3', true);
+                 pg_get_viewdef                 
+------------------------------------------------
+  SELECT b, tt3.c, tt2.a, tt3.ax, tt4.ay, tt4.q+
+    FROM tt2                                   +
+    JOIN tt3 tt3(ax, b, c, c_1, e) USING (b, c)+
+    FULL JOIN tt4 USING (b);
+(1 row)
+
+create table tt5 (a int, b int);
+create table tt6 (c int, d int);
+create view vv1 as select * from (tt5 cross join tt6) j(aa,bb,cc,dd);
+select pg_get_viewdef('vv1', true);
+            pg_get_viewdef            
+--------------------------------------
+  SELECT j.aa, j.bb, j.cc, j.dd      +
+    FROM (tt5                        +
+   CROSS JOIN tt6) j(aa, bb, cc, dd);
+(1 row)
+
+alter table tt5 add column c int;
+select pg_get_viewdef('vv1', true);
+             pg_get_viewdef              
+-----------------------------------------
+  SELECT j.aa, j.bb, j.cc, j.dd         +
+    FROM (tt5                           +
+   CROSS JOIN tt6) j(aa, bb, c, cc, dd);
+(1 row)
+
+alter table tt5 add column cc int;
+select pg_get_viewdef('vv1', true);
+                pg_get_viewdef                 
+-----------------------------------------------
+  SELECT j.aa, j.bb, j.cc, j.dd               +
+    FROM (tt5                                 +
+   CROSS JOIN tt6) j(aa, bb, c, cc_1, cc, dd);
+(1 row)
+
+alter table tt5 drop column c;
+select pg_get_viewdef('vv1', true);
+               pg_get_viewdef               
+--------------------------------------------
+  SELECT j.aa, j.bb, j.cc, j.dd            +
+    FROM (tt5                              +
+   CROSS JOIN tt6) j(aa, bb, cc_1, cc, dd);
+(1 row)
+
+-- Unnamed FULL JOIN USING is lots of fun too
+create table tt7 (x int, xx int, y int);
+alter table tt7 drop column xx;
+create table tt8 (x int, z int);
+create view vv2 as
+select * from (values(1,2,3,4,5)) v(a,b,c,d,e)
+union all
+select * from tt7 full join tt8 using (x), tt8 tt8x;
+select pg_get_viewdef('vv2', true);
+                               pg_get_viewdef                               
+----------------------------------------------------------------------------
+          SELECT v.a, v.b, v.c, v.d, v.e                                   +
+            FROM ( VALUES (1,2,3,4,5)) v(a, b, c, d, e)                    +
+ UNION ALL                                                                 +
+          SELECT x AS a, tt7.y AS b, tt8.z AS c, tt8x.x_1 AS d, tt8x.z AS e+
+            FROM tt7                                                       +
+       FULL JOIN tt8 USING (x), tt8 tt8x(x_1, z);
+(1 row)
+
+create view vv3 as
+select * from (values(1,2,3,4,5,6)) v(a,b,c,x,e,f)
+union all
+select * from
+  tt7 full join tt8 using (x),
+  tt7 tt7x full join tt8 tt8x using (x);
+select pg_get_viewdef('vv3', true);
+                             pg_get_viewdef                              
+-------------------------------------------------------------------------
+          SELECT v.a, v.b, v.c, v.x, v.e, v.f                           +
+            FROM ( VALUES (1,2,3,4,5,6)) v(a, b, c, x, e, f)            +
+ UNION ALL                                                              +
+          SELECT x AS a, tt7.y AS b, tt8.z AS c, x_1 AS x, tt7x.y AS e, +
+             tt8x.z AS f                                                +
+            FROM tt7                                                    +
+       FULL JOIN tt8 USING (x),                                         +
+     tt7 tt7x(x_1, y)                                                   +
+    FULL JOIN tt8 tt8x(x_1, z) USING (x_1);
+(1 row)
+
+create view vv4 as
+select * from (values(1,2,3,4,5,6,7)) v(a,b,c,x,e,f,g)
+union all
+select * from
+  tt7 full join tt8 using (x),
+  tt7 tt7x full join tt8 tt8x using (x) full join tt8 tt8y using (x);
+select pg_get_viewdef('vv4', true);
+                             pg_get_viewdef                              
+-------------------------------------------------------------------------
+          SELECT v.a, v.b, v.c, v.x, v.e, v.f, v.g                      +
+            FROM ( VALUES (1,2,3,4,5,6,7)) v(a, b, c, x, e, f, g)       +
+ UNION ALL                                                              +
+          SELECT x AS a, tt7.y AS b, tt8.z AS c, x_1 AS x, tt7x.y AS e, +
+             tt8x.z AS f, tt8y.z AS g                                   +
+            FROM tt7                                                    +
+       FULL JOIN tt8 USING (x),                                         +
+     tt7 tt7x(x_1, y)                                                   +
+    FULL JOIN tt8 tt8x(x_1, z) USING (x_1)                              +
+    FULL JOIN tt8 tt8y(x_1, z) USING (x_1);
+(1 row)
+
+alter table tt7 add column zz int;
+alter table tt7 add column z int;
+alter table tt7 drop column zz;
+alter table tt8 add column z2 int;
+select pg_get_viewdef('vv2', true);
+                               pg_get_viewdef                               
+----------------------------------------------------------------------------
+          SELECT v.a, v.b, v.c, v.d, v.e                                   +
+            FROM ( VALUES (1,2,3,4,5)) v(a, b, c, d, e)                    +
+ UNION ALL                                                                 +
+          SELECT x AS a, tt7.y AS b, tt8.z AS c, tt8x.x_1 AS d, tt8x.z AS e+
+            FROM tt7                                                       +
+       FULL JOIN tt8 USING (x), tt8 tt8x(x_1, z, z2);
+(1 row)
+
+select pg_get_viewdef('vv3', true);
+                             pg_get_viewdef                              
+-------------------------------------------------------------------------
+          SELECT v.a, v.b, v.c, v.x, v.e, v.f                           +
+            FROM ( VALUES (1,2,3,4,5,6)) v(a, b, c, x, e, f)            +
+ UNION ALL                                                              +
+          SELECT x AS a, tt7.y AS b, tt8.z AS c, x_1 AS x, tt7x.y AS e, +
+             tt8x.z AS f                                                +
+            FROM tt7                                                    +
+       FULL JOIN tt8 USING (x),                                         +
+     tt7 tt7x(x_1, y, z)                                                +
+    FULL JOIN tt8 tt8x(x_1, z, z2) USING (x_1);
+(1 row)
+
+select pg_get_viewdef('vv4', true);
+                             pg_get_viewdef                              
+-------------------------------------------------------------------------
+          SELECT v.a, v.b, v.c, v.x, v.e, v.f, v.g                      +
+            FROM ( VALUES (1,2,3,4,5,6,7)) v(a, b, c, x, e, f, g)       +
+ UNION ALL                                                              +
+          SELECT x AS a, tt7.y AS b, tt8.z AS c, x_1 AS x, tt7x.y AS e, +
+             tt8x.z AS f, tt8y.z AS g                                   +
+            FROM tt7                                                    +
+       FULL JOIN tt8 USING (x),                                         +
+     tt7 tt7x(x_1, y, z)                                                +
+    FULL JOIN tt8 tt8x(x_1, z, z2) USING (x_1)                          +
+    FULL JOIN tt8 tt8y(x_1, z, z2) USING (x_1);
+(1 row)
+
+-- clean up all the random objects we made above
+set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;
-NOTICE:  drop cascades to 27 other objects
-DETAIL:  drop cascades to table temp_view_test.base_table
-drop cascades to view v7_temp
-drop cascades to view v10_temp
-drop cascades to view v11_temp
-drop cascades to view v12_temp
-drop cascades to view v2_temp
-drop cascades to view v4_temp
-drop cascades to view v6_temp
-drop cascades to view v8_temp
-drop cascades to view v9_temp
-drop cascades to table temp_view_test.base_table2
-drop cascades to view v5_temp
-drop cascades to view temp_view_test.v1
-drop cascades to view temp_view_test.v2
-drop cascades to view temp_view_test.v3
-drop cascades to view temp_view_test.v4
-drop cascades to view temp_view_test.v5
-drop cascades to view temp_view_test.v6
-drop cascades to view temp_view_test.v7
-drop cascades to view temp_view_test.v8
-drop cascades to sequence temp_view_test.seq1
-drop cascades to view temp_view_test.v9
-drop cascades to table temp_view_test.tx1
-drop cascades to view aliased_view_1
-drop cascades to view aliased_view_2
-drop cascades to view aliased_view_3
-drop cascades to view aliased_view_4
 DROP SCHEMA testviewschm2 CASCADE;
-NOTICE:  drop cascades to 22 other objects
-DETAIL:  drop cascades to table t1
-drop cascades to view temporal1
-drop cascades to view temporal2
-drop cascades to view temporal3
-drop cascades to view temporal4
-drop cascades to table t2
-drop cascades to view nontemp1
-drop cascades to view nontemp2
-drop cascades to view nontemp3
-drop cascades to view nontemp4
-drop cascades to table tbl1
-drop cascades to table tbl2
-drop cascades to table tbl3
-drop cascades to table tbl4
-drop cascades to view mytempview
-drop cascades to view pubview
-drop cascades to view mysecview1
-drop cascades to view mysecview2
-drop cascades to view mysecview3
-drop cascades to view mysecview4
-drop cascades to table tt1
-drop cascades to table tx1
-SET search_path to public;
diff --git a/src/test/regress/sql/create_view.sql b/src/test/regress/sql/create_view.sql
index 07145911e6be500bfa8d7b99d06a7fd3819db79f..3d85d9cfdc50b454fb38d8a742770eda1e119a41 100644
--- a/src/test/regress/sql/create_view.sql
+++ b/src/test/regress/sql/create_view.sql
@@ -224,7 +224,7 @@ SELECT relname, relkind, reloptions FROM pg_class
                      'mysecview3'::regclass, 'mysecview4'::regclass)
        ORDER BY relname;
 
--- Test view decompilation in the face of renaming conflicts
+-- Test view decompilation in the face of relation renaming conflicts
 
 CREATE TABLE tt1 (f1 int, f2 int, f3 text);
 CREATE TABLE tx1 (x1 int, x2 int, x3 text);
@@ -286,7 +286,110 @@ ALTER TABLE tmp1 RENAME TO tx1;
 \d+ aliased_view_3
 \d+ aliased_view_4
 
+-- Test view decompilation in the face of column addition/deletion/renaming
+
+create table tt2 (a int, b int, c int);
+create table tt3 (ax int8, b int2, c numeric);
+create table tt4 (ay int, b int, q int);
+
+create view v1 as select * from tt2 natural join tt3;
+create view v1a as select * from (tt2 natural join tt3) j;
+create view v2 as select * from tt2 join tt3 using (b,c) join tt4 using (b);
+create view v2a as select * from (tt2 join tt3 using (b,c) join tt4 using (b)) j;
+create view v3 as select * from tt2 join tt3 using (b,c) full join tt4 using (b);
+
+select pg_get_viewdef('v1', true);
+select pg_get_viewdef('v1a', true);
+select pg_get_viewdef('v2', true);
+select pg_get_viewdef('v2a', true);
+select pg_get_viewdef('v3', true);
+
+alter table tt2 add column d int;
+alter table tt2 add column e int;
+
+select pg_get_viewdef('v1', true);
+select pg_get_viewdef('v1a', true);
+select pg_get_viewdef('v2', true);
+select pg_get_viewdef('v2a', true);
+select pg_get_viewdef('v3', true);
+
+alter table tt3 rename c to d;
+
+select pg_get_viewdef('v1', true);
+select pg_get_viewdef('v1a', true);
+select pg_get_viewdef('v2', true);
+select pg_get_viewdef('v2a', true);
+select pg_get_viewdef('v3', true);
+
+alter table tt3 add column c int;
+alter table tt3 add column e int;
+
+select pg_get_viewdef('v1', true);
+select pg_get_viewdef('v1a', true);
+select pg_get_viewdef('v2', true);
+select pg_get_viewdef('v2a', true);
+select pg_get_viewdef('v3', true);
+
+alter table tt2 drop column d;
+
+select pg_get_viewdef('v1', true);
+select pg_get_viewdef('v1a', true);
+select pg_get_viewdef('v2', true);
+select pg_get_viewdef('v2a', true);
+select pg_get_viewdef('v3', true);
+
+create table tt5 (a int, b int);
+create table tt6 (c int, d int);
+create view vv1 as select * from (tt5 cross join tt6) j(aa,bb,cc,dd);
+select pg_get_viewdef('vv1', true);
+alter table tt5 add column c int;
+select pg_get_viewdef('vv1', true);
+alter table tt5 add column cc int;
+select pg_get_viewdef('vv1', true);
+alter table tt5 drop column c;
+select pg_get_viewdef('vv1', true);
+
+-- Unnamed FULL JOIN USING is lots of fun too
+
+create table tt7 (x int, xx int, y int);
+alter table tt7 drop column xx;
+create table tt8 (x int, z int);
+
+create view vv2 as
+select * from (values(1,2,3,4,5)) v(a,b,c,d,e)
+union all
+select * from tt7 full join tt8 using (x), tt8 tt8x;
+
+select pg_get_viewdef('vv2', true);
+
+create view vv3 as
+select * from (values(1,2,3,4,5,6)) v(a,b,c,x,e,f)
+union all
+select * from
+  tt7 full join tt8 using (x),
+  tt7 tt7x full join tt8 tt8x using (x);
+
+select pg_get_viewdef('vv3', true);
+
+create view vv4 as
+select * from (values(1,2,3,4,5,6,7)) v(a,b,c,x,e,f,g)
+union all
+select * from
+  tt7 full join tt8 using (x),
+  tt7 tt7x full join tt8 tt8x using (x) full join tt8 tt8y using (x);
+
+select pg_get_viewdef('vv4', true);
+
+alter table tt7 add column zz int;
+alter table tt7 add column z int;
+alter table tt7 drop column zz;
+alter table tt8 add column z2 int;
+
+select pg_get_viewdef('vv2', true);
+select pg_get_viewdef('vv3', true);
+select pg_get_viewdef('vv4', true);
+
+-- clean up all the random objects we made above
+set client_min_messages = warning;
 DROP SCHEMA temp_view_test CASCADE;
 DROP SCHEMA testviewschm2 CASCADE;
-
-SET search_path to public;