(2018/06/15 20:56), Etsuro Fujita wrote:
Actually, I've
created a patch implementing that proposal.

But I think that patch needs more work, so I'm
planning to post it next week.

Here is a patch for that.

* As I said upthread, the patch makes code much more simple; I removed all the changes to setrefs.c added by the partitionwise-join patch. I also simplified the logic for building a tlist for a child-join rel. The original PWJ computes attr_needed data even for child rels, and build the tlist for a child-join by passing to build_joinrel_tlist that data for input child rels for the child-join. But I think that's redundant, and it's more straightforward to apply adjust_appendrel_attrs to the parent-join's tlist to get the child-join's tlist. So, I changed that way, which made unnecessary all the changes to build_joinrel_tlist and placeholder.c added by the PWJ patch, so I removed those as well.

* The patch contains all of the regression tests in the original patch proposed by Ashutosh.

Best regards,
Etsuro Fujita
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 8215,8222 **** ALTER TABLE fprt2_p1 SET (autovacuum_enabled = 'false');
  ALTER TABLE fprt2_p2 SET (autovacuum_enabled = 'false');
  INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i;
  INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i;
! CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES FROM (0) TO (250)
  	SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true');
  CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500)
  	SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true');
  ANALYZE fprt2;
--- 8215,8223 ----
  ALTER TABLE fprt2_p2 SET (autovacuum_enabled = 'false');
  INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i;
  INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i;
! CREATE FOREIGN TABLE ftprt2_p1 (b int, c varchar, a int)
  	SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true');
+ ALTER TABLE fprt2 ATTACH PARTITION ftprt2_p1 FOR VALUES FROM (0) TO (250);
  CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500)
  	SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true');
  ANALYZE fprt2;
***************
*** 8269,8294 **** SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10)
  
  -- with whole-row reference
  EXPLAIN (COSTS OFF)
! SELECT t1,t2 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a % 25 =0 ORDER BY 1,2;
!                                    QUERY PLAN                                    
! ---------------------------------------------------------------------------------
   Sort
     Sort Key: ((t1.*)::fprt1), ((t2.*)::fprt2)
     ->  Append
           ->  Foreign Scan
!                Relations: (public.ftprt1_p1 t1) INNER JOIN (public.ftprt2_p1 t2)
           ->  Foreign Scan
!                Relations: (public.ftprt1_p2 t1) INNER JOIN (public.ftprt2_p2 t2)
  (7 rows)
  
! SELECT t1,t2 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a % 25 =0 ORDER BY 1,2;
!        t1       |       t2       
  ----------------+----------------
   (0,0,0000)     | (0,0,0000)
   (150,150,0003) | (150,150,0003)
   (250,250,0005) | (250,250,0005)
   (400,400,0008) | (400,400,0008)
! (4 rows)
  
  -- join with lateral reference
  EXPLAIN (COSTS OFF)
--- 8270,8305 ----
  
  -- with whole-row reference
  EXPLAIN (COSTS OFF)
! SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2;
!                                    QUERY PLAN                                   
! --------------------------------------------------------------------------------
   Sort
     Sort Key: ((t1.*)::fprt1), ((t2.*)::fprt2)
     ->  Append
           ->  Foreign Scan
!                Relations: (public.ftprt1_p1 t1) FULL JOIN (public.ftprt2_p1 t2)
           ->  Foreign Scan
!                Relations: (public.ftprt1_p2 t1) FULL JOIN (public.ftprt2_p2 t2)
  (7 rows)
  
! SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2;
!        wr       |       wr       
  ----------------+----------------
   (0,0,0000)     | (0,0,0000)
+  (50,50,0001)   | 
+  (100,100,0002) | 
   (150,150,0003) | (150,150,0003)
+  (200,200,0004) | 
   (250,250,0005) | (250,250,0005)
+  (300,300,0006) | 
+  (350,350,0007) | 
   (400,400,0008) | (400,400,0008)
!  (450,450,0009) | 
!                 | (75,75,0001)
!                 | (225,225,0004)
!                 | (325,325,0006)
!                 | (475,475,0009)
! (14 rows)
  
  -- join with lateral reference
  EXPLAIN (COSTS OFF)
***************
*** 8352,8357 **** SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE
--- 8363,8398 ----
       |        | 475 | t2_phv
  (14 rows)
  
+ -- test FOR UPDATE
+ EXPLAIN (COSTS OFF)
+ SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1;
+                                       QUERY PLAN                                       
+ ---------------------------------------------------------------------------------------
+  LockRows
+    ->  Sort
+          Sort Key: t1.a
+          ->  Append
+                ->  Foreign Scan
+                      Relations: (public.ftprt1_p1 t1) INNER JOIN (public.ftprt2_p1 t2)
+                      ->  Nested Loop
+                            ->  Foreign Scan on ftprt1_p1 t1
+                            ->  Foreign Scan on ftprt2_p1 t2
+                ->  Foreign Scan
+                      Relations: (public.ftprt1_p2 t1) INNER JOIN (public.ftprt2_p2 t2)
+                      ->  Nested Loop
+                            ->  Foreign Scan on ftprt1_p2 t1_1
+                            ->  Foreign Scan on ftprt2_p2 t2_1
+ (14 rows)
+ 
+ SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1;
+   a  |  b  
+ -----+-----
+    0 |   0
+  150 | 150
+  250 | 250
+  400 | 400
+ (4 rows)
+ 
  RESET enable_partitionwise_join;
  -- ===================================================================
  -- test partitionwise aggregates
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 2216,2223 **** ALTER TABLE fprt2_p1 SET (autovacuum_enabled = 'false');
  ALTER TABLE fprt2_p2 SET (autovacuum_enabled = 'false');
  INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i;
  INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i;
! CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES FROM (0) TO (250)
  	SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true');
  CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500)
  	SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true');
  ANALYZE fprt2;
--- 2216,2224 ----
  ALTER TABLE fprt2_p2 SET (autovacuum_enabled = 'false');
  INSERT INTO fprt2_p1 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(0, 249, 3) i;
  INSERT INTO fprt2_p2 SELECT i, i, to_char(i/50, 'FM0000') FROM generate_series(250, 499, 3) i;
! CREATE FOREIGN TABLE ftprt2_p1 (b int, c varchar, a int)
  	SERVER loopback OPTIONS (table_name 'fprt2_p1', use_remote_estimate 'true');
+ ALTER TABLE fprt2 ATTACH PARTITION ftprt2_p1 FOR VALUES FROM (0) TO (250);
  CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (250) TO (500)
  	SERVER loopback OPTIONS (table_name 'fprt2_p2', use_remote_estimate 'true');
  ANALYZE fprt2;
***************
*** 2236,2243 **** SELECT t1.a,t2.b,t2.c FROM fprt1 t1 LEFT JOIN (SELECT * FROM fprt2 WHERE a < 10)
  
  -- with whole-row reference
  EXPLAIN (COSTS OFF)
! SELECT t1,t2 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a % 25 =0 ORDER BY 1,2;
! SELECT t1,t2 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.a = t2.b and t1.b = t2.a) WHERE t1.a % 25 =0 ORDER BY 1,2;
  
  -- join with lateral reference
  EXPLAIN (COSTS OFF)
--- 2237,2244 ----
  
  -- with whole-row reference
  EXPLAIN (COSTS OFF)
! SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2;
! SELECT t1.wr, t2.wr FROM (SELECT t1 wr, a FROM fprt1 t1 WHERE t1.a % 25 = 0) t1 FULL JOIN (SELECT t2 wr, b FROM fprt2 t2 WHERE t2.b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY 1,2;
  
  -- join with lateral reference
  EXPLAIN (COSTS OFF)
***************
*** 2249,2254 **** EXPLAIN (COSTS OFF)
--- 2250,2260 ----
  SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
  SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE a % 25 = 0) t1 FULL JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE b % 25 = 0) t2 ON (t1.a = t2.b) ORDER BY t1.a, t2.b;
  
+ -- test FOR UPDATE
+ EXPLAIN (COSTS OFF)
+ SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1;
+ SELECT t1.a, t2.b FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.a = t2.b) WHERE t1.a % 25 = 0 ORDER BY 1,2 FOR UPDATE OF t1;
+ 
  RESET enable_partitionwise_join;
  
  
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
***************
*** 956,1027 **** set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
  		childrel = find_base_rel(root, childRTindex);
  		Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
  
