placeholdervar-fix-this-time-for-sure.patch

text/x-patch

Filename: placeholdervar-fix-this-time-for-sure.patch
Type: text/x-patch
Part: 0
Message: Re: EquivalenceClasses and subqueries and PlaceHolderVars, oh my

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
File+
src/backend/optimizer/path/equivclass.c 0 0
src/backend/optimizer/path/indxpath.c 0 0
src/backend/optimizer/path/pathkeys.c 0 0
src/backend/optimizer/plan/createplan.c 0 0
src/backend/optimizer/plan/planagg.c 0 0
src/backend/optimizer/plan/planmain.c 0 0
src/backend/optimizer/prep/prepjointree.c 0 0
src/backend/optimizer/README 0 0
src/backend/optimizer/util/placeholder.c 0 0
src/include/nodes/relation.h 0 0
src/include/optimizer/paths.h 0 0
src/include/optimizer/placeholder.h 0 0
src/test/regress/expected/inherit.out 0 0
src/test/regress/expected/union.out 0 0
src/test/regress/sql/inherit.sql 0 0
src/test/regress/sql/union.sql 0 0
diff --git a/src/backend/optimizer/README b/src/backend/optimizer/README
index ee450fb02e4e9e8942ff5a0d6b062c9ae5d18cba..9ab1fdf8154b964be91eb54ff2a3f38e9705af74 100644
*** a/src/backend/optimizer/README
--- b/src/backend/optimizer/README
*************** it's possible that it belongs to more th
*** 496,501 ****
--- 496,509 ----
  families to ensure that we can make use of an index belonging to any one of
  the families for mergejoin purposes.)
  
