diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index f1636a5..fd1077a 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -443,6 +443,21 @@ ExecSupportsMarkRestore(Path *pathnode)
 				return false;	/* childless Result */
 			}
 
+		case T_Append:
+			{
+				AppendPath *appendPath = castNode(AppendPath, pathnode);
+
+				/*
+				 * AppendPaths acting as a proxy will never appear in the
+				 * final plan, so just return the mark/restore capability of
+				 * the proxied subpath.
+				 */
+				if (IS_PROXY_PATH(appendPath))
+					return ExecSupportsMarkRestore(
+									(Path *) linitial(appendPath->subpaths));
+
+				return false;
+			}
 		default:
 			break;
 	}
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 906d08a..1b14e38 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -135,6 +135,8 @@ static void recurse_push_qual(Node *setOp, Query *topquery,
 static void remove_unused_subquery_outputs(Query *subquery, RelOptInfo *rel);
 static void add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 						List *live_childrels);
+static void generate_proxy_paths(PlannerInfo *root, RelOptInfo *rel,
+					 RelOptInfo *childrel);
 
 
 /*
@@ -1488,11 +1490,30 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 	/*
 	 * If we found unparameterized paths for all children, build an unordered,
 	 * unparameterized Append path for the rel.  (Note: this is correct even
-	 * if we have zero or one live subpath due to constraint exclusion.)
+	 * if we have no live subpaths due to constraint exclusion.)
 	 */
 	if (subpaths_valid)
-		add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0,
-												  partitioned_rels));
+	{
+		/*
+		 * If only a single subpath exists then we've no need for the Append
+		 * at all. Here we'll generate a "proxy" path in rel for each of the
+		 * subpath rel's paths.
+		 */
+		if (list_length(subpaths) == 1)
+		{
+			generate_proxy_paths(root, rel,
+								 ((Path *) linitial(subpaths))->parent);
+
+			/* no need to generate any other paths */
+			return;
+		}
+		else
+		{
+			add_path(rel, (Path *) create_append_path(rel, subpaths, NULL, 0,
+													  partitioned_rels,
+													  NIL, NIL, false));
+		}
+	}
 
 	/*
 	 * Consider an append of partial unordered, unparameterized partial paths.
@@ -1519,7 +1540,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 
 		/* Generate a partial append path. */
 		appendpath = create_append_path(rel, partial_subpaths, NULL,
-										parallel_workers, partitioned_rels);
+										parallel_workers, partitioned_rels,
+										NIL, NIL, false);
 		add_partial_path(rel, (Path *) appendpath);
 	}
 
@@ -1571,9 +1593,60 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 		}
 
 		if (subpaths_valid)
-			add_path(rel, (Path *)
-					 create_append_path(rel, subpaths, required_outer, 0,
-										partitioned_rels));
+		{
+			if (list_length(subpaths) == 1)
+				generate_proxy_paths(root, rel,
+									 ((Path *) linitial(subpaths))->parent);
+			else
+				add_path(rel, (Path *)
+						 create_append_path(rel, subpaths, required_outer, 0,
+											partitioned_rels,
+											NIL, NIL, false));
+		}
+	}
+}
+
+/*
+ * generate_proxy_paths
+ *		Add all of childrel's paths to rel as proxy paths.
+ */
+static void
+generate_proxy_paths(PlannerInfo *root, RelOptInfo *rel, RelOptInfo *childrel)
+{
+	ListCell   *l;
+	List	   *oldtlist;
+	List	   *newtlist;
+
+	/*
+	 * Make a copy of the current target list.  Each path can share the same
+	 * copy.
+	 */
+	oldtlist = list_copy(rel->reltarget->exprs);
+
+	/* Perform translation to determine what the new tlist will be */
+	newtlist = (List *) adjust_appendrel_attrs_multilevel(root,
+											(Node *) rel->reltarget->exprs,
+											childrel->relids,
+											rel->relids);
+
+	foreach(l, childrel->pathlist)
+	{
+		Path *path = (Path *) lfirst(l);
+
+		add_path(rel, (Path *) create_append_path(rel,
+					list_make1(path),  PATH_REQ_OUTER(path),
+					path->parallel_workers, NULL,
+					oldtlist, newtlist, true));
+	}
+
+	foreach(l, childrel->partial_pathlist)
+	{
+		Path *path = (Path *) lfirst(l);
+
+		add_partial_path(rel, (Path *) create_append_path(rel,
+							list_make1(path),  PATH_REQ_OUTER(path),
+							path->parallel_workers, NULL,
+							oldtlist, newtlist, true));
 	}
 }
 
@@ -1809,7 +1882,8 @@ set_dummy_rel_pathlist(RelOptInfo *rel)
 	rel->pathlist = NIL;
 	rel->partial_pathlist = NIL;
 
-	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL));
+	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL, NIL,
+											  NIL, false));
 
 	/*
 	 * We set the cheapest path immediately, to ensure that IS_DUMMY_REL()
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 45a6889..3748a85 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -2094,6 +2094,55 @@ match_eclasses_to_foreign_key_col(PlannerInfo *root,
 
 
 /*
+ * promote_child_rel_equivalences
+ *		Remove em_is_child marker from any eclass members belonging to
+ *		childrelids
+ */
+void
+promote_child_rel_equivalences(PlannerInfo *root, Relids childrelids)
+{
+	ListCell   *lc1;
+
+	foreach(lc1, root->eq_classes)
+	{
+		EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc1);
+		ListCell   *lc2;
+
+		/*
+		 * No need to search in eclasses with volatile expressions, there will
+		 * be no child exprs in here.
+		 */
+		if (cur_ec->ec_has_volatile)
+			continue;
+
+		foreach(lc2, cur_ec->ec_members)
+		{
+			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
+
+			if (!cur_em->em_is_child)
+				continue;		/* ignore non-child members */
+
+			if (cur_em->em_is_const)
+				continue;		/* ignore consts here */
+
+			/* skip if it doesn't reference these childrelids */
+			if (!bms_overlap(cur_em->em_relids, childrelids))
+				continue;
+
+			cur_em->em_is_child = false;
+
+			/*
+			 * The eclass will need to be updated to say which relids it
+			 * contains members for.  These were previously not set due to the
+			 * member belonging to a child rel.
+			 */
+			cur_ec->ec_relids = bms_add_members(cur_ec->ec_relids,
+												cur_em->em_relids);
+		}
+	}
+}
+
+/*
  * add_child_rel_equivalences
  *	  Search for EC members that reference the parent_rel, and
  *	  add transformed members referencing the child_rel.
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 453f259..d4b50b8 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -1232,7 +1232,8 @@ mark_dummy_rel(RelOptInfo *rel)
 	rel->partial_pathlist = NIL;
 
 	/* Set up the dummy path */