! 		if (rel->part_scheme)
  		{
- 			AttrNumber	attno;
- 
  			/*
! 			 * We need attr_needed data for building targetlist of a join
! 			 * relation representing join between matching partitions for
! 			 * partitionwise join. A given attribute of a child will be needed
! 			 * in the same highest joinrel where the corresponding attribute
! 			 * of parent is needed. Hence it suffices to use the same Relids
! 			 * set for parent and child.
  			 */
! 			for (attno = rel->min_attr; attno <= rel->max_attr; attno++)
! 			{
! 				int			index = attno - rel->min_attr;
! 				Relids		attr_needed = rel->attr_needed[index];
! 
! 				/* System attributes do not need translation. */
! 				if (attno <= 0)
! 				{
! 					Assert(rel->min_attr == childrel->min_attr);
! 					childrel->attr_needed[index] = attr_needed;
! 				}
! 				else
! 				{
! 					Var		   *var = list_nth_node(Var,
! 													appinfo->translated_vars,
! 													attno - 1);
! 					int			child_index;
! 
! 					/*
! 					 * Ignore any column dropped from the parent.
! 					 * Corresponding Var won't have any translation. It won't
! 					 * have attr_needed information, since it can not be
! 					 * referenced in the query.
! 					 */
! 					if (var == NULL)
! 					{
! 						Assert(attr_needed == NULL);
! 						continue;
! 					}
! 
! 					child_index = var->varattno - childrel->min_attr;
! 					childrel->attr_needed[child_index] = attr_needed;
! 				}
! 			}
  		}
  
  		/*
- 		 * Copy/Modify targetlist. Even if this child is deemed empty, we need
- 		 * its targetlist in case it falls on nullable side in a child-join
- 		 * because of partitionwise join.
- 		 *
- 		 * NB: the resulting childrel->reltarget->exprs may contain arbitrary
- 		 * expressions, which otherwise would not occur in a rel's targetlist.
- 		 * Code that might be looking at an appendrel child must cope with
- 		 * such.  (Normally, a rel's targetlist would only include Vars and
- 		 * PlaceHolderVars.)  XXX we do not bother to update the cost or width
- 		 * fields of childrel->reltarget; not clear if that would be useful.
- 		 */
- 		childrel->reltarget->exprs = (List *)
- 			adjust_appendrel_attrs(root,
- 								   (Node *) rel->reltarget->exprs,
- 								   1, &appinfo);
- 
- 		/*
  		 * We have to make child entries in the EquivalenceClass data
  		 * structures as well.  This is needed either if the parent
  		 * participates in some eclass joins (because we will want to consider
--- 956,996 ----
  		childrel = find_base_rel(root, childRTindex);
  		Assert(childrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
  
! 		/*
! 		 * We have to copy the parent's targetlist to the child, with
! 		 * appropriate substitution of variables.
! 		 *
! 		 * We need special handling for partitioned inheritance cases if
! 		 * partitionwise join technique is enabled: if the entry in the parent
! 		 * rel's targetlist is a whole-row Var for the parent rel, map it into
! 		 * a whole-row Var for the child rel (we convert the tuple layout back
! 		 * to the parent's rowtype at plan creation time); otherwise, map
! 		 * expressions in the targetlist with adjust_appendrel_attrs.  Note:
! 		 * even if the child is deemed empty, we need the targetlist for it in
! 		 * case it falls on nullable side in a child-join.
! 		 */
! 		if (enable_partitionwise_join && rel->top_parent_is_partitioned)
! 		{
! 			build_childrel_tlist(root, rel, childrel, 1, &appinfo);
! 		}
! 		else
  		{
  			/*
! 			 * NB: the resulting childrel->reltarget->exprs may contain
! 			 * arbitrary expressions, which otherwise would not occur in a
! 			 * rel's targetlist.  Code that might be looking at an appendrel
! 			 * child must cope with such.  (Normally, a rel's targetlist would
! 			 * only include Vars and PlaceHolderVars.)  XXX we do not bother
! 			 * to update the cost or width fields of childrel->reltarget; not
! 			 * clear if that would be useful.
  			 */
! 			childrel->reltarget->exprs = (List *)
! 				adjust_appendrel_attrs(root,
! 									   (Node *) rel->reltarget->exprs,
! 									   1, &appinfo);
  		}
  
  		/*
  		 * We have to make child entries in the EquivalenceClass data
  		 * structures as well.  This is needed either if the parent
  		 * participates in some eclass joins (because we will want to consider
***************
*** 1181,1186 **** set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
--- 1150,1163 ----
  								   1, &appinfo);
  
  		/*
+ 		 * Note: we could compute appropriate attr_needed data for the child's
+ 		 * variables, by transforming the parent's attr_needed through the
+ 		 * translated_vars mapping.  However, currently there's no need
+ 		 * because attr_needed is only examined for base relations not
+ 		 * otherrels.  So we just leave the child's attr_needed empty.
+ 		 */
+ 
+ 		/*
  		 * If parallelism is allowable for this query in general, see whether
  		 * it's allowable for this childrel in particular.  But if we've
  		 * already decided the appendrel is not parallel-safe as a whole,
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 29,34 ****
--- 29,35 ----
  #include "nodes/nodeFuncs.h"
  #include "optimizer/clauses.h"
  #include "optimizer/cost.h"
+ #include "optimizer/pathnode.h"
  #include "optimizer/paths.h"
  #include "optimizer/placeholder.h"
  #include "optimizer/plancat.h"
***************
*** 171,176 **** static void copy_generic_path_info(Plan *dest, Path *src);
--- 172,178 ----
  static void copy_plan_costsize(Plan *dest, Plan *src);
  static void label_sort_with_costsize(PlannerInfo *root, Sort *plan,
  						 double limit_tuples);
+ static void adjust_subplan_tlist(PlannerInfo *root, RelOptInfo *rel, Plan *subplan);
  static SeqScan *make_seqscan(List *qptlist, List *qpqual, Index scanrelid);
  static SampleScan *make_samplescan(List *qptlist, List *qpqual, Index scanrelid,
  				TableSampleClause *tsc);
***************
*** 1074,1079 **** create_append_plan(PlannerInfo *root, AppendPath *best_path)
--- 1076,1084 ----
  		/* Must insist that all children return the same tlist */
  		subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
  
+ 		/* Adjust the plan's targetlist (if necessary) */
+ 		adjust_subplan_tlist(root, subpath->parent, subplan);
+ 
  		subplans = lappend(subplans, subplan);
  	}
  
***************
*** 1185,1190 **** create_merge_append_plan(PlannerInfo *root, MergeAppendPath *best_path)
--- 1190,1198 ----
  		/* Must insist that all children return the same tlist */
  		subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
  
+ 		/* Adjust the plan's targetlist (if necessary) */
+ 		adjust_subplan_tlist(root, subpath->parent, subplan);
+ 
  		/* Compute sort column info, and adjust subplan's tlist as needed */
  		subplan = prepare_sort_from_pathkeys(subplan, pathkeys,
  											 subpath->parent->relids,
***************
*** 5074,5079 **** bitmap_subplan_mark_shared(Plan *plan)
--- 5082,5144 ----
  		elog(ERROR, "unrecognized node type: %d", nodeTag(plan));
  }
  