+ An EquivalenceClass can contain "em_is_child" members, which are copies
+ of members that contain appendrel parent relation Vars, transposed to
+ contain the equivalent child-relation variables or expressions.  These
+ members are *not* full-fledged members of the EquivalenceClass and do not
+ affect the class's overall properties at all.  They are kept only to
+ simplify matching of child-relation expressions to EquivalenceClasses.
+ Most operations on EquivalenceClasses should ignore child members.
+ 
  
  PathKeys
  --------
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index b653d6cb35ca338c183bb944757081e46ca50c50..c115c2148db109476dd607f99a178e25a5f76a24 100644
*** a/src/backend/optimizer/path/equivclass.c
--- b/src/backend/optimizer/path/equivclass.c
*************** add_eq_member(EquivalenceClass *ec, Expr
*** 491,496 ****
--- 491,505 ----
   * sortref is the SortGroupRef of the originating SortGroupClause, if any,
   * or zero if not.	(It should never be zero if the expression is volatile!)
   *
+  * If rel is not NULL, it identifies a specific relation we're considering
+  * a path for, and indicates that child EC members for that relation can be
+  * considered.  Otherwise child members are ignored.  (Note: since child EC
+  * members aren't guaranteed unique, a non-NULL value means that there could
+  * be more than one EC that matches the expression; if so it's order-dependent
+  * which one you get.  This is annoying but it only happens in corner cases,
+  * so for now we live with just reporting the first match.  See also
+  * generate_implied_equalities_for_indexcol and match_pathkeys_to_index.)
+  *
   * If create_it is TRUE, we'll build a new EquivalenceClass when there is no
   * match.  If create_it is FALSE, we just return NULL when no match.
   *
*************** get_eclass_for_sort_expr(PlannerInfo *ro
*** 511,516 ****
--- 520,526 ----
  						 Oid opcintype,
  						 Oid collation,
  						 Index sortref,
+ 						 Relids rel,
  						 bool create_it)
  {
  	EquivalenceClass *newec;
*************** get_eclass_for_sort_expr(PlannerInfo *ro
*** 549,554 ****
--- 559,571 ----
  			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
  
  			/*
+ 			 * Ignore child members unless they match the request.
+ 			 */
+ 			if (cur_em->em_is_child &&
+ 				!bms_equal(cur_em->em_relids, rel))
+ 				continue;
+ 
+ 			/*
  			 * If below an outer join, don't match constants: they're not as
  			 * constant as they look.
  			 */
*************** reconsider_outer_join_clause(PlannerInfo
*** 1505,1510 ****
--- 1522,1528 ----
  		{
  			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
  
+ 			Assert(!cur_em->em_is_child);		/* no children yet */
  			if (equal(outervar, cur_em->em_expr))
  			{
  				match = true;
*************** reconsider_full_join_clause(PlannerInfo 
*** 1626,1631 ****
--- 1644,1650 ----
  		foreach(lc2, cur_ec->ec_members)
  		{
  			coal_em = (EquivalenceMember *) lfirst(lc2);
+ 			Assert(!coal_em->em_is_child);		/* no children yet */
  			if (IsA(coal_em->em_expr, CoalesceExpr))
  			{
  				CoalesceExpr *cexpr = (CoalesceExpr *) coal_em->em_expr;
*************** exprs_known_equal(PlannerInfo *root, Nod
*** 1747,1752 ****
--- 1766,1773 ----
  		{
  			EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2);
  
+ 			if (em->em_is_child)
+ 				continue;		/* ignore children here */
  			if (equal(item1, em->em_expr))
  				item1member = true;
  			else if (equal(item2, em->em_expr))
*************** add_child_rel_equivalences(PlannerInfo *
*** 1800,1805 ****
--- 1821,1829 ----
  		{
  			EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc2);
  
+ 			if (cur_em->em_is_child)
+ 				continue;		/* ignore children here */
+ 
  			/* Does it reference (only) parent_rel? */
  			if (bms_equal(cur_em->em_relids, parent_rel->relids))
  			{
*************** generate_implied_equalities_for_indexcol
*** 1908,1914 ****
  			!bms_is_subset(rel->relids, cur_ec->ec_relids))
  			continue;
  
! 		/* Scan members, looking for a match to the indexable column */
  		cur_em = NULL;
  		foreach(lc2, cur_ec->ec_members)
  		{
--- 1932,1947 ----
  			!bms_is_subset(rel->relids, cur_ec->ec_relids))
  			continue;
  
! 		/*
! 		 * Scan members, looking for a match to the indexable column.  Note
! 		 * that child EC members are considered, but only when they belong to
! 		 * the target relation.  (Unlike regular members, the same expression
! 		 * could be a child member of more than one EC.  Therefore, it's
! 		 * potentially order-dependent which EC a child relation's index
! 		 * column gets matched to.  This is annoying but it only happens in
! 		 * corner cases, so for now we live with just reporting the first
! 		 * match.  See also get_eclass_for_sort_expr.)
! 		 */
  		cur_em = NULL;
  		foreach(lc2, cur_ec->ec_members)
  		{
*************** generate_implied_equalities_for_indexcol
*** 1933,1938 ****
--- 1966,1974 ----
  			Oid			eq_op;
  			RestrictInfo *rinfo;
  
+ 			if (other_em->em_is_child)
+ 				continue;		/* ignore children here */
+ 
  			/* Make sure it'll be a join to a different rel */
  			if (other_em == cur_em ||
  				bms_overlap(other_em->em_relids, rel->relids))
*************** eclass_useful_for_merging(EquivalenceCla
*** 2187,2194 ****
  	{
  		EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
  
! 		if (!cur_em->em_is_child &&
! 			!bms_overlap(cur_em->em_relids, rel->relids))
  			return true;
  	}
  
--- 2223,2232 ----
  	{
  		EquivalenceMember *cur_em = (EquivalenceMember *) lfirst(lc);
  
! 		if (cur_em->em_is_child)
! 			continue;			/* ignore children here */
! 
! 		if (!bms_overlap(cur_em->em_relids, rel->relids))
  			return true;
  	}
  
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 2f088b797879fd67f27b5b8adbcdfce292fce0ea..89b42da6b46517d86ffebe882348aa7376520b81 100644
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
*************** match_pathkeys_to_index(IndexOptInfo *in
*** 2157,2163 ****
  		if (pathkey->pk_eclass->ec_has_volatile)
  			return;
  
! 		/* Try to match eclass member expression(s) to index */
  		foreach(lc2, pathkey->pk_eclass->ec_members)
  		{
  			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
--- 2157,2170 ----
  		if (pathkey->pk_eclass->ec_has_volatile)
  			return;
  
! 		/*
! 		 * Try to match eclass member expression(s) to index.  Note that child
! 		 * EC members are considered, but only when they belong to the target
! 		 * relation.  (Unlike regular members, the same expression could be a
! 		 * child member of more than one EC.  Therefore, the same index could
! 		 * be considered to match more than one pathkey list, which is OK
! 		 * here.  See also get_eclass_for_sort_expr.)
! 		 */
  		foreach(lc2, pathkey->pk_eclass->ec_members)
  		{
  			EquivalenceMember *member = (EquivalenceMember *) lfirst(lc2);
*************** match_index_to_operand(Node *operand,
*** 2581,2595 ****
  	int			indkey;
  
  	/*
- 	 * Ignore any PlaceHolderVar nodes above the operand.  This is needed so
- 	 * that we can successfully use expression-index constraints pushed down
- 	 * through appendrels (UNION ALL).  It's safe because a PlaceHolderVar
- 	 * appearing in a relation-scan-level expression is certainly a no-op.
- 	 */
- 	while (operand && IsA(operand, PlaceHolderVar))
- 		operand = (Node *) ((PlaceHolderVar *) operand)->phexpr;
- 
- 	/*
  	 * Ignore any RelabelType node above the operand.	This is needed to be
  	 * able to apply indexscanning in binary-compatible-operator cases. Note:
  	 * we can assume there is at most one RelabelType node;
--- 2588,2593 ----
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 653387e582d8900bd9353d8f78ccb7764f5dc727..4ff8016666d7f2785c0d64ae9012d686e1ab7382 100644
*** a/src/backend/optimizer/path/pathkeys.c
--- b/src/backend/optimizer/path/pathkeys.c
*************** canonicalize_pathkeys(PlannerInfo *root,
*** 221,226 ****
--- 221,231 ----
   * If the PathKey is being generated from a SortGroupClause, sortref should be
   * the SortGroupClause's SortGroupRef; otherwise zero.
   *
+  * If rel is not NULL, it identifies a specific relation we're considering
+  * a path for, and indicates that child EC members for that relation can be
+  * considered.  Otherwise child members are ignored.  (See the comments for
+  * get_eclass_for_sort_expr.)
+  *
   * create_it is TRUE if we should create any missing EquivalenceClass
   * needed to represent the sort key.  If it's FALSE, we return NULL if the
   * sort key isn't already present in any EquivalenceClass.
*************** make_pathkey_from_sortinfo(PlannerInfo *
*** 237,242 ****
--- 242,248 ----
  						   bool reverse_sort,
  						   bool nulls_first,
  						   Index sortref,
+ 						   Relids rel,
  						   bool create_it,
  						   bool canonicalize)
  {
*************** make_pathkey_from_sortinfo(PlannerInfo *
*** 268,274 ****
  	/* Now find or (optionally) create a matching EquivalenceClass */
  	eclass = get_eclass_for_sort_expr(root, expr, opfamilies,
  									  opcintype, collation,
! 									  sortref, create_it);
  
  	/* Fail if no EC and !create_it */
  	if (!eclass)
--- 274,280 ----
  	/* Now find or (optionally) create a matching EquivalenceClass */
  	eclass = get_eclass_for_sort_expr(root, expr, opfamilies,
  									  opcintype, collation,
! 									  sortref, rel, create_it);
  
  	/* Fail if no EC and !create_it */
  	if (!eclass)
*************** make_pathkey_from_sortop(PlannerInfo *ro
*** 320,325 ****
--- 326,332 ----
  									  (strategy == BTGreaterStrategyNumber),
  									  nulls_first,
  									  sortref,
+ 									  NULL,
  									  create_it,
  									  canonicalize);
  }
*************** build_index_pathkeys(PlannerInfo *root,
*** 546,551 ****
--- 553,559 ----
  											  reverse_sort,
  											  nulls_first,
  											  0,
+ 											  index->rel->relids,
  											  false,
  											  true);
  
*************** convert_subquery_pathkeys(PlannerInfo *r
*** 636,641 ****
--- 644,650 ----
  											 sub_member->em_datatype,
  											 sub_eclass->ec_collation,
  											 0,
+ 											 rel->relids,
  											 false);
  
  				/*
*************** convert_subquery_pathkeys(PlannerInfo *r
*** 680,685 ****
--- 689,697 ----
  				Oid			sub_expr_coll = sub_eclass->ec_collation;
  				ListCell   *k;
  
+ 				if (sub_member->em_is_child)
+ 					continue;	/* ignore children here */
+ 
  				foreach(k, sub_tlist)
  				{
  					TargetEntry *tle = (TargetEntry *) lfirst(k);
*************** convert_subquery_pathkeys(PlannerInfo *r
*** 719,724 ****
--- 731,737 ----
  														sub_expr_type,
  														sub_expr_coll,
  														0,
+ 														rel->relids,
  														false);
  
  					/*
*************** initialize_mergeclause_eclasses(PlannerI
*** 910,915 ****
--- 923,929 ----
  								 lefttype,
  								 ((OpExpr *) clause)->inputcollid,
  								 0,
+ 								 NULL,
  								 true);
  	restrictinfo->right_ec =
  		get_eclass_for_sort_expr(root,
*************** initialize_mergeclause_eclasses(PlannerI
*** 918,923 ****
--- 932,938 ----
  								 righttype,
  								 ((OpExpr *) clause)->inputcollid,
  								 0,
+ 								 NULL,
  								 true);
  }
  
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 94140d304f754236955452e26b80de8276ca729b..ef2a1e70c96607f40290bf58770befc00da9853c 100644
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** static Sort *make_sort(PlannerInfo *root
*** 152,163 ****
--- 152,168 ----
  		  double limit_tuples);
  static Plan *prepare_sort_from_pathkeys(PlannerInfo *root,
  						   Plan *lefttree, List *pathkeys,
+ 						   Relids relids,
+ 						   const AttrNumber *reqColIdx,
  						   bool adjust_tlist_in_place,
  						   int *p_numsortkeys,
  						   AttrNumber **p_sortColIdx,
  						   Oid **p_sortOperators,
  						   Oid **p_collations,
  						   bool **p_nullsFirst);
+ static EquivalenceMember *find_ec_member_for_tle(EquivalenceClass *ec,
+ 					   TargetEntry *tle,
+ 					   Relids relids);
  static Material *make_material(Plan *lefttree);
  
  
*************** create_merge_append_plan(PlannerInfo *ro
*** 706,711 ****
--- 711,718 ----
  
  	/* Compute sort column info, and adjust MergeAppend's tlist as needed */
  	(void) prepare_sort_from_pathkeys(root, plan, pathkeys,
+ 									  NULL,
+ 									  NULL,
  									  true,
  									  &node->numCols,
  									  &node->sortColIdx,
*************** create_merge_append_plan(PlannerInfo *ro
*** 733,738 ****
--- 740,747 ----
  
  		/* Compute sort column info, and adjust subplan's tlist as needed */
  		subplan = prepare_sort_from_pathkeys(root, subplan, pathkeys,
+ 											 subpath->parent->relids,
+ 											 node->sortColIdx,
  											 false,
  											 &numsortkeys,
  											 &sortColIdx,
*************** fix_indexqual_operand(Node *node, IndexO
*** 2695,2705 ****
  	ListCell   *indexpr_item;
  
  	/*
! 	 * Remove any PlaceHolderVars or binary-compatible relabeling of the
! 	 * indexkey (this must match logic in match_index_to_operand()).
  	 */
- 	while (IsA(node, PlaceHolderVar))
- 		node = (Node *) ((PlaceHolderVar *) node)->phexpr;
  	if (IsA(node, RelabelType))
  		node = (Node *) ((RelabelType *) node)->arg;
  
--- 2704,2711 ----
  	ListCell   *indexpr_item;
  
  	/*
! 	 * Remove any binary-compatible relabeling of the indexkey
  	 */
  	if (IsA(node, RelabelType))
  		node = (Node *) ((RelabelType *) node)->arg;
  
*************** make_sort(PlannerInfo *root, Plan *leftt
*** 3516,3570 ****
  }
  
  /*
-  * add_sort_column --- utility subroutine for building sort info arrays
-  *
-  * We need this routine because the same column might be selected more than
-  * once as a sort key column; if so, the extra mentions are redundant.
-  *
-  * Caller is assumed to have allocated the arrays large enough for the
-  * max possible number of columns.	Return value is the new column count.
-  */
- static int
- add_sort_column(AttrNumber colIdx, Oid sortOp, Oid coll, bool nulls_first,
- 				int numCols, AttrNumber *sortColIdx,
- 				Oid *sortOperators, Oid *collations, bool *nullsFirst)
- {
- 	int			i;
- 
- 	Assert(OidIsValid(sortOp));
- 
- 	for (i = 0; i < numCols; i++)
- 	{
- 		/*
- 		 * Note: we check sortOp because it's conceivable that "ORDER BY foo
- 		 * USING <, foo USING <<<" is not redundant, if <<< distinguishes
- 		 * values that < considers equal.  We need not check nulls_first
- 		 * however because a lower-order column with the same sortop but
- 		 * opposite nulls direction is redundant.
- 		 *
- 		 * We could probably consider sort keys with the same sortop and
- 		 * different collations to be redundant too, but for the moment treat
- 		 * them as not redundant.  This will be needed if we ever support
- 		 * collations with different notions of equality.
- 		 */
- 		if (sortColIdx[i] == colIdx &&
- 			sortOperators[numCols] == sortOp &&
- 			collations[numCols] == coll)
- 		{
- 			/* Already sorting by this col, so extra sort key is useless */
- 			return numCols;
- 		}
- 	}
- 
- 	/* Add the column */
- 	sortColIdx[numCols] = colIdx;
- 	sortOperators[numCols] = sortOp;
- 	collations[numCols] = coll;
- 	nullsFirst[numCols] = nulls_first;
- 	return numCols + 1;
- }
- 
- /*
   * prepare_sort_from_pathkeys
   *	  Prepare to sort according to given pathkeys
   *
--- 3522,3527 ----
*************** add_sort_column(AttrNumber colIdx, Oid s
*** 3573,3580 ****
   * plan targetlist if needed to add resjunk sort columns.
   *
   * Input parameters:
!  *	  'lefttree' is the node which yields input tuples
   *	  'pathkeys' is the list of pathkeys by which the result is to be sorted
   *	  'adjust_tlist_in_place' is TRUE if lefttree must be modified in-place
   *
   * We must convert the pathkey information into arrays of sort key column
--- 3530,3539 ----
   * plan targetlist if needed to add resjunk sort columns.
   *
   * Input parameters:
!  *	  'lefttree' is the plan node which yields input tuples
   *	  'pathkeys' is the list of pathkeys by which the result is to be sorted
+  *	  'relids' identifies the child relation being sorted, if any
+  *	  'reqColIdx' is NULL or an array of required sort key column numbers
   *	  'adjust_tlist_in_place' is TRUE if lefttree must be modified in-place
   *
   * We must convert the pathkey information into arrays of sort key column
*************** add_sort_column(AttrNumber colIdx, Oid s
*** 3582,3587 ****
--- 3541,3554 ----
   * which is the representation the executor wants.	These are returned into
   * the output parameters *p_numsortkeys etc.
   *
+  * When looking for matches to an EquivalenceClass's members, we will only
+  * consider child EC members if they match 'relids'.  This protects against
+  * possible incorrect matches to child expressions that contain no Vars.
+  *
+  * If reqColIdx isn't NULL then it contains sort key column numbers that
+  * we should match.  This is used when making child plans for a MergeAppend;
+  * it's an error if we can't match the columns.
+  *
   * If the pathkeys include expressions that aren't simple Vars, we will
   * usually need to add resjunk items to the input plan's targetlist to
   * compute these expressions, since the Sort/MergeAppend node itself won't
*************** add_sort_column(AttrNumber colIdx, Oid s
*** 3596,3601 ****
--- 3563,3570 ----
   */
  static Plan *
  prepare_sort_from_pathkeys(PlannerInfo *root, Plan *lefttree, List *pathkeys,
+ 						   Relids relids,
+ 						   const AttrNumber *reqColIdx,
  						   bool adjust_tlist_in_place,
  						   int *p_numsortkeys,
  						   AttrNumber **p_sortColIdx,
*************** prepare_sort_from_pathkeys(PlannerInfo *
*** 3626,3631 ****
--- 3595,3601 ----
  	{
  		PathKey    *pathkey = (PathKey *) lfirst(i);
  		EquivalenceClass *ec = pathkey->pk_eclass;
+ 		EquivalenceMember *em;
  		TargetEntry *tle = NULL;
  		Oid			pk_datatype = InvalidOid;
  		Oid			sortop;
*************** prepare_sort_from_pathkeys(PlannerInfo *
*** 3645,3660 ****
  			Assert(list_length(ec->ec_members) == 1);
  			pk_datatype = ((EquivalenceMember *) linitial(ec->ec_members))->em_datatype;
  		}
  		else
  		{
  			/*
  			 * Otherwise, we can sort by any non-constant expression listed in
! 			 * the pathkey's EquivalenceClass.  For now, we take the first one
! 			 * that corresponds to an available item in the tlist.	If there
! 			 * isn't any, use the first one that is an expression in the
! 			 * input's vars.  (The non-const restriction only matters if the
! 			 * EC is below_outer_join; but if it isn't, it won't contain
! 			 * consts anyway, else we'd have discarded the pathkey as
  			 * redundant.)
  			 *
  			 * XXX if we have a choice, is there any way of figuring out which
--- 3615,3655 ----
  			Assert(list_length(ec->ec_members) == 1);
  			pk_datatype = ((EquivalenceMember *) linitial(ec->ec_members))->em_datatype;
  		}
+ 		else if (reqColIdx != NULL)
+ 		{
+ 			/*
+ 			 * If we are given a sort column number to match, only consider
+ 			 * the single TLE at that position.  It's possible that there
+ 			 * is no such TLE, in which case fall through and generate a
+ 			 * resjunk targetentry (we assume this must have happened in the
+ 			 * parent plan as well).  If there is a TLE but it doesn't match
+ 			 * the pathkey's EC, we do the same, which is probably the wrong
+ 			 * thing but we'll leave it to caller to complain about the
+ 			 * mismatch.
+ 			 */
+ 			tle = get_tle_by_resno(tlist, reqColIdx[numsortkeys]);
+ 			if (tle)
+ 			{
+ 				em = find_ec_member_for_tle(ec, tle, relids);
+ 				if (em)
+ 				{
+ 					/* found expr at right place in tlist */
+ 					pk_datatype = em->em_datatype;
+ 				}
+ 				else
+ 					tle = NULL;
+ 			}
+ 		}
  		else
  		{
  			/*
  			 * Otherwise, we can sort by any non-constant expression listed in
! 			 * the pathkey's EquivalenceClass.  For now, we take the first
! 			 * tlist item found in the EC. If there's no match, we'll generate
! 			 * a resjunk entry using the first EC member that is an expression
! 			 * in the input's vars.  (The non-const restriction only matters
! 			 * if the EC is below_outer_join; but if it isn't, it won't
! 			 * contain consts anyway, else we'd have discarded the pathkey as
  			 * redundant.)
  			 *
  			 * XXX if we have a choice, is there any way of figuring out which
*************** prepare_sort_from_pathkeys(PlannerInfo *
*** 3663,3671 ****
--- 3658,3693 ----
  			 * in the same equivalence class...)  Not clear that we ever will
  			 * have an interesting choice in practice, so it may not matter.
  			 */
+ 			foreach(j, tlist)
+ 			{
+ 				tle = (TargetEntry *) lfirst(j);
+ 				em = find_ec_member_for_tle(ec, tle, relids);
+ 				if (em)
+ 				{
+ 					/* found expr already in tlist */
+ 					pk_datatype = em->em_datatype;
+ 					break;
+ 				}
+ 				tle = NULL;
+ 			}
+ 		}
+ 
+ 		if (!tle)
+ 		{
+ 			/*
+ 			 * No matching tlist item; look for a computable expression.
+ 			 * Note that we treat Aggrefs as if they were variables; this
+ 			 * is necessary when attempting to sort the output from an Agg
+ 			 * node for use in a WindowFunc (since grouping_planner will
+ 			 * have treated the Aggrefs as variables, too).
+ 			 */
+ 			Expr	   *sortexpr = NULL;
+ 
  			foreach(j, ec->ec_members)
  			{
  				EquivalenceMember *em = (EquivalenceMember *) lfirst(j);
+ 				List	   *exprvars;
+ 				ListCell   *k;
  
  				/*
  				 * We shouldn't be trying to sort by an equivalence class that
*************** prepare_sort_from_pathkeys(PlannerInfo *
*** 3675,3765 ****
  				if (em->em_is_const)
  					continue;
  
- 				tle = tlist_member((Node *) em->em_expr, tlist);
- 				if (tle)
- 				{
- 					pk_datatype = em->em_datatype;
- 					break;		/* found expr already in tlist */
- 				}
- 
  				/*
! 				 * We can also use it if the pathkey expression is a relabel
! 				 * of the tlist entry, or vice versa.  This is needed for
! 				 * binary-compatible cases (cf. make_pathkey_from_sortinfo).
! 				 * We prefer an exact match, though, so we do the basic search
! 				 * first.
  				 */
! 				tle = tlist_member_ignore_relabel((Node *) em->em_expr, tlist);
! 				if (tle)
  				{
  					pk_datatype = em->em_datatype;
! 					break;		/* found expr already in tlist */
  				}
  			}
  
! 			if (!tle)
  			{
! 				/*
! 				 * No matching tlist item; look for a computable expression.
! 				 * Note that we treat Aggrefs as if they were variables; this
! 				 * is necessary when attempting to sort the output from an Agg
! 				 * node for use in a WindowFunc (since grouping_planner will
! 				 * have treated the Aggrefs as variables, too).
! 				 */
! 				Expr	   *sortexpr = NULL;
! 
! 				foreach(j, ec->ec_members)
! 				{
! 					EquivalenceMember *em = (EquivalenceMember *) lfirst(j);
! 					List	   *exprvars;
! 					ListCell   *k;
! 
! 					if (em->em_is_const)
! 						continue;
! 					sortexpr = em->em_expr;
! 					exprvars = pull_var_clause((Node *) sortexpr,
! 											   PVC_INCLUDE_AGGREGATES,
! 											   PVC_INCLUDE_PLACEHOLDERS);
! 					foreach(k, exprvars)
! 					{
! 						if (!tlist_member_ignore_relabel(lfirst(k), tlist))
! 							break;
! 					}
! 					list_free(exprvars);
! 					if (!k)
! 					{
! 						pk_datatype = em->em_datatype;
! 						break;	/* found usable expression */
! 					}
! 				}
! 				if (!j)
! 					elog(ERROR, "could not find pathkey item to sort");
! 
! 				/*
! 				 * Do we need to insert a Result node?
! 				 */
! 				if (!adjust_tlist_in_place &&
! 					!is_projection_capable_plan(lefttree))
! 				{
! 					/* copy needed so we don't modify input's tlist below */
! 					tlist = copyObject(tlist);
! 					lefttree = (Plan *) make_result(root, tlist, NULL,
! 													lefttree);
! 				}
  
! 				/* Don't bother testing is_projection_capable_plan again */
! 				adjust_tlist_in_place = true;
  
! 				/*
! 				 * Add resjunk entry to input's tlist
! 				 */
! 				tle = makeTargetEntry(sortexpr,
! 									  list_length(tlist) + 1,
! 									  NULL,
! 									  true);
! 				tlist = lappend(tlist, tle);
! 				lefttree->targetlist = tlist;	/* just in case NIL before */
! 			}
  		}
  
  		/*
--- 3697,3753 ----
  				if (em->em_is_const)
  					continue;
  
  				/*
! 				 * Ignore child members unless they match the rel being sorted.
  				 */
! 				if (em->em_is_child &&
! 					!bms_equal(em->em_relids, relids))
! 					continue;
! 
! 				sortexpr = em->em_expr;
! 				exprvars = pull_var_clause((Node *) sortexpr,
! 										   PVC_INCLUDE_AGGREGATES,
! 										   PVC_INCLUDE_PLACEHOLDERS);
! 				foreach(k, exprvars)
! 				{
! //					if (!tlist_member_ignore_relabel(lfirst(k), tlist))
! 					if (!tlist_member(lfirst(k), tlist))
! 						break;
! 				}
! 				list_free(exprvars);
! 				if (!k)
  				{
  					pk_datatype = em->em_datatype;
! 					break;		/* found usable expression */
  				}
  			}
+ 			if (!j)
+ 				elog(ERROR, "could not find pathkey item to sort");
  
! 			/*
! 			 * Do we need to insert a Result node?
! 			 */
! 			if (!adjust_tlist_in_place &&
! 				!is_projection_capable_plan(lefttree))
  			{
! 				/* copy needed so we don't modify input's tlist below */
! 				tlist = copyObject(tlist);
! 				lefttree = (Plan *) make_result(root, tlist, NULL,
! 												lefttree);
! 			}
  
! 			/* Don't bother testing is_projection_capable_plan again */
! 			adjust_tlist_in_place = true;
  
! 			/*
! 			 * Add resjunk entry to input's tlist
! 			 */
! 			tle = makeTargetEntry(sortexpr,
! 								  list_length(tlist) + 1,
! 								  NULL,
! 								  true);
! 			tlist = lappend(tlist, tle);
! 			lefttree->targetlist = tlist;	/* just in case NIL before */
  		}
  
  		/*
*************** prepare_sort_from_pathkeys(PlannerInfo *
*** 3775,3793 ****
  				 pathkey->pk_strategy, pk_datatype, pk_datatype,
  				 pathkey->pk_opfamily);
  
! 		/*
! 		 * The column might already be selected as a sort key, if the pathkeys
! 		 * contain duplicate entries.  (This can happen in scenarios where
! 		 * multiple mergejoinable clauses mention the same var, for example.)
! 		 * So enter it only once in the sort arrays.
! 		 */
! 		numsortkeys = add_sort_column(tle->resno,
! 									  sortop,
! 									  pathkey->pk_eclass->ec_collation,
! 									  pathkey->pk_nulls_first,
! 									  numsortkeys,
! 									  sortColIdx, sortOperators,
! 									  collations, nullsFirst);
  	}
  
  	Assert(numsortkeys > 0);
--- 3763,3774 ----
  				 pathkey->pk_strategy, pk_datatype, pk_datatype,
  				 pathkey->pk_opfamily);
  
! 		/* Add the column to the sort arrays */
! 		sortColIdx[numsortkeys] = tle->resno;
! 		sortOperators[numsortkeys] = sortop;
! 		collations[numsortkeys] = ec->ec_collation;
! 		nullsFirst[numsortkeys] = pathkey->pk_nulls_first;
! 		numsortkeys++;
  	}
  
  	Assert(numsortkeys > 0);
*************** prepare_sort_from_pathkeys(PlannerInfo *
*** 3803,3808 ****
--- 3784,3840 ----
  }
  
  /*
+  * find_ec_member_for_tle
+  *		Locate an EquivalenceClass member matching the given TLE, if any
+  *
+  * Child EC members are ignored unless they match 'relids'.
+  */
+ static EquivalenceMember *
+ find_ec_member_for_tle(EquivalenceClass *ec,
+ 					   TargetEntry *tle,
+ 					   Relids relids)
+ {
+ 	Expr	   *tlexpr;
+ 	ListCell   *lc;
+ 
+ 	/* We ignore binary-compatible relabeling on both ends */
+ 	tlexpr = tle->expr;
+ 	while (tlexpr && IsA(tlexpr, RelabelType))
+ 		tlexpr = ((RelabelType *) tlexpr)->arg;
+ 
+ 	foreach(lc, ec->ec_members)
+ 	{
+ 		EquivalenceMember *em = (EquivalenceMember *) lfirst(lc);
+ 		Expr	   *emexpr;
+ 
+ 		/*
+ 		 * We shouldn't be trying to sort by an equivalence class that
+ 		 * contains a constant, so no need to consider such cases any
+ 		 * further.
+ 		 */
+ 		if (em->em_is_const)
+ 			continue;
+ 
+ 		/*
+ 		 * Ignore child members unless they match the rel being sorted.
+ 		 */
+ 		if (em->em_is_child &&
+ 			!bms_equal(em->em_relids, relids))
+ 			continue;
+ 
+ 		/* Match if same expression (after stripping relabel) */
+ 		emexpr = em->em_expr;
+ 		while (emexpr && IsA(emexpr, RelabelType))
+ 			emexpr = ((RelabelType *) emexpr)->arg;
+ 
+ 		if (equal(emexpr, tlexpr))
+ 			return em;
+ 	}
+ 
+ 	return NULL;
+ }
+ 
+ /*
   * make_sort_from_pathkeys
   *	  Create sort plan to sort according to given pathkeys
   *
*************** make_sort_from_pathkeys(PlannerInfo *roo
*** 3823,3828 ****
--- 3855,3862 ----
  
  	/* Compute sort column info, and adjust lefttree as needed */
  	lefttree = prepare_sort_from_pathkeys(root, lefttree, pathkeys,
+ 										  NULL,
+ 										  NULL,
  										  false,
  										  &numsortkeys,
  										  &sortColIdx,
*************** make_sort_from_sortclauses(PlannerInfo *
*** 3854,3862 ****
  	Oid		   *collations;
  	bool	   *nullsFirst;
  
! 	/*
! 	 * We will need at most list_length(sortcls) sort columns; possibly less
! 	 */
  	numsortkeys = list_length(sortcls);
  	sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber));
  	sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid));
--- 3888,3894 ----
  	Oid		   *collations;
  	bool	   *nullsFirst;
  
! 	/* Convert list-ish representation to arrays wanted by executor */
  	numsortkeys = list_length(sortcls);
  	sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber));
  	sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid));
*************** make_sort_from_sortclauses(PlannerInfo *
*** 3864,3890 ****
  	nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool));
  
  	numsortkeys = 0;
- 
  	foreach(l, sortcls)
  	{
  		SortGroupClause *sortcl = (SortGroupClause *) lfirst(l);
  		TargetEntry *tle = get_sortgroupclause_tle(sortcl, sub_tlist);
  
! 		/*
! 		 * Check for the possibility of duplicate order-by clauses --- the
! 		 * parser should have removed 'em, but no point in sorting
! 		 * redundantly.
! 		 */
! 		numsortkeys = add_sort_column(tle->resno, sortcl->sortop,
! 									  exprCollation((Node *) tle->expr),
! 									  sortcl->nulls_first,
! 									  numsortkeys,
! 									  sortColIdx, sortOperators,
! 									  collations, nullsFirst);
  	}
  
- 	Assert(numsortkeys > 0);
- 
  	return make_sort(root, lefttree, numsortkeys,
  					 sortColIdx, sortOperators, collations,
  					 nullsFirst, -1.0);
--- 3896,3913 ----
  	nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool));
  
  	numsortkeys = 0;
  	foreach(l, sortcls)
  	{
  		SortGroupClause *sortcl = (SortGroupClause *) lfirst(l);
  		TargetEntry *tle = get_sortgroupclause_tle(sortcl, sub_tlist);
  
! 		sortColIdx[numsortkeys] = tle->resno;
! 		sortOperators[numsortkeys] = sortcl->sortop;
! 		collations[numsortkeys] = exprCollation((Node *) tle->expr);
! 		nullsFirst[numsortkeys] = sortcl->nulls_first;
! 		numsortkeys++;
  	}
  
  	return make_sort(root, lefttree, numsortkeys,
  					 sortColIdx, sortOperators, collations,
  					 nullsFirst, -1.0);
*************** make_sort_from_groupcols(PlannerInfo *ro
*** 3910,3916 ****
  						 Plan *lefttree)
  {
  	List	   *sub_tlist = lefttree->targetlist;
- 	int			grpno = 0;
  	ListCell   *l;
  	int			numsortkeys;
  	AttrNumber *sortColIdx;
--- 3933,3938 ----
*************** make_sort_from_groupcols(PlannerInfo *ro
*** 3918,3926 ****
  	Oid		   *collations;
  	bool	   *nullsFirst;
  
! 	/*
! 	 * We will need at most list_length(groupcls) sort columns; possibly less
! 	 */
  	numsortkeys = list_length(groupcls);
  	sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber));
  	sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid));
--- 3940,3946 ----
  	Oid		   *collations;
  	bool	   *nullsFirst;
  
! 	/* Convert list-ish representation to arrays wanted by executor */
  	numsortkeys = list_length(groupcls);
  	sortColIdx = (AttrNumber *) palloc(numsortkeys * sizeof(AttrNumber));
  	sortOperators = (Oid *) palloc(numsortkeys * sizeof(Oid));
*************** make_sort_from_groupcols(PlannerInfo *ro
*** 3928,3955 ****
  	nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool));
  
  	numsortkeys = 0;
- 
  	foreach(l, groupcls)
  	{
  		SortGroupClause *grpcl = (SortGroupClause *) lfirst(l);
! 		TargetEntry *tle = get_tle_by_resno(sub_tlist, grpColIdx[grpno]);
  
! 		/*
! 		 * Check for the possibility of duplicate group-by clauses --- the
! 		 * parser should have removed 'em, but no point in sorting
! 		 * redundantly.
! 		 */
! 		numsortkeys = add_sort_column(tle->resno, grpcl->sortop,
! 									  exprCollation((Node *) tle->expr),
! 									  grpcl->nulls_first,
! 									  numsortkeys,
! 									  sortColIdx, sortOperators,
! 									  collations, nullsFirst);
! 		grpno++;
  	}
  
- 	Assert(numsortkeys > 0);
- 
  	return make_sort(root, lefttree, numsortkeys,
  					 sortColIdx, sortOperators, collations,
  					 nullsFirst, -1.0);
--- 3948,3965 ----
  	nullsFirst = (bool *) palloc(numsortkeys * sizeof(bool));
  
  	numsortkeys = 0;
  	foreach(l, groupcls)
  	{
  		SortGroupClause *grpcl = (SortGroupClause *) lfirst(l);
! 		TargetEntry *tle = get_tle_by_resno(sub_tlist, grpColIdx[numsortkeys]);
  
! 		sortColIdx[numsortkeys] = tle->resno;
! 		sortOperators[numsortkeys] = grpcl->sortop;
! 		collations[numsortkeys] = exprCollation((Node *) tle->expr);
! 		nullsFirst[numsortkeys] = grpcl->nulls_first;
! 		numsortkeys++;
  	}
  
  	return make_sort(root, lefttree, numsortkeys,
  					 sortColIdx, sortOperators, collations,
  					 nullsFirst, -1.0);
diff --git a/src/backend/optimizer/plan/planagg.c b/src/backend/optimizer/plan/planagg.c
index 1e2284a9306c17f0bcaa303f1fa645a712ab5b7c..7e2c6d2c31f269a1deaaabe1bf0d0930150ebe18 100644
*** a/src/backend/optimizer/plan/planagg.c
--- b/src/backend/optimizer/plan/planagg.c
*************** preprocess_minmax_aggregates(PlannerInfo
*** 99,107 ****
  	 * We also restrict the query to reference exactly one table, since join
  	 * conditions can't be handled reasonably.  (We could perhaps handle a
  	 * query containing cartesian-product joins, but it hardly seems worth the
! 	 * trouble.)  However, the single real table could be buried in several
! 	 * levels of FromExpr due to subqueries.  Note the single table could be
! 	 * an inheritance parent, too.
  	 */
  	jtnode = parse->jointree;
  	while (IsA(jtnode, FromExpr))
--- 99,108 ----
  	 * We also restrict the query to reference exactly one table, since join
  	 * conditions can't be handled reasonably.  (We could perhaps handle a
  	 * query containing cartesian-product joins, but it hardly seems worth the
! 	 * trouble.)  However, the single table could be buried in several levels
! 	 * of FromExpr due to subqueries.  Note the "single" table could be an
! 	 * inheritance parent, too, including the case of a UNION ALL subquery
! 	 * that's been flattened to an appendrel.
  	 */
  	jtnode = parse->jointree;
  	while (IsA(jtnode, FromExpr))
*************** preprocess_minmax_aggregates(PlannerInfo
*** 114,120 ****
  		return;
  	rtr = (RangeTblRef *) jtnode;
  	rte = planner_rt_fetch(rtr->rtindex, root);
! 	if (rte->rtekind != RTE_RELATION)
  		return;
  
  	/*
--- 115,125 ----
  		return;
  	rtr = (RangeTblRef *) jtnode;
  	rte = planner_rt_fetch(rtr->rtindex, root);
! 	if (rte->rtekind == RTE_RELATION)
! 		/* ordinary relation, ok */ ;
! 	else if (rte->rtekind == RTE_SUBQUERY && rte->inh)
! 		/* flattened UNION ALL subquery, ok */ ;
! 	else
  		return;
  
  	/*
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 68968d9655a43f5b33e578406cc6a72e1995067d..c439e9652c1b2be8456eac2a382cc8b02b4699f4 100644
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** query_planner(PlannerInfo *root, List *t
*** 176,182 ****
  	 */
  	build_base_rel_tlists(root, tlist);
  
! 	find_placeholders_in_query(root);
  
  	joinlist = deconstruct_jointree(root);
  
--- 176,182 ----
  	 */
  	build_base_rel_tlists(root, tlist);
  
! 	find_placeholders_in_jointree(root);
  
  	joinlist = deconstruct_jointree(root);
  
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 59a52687f9dfc082b3b01321fe3eada7997498a0..2fd795b45929014257930edd4af620e7e8d7a5a7 100644
*** a/src/backend/optimizer/prep/prepjointree.c
--- b/src/backend/optimizer/prep/prepjointree.c
*************** pull_up_simple_subquery(PlannerInfo *roo
*** 866,880 ****
  	parse->havingQual = pullup_replace_vars(parse->havingQual, &rvcontext);
  
  	/*
! 	 * Replace references in the translated_vars lists of appendrels, too.
! 	 * We do it this way because we must preserve the AppendRelInfo structs.
  	 */
  	foreach(lc, root->append_rel_list)
  	{
  		AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
  
  		appinfo->translated_vars = (List *)
  			pullup_replace_vars((Node *) appinfo->translated_vars, &rvcontext);
  	}
  
  	/*
--- 866,887 ----
  	parse->havingQual = pullup_replace_vars(parse->havingQual, &rvcontext);
  
  	/*
! 	 * Replace references in the translated_vars lists of appendrels. When
! 	 * pulling up an appendrel member, we do not need PHVs in the list of the
! 	 * parent appendrel --- there isn't any outer join between. Elsewhere, use
! 	 * PHVs for safety.  (This analysis could be made tighter but it seems
! 	 * unlikely to be worth much trouble.)
  	 */
  	foreach(lc, root->append_rel_list)
  	{
  		AppendRelInfo *appinfo = (AppendRelInfo *) lfirst(lc);
+ 		bool		save_need_phvs = rvcontext.need_phvs;
  
+ 		if (appinfo == containing_appendrel)
+ 			rvcontext.need_phvs = false;
  		appinfo->translated_vars = (List *)
  			pullup_replace_vars((Node *) appinfo->translated_vars, &rvcontext);
+ 		rvcontext.need_phvs = save_need_phvs;
  	}
  
  	/*
*************** pullup_replace_vars_callback(Var *var,
*** 1482,1512 ****
  			if (newnode && IsA(newnode, Var) &&
  				((Var *) newnode)->varlevelsup == 0)
  			{
! 				/*
! 				 * Simple Vars normally escape being wrapped.  However, in
! 				 * wrap_non_vars mode (ie, we are dealing with an appendrel
! 				 * member), we must ensure that each tlist entry expands to a
! 				 * distinct expression, else we may have problems with
! 				 * improperly placing identical entries into different
! 				 * EquivalenceClasses.  Therefore, we wrap a Var in a
! 				 * PlaceHolderVar if it duplicates any earlier entry in the
! 				 * tlist (ie, we've got "SELECT x, x, ...").  Since each PHV
! 				 * is distinct, this fixes the ambiguity.  We can use
! 				 * tlist_member to detect whether there's an earlier
! 				 * duplicate.
! 				 */
! 				wrap = (rcon->wrap_non_vars &&
! 						tlist_member(newnode, rcon->targetlist) != tle);
  			}
  			else if (newnode && IsA(newnode, PlaceHolderVar) &&
  					 ((PlaceHolderVar *) newnode)->phlevelsup == 0)
  			{
! 				/*
! 				 * No need to directly wrap a PlaceHolderVar with another one,
! 				 * either, unless we need to prevent duplication.
! 				 */
! 				wrap = (rcon->wrap_non_vars &&
! 						tlist_member(newnode, rcon->targetlist) != tle);
  			}
  			else if (rcon->wrap_non_vars)
  			{
--- 1489,1502 ----
  			if (newnode && IsA(newnode, Var) &&
  				((Var *) newnode)->varlevelsup == 0)
  			{
! 				/* Simple Vars always escape being wrapped */
! 				wrap = false;
  			}
  			else if (newnode && IsA(newnode, PlaceHolderVar) &&
  					 ((PlaceHolderVar *) newnode)->phlevelsup == 0)
  			{
! 				/* No need to wrap a PlaceHolderVar with another one, either */
! 				wrap = false;
  			}
  			else if (rcon->wrap_non_vars)
  			{
diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c
index 44a41e3e9b89a811f779efbfedf8b1dfab13b503..93f1c2cdfa4e1aadec221fa7ef2ccc833709daf8 100644
*** a/src/backend/optimizer/util/placeholder.c
--- b/src/backend/optimizer/util/placeholder.c
*************** find_placeholder_info(PlannerInfo *root,
*** 104,144 ****
  }
  
  /*
!  * find_placeholders_in_query
!  *		Search the query for PlaceHolderVars, and build PlaceHolderInfos
!  *
!  * We need to examine the jointree, but not the targetlist, because
!  * build_base_rel_tlists() will already have made entries for any PHVs
!  * in the targetlist.
   *
!  * We also need to search for PHVs in AppendRelInfo translated_vars
!  * lists.  In most cases, translated_vars entries aren't directly referenced
!  * elsewhere, but we need to create PlaceHolderInfo entries for them to
!  * support set_rel_width() calculations for the appendrel child relations.
   */
  void
! find_placeholders_in_query(PlannerInfo *root)
  {
  	/* We need do nothing if the query contains no PlaceHolderVars */
  	if (root->glob->lastPHId != 0)
  	{
! 		/* Recursively search the jointree */
  		Assert(root->parse->jointree != NULL &&
  			   IsA(root->parse->jointree, FromExpr));
  		(void) find_placeholders_recurse(root, (Node *) root->parse->jointree);
- 
- 		/*
- 		 * Also search the append_rel_list for translated vars that are PHVs.
- 		 * Barring finding them elsewhere in the query, they do not need any
- 		 * ph_may_need bits, only to be present in the PlaceHolderInfo list.
- 		 */
- 		mark_placeholders_in_expr(root, (Node *) root->append_rel_list, NULL);
  	}
  }
  
  /*
   * find_placeholders_recurse
!  *	  One recursion level of jointree search for find_placeholders_in_query.
   *
   * jtnode is the current jointree node to examine.
   *
--- 104,131 ----
  }
  
  /*
!  * find_placeholders_in_jointree
!  *		Search the jointree for PlaceHolderVars, and build PlaceHolderInfos
   *
!  * We don't need to look at the targetlist because build_base_rel_tlists()
!  * will already have made entries for any PHVs in the tlist.
   */
  void
! find_placeholders_in_jointree(PlannerInfo *root)
  {
  	/* We need do nothing if the query contains no PlaceHolderVars */
  	if (root->glob->lastPHId != 0)
  	{
! 		/* Start recursion at top of jointree */
  		Assert(root->parse->jointree != NULL &&
  			   IsA(root->parse->jointree, FromExpr));
  		(void) find_placeholders_recurse(root, (Node *) root->parse->jointree);
  	}
  }
  
  /*
   * find_placeholders_recurse
!  *	  One recursion level of find_placeholders_in_jointree.
   *
   * jtnode is the current jointree node to examine.
   *
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index 8616223f24a8cbe5424b89599b331538db05dd76..d901a85e7ac680d89d8e71569da73d901991d771 100644
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct EquivalenceClass
*** 572,583 ****
   * EquivalenceMember - one member expression of an EquivalenceClass
   *
   * em_is_child signifies that this element was built by transposing a member
!  * for an inheritance parent relation to represent the corresponding expression
!  * on an inheritance child.  These elements are used for constructing
!  * inner-indexscan paths for the child relation (other types of join are
!  * driven from transposed joininfo-list entries) and for constructing
!  * MergeAppend paths for the whole inheritance tree.  Note that the EC's
!  * ec_relids field does NOT include the child relation.
   *
   * em_datatype is usually the same as exprType(em_expr), but can be
   * different when dealing with a binary-compatible opfamily; in particular
--- 572,589 ----
   * EquivalenceMember - one member expression of an EquivalenceClass
   *
   * em_is_child signifies that this element was built by transposing a member
!  * for an appendrel parent relation to represent the corresponding expression
!  * for an appendrel child.  These members are used for determining the
!  * pathkeys of scans on the child relation and for explicitly sorting the
!  * child when necessary to build a MergeAppend path for the whole appendrel
!  * tree.  An em_is_child member has no impact on the properties of the EC as a
!  * whole; in particular the EC's ec_relids field does NOT include the child
!  * relation.  An em_is_child member should never be marked em_is_const nor
!  * cause ec_has_const or ec_has_volatile to be set, either.  Thus, em_is_child
!  * members are not really full-fledged members of the EC, but just reflections
!  * or doppelgangers of real members.  Most operations on EquivalenceClasses
!  * should ignore em_is_child members, and those that don't should test
!  * em_relids to make sure they only consider relevant members.
   *
   * em_datatype is usually the same as exprType(em_expr), but can be
   * different when dealing with a binary-compatible opfamily; in particular
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index 082b54e120f3c4e6ae0f91cacc24bd1bcda98323..caaa7c24a1cf21230e44a5db83c34c8d9d7608e3 100644
*** a/src/include/optimizer/paths.h
--- b/src/include/optimizer/paths.h
*************** extern EquivalenceClass *get_eclass_for_
*** 110,115 ****
--- 110,116 ----
  						 Oid opcintype,
  						 Oid collation,
  						 Index sortref,
+ 						 Relids rel,
  						 bool create_it);
  extern void generate_base_implied_equalities(PlannerInfo *root);
  extern List *generate_join_implied_equalities(PlannerInfo *root,
diff --git a/src/include/optimizer/placeholder.h b/src/include/optimizer/placeholder.h
index 2d83fb1578fc007d6b16607aef10615c2db78acb..b8ac25367e54366560465ad045b02622363751c7 100644
*** a/src/include/optimizer/placeholder.h
--- b/src/include/optimizer/placeholder.h
*************** extern PlaceHolderVar *make_placeholder_
*** 21,27 ****
  					  Relids phrels);
  extern PlaceHolderInfo *find_placeholder_info(PlannerInfo *root,
  					  PlaceHolderVar *phv, bool create_new_ph);
! extern void find_placeholders_in_query(PlannerInfo *root);
  extern void mark_placeholder_maybe_needed(PlannerInfo *root,
  							  PlaceHolderInfo *phinfo, Relids relids);
  extern void update_placeholder_eval_levels(PlannerInfo *root,
--- 21,27 ----
  					  Relids phrels);
  extern PlaceHolderInfo *find_placeholder_info(PlannerInfo *root,
  					  PlaceHolderVar *phv, bool create_new_ph);
! extern void find_placeholders_in_jointree(PlannerInfo *root);
  extern void mark_placeholder_maybe_needed(PlannerInfo *root,
  							  PlaceHolderInfo *phinfo, Relids relids);
  extern void update_placeholder_eval_levels(PlannerInfo *root,
diff --git a/src/test/regress/expected/inherit.out b/src/test/regress/expected/inherit.out
index 6ef1cbdec7972a1d32a6fe2d1c6f1a227eb84f1c..d8f20e8ce2ce1d89540b806c8861e5a3ee1a3175 100644
*** a/src/test/regress/expected/inherit.out
--- b/src/test/regress/expected/inherit.out
*************** drop cascades to table matest2
*** 1067,1077 ****
  drop cascades to table matest3
  --
  -- Test merge-append for UNION ALL append relations
- -- Check handling of duplicated, constant, or volatile targetlist items
  --
  set enable_seqscan = off;
  set enable_indexscan = on;
  set enable_bitmapscan = off;
  explain (costs off)
  SELECT thousand, tenthous FROM tenk1
  UNION ALL
--- 1067,1077 ----
  drop cascades to table matest3
  --
  -- Test merge-append for UNION ALL append relations
  --
  set enable_seqscan = off;
  set enable_indexscan = on;
  set enable_bitmapscan = off;
+ -- Check handling of duplicated, constant, or volatile targetlist items
  explain (costs off)
  SELECT thousand, tenthous FROM tenk1
  UNION ALL
*************** ORDER BY thousand, tenthous;
*** 1120,1125 ****
--- 1120,1180 ----
                 ->  Index Only Scan using tenk1_thous_tenthous on tenk1
  (7 rows)
  
+ -- Check min/max aggregate optimization
+ explain (costs off)
+ SELECT min(x) FROM
+   (SELECT unique1 AS x FROM tenk1 a
+    UNION ALL
+    SELECT unique2 AS x FROM tenk1 b) s;
+                              QUERY PLAN                             
+ --------------------------------------------------------------------
+  Result
+    InitPlan 1 (returns $0)
+      ->  Limit
+            ->  Merge Append
+                  Sort Key: a.unique1
+                  ->  Index Only Scan using tenk1_unique1 on tenk1 a
+                        Index Cond: (unique1 IS NOT NULL)
+                  ->  Index Only Scan using tenk1_unique2 on tenk1 b
+                        Index Cond: (unique2 IS NOT NULL)
+ (9 rows)
+ 
+ explain (costs off)
+ SELECT min(y) FROM
+   (SELECT unique1 AS x, unique1 AS y FROM tenk1 a
+    UNION ALL
+    SELECT unique2 AS x, unique2 AS y FROM tenk1 b) s;
+                              QUERY PLAN                             
+ --------------------------------------------------------------------
+  Result
+    InitPlan 1 (returns $0)
+      ->  Limit
+            ->  Merge Append
+                  Sort Key: a.unique1
+                  ->  Index Only Scan using tenk1_unique1 on tenk1 a
+                        Index Cond: (unique1 IS NOT NULL)
+                  ->  Index Only Scan using tenk1_unique2 on tenk1 b
+                        Index Cond: (unique2 IS NOT NULL)
+ (9 rows)
+ 
+ -- XXX planner doesn't recognize that index on unique2 is sufficiently sorted
+ explain (costs off)
+ SELECT x, y FROM
+   (SELECT thousand AS x, tenthous AS y FROM tenk1 a
+    UNION ALL
+    SELECT unique2 AS x, unique2 AS y FROM tenk1 b) s
+ ORDER BY x, y;
+                             QUERY PLAN                             
+ -------------------------------------------------------------------
+  Result
+    ->  Merge Append
+          Sort Key: a.thousand, a.tenthous
+          ->  Index Only Scan using tenk1_thous_tenthous on tenk1 a
+          ->  Sort
+                Sort Key: b.unique2, b.unique2
+                ->  Index Only Scan using tenk1_unique2 on tenk1 b
+ (7 rows)
+ 
  reset enable_seqscan;
  reset enable_indexscan;
  reset enable_bitmapscan;
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 2913f3d8f378796b711f78404dcc90620f5eadf2..405c5847cd1b5cbdddb92b08b30f13b5feac869d 100644
*** a/src/test/regress/expected/union.out
--- b/src/test/regress/expected/union.out
*************** explain (costs off)
*** 503,505 ****
--- 503,519 ----
  reset enable_seqscan;
  reset enable_indexscan;
  reset enable_bitmapscan;
+ -- Test constraint exclusion of UNION ALL subqueries
+ explain (costs off)
+  SELECT * FROM
+   (SELECT 1 AS t, * FROM tenk1 a
+    UNION ALL
+    SELECT 2 AS t, * FROM tenk1 b) c
+  WHERE t = 2;
+            QUERY PLAN            
+ ---------------------------------
+  Result
+    ->  Append
+          ->  Seq Scan on tenk1 b
+ (3 rows)
+ 
diff --git a/src/test/regress/sql/inherit.sql b/src/test/regress/sql/inherit.sql
index 6fdbd18c25173dab58c069b8a93a7bf86e7a716e..eec8192928d92e5b6c19586cdbd0103e58a13875 100644
*** a/src/test/regress/sql/inherit.sql
--- b/src/test/regress/sql/inherit.sql
*************** drop table matest0 cascade;
*** 326,338 ****
  
  --
  -- Test merge-append for UNION ALL append relations
- -- Check handling of duplicated, constant, or volatile targetlist items
  --
  
  set enable_seqscan = off;
  set enable_indexscan = on;
  set enable_bitmapscan = off;
  
  explain (costs off)
  SELECT thousand, tenthous FROM tenk1
  UNION ALL
--- 326,338 ----
  
  --
  -- Test merge-append for UNION ALL append relations
  --
  
  set enable_seqscan = off;
  set enable_indexscan = on;
  set enable_bitmapscan = off;
  
+ -- Check handling of duplicated, constant, or volatile targetlist items
  explain (costs off)
  SELECT thousand, tenthous FROM tenk1
  UNION ALL
*************** UNION ALL
*** 351,356 ****
--- 351,377 ----
  SELECT thousand, random()::integer FROM tenk1
  ORDER BY thousand, tenthous;
  
+ -- Check min/max aggregate optimization
+ explain (costs off)
+ SELECT min(x) FROM
+   (SELECT unique1 AS x FROM tenk1 a
+    UNION ALL
+    SELECT unique2 AS x FROM tenk1 b) s;
+ 
+ explain (costs off)
+ SELECT min(y) FROM
+   (SELECT unique1 AS x, unique1 AS y FROM tenk1 a
+    UNION ALL
+    SELECT unique2 AS x, unique2 AS y FROM tenk1 b) s;
+ 
+ -- XXX planner doesn't recognize that index on unique2 is sufficiently sorted
+ explain (costs off)
+ SELECT x, y FROM
+   (SELECT thousand AS x, tenthous AS y FROM tenk1 a
+    UNION ALL
+    SELECT unique2 AS x, unique2 AS y FROM tenk1 b) s
+ ORDER BY x, y;
+ 
  reset enable_seqscan;
  reset enable_indexscan;
  reset enable_bitmapscan;
diff --git a/src/test/regress/sql/union.sql b/src/test/regress/sql/union.sql
index b5c2128f3f3826f49cd1a7287067dbc7ff275ed4..752ae470f0d1b38a1aeda87ae31d6b63e3e5cb3c 100644
*** a/src/test/regress/sql/union.sql
--- b/src/test/regress/sql/union.sql
*************** explain (costs off)
*** 199,201 ****
--- 199,209 ----
  reset enable_seqscan;
  reset enable_indexscan;
  reset enable_bitmapscan;
+ 
+ -- Test constraint exclusion of UNION ALL subqueries
+ explain (costs off)
+  SELECT * FROM
+   (SELECT 1 AS t, * FROM tenk1 a
+    UNION ALL
+    SELECT 2 AS t, * FROM tenk1 b) c
+  WHERE t = 2;