-	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL));
+	add_path(rel, (Path *) create_append_path(rel, NIL, NULL, 0, NIL, NIL,
+											  NIL, false));
 
 	/* Set or update cheapest_total_path and related fields */
 	set_cheapest(rel);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 9c74e39..bdbfe83 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -35,6 +35,7 @@
 #include "optimizer/planmain.h"
 #include "optimizer/planner.h"
 #include "optimizer/predtest.h"
+#include "optimizer/prep.h"
 #include "optimizer/restrictinfo.h"
 #include "optimizer/subselect.h"
 #include "optimizer/tlist.h"
@@ -73,12 +74,13 @@ static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path,
 static Plan *create_scan_plan(PlannerInfo *root, Path *best_path,
 				 int flags);
 static List *build_path_tlist(PlannerInfo *root, Path *path);
+static List *finalize_plan_tlist(PlannerInfo *root, List *tlist);
 static bool use_physical_tlist(PlannerInfo *root, Path *path, int flags);
 static List *get_gating_quals(PlannerInfo *root, List *quals);
 static Plan *create_gating_plan(PlannerInfo *root, Path *path, Plan *plan,
 				   List *gating_quals);
 static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
-static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path);
+static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags);
 static Plan *create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path);
 static Result *create_result_plan(PlannerInfo *root, ResultPath *best_path);
 static ProjectSet *create_project_set_plan(PlannerInfo *root, ProjectSetPath *best_path);
@@ -383,7 +385,7 @@ create_plan_recurse(PlannerInfo *root, Path *best_path, int flags)
 			break;
 		case T_Append:
 			plan = create_append_plan(root,
-									  (AppendPath *) best_path);
+									  (AppendPath *) best_path, flags);
 			break;
 		case T_MergeAppend:
 			plan = create_merge_append_plan(root,
@@ -747,6 +749,12 @@ build_path_tlist(PlannerInfo *root, Path *path)
 		if (path->param_info)
 			node = replace_nestloop_params(root, node);
 
+		/*
+		 * Perform any translations required from any Append nodes which
+		 * were removed.
+		 */
+		node = replace_translatable_exprs(root, node);
+
 		tle = makeTargetEntry((Expr *) node,
 							  resno,
 							  NULL,
@@ -761,6 +769,18 @@ build_path_tlist(PlannerInfo *root, Path *path)
 }
 
 /*
+ * finalize_plan_tlist
+ *		Finalize the plan's targetlist. This must be used in places where
+ *		build_path_tlist is called before create_plan_recurse is called for
+ *		any of the plan's subplans.
+ */
+static List *
+finalize_plan_tlist(PlannerInfo *root, List *tlist)
+{
+	return (List *) replace_translatable_exprs(root, (Node *) tlist);
+}
+
+/*
  * use_physical_tlist
  *		Decide whether to use a tlist matching relation structure,
  *		rather than only those Vars actually referenced.
@@ -793,9 +813,13 @@ use_physical_tlist(PlannerInfo *root, Path *path, int flags)
 	/*
 	 * Can't do it with inheritance cases either (mainly because Append
 	 * doesn't project; this test may be unnecessary now that
-	 * create_append_plan instructs its children to return an exact tlist).
+	 * create_append_plan instructs its children to return an exact tlist),
+	 * however, we must allow any inherited children that have been pulled up
+	 * from below and Append.
 	 */
-	if (rel->reloptkind != RELOPT_BASEREL)
+	if (rel->reloptkind != RELOPT_BASEREL &&
+		(rel->reloptkind != RELOPT_OTHER_MEMBER_REL ||
+		!bms_is_member(rel->relid, root->translated_childrelids)))
 		return false;
 
 	/*
@@ -1009,14 +1033,56 @@ create_join_plan(PlannerInfo *root, JoinPath *best_path)
  *	  Returns a Plan node.
  */
 static Plan *
-create_append_plan(PlannerInfo *root, AppendPath *best_path)
+create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 {
 	Append	   *plan;
-	List	   *tlist = build_path_tlist(root, &best_path->path);
+	List	   *tlist;
 	List	   *subplans = NIL;
 	ListCell   *subpaths;
 
 	/*
+	 * If the AppendPath is acting as a proxy to a single subpath then we
+	 * don't create the Append node at all. Instead we'll just generate the
+	 * plan node for the only subpath.
+	 */
+	if (IS_PROXY_PATH(best_path))
+	{
+		Path	   *subpath = (Path *) linitial(best_path->subpaths);
+		Plan	   *plan;
+
+		/* Just verify we've only one, otherwise the planner messed up */
+		Assert(list_length(best_path->subpaths) == 1);
+
+		/*
+		 * Make the required changes to the child relation to allow it to
+		 * be scanned in place of its parent.
+		 */
+		promote_child_relation(root, best_path->path.parent, subpath->parent,
+							   best_path->translate_from,
+							   best_path->translate_to);
+
+		/* Generate a new PathTarget using the Append relation's one */
+		subpath->pathtarget = copy_pathtarget(best_path->path.pathtarget);
+		subpath->pathtarget->exprs = (List *) replace_translatable_exprs(root,
+										(Node *) subpath->pathtarget->exprs);
+
+		plan = create_plan_recurse(root, subpath, flags);
+
+		/*
+		 * If we require an exact tlist then we'd better use a translated
+		 * version of the, would be, Append node's tlist.
+		 */
+		if (flags & CP_EXACT_TLIST)
+			plan->targetlist = build_path_tlist(root, &best_path->path);
+
+		copy_generic_path_info(plan, (Path *) subpath);
+
+		return plan;
+	}
+
+	tlist = build_path_tlist(root, &best_path->path);
+
+	/*
 	 * The subpaths list could be empty, if every child was proven empty by
 	 * constraint exclusion.  In that case generate a dummy plan that returns
 	 * no rows.
@@ -1052,13 +1118,6 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
 		subplans = lappend(subplans, subplan);
 	}
 
-	/*
-	 * XXX ideally, if there's just one child, we'd not bother to generate an
-	 * Append node but just return the single child.  At the moment this does
-	 * not work because the varno of the child scan plan won't match the
-	 * parent-rel Vars it'll be asked to emit.
-	 */
-
 	plan = make_append(subplans, tlist, best_path->partitioned_rels);
 
 	copy_generic_path_info(&plan->plan, (Path *) best_path);
@@ -1170,6 +1229,14 @@ create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path)
 	node->partitioned_rels = best_path->partitioned_rels;
 	node->mergeplans = subplans;
 
+	/*
+	 * Since we called build_path_tlist() before create_plan_recurse some
+	 * Append nodes may have been removed and new translation Vars added.
+	 * We'll need to perform a final translation to perform any further
+	 * translations which were added since build_path_tlist was called.
+	 */
+	plan->targetlist = finalize_plan_tlist(root, plan->targetlist);
+
 	return (Plan *) node;
 }
 