+ /*
+  * adjust_subplan_tlist
+  *	  If the child plan's targetlist has a whole-row Var as an entry of the
+  *	  list, add a ConvertRowtypeExpr above the Var to convert the tuple layout
+  *	  back to the top parent's rowtype.
+  */
+ static void
+ adjust_subplan_tlist(PlannerInfo *root, RelOptInfo *rel, Plan *subplan)
+ {
+ 	List	   *newtlist;
+ 	ListCell   *l;
+ 
+ 	/* No work if the given rel's tlist doesn't have whole-row Vars */
+ 	if (!rel->has_child_wholerow)
+ 		return;
+ 
+ 	/* The given rel should be a scan or join relation */
+ 	Assert(rel->reloptkind == RELOPT_OTHER_MEMBER_REL ||
+ 		   rel->reloptkind == RELOPT_OTHER_JOINREL);
+ 
+ 	/* No work if the child plan is an Append or MergeAppend */
+ 	if (IsA(subplan, Append) || IsA(subplan, MergeAppend))
+ 		return;
+ 
+ 	/* The child plan should be able to do projection */
+ 	Assert(is_projection_capable_plan(subplan));
+ 
+ 	newtlist = NIL;
+ 	foreach(l, subplan->targetlist)
+ 	{
+ 		TargetEntry *tle = (TargetEntry *) lfirst(l);
+ 		Var		   *var = (Var *) tle->expr;
+ 
+ 		if (IsA(var, Var) && var->varattno == 0)
+ 		{
+ 			RelOptInfo *partrel = find_base_rel(root, var->varno);
+ 			ConvertRowtypeExpr *cre = makeNode(ConvertRowtypeExpr);
+ 
+ 			/* The rel should be a partition */
+ 			Assert(partrel->reloptkind == RELOPT_OTHER_MEMBER_REL);
+ 
+ 			/* The top parent rel should be a partitioned table */
+ 			Assert(partrel->top_parent_is_partitioned);
+ 
+ 			cre->arg = (Expr *) var;
+ 			cre->resulttype = partrel->top_parent_rowtype;
+ 			cre->convertformat = COERCE_IMPLICIT_CAST;
+ 			cre->location = -1;
+ 
+ 			tle = flatCopyTargetEntry(tle);
+ 			tle->expr = (Expr *) cre;
+ 		}
+ 		newtlist = lappend(newtlist, tle);
+ 	}
+ 	subplan->targetlist = newtlist;
+ }
+ 
  /*****************************************************************************
   *
   *	PLAN NODE BUILDING ROUTINES
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 41,49 **** typedef struct
  	int			num_vars;		/* number of plain Var tlist entries */
  	bool		has_ph_vars;	/* are there PlaceHolderVar entries? */
  	bool		has_non_vars;	/* are there other entries? */
- 	bool		has_conv_whole_rows;	/* are there ConvertRowtypeExpr
- 										 * entries encapsulating a whole-row
- 										 * Var? */
  	tlist_vinfo vars[FLEXIBLE_ARRAY_MEMBER];	/* has num_vars entries */
  } indexed_tlist;
  
--- 41,46 ----
***************
*** 143,149 **** static List *set_returning_clause_references(PlannerInfo *root,
  								int rtoffset);
  static bool extract_query_dependencies_walker(Node *node,
  								  PlannerInfo *context);
- static bool is_converted_whole_row_reference(Node *node);
  
  /*****************************************************************************
   *
--- 140,145 ----
***************
*** 1997,2003 **** build_tlist_index(List *tlist)
  	itlist->tlist = tlist;
  	itlist->has_ph_vars = false;
  	itlist->has_non_vars = false;
- 	itlist->has_conv_whole_rows = false;
  
  	/* Find the Vars and fill in the index array */
  	vinfo = itlist->vars;
--- 1993,1998 ----
***************
*** 2016,2023 **** build_tlist_index(List *tlist)
  		}
  		else if (tle->expr && IsA(tle->expr, PlaceHolderVar))
  			itlist->has_ph_vars = true;
- 		else if (is_converted_whole_row_reference((Node *) tle->expr))
- 			itlist->has_conv_whole_rows = true;
  		else
  			itlist->has_non_vars = true;
  	}
--- 2011,2016 ----
***************
*** 2033,2042 **** build_tlist_index(List *tlist)
   * This is like build_tlist_index, but we only index tlist entries that
   * are Vars belonging to some rel other than the one specified.  We will set
   * has_ph_vars (allowing PlaceHolderVars to be matched), but not has_non_vars
!  * (so nothing other than Vars and PlaceHolderVars can be matched). In case of
!  * DML, where this function will be used, returning lists from child relations
!  * will be appended similar to a simple append relation. That does not require
!  * fixing ConvertRowtypeExpr references. So, those are not considered here.
   */
  static indexed_tlist *
  build_tlist_index_other_vars(List *tlist, Index ignore_rel)
--- 2026,2032 ----
   * This is like build_tlist_index, but we only index tlist entries that
   * are Vars belonging to some rel other than the one specified.  We will set
   * has_ph_vars (allowing PlaceHolderVars to be matched), but not has_non_vars
!  * (so nothing other than Vars and PlaceHolderVars can be matched).
   */
  static indexed_tlist *
  build_tlist_index_other_vars(List *tlist, Index ignore_rel)
***************
*** 2053,2059 **** build_tlist_index_other_vars(List *tlist, Index ignore_rel)
  	itlist->tlist = tlist;
  	itlist->has_ph_vars = false;
  	itlist->has_non_vars = false;
- 	itlist->has_conv_whole_rows = false;
  
  	/* Find the desired Vars and fill in the index array */
  	vinfo = itlist->vars;
--- 2043,2048 ----
***************
*** 2256,2262 **** static Node *
  fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
  {
  	Var		   *newvar;
- 	bool		converted_whole_row;
  
  	if (node == NULL)
  		return NULL;
--- 2245,2250 ----
***************
*** 2326,2337 **** fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
  	}
  	if (IsA(node, Param))
  		return fix_param_node(context->root, (Param *) node);
- 
  	/* Try matching more complex expressions too, if tlists have any */