@@ -1193,6 +1260,8 @@ create_result_plan(PlannerInfo *root, ResultPath *best_path)
 	/* best_path->quals is just bare clauses */
 	quals = order_qual_clauses(root, best_path->quals);
 
+	quals = (List *) replace_translatable_exprs(root, (Node *) quals);
+
 	plan = make_result(tlist, (Node *) quals, NULL);
 
 	copy_generic_path_info(&plan->plan, (Path *) best_path);
@@ -1299,7 +1368,8 @@ create_unique_plan(PlannerInfo *root, UniquePath *best_path, int flags)
 	 * sorting or stuff has to be added.
 	 */
 	in_operators = best_path->in_operators;
-	uniq_exprs = best_path->uniq_exprs;
+	uniq_exprs = (List *) replace_translatable_exprs(root,
+											(Node *) best_path->uniq_exprs);
 
 	/* initialize modified subplan tlist as just the "required" vars */
 	newtlist = build_path_tlist(root, &best_path->path);
@@ -1548,6 +1618,15 @@ create_gather_merge_plan(PlannerInfo *root, GatherMergePath *best_path)
 	/* use parallel mode for parallel plans. */
 	root->glob->parallelModeNeeded = true;
 
+	/*
+	 * Since we called build_path_tlist() before create_plan_recurse some
+	 * Append nodes may have been removed and new translation Vars added.
+	 * We'll need to perform a final translation to perform any further
+	 * translations which were added since build_path_tlist was called.
+	 */
+	gm_plan->plan.targetlist = finalize_plan_tlist(root,
+												   gm_plan->plan.targetlist);
+
 	return gm_plan;
 }
 
@@ -1693,6 +1772,8 @@ create_group_plan(PlannerInfo *root, GroupPath *best_path)
 
 	quals = order_qual_clauses(root, best_path->qual);
 