! 	converted_whole_row = is_converted_whole_row_reference(node);
! 	if (context->outer_itlist &&
! 		(context->outer_itlist->has_non_vars ||
! 		 (context->outer_itlist->has_conv_whole_rows && converted_whole_row)))
  	{
  		newvar = search_indexed_tlist_for_non_var((Expr *) node,
  												  context->outer_itlist,
--- 2314,2321 ----
  	}
  	if (IsA(node, Param))
  		return fix_param_node(context->root, (Param *) node);
  	/* Try matching more complex expressions too, if tlists have any */
! 	if (context->outer_itlist && context->outer_itlist->has_non_vars)
  	{
  		newvar = search_indexed_tlist_for_non_var((Expr *) node,
  												  context->outer_itlist,
***************
*** 2339,2347 **** fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
  		if (newvar)
  			return (Node *) newvar;
  	}
! 	if (context->inner_itlist &&
! 		(context->inner_itlist->has_non_vars ||
! 		 (context->inner_itlist->has_conv_whole_rows && converted_whole_row)))
  	{
  		newvar = search_indexed_tlist_for_non_var((Expr *) node,
  												  context->inner_itlist,
--- 2323,2329 ----
  		if (newvar)
  			return (Node *) newvar;
  	}
! 	if (context->inner_itlist && context->inner_itlist->has_non_vars)
  	{
  		newvar = search_indexed_tlist_for_non_var((Expr *) node,
  												  context->inner_itlist,
***************
*** 2461,2469 **** fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
  		/* If no match, just fall through to process it normally */
  	}
  	/* Try matching more complex expressions too, if tlist has any */
! 	if (context->subplan_itlist->has_non_vars ||
! 		(context->subplan_itlist->has_conv_whole_rows &&
! 		 is_converted_whole_row_reference(node)))
  	{
  		newvar = search_indexed_tlist_for_non_var((Expr *) node,
  												  context->subplan_itlist,
--- 2443,2449 ----
  		/* If no match, just fall through to process it normally */
  	}
  	/* Try matching more complex expressions too, if tlist has any */
! 	if (context->subplan_itlist->has_non_vars)
  	{
  		newvar = search_indexed_tlist_for_non_var((Expr *) node,
  												  context->subplan_itlist,
***************
*** 2670,2702 **** extract_query_dependencies_walker(Node *node, PlannerInfo *context)
  	return expression_tree_walker(node, extract_query_dependencies_walker,
  								  (void *) context);
  }
- 
- /*
-  * is_converted_whole_row_reference
-  *		If the given node is a ConvertRowtypeExpr encapsulating a whole-row
-  *		reference as implicit cast, return true. Otherwise return false.
-  */
- static bool
- is_converted_whole_row_reference(Node *node)
- {
- 	ConvertRowtypeExpr *convexpr;
- 
- 	if (!node || !IsA(node, ConvertRowtypeExpr))
- 		return false;
- 
- 	/* Traverse nested ConvertRowtypeExpr's. */
- 	convexpr = castNode(ConvertRowtypeExpr, node);
- 	while (convexpr->convertformat == COERCE_IMPLICIT_CAST &&
- 		   IsA(convexpr->arg, ConvertRowtypeExpr))
- 		convexpr = castNode(ConvertRowtypeExpr, convexpr->arg);
- 
- 	if (IsA(convexpr->arg, Var))
- 	{
- 		Var		   *var = castNode(Var, convexpr->arg);
- 
- 		if (var->varattno == 0)
- 			return true;
- 	}
- 
- 	return false;
- }
--- 2650,2652 ----
*** a/src/backend/optimizer/util/placeholder.c
--- b/src/backend/optimizer/util/placeholder.c
***************
*** 20,26 ****
  #include "optimizer/pathnode.h"
  #include "optimizer/placeholder.h"
  #include "optimizer/planmain.h"
- #include "optimizer/prep.h"
  #include "optimizer/var.h"
  #include "utils/lsyscache.h"
  
--- 20,25 ----
***************
*** 415,424 **** add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
  	Relids		relids = joinrel->relids;
  	ListCell   *lc;
  
- 	/* This function is called only on the parent relations. */
- 	Assert(!IS_OTHER_REL(joinrel) && !IS_OTHER_REL(outer_rel) &&
- 		   !IS_OTHER_REL(inner_rel));
- 
  	foreach(lc, root->placeholder_list)
  	{
  		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
--- 414,419 ----
***************
*** 464,519 **** add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel,
  		}
  	}
  }
- 
- /*
-  * add_placeholders_to_child_joinrel
-  *		Translate the PHVs in parent's targetlist and add them to the child's
-  *		targetlist. Also adjust the cost
-  */
- void
- add_placeholders_to_child_joinrel(PlannerInfo *root, RelOptInfo *childrel,
- 								  RelOptInfo *parentrel)
- {
- 	ListCell   *lc;
- 	AppendRelInfo **appinfos;
- 	int			nappinfos;
- 
- 	Assert(IS_JOIN_REL(childrel) && IS_JOIN_REL(parentrel));
- 	Assert(IS_OTHER_REL(childrel));
- 
- 	/* Nothing to do if no PHVs. */
- 	if (root->placeholder_list == NIL)
- 		return;
- 
- 	appinfos = find_appinfos_by_relids(root, childrel->relids, &nappinfos);
- 	foreach(lc, parentrel->reltarget->exprs)
- 	{
- 		PlaceHolderVar *phv = lfirst(lc);
- 
- 		if (IsA(phv, PlaceHolderVar))
- 		{
- 			/*
- 			 * In case the placeholder Var refers to any of the parent
- 			 * relations, translate it to refer to the corresponding child.
- 			 */
- 			if (bms_overlap(phv->phrels, parentrel->relids) &&
- 				childrel->reloptkind == RELOPT_OTHER_JOINREL)
- 			{
- 				phv = (PlaceHolderVar *) adjust_appendrel_attrs(root,
- 																(Node *) phv,
- 																nappinfos,
- 																appinfos);
- 			}
- 
- 			childrel->reltarget->exprs = lappend(childrel->reltarget->exprs,
- 												 phv);
- 		}
- 	}
- 
- 	/* Adjust the cost and width of child targetlist. */
- 	childrel->reltarget->cost.startup = parentrel->reltarget->cost.startup;
- 	childrel->reltarget->cost.per_tuple = parentrel->reltarget->cost.per_tuple;
- 	childrel->reltarget->width = parentrel->reltarget->width;
- 
- 	pfree(appinfos);
- }
--- 459,461 ----
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
***************
*** 16,21 ****
--- 16,22 ----
  
  #include <limits.h>
  
+ #include "catalog/pg_class.h"
  #include "miscadmin.h"
  #include "optimizer/clauses.h"
  #include "optimizer/cost.h"
***************
*** 28,33 ****
--- 29,35 ----
  #include "optimizer/tlist.h"
  #include "partitioning/partbounds.h"
  #include "utils/hsearch.h"
+ #include "utils/lsyscache.h"
  
  
  typedef struct JoinHashEntry
***************
*** 57,62 **** static void add_join_rel(PlannerInfo *root, RelOptInfo *joinrel);
--- 59,69 ----
  static void build_joinrel_partition_info(RelOptInfo *joinrel,
  							 RelOptInfo *outer_rel, RelOptInfo *inner_rel,
  							 List *restrictlist, JoinType jointype);
+ static void build_child_join_reltarget(PlannerInfo *root,
+ 						   RelOptInfo *parentrel,
+ 						   RelOptInfo *childrel,
+ 						   int nappinfos,
+ 						   AppendRelInfo **appinfos);
  
  
  /*
***************
*** 151,156 **** build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
--- 158,166 ----
  	rel->baserestrict_min_security = UINT_MAX;
  	rel->joininfo = NIL;
  	rel->has_eclass_joins = false;
+ 	rel->top_parent_is_partitioned = false;
+ 	rel->top_parent_rowtype = InvalidOid;
+ 	rel->has_child_wholerow = false;
  	rel->part_scheme = NULL;
  	rel->nparts = 0;
  	rel->boundinfo = NULL;
***************
*** 165,170 **** build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
--- 175,183 ----
  	 * has top_parent_relids set, it's a direct or an indirect child of the
  	 * top parent indicated by top_parent_relids. By extension this child is
  	 * also an indirect child of that parent.
+ 	 *
+ 	 * Also pass the flag to indicate whether the top parent is a partitioned
+ 	 * table and the OID of the top parent rowtype down the hierarchy.
  	 */
  	if (parent)
  	{
***************
*** 172,181 **** build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
--- 185,205 ----
  			rel->top_parent_relids = parent->top_parent_relids;
  		else
  			rel->top_parent_relids = bms_copy(parent->relids);
+ 
+ 		rel->top_parent_is_partitioned = parent->top_parent_is_partitioned;
+ 		rel->top_parent_rowtype = parent->top_parent_rowtype;
  	}
  	else
+ 	{
  		rel->top_parent_relids = NULL;
  
+ 		if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+ 		{
+ 			rel->top_parent_is_partitioned = true;
+ 			rel->top_parent_rowtype = get_rel_type_id(rte->relid);
+ 		}
+ 	}
+ 
  	/* Check type of rtable entry */
  	switch (rte->rtekind)
  	{
***************
*** 565,571 **** build_join_rel(PlannerInfo *root,
--- 589,598 ----
  	joinrel->baserestrict_min_security = UINT_MAX;
  	joinrel->joininfo = NIL;
  	joinrel->has_eclass_joins = false;
+ 	joinrel->top_parent_is_partitioned = false;
+ 	joinrel->top_parent_rowtype = InvalidOid;
  	joinrel->top_parent_relids = NULL;
+ 	joinrel->has_child_wholerow = false;
  	joinrel->part_scheme = NULL;
  	joinrel->nparts = 0;
  	joinrel->boundinfo = NULL;
***************
*** 736,742 **** build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
--- 763,772 ----
  	joinrel->baserestrictcost.per_tuple = 0;
  	joinrel->joininfo = NIL;
  	joinrel->has_eclass_joins = false;
+ 	joinrel->top_parent_is_partitioned = false;
+ 	joinrel->top_parent_rowtype = InvalidOid;
  	joinrel->top_parent_relids = NULL;
+ 	joinrel->has_child_wholerow = false;
  	joinrel->part_scheme = NULL;
  	joinrel->nparts = 0;
  	joinrel->boundinfo = NULL;
***************
*** 752,765 **** build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
  	/* Compute information relevant to foreign relations. */
  	set_foreign_rel_properties(joinrel, outer_rel, inner_rel);
  
! 	/* Build targetlist */
! 	build_joinrel_tlist(root, joinrel, outer_rel);
! 	build_joinrel_tlist(root, joinrel, inner_rel);
! 	/* Add placeholder variables. */
! 	add_placeholders_to_child_joinrel(root, joinrel, parent_joinrel);
  
  	/* Construct joininfo list. */
- 	appinfos = find_appinfos_by_relids(root, joinrel->relids, &nappinfos);
  	joinrel->joininfo = (List *) adjust_appendrel_attrs(root,
  														(Node *) parent_joinrel->joininfo,
  														nappinfos,
--- 782,794 ----
  	/* Compute information relevant to foreign relations. */
  	set_foreign_rel_properties(joinrel, outer_rel, inner_rel);
  
! 	appinfos = find_appinfos_by_relids(root, joinrel->relids, &nappinfos);
! 
! 	/* Set up reltarget struct */
! 	build_child_join_reltarget(root, parent_joinrel, joinrel,
! 							   nappinfos, appinfos);
  
  	/* Construct joininfo list. */
  	joinrel->joininfo = (List *) adjust_appendrel_attrs(root,
  														(Node *) parent_joinrel->joininfo,
  														nappinfos,
***************
*** 858,872 **** static void
  build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
  					RelOptInfo *input_rel)
  {
! 	Relids		relids;
  	ListCell   *vars;
  
- 	/* attrs_needed refers to parent relids and not those of a child. */
- 	if (joinrel->top_parent_relids)
- 		relids = joinrel->top_parent_relids;
- 	else
- 		relids = joinrel->relids;
- 
  	foreach(vars, input_rel->reltarget->exprs)
  	{
  		Var		   *var = (Var *) lfirst(vars);
--- 887,895 ----
  build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
  					RelOptInfo *input_rel)
  {
! 	Relids		relids = joinrel->relids;
  	ListCell   *vars;
  
  	foreach(vars, input_rel->reltarget->exprs)
  	{
  		Var		   *var = (Var *) lfirst(vars);
***************
*** 882,935 **** build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
  
  		/*
  		 * Otherwise, anything in a baserel or joinrel targetlist ought to be
! 		 * a Var. Children of a partitioned table may have ConvertRowtypeExpr
! 		 * translating whole-row Var of a child to that of the parent.
! 		 * Children of an inherited table or subquery child rels can not
! 		 * directly participate in a join, so other kinds of nodes here.
  		 */
! 		if (IsA(var, Var))
! 		{
! 			baserel = find_base_rel(root, var->varno);
! 			ndx = var->varattno - baserel->min_attr;
! 		}
! 		else if (IsA(var, ConvertRowtypeExpr))
! 		{
! 			ConvertRowtypeExpr *child_expr = (ConvertRowtypeExpr *) var;
! 			Var		   *childvar = (Var *) child_expr->arg;
! 
! 			/*
! 			 * Child's whole-row references are converted to look like those
! 			 * of parent using ConvertRowtypeExpr. There can be as many
! 			 * ConvertRowtypeExpr decorations as the depth of partition tree.
! 			 * The argument to the deepest ConvertRowtypeExpr is expected to
! 			 * be a whole-row reference of the child.
! 			 */
! 			while (IsA(childvar, ConvertRowtypeExpr))
! 			{
! 				child_expr = (ConvertRowtypeExpr *) childvar;
! 				childvar = (Var *) child_expr->arg;
! 			}
! 			Assert(IsA(childvar, Var) &&childvar->varattno == 0);
! 
! 			baserel = find_base_rel(root, childvar->varno);
! 			ndx = 0 - baserel->min_attr;
! 		}
! 		else
  			elog(ERROR, "unexpected node type in rel targetlist: %d",
  				 (int) nodeTag(var));
  
  
! 		/* Is the target expression still needed above this joinrel? */
  		if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
  		{
  			/* Yup, add it to the output */
  			joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs, var);
! 
! 			/*
! 			 * Vars have cost zero, so no need to adjust reltarget->cost. Even
! 			 * if it's a ConvertRowtypeExpr, it will be computed only for the
! 			 * base relation, costing nothing for a join.
! 			 */
  			joinrel->reltarget->width += baserel->attr_widths[ndx];
  		}
  	}
--- 905,927 ----
  
  		/*
  		 * Otherwise, anything in a baserel or joinrel targetlist ought to be
! 		 * a Var.  (More general cases can only appear in appendrel child
! 		 * rels, which will never be seen here.)
  		 */
! 		if (!IsA(var, Var))
  			elog(ERROR, "unexpected node type in rel targetlist: %d",
  				 (int) nodeTag(var));
  
+ 		/* Get the Var's original base rel */
+ 		baserel = find_base_rel(root, var->varno);
  
! 		/* Is it still needed above this joinrel? */
! 		ndx = var->varattno - baserel->min_attr;
  		if (bms_nonempty_difference(baserel->attr_needed[ndx], relids))
  		{
  			/* Yup, add it to the output */
  			joinrel->reltarget->exprs = lappend(joinrel->reltarget->exprs, var);
! 			/* Vars have cost zero, so no need to adjust reltarget->cost */
  			joinrel->reltarget->width += baserel->attr_widths[ndx];
  		}
  	}
***************
*** 1178,1183 **** fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind, Relids relids)
--- 1170,1177 ----
  	upperrel->cheapest_unique_path = NULL;
  	upperrel->cheapest_parameterized_paths = NIL;
  
+ 	upperrel->has_child_wholerow = false;
+ 
  	root->upper_rels[kind] = lappend(root->upper_rels[kind], upperrel);
  
  	return upperrel;
***************
*** 1760,1762 **** build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
--- 1754,1834 ----
  		joinrel->nullable_partexprs[cnt] = nullable_partexpr;
  	}
  }
+ 
+ /*
+  * build_childrel_tlist
+  *	  Build a child relation's targetlist from a parent relation.
+  */
+ void
+ build_childrel_tlist(PlannerInfo *root,
+ 					 RelOptInfo *parentrel,
+ 					 RelOptInfo *childrel,
+ 					 int nappinfos,
+ 					 AppendRelInfo **appinfos)
+ {
+ 	ListCell   *lc;
+ 
+ 	foreach(lc, parentrel->reltarget->exprs)
+ 	{
+ 		Node	   *node = (Node *) lfirst(lc);
+ 
+ 		/* Should be a simple Var or PlaceHolderVar ... */
+ 		Assert(IsA(node, Var) || IsA(node, PlaceHolderVar));
+ 
+ 		if (IsA(node, Var) && ((Var *) node)->varattno == 0)
+ 		{
+ 			Var		   *var = (Var *) copyObject(node);
+ 			AppendRelInfo *appinfo = NULL;
+ 			int			cnt;
+ 
+ 			/* Find the AppendRelInfo associated with the parent */
+ 			for (cnt = 0; cnt < nappinfos; cnt++)
+ 			{
+ 				if (var->varno == appinfos[cnt]->parent_relid)
+ 				{
+ 					appinfo = appinfos[cnt];
+ 					break;
+ 				}
+ 			}
+ 
+ 			/* Should have found the entry ... */
+ 			Assert(appinfo);
+ 
+ 			var->varno = appinfo->child_relid;
+ 			var->varnoold = appinfo->child_relid;
+ 			var->vartype = appinfo->child_reltype;
+ 
+ 			childrel->reltarget->exprs =
+ 				lappend(childrel->reltarget->exprs, var);
+ 			childrel->has_child_wholerow = true;
+ 		}
+ 		else
+ 		{
+ 			childrel->reltarget->exprs =
+ 				lappend(childrel->reltarget->exprs,
+ 						adjust_appendrel_attrs(root, node,
+ 											   nappinfos,
+ 											   appinfos));
+ 		}
+ 	}
+ }
+ 
+ /*
+  * build_child_join_reltarget
+  *	  Set up a child-join relation's reltarget from a parent-join relation.
+  */
+ static void
+ build_child_join_reltarget(PlannerInfo *root,
+ 						   RelOptInfo *parentrel,
+ 						   RelOptInfo *childrel,
+ 						   int nappinfos,
+ 						   AppendRelInfo **appinfos)
+ {
+ 	/* Build the targetlist */
+ 	build_childrel_tlist(root, parentrel, childrel, nappinfos, appinfos);
+ 
+ 	/* Set the cost and width fields */
+ 	childrel->reltarget->cost.startup = parentrel->reltarget->cost.startup;
+ 	childrel->reltarget->cost.per_tuple = parentrel->reltarget->cost.per_tuple;
+ 	childrel->reltarget->width = parentrel->reltarget->width;
+ }
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
***************
*** 678,684 **** typedef struct RelOptInfo
--- 678,689 ----
  	bool		has_eclass_joins;	/* T means joininfo is incomplete */
  
  	/* used by "other" relations */
+ 	bool		top_parent_is_partitioned;	/* is topmost parent a partitioned
+ 											 * table? */
+ 	Oid			top_parent_rowtype; /* OID of topmost parent's rowtype */
  	Relids		top_parent_relids;	/* Relids of topmost parents */
+ 	bool		has_child_wholerow;	/* does reltarget->exprs have child
+ 									 * whole-row Vars? */
  
  	/* used for partitioned relations */
  	PartitionScheme part_scheme;	/* Partitioning scheme. */
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
***************
*** 299,303 **** extern RelOptInfo *build_child_join_rel(PlannerInfo *root,
--- 299,308 ----
  					 RelOptInfo *outer_rel, RelOptInfo *inner_rel,
  					 RelOptInfo *parent_joinrel, List *restrictlist,
  					 SpecialJoinInfo *sjinfo, JoinType jointype);
+ extern void build_childrel_tlist(PlannerInfo *root,
+ 					 RelOptInfo *parentrel,
+ 					 RelOptInfo *childrel,
+ 					 int nappinfos,
+ 					 AppendRelInfo **appinfos);
  
  #endif							/* PATHNODE_H */
*** a/src/test/regress/expected/partition_join.out
--- b/src/test/regress/expected/partition_join.out
***************
*** 1042,1047 **** SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT *
--- 1042,1090 ----
   400 |    
  (9 rows)
  
+ -- merge join when expression with whole-row reference needs to be sorted
+ EXPLAIN (COSTS OFF)
+ SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a;
+                                     QUERY PLAN                                     
+ -----------------------------------------------------------------------------------
+  Merge Append
+    Sort Key: t1.a
+    ->  Merge Join
+          Merge Cond: ((t1.a = t2.b) AND (((t1.*)::text) = ((t2.*)::text)))
+          ->  Sort
+                Sort Key: t1.a, ((t1.*)::text)
+                ->  Seq Scan on prt1_p1 t1
+          ->  Sort
+                Sort Key: t2.b, ((t2.*)::text)
+                ->  Seq Scan on prt2_p1 t2
+    ->  Merge Join
+          Merge Cond: ((t1_1.a = t2_1.b) AND (((t1_1.*)::text) = ((t2_1.*)::text)))
+          ->  Sort
+                Sort Key: t1_1.a, ((t1_1.*)::text)
+                ->  Seq Scan on prt1_p2 t1_1
+          ->  Sort
+                Sort Key: t2_1.b, ((t2_1.*)::text)
+                ->  Seq Scan on prt2_p2 t2_1
+    ->  Merge Join
+          Merge Cond: ((t1_2.a = t2_2.b) AND (((t1_2.*)::text) = ((t2_2.*)::text)))
+          ->  Sort
+                Sort Key: t1_2.a, ((t1_2.*)::text)
+                ->  Seq Scan on prt1_p3 t1_2
+          ->  Sort
+                Sort Key: t2_2.b, ((t2_2.*)::text)
+                ->  Seq Scan on prt2_p3 t2_2
+ (26 rows)
+ 
+ SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a;
+  a  | b  
+ ----+----
+   0 |  0
+   6 |  6
+  12 | 12
+  18 | 18
+  24 | 24
+ (5 rows)
+ 
  RESET enable_hashjoin;
  RESET enable_nestloop;
  --
*** a/src/test/regress/sql/partition_join.sql
--- b/src/test/regress/sql/partition_join.sql
***************
*** 160,165 **** EXPLAIN (COSTS OFF)
--- 160,170 ----
  SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b;
  SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT * FROM prt2 WHERE b > 250) t2 ON t1.a = t2.b WHERE t1.b = 0 ORDER BY t1.a, t2.b;
  
+ -- merge join when expression with whole-row reference needs to be sorted
+ EXPLAIN (COSTS OFF)
+ SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a;
+ SELECT t1.a, t2.b FROM prt1 t1, prt2 t2 WHERE t1::text = t2::text AND t1.a = t2.b ORDER BY t1.a;
+ 
  RESET enable_hashjoin;
  RESET enable_nestloop;
  

Reply via email to