+	quals = (List *) replace_translatable_exprs(root, (Node *) quals);
+
 	plan = make_group(tlist,
 					  quals,
 					  list_length(best_path->groupClause),
@@ -1758,6 +1839,8 @@ create_agg_plan(PlannerInfo *root, AggPath *best_path)
 
 	quals = order_qual_clauses(root, best_path->qual);
 
+	quals = (List *) replace_translatable_exprs(root, (Node *) quals);
+
 	plan = make_agg(tlist, quals,
 					best_path->aggstrategy,
 					best_path->aggsplit,
@@ -1951,13 +2034,18 @@ create_groupingsets_plan(PlannerInfo *root, GroupingSetsPath *best_path)
 		RollupData *rollup = linitial(rollups);
 		AttrNumber *top_grpColIdx;
 		int			numGroupCols;
+		List	   *quals;
 
 		top_grpColIdx = remap_groupColIdx(root, rollup->groupClause);
 
 		numGroupCols = list_length((List *) linitial(rollup->gsets));
 
+		quals = (List *) replace_translatable_exprs(root,
+													(Node *) best_path->qual);
+
+
 		plan = make_agg(build_path_tlist(root, &best_path->path),
-						best_path->qual,
+						quals,
 						best_path->aggstrategy,
 						AGGSPLIT_SIMPLE,
 						numGroupCols,
@@ -1997,6 +2085,13 @@ create_minmaxagg_plan(PlannerInfo *root, MinMaxAggPath *best_path)
 		Plan	   *plan;
 
 		/*
+		 * This minmaxagg may have been below a single path Append node which
+		 * has been removed.  Apply expr translations, if required.
+		 */
+		mminfo->target = (Expr *) replace_translatable_exprs(root,
+													(Node *) mminfo->target);
+
+		/*
 		 * Generate the plan for the subquery. We already have a Path, but we
 		 * have to convert it to a Plan and attach a LIMIT node above it.
 		 * Since we are entering a different planner context (subroot),
@@ -3051,6 +3146,11 @@ create_tidscan_plan(PlannerInfo *root, TidPath *best_path,
 		ortidquals = list_make1(make_orclause(ortidquals));
 	scan_clauses = list_difference(scan_clauses, ortidquals);
 
+	/*
+	 * tidquals won't need to be passed through replace_translatable_exprs()
+	 * as ctid is the same varattno in every relation.
+	 */
+
 	scan_plan = make_tidscan(tlist,
 							 scan_clauses,
 							 scan_relid,
@@ -3727,6 +3827,8 @@ create_nestloop_plan(PlannerInfo *root,
 		{
 			root->curOuterParams = list_delete_cell(root->curOuterParams,
 													cell, prev);
+			nlp->paramval = (Var *) replace_translatable_exprs(root,
+													(Node *) nlp->paramval);
 			nestParams = lappend(nestParams, nlp);
 		}
 		else if (IsA(nlp->paramval, PlaceHolderVar) &&
@@ -3739,12 +3841,20 @@ create_nestloop_plan(PlannerInfo *root,
 		{
 			root->curOuterParams = list_delete_cell(root->curOuterParams,
 													cell, prev);
+			nlp->paramval = (Var *) replace_translatable_exprs(root,
+													(Node *) nlp->paramval);
 			nestParams = lappend(nestParams, nlp);
 		}
 		else
 			prev = cell;
 	}
 
+	/* perform translation of join clauses, if required */
+	joinclauses = (List *) replace_translatable_exprs(root,
+														(Node *) joinclauses);
+	otherclauses = (List *) replace_translatable_exprs(root,
+													(Node *) otherclauses);
+
 	join_plan = make_nestloop(tlist,
 							  joinclauses,
 							  otherclauses,
@@ -3754,6 +3864,15 @@ create_nestloop_plan(PlannerInfo *root,
 							  best_path->jointype,
 							  best_path->inner_unique);
 
+	/*
+	 * Since we called build_path_tlist() before create_plan_recurse some
+	 * Append nodes may have been removed and new translation Vars added.
+	 * We'll need to perform a final translation to perform any further
+	 * translations which were added since build_path_tlist was called.
+	 */
+	join_plan->join.plan.targetlist = finalize_plan_tlist(root,
+											join_plan->join.plan.targetlist);
+
 	copy_generic_path_info(&join_plan->join.plan, &best_path->path);
 
 	return join_plan;
@@ -4047,6 +4166,14 @@ create_mergejoin_plan(PlannerInfo *root,
 	 * than we need for the current mergejoin.
 	 */
 
+	/* perform translation of join clauses, if required */
+	mergeclauses = (List *) replace_translatable_exprs(root,
+													(Node *) mergeclauses);
+	joinclauses = (List *) replace_translatable_exprs(root,
+														(Node *) joinclauses);
+	otherclauses = (List *) replace_translatable_exprs(root,
+													(Node *) otherclauses);
+
 	/*
 	 * Now we can build the mergejoin node.
 	 */
@@ -4064,6 +4191,15 @@ create_mergejoin_plan(PlannerInfo *root,
 							   best_path->jpath.inner_unique,
 							   best_path->skip_mark_restore);
 
+	/*
+	 * Since we called build_path_tlist() before create_plan_recurse some
+	 * Append nodes may have been removed and new translation Vars added.
+	 * We'll need to perform a final translation to perform any further
+	 * translations which were added since build_path_tlist was called.
+	 */
+	join_plan->join.plan.targetlist = finalize_plan_tlist(root,
+											join_plan->join.plan.targetlist);
+
 	/* Costs of sort and material steps are included in path cost already */
 	copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
 
@@ -4190,6 +4326,14 @@ create_hashjoin_plan(PlannerInfo *root,
 	copy_plan_costsize(&hash_plan->plan, inner_plan);
 	hash_plan->plan.startup_cost = hash_plan->plan.total_cost;
 
+	/* perform translation of join clauses, if required */
+	hashclauses = (List *) replace_translatable_exprs(root,
+														(Node *) hashclauses);
+	joinclauses = (List *) replace_translatable_exprs(root,
+														(Node *) joinclauses);
+	otherclauses = (List *) replace_translatable_exprs(root,
+														(Node *) otherclauses);
+
 	join_plan = make_hashjoin(tlist,
 							  joinclauses,
 							  otherclauses,
@@ -4199,6 +4343,15 @@ create_hashjoin_plan(PlannerInfo *root,
 							  best_path->jpath.jointype,
 							  best_path->jpath.inner_unique);
 
+	/*
+	 * Since we called build_path_tlist() before create_plan_recurse some
+	 * Append nodes may have been removed and new translation Vars added.
+	 * We'll need to perform a final translation to perform any further
+	 * translations which were added since build_path_tlist was called.
+	 */
+	join_plan->join.plan.targetlist = finalize_plan_tlist(root,
+											join_plan->join.plan.targetlist);
+
 	copy_generic_path_info(&join_plan->join.plan, &best_path->jpath.path);
 
 	return join_plan;
@@ -6582,14 +6735,24 @@ is_projection_capable_path(Path *path)
 		case T_RecursiveUnion:
 			return false;
 		case T_Append:
+			{
+				AppendPath *apath = (AppendPath *) path;
+				/*
+				 * For Appends acting as a proxy to a single subpath, just
+				 * return the projection capability of that subpath.
+				 */
+				if (IS_PROXY_PATH(apath))
+					return is_projection_capable_path(
+										(Path *) linitial(apath->subpaths));
 
-			/*
-			 * Append can't project, but if it's being used to represent a
-			 * dummy path, claim that it can project.  This prevents us from
-			 * converting a rel from dummy to non-dummy status by applying a
-			 * projection to its dummy path.
-			 */
-			return IS_DUMMY_PATH(path);
+				/*
+				 * Otherwise, Append can't project, but if it's being used to
+				 * represent a dummy path, claim that it can project.  This
+				 * prevents us from converting a rel from dummy to non-dummy
+				 * status by applying a projection to its dummy path.
+				 */
+				return IS_DUMMY_PATH(path);
+			}
 		case T_ProjectSet:
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 90fd9cc..612c698 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -3679,7 +3679,10 @@ create_grouping_paths(PlannerInfo *root,
 								   paths,
 								   NULL,
 								   0,
-								   NIL);
+								   NIL,
+								   NIL,
+								   NIL,
+								   false);
 			path->pathtarget = target;
 		}
 		else
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 1d7e499..1b34e70 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -918,6 +918,9 @@ pull_up_simple_subquery(PlannerInfo *root, Node *jtnode, RangeTblEntry *rte,
 	subroot->hasRecursion = false;
 	subroot->wt_param_id = -1;
 	subroot->non_recursive_path = NULL;
+	subroot->translate_from_exprs = NIL;
+	subroot->translate_to_exprs = NIL;
+	subroot->translated_childrelids = NULL;
 
 	/* No CTEs to worry about */
 	Assert(subquery->cteList == NIL);
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index f620243..bcec7de 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -590,7 +590,8 @@ generate_union_path(SetOperationStmt *op, PlannerInfo *root,
 	/*
 	 * Append the child results together.
 	 */
-	path = (Path *) create_append_path(result_rel, pathlist, NULL, 0, NIL);
+	path = (Path *) create_append_path(result_rel, pathlist, NULL, 0, NIL,
+									   NIL, NIL, false);
 
 	/* We have to manually jam the right tlist into the path; ick */
 	path->pathtarget = create_pathtarget(root, tlist);
@@ -702,7 +703,8 @@ generate_nonunion_path(SetOperationStmt *op, PlannerInfo *root,
 	/*
 	 * Append the child results together.
 	 */
-	path = (Path *) create_append_path(result_rel, pathlist, NULL, 0, NIL);
+	path = (Path *) create_append_path(result_rel, pathlist, NULL, 0, NIL,
+									   NIL, NIL, false);
 
 	/* We have to manually jam the right tlist into the path; ick */
 	path->pathtarget = create_pathtarget(root, tlist);
@@ -1929,6 +1931,87 @@ translate_col_privs(const Bitmapset *parent_privs,
 }
 
 /*
+ * promote_child_relation
+ *		Make the required changes to allow a child relation to be used instead
+ *		of an Append node.
+ */
+void
+promote_child_relation(PlannerInfo *root, RelOptInfo *parent,
+					   RelOptInfo *child,
+					   List *translate_from_exprs,
+					   List *translate_to_exprs)
+{
+	Bitmapset  *cur_relids;
+
+	/*
+	 * First we must record the translation expressions in the PlannerInfo.
+	 * These need to be found when the expression translation is being done
+	 * when the final plan is being assembled.
+	 */
+	root->translate_from_exprs = list_concat(root->translate_from_exprs,
+									list_copy(translate_from_exprs));
+
+	root->translate_to_exprs = list_concat(root->translate_to_exprs,
+									list_copy(translate_to_exprs));
+
+	/*
+	 * Record this child as having been promoted.  Some places treat child
+	 * relations in a special way, and this will give them a VIP ticket to
+	 * adulthood, where required.
+	 */
+	root->translated_childrelids =
+				bms_add_members(root->translated_childrelids, child->relids);
+
+	cur_relids = child->relids;
+
+	do {
+		AppendRelInfo **appinfos;
+		int			nappinfos;
+		int			i;
+
+		appinfos = find_appinfos_by_relids(root, cur_relids, &nappinfos);
+
+		/* free any bitmapset we used in the last iteration */
+		if (cur_relids != child->relids)
+			bms_free(cur_relids);
+
+		cur_relids = NULL;
+
+		for (i = 0; i < nappinfos; i++)
+		{
+			AppendRelInfo *appinfo = appinfos[i];
+
+			RelOptInfo *parent = find_base_rel(root, appinfo->parent_relid);
+			RelOptInfo *child = find_base_rel(root, appinfo->child_relid);
+
+			/*
+			 * Some childrel equivalences may not exist due to some eclasses
+			 * having been added since add_child_rel_equivalences was called
+			 * originally.  Calling this again will add child members for any
+			 * newly added eclasses.
+			 */
+			add_child_rel_equivalences(root, appinfo, parent, child);
+
+			cur_relids = bms_add_member(cur_relids, appinfo->parent_relid);
+		}
+
+		/*
+		 * There may be multiple levels between the parent and child, so keep
+		 * going until we reach the top-level parent.
+		 */
+	} while (!bms_equal(cur_relids, parent->relids));
+
+	bms_free(cur_relids);
+
+	/*
+	 * Finally, we remove em_is_child markers for child eclass members
+	 * belonging to this child rel.  This is required so we can find eclass
+	 * members for sort PathKeys.
+	 */
+	promote_child_rel_equivalences(root, child->relids);
+}
+
+/*
  * adjust_appendrel_attrs
  *	  Copy the specified query or expression and translate Vars referring to a
  *	  parent rel to refer to the corresponding child rel instead.  We also
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 66e098f..fc2ba3c 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -156,6 +156,7 @@ static Query *substitute_actual_srf_parameters(Query *expr,
 static Node *substitute_actual_srf_parameters_mutator(Node *node,
 										 substitute_actual_srf_parameters_context *context);
 static bool tlist_matches_coltypelist(List *tlist, List *coltypelist);
+static Node *replace_translatable_exprs_mutator(Node *node, PlannerInfo *root);
 
 
 /*****************************************************************************
@@ -5197,3 +5198,48 @@ tlist_matches_coltypelist(List *tlist, List *coltypelist)
 
 	return true;
 }
+
+/*
+ * replace_translatable_exprs
+ *		Append paths with a single subpath are special because its possible
+ *		to disregard the AppendPath and just build the plan using that single
+ *		subpath instead.  Expr translation is required to translate the
+ *		targetlists of nodes above the Append, this function performs that
+ *		translation.
+ *
+ * For now, we really only ever expect to translate from a Var. However, the
+ * Var may get translated into something other than a Var.
+ */
+Node *
+replace_translatable_exprs(PlannerInfo *root, Node *expr)
+{
+	if (root->translate_from_exprs != NIL)
+		return replace_translatable_exprs_mutator(expr, root);
+	return expr;
+}
+
+static Node *
+replace_translatable_exprs_mutator(Node *node, PlannerInfo *root)
+{
+	if (node == NULL)
+		return NULL;
+
+	if (IsA(node, Var))
+	{
+		ListCell *l1;
+		ListCell *l2;
+
+		forboth(l1, root->translate_from_exprs, l2, root->translate_to_exprs)
+		{
+			if (equal(node, lfirst(l1)))
+			{
+				/* XXX do we need to do copyObject? */
+				return (Node *) copyObject(lfirst(l2));
+			}
+		}
+		return node;
+	}
+	return expression_tree_mutator(node,
+								   replace_translatable_exprs_mutator,
+								   (void *) root);
+}
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 68dee0f..1b919ba 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -1206,14 +1206,22 @@ create_tidscan_path(PlannerInfo *root, RelOptInfo *rel, List *tidquals,
  *	  pathnode.
  *
  * Note that we must handle subpaths = NIL, representing a dummy access path.
+ * If isproxy is true, then we expect only a single subpath. translate_from
+ * and translate_to can be set to allow translation of Exprs between the
+ * subpath and the Append Rel.
  */
 AppendPath *
 create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer,
-				   int parallel_workers, List *partitioned_rels)
+				   int parallel_workers, List *partitioned_rels,
+				   List *translate_from, List *translate_to,
+				   bool isproxy)
 {
 	AppendPath *pathnode = makeNode(AppendPath);
 	ListCell   *l;
 
+	/* Might both be NIL, but must contain the same number of elements. */
+	Assert(list_length(translate_from) == list_length(translate_to));
+
 	pathnode->path.pathtype = T_Append;
 	pathnode->path.parent = rel;
 	pathnode->path.pathtarget = rel->reltarget;
@@ -1222,9 +1230,26 @@ create_append_path(RelOptInfo *rel, List *subpaths, Relids required_outer,
 	pathnode->path.parallel_aware = false;
 	pathnode->path.parallel_safe = rel->consider_parallel;
 	pathnode->path.parallel_workers = parallel_workers;
-	pathnode->path.pathkeys = NIL;	/* result is always considered unsorted */
 	pathnode->partitioned_rels = list_copy(partitioned_rels);
+	pathnode->translate_from = translate_from;
+	pathnode->translate_to = translate_to;
 	pathnode->subpaths = subpaths;
+	pathnode->isproxy = isproxy;
+
+	/*
+	 * If this is a proxy Append, there can be only one subpath, so we're able
+	 * to use its PathKeys.
+	 */
+	if (isproxy)
+	{
+		Assert(list_length(subpaths) == 1);
+		pathnode->path.pathkeys = ((Path *) linitial(subpaths))->pathkeys;
+	}
+	else
+	{
+		Assert(translate_from == NIL);
+		pathnode->path.pathkeys = NIL;
+	}
 
 	/*
 	 * We don't bother with inventing a cost_append(), but just do it here.
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 9e68e65..14978ce 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -317,6 +317,16 @@ typedef struct PlannerInfo
 
 	/* optional private data for join_search_hook, e.g., GEQO */
 	void	   *join_search_private;
+
+	/*
+	 * Used for expr translation for some proxy path Append nodes during
+	 * createplan.
+	 */
+	List	   *translate_from_exprs;
+	List	   *translate_to_exprs;
+
+	Relids		translated_childrelids; /* All child rels that have been
+										 * promoted to parents */
 } PlannerInfo;
 
 
@@ -1259,13 +1269,28 @@ typedef struct CustomPath
  * elements.  These cases are optimized during create_append_plan.
  * In particular, an AppendPath with no subpaths is a "dummy" path that
  * is created to represent the case that a relation is provably empty.
+ *
+ * An AppendPath with a single subpath can be set up to become "proxy" a path.
+ * This allows a Path which belongs to one relation to be added to the pathlist
+ * of some other relation.  This is intended as generic infrastructure, but its
+ * primary use case is to allow Appends with only a single subpath to be
+ * removed from the final plan.
+ *
+ * A path's targetlist naturally will contain Vars belonging to its parent
+ * rel, so we must also provide a mechanism to allow the translation of any
+ * Vars which reference the original Append relation's Vars to allow them to
+ * be translated into the proxied path Vars. translate_from and translate_to
+ * serve this purpose.  They must only be set when isproxy is true.
  */
 typedef struct AppendPath
 {
 	Path		path;
 	/* RT indexes of non-leaf tables in a partition tree */
 	List	   *partitioned_rels;
+	List	   *translate_from;
+	List	   *translate_to;
 	List	   *subpaths;		/* list of component Paths */
+	bool		isproxy;
 } AppendPath;
 
 #define IS_DUMMY_PATH(p) \
@@ -1276,6 +1301,10 @@ typedef struct AppendPath
 	((r)->cheapest_total_path != NULL && \
 	 IS_DUMMY_PATH((r)->cheapest_total_path))
 
+/* Append path is acting as a proxy for its single subpath */
+#define IS_PROXY_PATH(p) \
+	(IsA((p), AppendPath) && ((AppendPath *) (p))->isproxy)
+
 /*
  * MergeAppendPath represents a MergeAppend plan, ie, the merging of sorted
  * results from several member plans to produce similarly-sorted output.
diff --git a/src/include/optimizer/clauses.h b/src/include/optimizer/clauses.h
index e367221..232a0a4 100644
--- a/src/include/optimizer/clauses.h
+++ b/src/include/optimizer/clauses.h
@@ -85,4 +85,6 @@ extern Node *estimate_expression_value(PlannerInfo *root, Node *node);
 extern Query *inline_set_returning_function(PlannerInfo *root,
 							  RangeTblEntry *rte);
 
+extern Node *replace_translatable_exprs(PlannerInfo *root, Node *expr);
+
 #endif							/* CLAUSES_H */
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index e9ed16a..ad5787c 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -65,7 +65,9 @@ extern TidPath *create_tidscan_path(PlannerInfo *root, RelOptInfo *rel,
 					List *tidquals, Relids required_outer);
 extern AppendPath *create_append_path(RelOptInfo *rel, List *subpaths,
 				   Relids required_outer, int parallel_workers,
-				   List *partitioned_rels);
+				   List *partitioned_rels,
+				   List *translate_from, List *translate_to,
+				   bool isproxy);
 extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
 						 RelOptInfo *rel,
 						 List *subpaths,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index ea886b6..c8bbea0 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -156,6 +156,8 @@ extern bool exprs_known_equal(PlannerInfo *root, Node *item1, Node *item2);
 extern EquivalenceClass *match_eclasses_to_foreign_key_col(PlannerInfo *root,
 								  ForeignKeyOptInfo *fkinfo,
 								  int colno);
+extern void promote_child_rel_equivalences(PlannerInfo *root,
+												Relids childrelids);
 extern void add_child_rel_equivalences(PlannerInfo *root,
 						   AppendRelInfo *appinfo,
 						   RelOptInfo *parent_rel,
diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h
index 80fbfd6..a083c75 100644
--- a/src/include/optimizer/prep.h
+++ b/src/include/optimizer/prep.h
@@ -52,6 +52,11 @@ extern RelOptInfo *plan_set_operations(PlannerInfo *root);
 
 extern void expand_inherited_tables(PlannerInfo *root);
 
+extern void promote_child_relation(PlannerInfo *root, RelOptInfo *parent,
+					   RelOptInfo *child,
+					   List *translate_from_exprs,
+					   List *translate_to_exprs);
+
 extern Node *adjust_appendrel_attrs(PlannerInfo *root, Node *node,
 					   int nappinfos, AppendRelInfo **appinfos);
 
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index c698faf..6d27dd0 100644
--- a/src/test/regress/expected/inherit.out
+++ b/src/test/regress/expected/inherit.out
@@ -1678,12 +1678,11 @@ explain (costs off) select * from list_parted;
 (4 rows)
 
 explain (costs off) select * from list_parted where a is null;
-           QUERY PLAN           
---------------------------------
- Append
-   ->  Seq Scan on part_null_xy
-         Filter: (a IS NULL)
-(3 rows)
+        QUERY PLAN        
+--------------------------
+ Seq Scan on part_null_xy
+   Filter: (a IS NULL)
+(2 rows)
 
 explain (costs off) select * from list_parted where a is not null;
            QUERY PLAN            
@@ -1720,12 +1719,11 @@ explain (costs off) select * from list_parted where a = 'ab' or a in (null, 'cd'
 (7 rows)
 
 explain (costs off) select * from list_parted where a = 'ab';
-                QUERY PLAN                
-------------------------------------------
- Append
-   ->  Seq Scan on part_ab_cd
-         Filter: ((a)::text = 'ab'::text)
-(3 rows)
+             QUERY PLAN             
+------------------------------------
+ Seq Scan on part_ab_cd
+   Filter: ((a)::text = 'ab'::text)
+(2 rows)
 
 create table range_list_parted (
 	a	int,
@@ -1805,12 +1803,11 @@ explain (costs off) select * from range_list_parted where a is null;
 
 /* Should only select rows from the null-accepting partition */
 explain (costs off) select * from range_list_parted where b is null;
-             QUERY PLAN             
-------------------------------------
- Append
-   ->  Seq Scan on part_40_inf_null
-         Filter: (b IS NULL)
-(3 rows)
+          QUERY PLAN          
+------------------------------
+ Seq Scan on part_40_inf_null
+   Filter: (b IS NULL)
+(2 rows)
 
 explain (costs off) select * from range_list_parted where a is not null and a < 67;
                    QUERY PLAN                   
@@ -1860,20 +1857,18 @@ create table mcrparted3 partition of mcrparted for values from (11, 1, 1) to (20
 create table mcrparted4 partition of mcrparted for values from (20, 10, 10) to (20, 20, 20);
 create table mcrparted5 partition of mcrparted for values from (20, 20, 20) to (maxvalue, maxvalue, maxvalue);
 explain (costs off) select * from mcrparted where a = 0;	-- scans mcrparted0
-          QUERY PLAN          
-------------------------------
- Append
-   ->  Seq Scan on mcrparted0
-         Filter: (a = 0)
-(3 rows)
+       QUERY PLAN       
+------------------------
+ Seq Scan on mcrparted0
+   Filter: (a = 0)
+(2 rows)
 
 explain (costs off) select * from mcrparted where a = 10 and abs(b) < 5;	-- scans mcrparted1
-                 QUERY PLAN                  
----------------------------------------------
- Append
-   ->  Seq Scan on mcrparted1
-         Filter: ((a = 10) AND (abs(b) < 5))
-(3 rows)
+              QUERY PLAN               
+---------------------------------------
+ Seq Scan on mcrparted1
+   Filter: ((a = 10) AND (abs(b) < 5))
+(2 rows)
 
 explain (costs off) select * from mcrparted where a = 10 and abs(b) = 5;	-- scans mcrparted1, mcrparted2
                  QUERY PLAN                  
@@ -1920,12 +1915,11 @@ explain (costs off) select * from mcrparted where a > -1;	-- scans all partition
 (13 rows)
 
 explain (costs off) select * from mcrparted where a = 20 and abs(b) = 10 and c > 10;	-- scans mcrparted4
-                        QUERY PLAN                         
------------------------------------------------------------
- Append
-   ->  Seq Scan on mcrparted4
-         Filter: ((c > 10) AND (a = 20) AND (abs(b) = 10))
-(3 rows)
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Seq Scan on mcrparted4
+   Filter: ((c > 10) AND (a = 20) AND (abs(b) = 10))
+(2 rows)
 
 explain (costs off) select * from mcrparted where a = 20 and c > 20; -- scans mcrparted3, mcrparte4, mcrparte5
                QUERY PLAN                
@@ -1947,22 +1941,18 @@ create table parted_minmax1 partition of parted_minmax for values from (1) to (1
 create index parted_minmax1i on parted_minmax1 (a, b);
 insert into parted_minmax values (1,'12345');
 explain (costs off) select min(a), max(a) from parted_minmax where b = '12345';
-                                              QUERY PLAN                                               
--------------------------------------------------------------------------------------------------------
+                                           QUERY PLAN                                            
+-------------------------------------------------------------------------------------------------
  Result
    InitPlan 1 (returns $0)
      ->  Limit
-           ->  Merge Append
-                 Sort Key: parted_minmax1.a
-                 ->  Index Only Scan using parted_minmax1i on parted_minmax1
-                       Index Cond: ((a IS NOT NULL) AND (b = '12345'::text))
+           ->  Index Only Scan using parted_minmax1i on parted_minmax1
+                 Index Cond: ((a IS NOT NULL) AND (b = '12345'::text))
    InitPlan 2 (returns $1)
      ->  Limit
-           ->  Merge Append
-                 Sort Key: parted_minmax1_1.a DESC
-                 ->  Index Only Scan Backward using parted_minmax1i on parted_minmax1 parted_minmax1_1
-                       Index Cond: ((a IS NOT NULL) AND (b = '12345'::text))
-(13 rows)
+           ->  Index Only Scan Backward using parted_minmax1i on parted_minmax1 parted_minmax1_1
+                 Index Cond: ((a IS NOT NULL) AND (b = '12345'::text))
+(9 rows)
 
 select min(a), max(a) from parted_minmax where b = '12345';
  min | max 
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 27ab852..78664fa 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -193,19 +193,18 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT 50 phv, * FROM prt1 WHERE prt1.b = 0)
 -- Join with pruned partitions from joining relations
 EXPLAIN (COSTS OFF)
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
-                        QUERY PLAN                         
------------------------------------------------------------
+                     QUERY PLAN                      
+-----------------------------------------------------
  Sort
    Sort Key: t1.a
-   ->  Append
-         ->  Hash Join
-               Hash Cond: (t2.b = t1.a)
-               ->  Seq Scan on prt2_p2 t2
-                     Filter: (b > 250)
-               ->  Hash
-                     ->  Seq Scan on prt1_p2 t1
-                           Filter: ((a < 450) AND (b = 0))
-(10 rows)
+   ->  Hash Join
+         Hash Cond: (t2.b = t1.a)
+         ->  Seq Scan on prt2_p2 t2
+               Filter: (b > 250)
+         ->  Hash
+               ->  Seq Scan on prt1_p2 t1
+                     Filter: ((a < 450) AND (b = 0))
+(9 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt2 t2 WHERE t1.a = t2.b AND t1.a < 450 AND t2.b > 250 AND t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -1391,10 +1390,9 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1
                      ->  Seq Scan on prt2_l_p3_p1 t2_3
                      ->  Seq Scan on prt2_l_p3_p2 t2_4
                ->  Hash
-                     ->  Append
-                           ->  Seq Scan on prt1_l_p3_p1 t1_3
-                                 Filter: (b = 0)
-(29 rows)
+                     ->  Seq Scan on prt1_l_p3_p1 t1_3
+                           Filter: (b = 0)
+(28 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1, prt2_l t2 WHERE t1.a = t2.b AND t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -1437,10 +1435,9 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b
                      ->  Seq Scan on prt2_l_p3_p1 t2_3
                      ->  Seq Scan on prt2_l_p3_p2 t2_4
                ->  Hash
-                     ->  Append
-                           ->  Seq Scan on prt1_l_p3_p1 t1_3
-                                 Filter: (b = 0)
-(30 rows)
+                     ->  Seq Scan on prt1_l_p3_p1 t1_3
+                           Filter: (b = 0)
+(29 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 LEFT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t1.b = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -1492,10 +1489,9 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b
                            ->  Seq Scan on prt1_l_p3_p1 t1_3
                            ->  Seq Scan on prt1_l_p3_p2 t1_4
                      ->  Hash
-                           ->  Append
-                                 ->  Seq Scan on prt2_l_p3_p1 t2_3
-                                       Filter: (a = 0)
-(31 rows)
+                           ->  Seq Scan on prt2_l_p3_p1 t2_3
+                                 Filter: (a = 0)
+(30 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM prt1_l t1 RIGHT JOIN prt2_l t2 ON t1.a = t2.b AND t1.c = t2.c WHERE t2.a = 0 ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -1541,14 +1537,12 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.b = 0) t1
                            Filter: (a = 0)
          ->  Hash Full Join
                Hash Cond: ((prt1_l_p3_p1.a = prt2_l_p3_p1.b) AND ((prt1_l_p3_p1.c)::text = (prt2_l_p3_p1.c)::text))
-               ->  Append
-                     ->  Seq Scan on prt1_l_p3_p1
-                           Filter: (b = 0)
+               ->  Seq Scan on prt1_l_p3_p1
+                     Filter: (b = 0)
                ->  Hash
-                     ->  Append
-                           ->  Seq Scan on prt2_l_p3_p1
-                                 Filter: (a = 0)
-(33 rows)
+                     ->  Seq Scan on prt2_l_p3_p1
+                           Filter: (a = 0)
+(31 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c FROM (SELECT * FROM prt1_l WHERE prt1_l.b = 0) t1 FULL JOIN (SELECT * FROM prt2_l WHERE prt2_l.a = 0) t2 ON (t1.a = t2.b AND t1.c = t2.c) ORDER BY t1.a, t2.b;
   a  |  c   |  b  |  c   
@@ -1610,9 +1604,8 @@ SELECT * FROM prt1_l t1 LEFT JOIN LATERAL
                                  ->  Seq Scan on prt1_l_p2_p2 t2_2
                                        Filter: ((t1_2.a = a) AND ((t1_2.c)::text = (c)::text))
                ->  Nested Loop Left Join
-                     ->  Append
-                           ->  Seq Scan on prt1_l_p3_p1 t1_3
-                                 Filter: (b = 0)
+                     ->  Seq Scan on prt1_l_p3_p1 t1_3
+                           Filter: (b = 0)
                      ->  Hash Join
                            Hash Cond: ((t3_3.b = t2_3.a) AND ((t3_3.c)::text = (t2_3.c)::text))
                            ->  Append
@@ -1624,7 +1617,7 @@ SELECT * FROM prt1_l t1 LEFT JOIN LATERAL
                                              Filter: ((t1_3.a = a) AND ((t1_3.c)::text = (c)::text))
                                        ->  Seq Scan on prt1_l_p3_p2 t2_4
                                              Filter: ((t1_3.a = a) AND ((t1_3.c)::text = (c)::text))
-(46 rows)
+(45 rows)
 
 SELECT * FROM prt1_l t1 LEFT JOIN LATERAL
 			  (SELECT t2.a AS t2a, t2.c AS t2c, t2.b AS t2b, t3.b AS t3b, least(t1.a,t2.a,t3.b) FROM prt1_l t2 JOIN prt2_l t3 ON (t2.a = t3.b AND t2.c = t3.c)) ss
diff --git a/src/test/regress/expected/rowsecurity.out b/src/test/regress/expected/rowsecurity.out
index b8dcf51..8b31ced 100644
--- a/src/test/regress/expected/rowsecurity.out
+++ b/src/test/regress/expected/rowsecurity.out
@@ -1057,15 +1057,14 @@ NOTICE:  f_leak => awesome science fiction
 (4 rows)
 
 EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
-                             QUERY PLAN                             
---------------------------------------------------------------------
- Append
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Seq Scan on part_document_fiction
+   Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
    InitPlan 1 (returns $0)
      ->  Index Scan using uaccount_pkey on uaccount
            Index Cond: (pguser = CURRENT_USER)
-   ->  Seq Scan on part_document_fiction
-         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
-(6 rows)
+(5 rows)
 
 -- pp1 ERROR
 INSERT INTO part_document VALUES (100, 11, 5, 'regress_rls_dave', 'testing pp1'); -- fail
@@ -1136,15 +1135,14 @@ NOTICE:  f_leak => awesome science fiction
 (4 rows)
 
 EXPLAIN (COSTS OFF) SELECT * FROM part_document WHERE f_leak(dtitle);
-                             QUERY PLAN                             
---------------------------------------------------------------------
- Append
+                          QUERY PLAN                          
+--------------------------------------------------------------
+ Seq Scan on part_document_fiction
+   Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
    InitPlan 1 (returns $0)
      ->  Index Scan using uaccount_pkey on uaccount
            Index Cond: (pguser = CURRENT_USER)
-   ->  Seq Scan on part_document_fiction
-         Filter: ((cid < 55) AND (dlevel <= $0) AND f_leak(dtitle))
-(6 rows)
+(5 rows)
 
 -- viewpoint from regress_rls_carol
 SET SESSION AUTHORIZATION regress_rls_carol;
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index ee26b16..11d30a3 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -697,11 +697,10 @@ explain (costs off)
    UNION ALL
    SELECT 2 AS t, * FROM tenk1 b) c
  WHERE t = 2;
-        QUERY PLAN         
----------------------------
- Append
-   ->  Seq Scan on tenk1 b
-(2 rows)
+     QUERY PLAN      
+---------------------
+ Seq Scan on tenk1 b
+(1 row)
 
 -- Test that we push quals into UNION sub-selects only when it's safe
 explain (costs off)
