*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 8337,8344 **** 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;
--- 8337,8345 ----
  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;
***************
*** 8391,8416 **** 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)
--- 8392,8427 ----
  
  -- 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)
***************
*** 8474,8479 **** SELECT t1.a, t1.phv, t2.b, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE
--- 8485,8520 ----
       |        | 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
***************
*** 2263,2270 **** 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;
--- 2263,2271 ----
  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;
***************
*** 2283,2290 **** 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)
--- 2284,2291 ----
  
  -- 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)
***************
*** 2296,2301 **** EXPLAIN (COSTS OFF)
--- 2297,2307 ----
  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/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2359,2365 **** _outRelOptInfo(StringInfo str, const RelOptInfo *node)
--- 2359,2368 ----
  	WRITE_UINT_FIELD(baserestrict_min_security);
  	WRITE_NODE_FIELD(joininfo);
  	WRITE_BOOL_FIELD(has_eclass_joins);
+ 	WRITE_BOOL_FIELD(consider_partitionwise_join);
+ 	WRITE_OID_FIELD(top_parent_rowtype);
  	WRITE_BITMAPSET_FIELD(top_parent_relids);
+ 	WRITE_BOOL_FIELD(has_target_wrvs);
  	WRITE_NODE_FIELD(partitioned_child_rels);
  }
  
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
***************
*** 910,915 **** set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
--- 910,928 ----
  	}
  
  	/*
+ 	 * If this is a baserel, set the consider_partitionwise_join flag and
+ 	 * check whether the baserel's targetlist contain a whole-row Var.
+ 	 */
+ 	if (rel->reloptkind == RELOPT_BASEREL)
+ 	{
+ 		if (enable_partitionwise_join && rel->part_scheme &&
+ 			!bms_equal(rel->relids, root->all_baserels))
+ 			rel->consider_partitionwise_join = true;
+ 		if (rel->attr_needed[InvalidAttrNumber - rel->min_attr] != NULL)
+ 			rel->has_target_wrvs = true;
+ 	}
+ 
+ 	/*
  	 * Initialize to compute size estimates for whole append relation.
  	 *
  	 * We handle width estimates by weighting the widths of different child
***************
*** 956,1025 **** 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
--- 969,983 ----
  		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.
  		 *
! 		 * Note: when considering partitionwise joining, we need the child's
! 		 * targetlist in case it falls on nullable side in a child join even
! 		 * if the child is deemed empty.
  		 */
! 		build_childrel_tlist(root, rel, childrel, 1, &appinfo);
  
  		/*
  		 * We have to make child entries in the EquivalenceClass data
***************
*** 1181,1186 **** set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
--- 1139,1152 ----
  								   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/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/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
***************
*** 53,58 **** typedef enum
--- 53,62 ----
  static List *translate_sub_tlist(List *tlist, int relid);
  static int	append_total_cost_compare(const void *a, const void *b);
  static int	append_startup_cost_compare(const void *a, const void *b);
+ static List *add_projection_to_subpaths(PlannerInfo *root, RelOptInfo *rel,
+ 						   List *subpaths);
+ static Path *add_projection_to_subpath(PlannerInfo *root, RelOptInfo *rel,
+ 						  Path *subpath);
  static List *reparameterize_pathlist_by_child(PlannerInfo *root,
  								 List *pathlist,
  								 RelOptInfo *child_rel);
***************
*** 1274,1279 **** create_append_path(PlannerInfo *root,
--- 1278,1288 ----
  	pathnode->first_partial_path = list_length(subpaths);
  	pathnode->subpaths = list_concat(subpaths, partial_subpaths);
  
+ 	/* Add a projection step to each subpath if needed. */
+ 	if (rel->consider_partitionwise_join && rel->has_target_wrvs)
+ 		pathnode->subpaths = add_projection_to_subpaths(root, rel,
+ 														pathnode->subpaths);
+ 
  	foreach(l, pathnode->subpaths)
  	{
  		Path	   *subpath = (Path *) lfirst(l);
***************
*** 1366,1371 **** create_merge_append_path(PlannerInfo *root,
--- 1375,1384 ----
  	pathnode->path.parallel_workers = 0;
  	pathnode->path.pathkeys = pathkeys;
  	pathnode->partitioned_rels = list_copy(partitioned_rels);
+ 
+ 	/* Add a projection step to each subpath if needed. */
+ 	if (rel->consider_partitionwise_join && rel->has_target_wrvs)
+ 		subpaths = add_projection_to_subpaths(root, rel, subpaths);
  	pathnode->subpaths = subpaths;
  
  	/*
***************
*** 1429,1434 **** create_merge_append_path(PlannerInfo *root,
--- 1442,1524 ----
  }
  
  /*
+  * add_projection_to_subpaths
+  *	  Add a projection step for rowtype conversion to given subpaths.
+  *
+  * This function adjusts subpaths accumulated for creating a partitionwise
+  * join path, by adding a projection to each subpath to convert the tuple
+  * layouts of whole-row Vars in the targetlist for that subpath back to their
+  * respective parents' rowtypes.  Note that that would be safe, because it
+  * seems unlikely that adding that projection to such a subpath would alter
+  * the parallel safety, the ordering (pathkeys), the parameterization
+  * (required_outer rels), and the property of being the cheapest.
+  */
+ static List *
+ add_projection_to_subpaths(PlannerInfo *root, RelOptInfo *rel, List *subpaths)
+ {
+ 	List	   *result = NIL;
+ 	ListCell   *l;
+ 
+ 	Assert(rel->consider_partitionwise_join && rel->has_target_wrvs);
+ 
+ 	foreach(l, subpaths)
+ 	{
+ 		Path	   *subpath = (Path *) lfirst(l);
+ 
+ 		result = lappend(result,
+ 						 add_projection_to_subpath(root, rel, subpath));
+ 	}
+ 	return result;
+ }
+ 
+ /*
+  * add_projection_to_subpath
+  *	  Add a projection step for rowtype conversion to given subpath.
+  */
+ static Path *
+ add_projection_to_subpath(PlannerInfo *root, RelOptInfo *rel, Path *subpath)
+ {
+ 	PathTarget *target = copy_pathtarget(subpath->pathtarget);
+ 	List	   *new_exprs = NIL;
+ 	ListCell   *parentvars;
+ 	ListCell   *childvars;
+ 
+ 	Assert(subpath->parent->has_target_wrvs);
+ 
+ 	/*
+ 	 * Build a new targetlist for the subpath by adding a conversion node
+ 	 * above each (converted) whole-row Var in the subpath's targetlist to
+ 	 * convert the tuple layout of that Var back to its parent rowtype.
+ 	 *
+ 	 * By construction, subpath's targetlist is 1-to-1 with parent's.
+ 	 */
+ 	forboth(parentvars, rel->reltarget->exprs, childvars, target->exprs)
+ 	{
+ 		Var		   *parentvar = (Var *) lfirst(parentvars);
+ 		Node	   *childvar = (Node *) lfirst(childvars);
+ 
+ 		if (IsA(parentvar, Var) && parentvar->varattno == 0)
+ 		{
+ 			ConvertRowtypeExpr *cre = makeNode(ConvertRowtypeExpr);
+ 
+ 			cre->arg = (Expr *) childvar;
+ 			cre->resulttype = parentvar->vartype;
+ 			cre->convertformat = COERCE_IMPLICIT_CAST;
+ 			cre->location = -1;
+ 			new_exprs = lappend(new_exprs, cre);
+ 		}
+ 		else
+ 			new_exprs = lappend(new_exprs, childvar);
+ 	}
+ 	target->exprs = new_exprs;
+ 
+ 	return (Path *) create_projection_path(root,
+ 										   subpath->parent,
+ 										   subpath,
+ 										   target);
+ }
+ 
+ /*
   * create_result_path
   *	  Creates a path representing a Result-and-nothing-else plan.
   *
***************
*** 2392,2399 **** create_projection_path(PlannerInfo *root,
  	pathnode->path.pathtype = T_Result;
  	pathnode->path.parent = rel;
  	pathnode->path.pathtarget = target;
! 	/* For now, assume we are above any joins, so no parameterization */
! 	pathnode->path.param_info = NULL;
  	pathnode->path.parallel_aware = false;
  	pathnode->path.parallel_safe = rel->consider_parallel &&
  		subpath->parallel_safe &&
--- 2482,2488 ----
  	pathnode->path.pathtype = T_Result;
  	pathnode->path.parent = rel;
  	pathnode->path.pathtarget = target;
! 	pathnode->path.param_info = subpath->param_info;
  	pathnode->path.parallel_aware = false;
  	pathnode->path.parallel_safe = rel->consider_parallel &&
  		subpath->parallel_safe &&
*** 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
***************
*** 37,43 **** typedef struct JoinHashEntry
  } JoinHashEntry;
  
  static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! 					RelOptInfo *input_rel);
  static List *build_joinrel_restrictlist(PlannerInfo *root,
  						   RelOptInfo *joinrel,
  						   RelOptInfo *outer_rel,
--- 39,45 ----
  } JoinHashEntry;
  
  static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! 					RelOptInfo *input_rel, bool *has_wholerow);
  static List *build_joinrel_restrictlist(PlannerInfo *root,
  						   RelOptInfo *joinrel,
  						   RelOptInfo *outer_rel,
***************
*** 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);
  
  
  /*
***************
*** 188,193 **** build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
--- 195,204 ----
  	rel->baserestrict_min_security = UINT_MAX;
  	rel->joininfo = NIL;
  	rel->has_eclass_joins = false;
+ 	rel->consider_partitionwise_join = false; /* might get changed later */
+ 	rel->top_parent_rowtype = InvalidOid;
+ 	rel->top_parent_relids = NULL;
+ 	rel->has_target_wrvs = false;
  	rel->part_scheme = NULL;
  	rel->nparts = 0;
  	rel->boundinfo = NULL;
***************
*** 201,207 **** build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
  	 * Pass top parent's relids down the inheritance hierarchy. If the parent
  	 * 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.
  	 */
  	if (parent)
  	{
--- 212,219 ----
  	 * Pass top parent's relids down the inheritance hierarchy. If the parent
  	 * 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.  Likeweise for the OID of that
! 	 * parent's rowtype.
  	 */
  	if (parent)
  	{
***************
*** 209,218 **** build_simple_rel(PlannerInfo *root, int relid, RelOptInfo *parent)
--- 221,237 ----
  			rel->top_parent_relids = parent->top_parent_relids;
  		else
  			rel->top_parent_relids = bms_copy(parent->relids);
+ 
+ 		rel->top_parent_rowtype = parent->top_parent_rowtype;
  	}
  	else
+ 	{
  		rel->top_parent_relids = NULL;
  
+ 		if (rte->relkind == RELKIND_PARTITIONED_TABLE)
+ 			rel->top_parent_rowtype = get_rel_type_id(rte->relid);
+ 	}
+ 
  	/* Check type of rtable entry */
  	switch (rte->rtekind)
  	{
***************
*** 525,530 **** build_join_rel(PlannerInfo *root,
--- 544,550 ----
  {
  	RelOptInfo *joinrel;
  	List	   *restrictlist;
+ 	bool		has_wholerow;
  
  	/* This function should be used only for join between parents. */
  	Assert(!IS_OTHER_REL(outer_rel) && !IS_OTHER_REL(inner_rel));
***************
*** 602,608 **** build_join_rel(PlannerInfo *root,
--- 622,631 ----
  	joinrel->baserestrict_min_security = UINT_MAX;
  	joinrel->joininfo = NIL;
  	joinrel->has_eclass_joins = false;
+ 	joinrel->consider_partitionwise_join = false; /* might get changed later */
+ 	joinrel->top_parent_rowtype = InvalidOid;
  	joinrel->top_parent_relids = NULL;
+ 	joinrel->has_target_wrvs = false;
  	joinrel->part_scheme = NULL;
  	joinrel->nparts = 0;
  	joinrel->boundinfo = NULL;
***************
*** 623,630 **** build_join_rel(PlannerInfo *root,
  	 * and inner rels we first try to build it from.  But the contents should
  	 * be the same regardless.
  	 */
! 	build_joinrel_tlist(root, joinrel, outer_rel);
! 	build_joinrel_tlist(root, joinrel, inner_rel);
  	add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
  
  	/*
--- 646,654 ----
  	 * and inner rels we first try to build it from.  But the contents should
  	 * be the same regardless.
  	 */
! 	has_wholerow = false;
! 	build_joinrel_tlist(root, joinrel, outer_rel, &has_wholerow);
! 	build_joinrel_tlist(root, joinrel, inner_rel, &has_wholerow);
  	add_placeholders_to_joinrel(root, joinrel, outer_rel, inner_rel);
  
  	/*
***************
*** 660,665 **** build_join_rel(PlannerInfo *root,
--- 684,698 ----
  	build_joinrel_partition_info(joinrel, outer_rel, inner_rel, restrictlist,
  								 sjinfo->jointype);
  
+ 	/* Set the consider_partitionwise_join flag. */
+ 	if (IS_PARTITIONED_REL(joinrel) &&
+ 		!bms_equal(joinrel->relids, root->all_baserels))
+ 		joinrel->consider_partitionwise_join = true;
+ 
+ 	/* Check whether the joinrel's targetlist contain whole-row Vars. */
+ 	if (has_wholerow)
+ 		joinrel->has_target_wrvs = true;
+ 
  	/*
  	 * Set estimates of the joinrel's size.
  	 */
***************
*** 773,779 **** build_child_join_rel(PlannerInfo *root, RelOptInfo *outer_rel,
--- 806,815 ----
  	joinrel->baserestrictcost.per_tuple = 0;
  	joinrel->joininfo = NIL;
  	joinrel->has_eclass_joins = false;
+ 	joinrel->consider_partitionwise_join = false; /* might get changed later */
+ 	joinrel->top_parent_rowtype = InvalidOid;
  	joinrel->top_parent_relids = NULL;
+ 	joinrel->has_target_wrvs = false;
  	joinrel->part_scheme = NULL;
  	joinrel->nparts = 0;
  	joinrel->boundinfo = NULL;
***************
*** 789,802 **** 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,
--- 825,837 ----
  	/* 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,
***************
*** 889,909 **** min_join_parameterization(PlannerInfo *root,
   * Vars from the specified input rel's tlist to the join rel's tlist.
   *
   * We also compute the expected width of the join's output, making use
!  * of data that was cached at the baserel level by set_rel_width().
   */
  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);
--- 924,940 ----
   * Vars from the specified input rel's tlist to the join rel's tlist.
   *
   * We also compute the expected width of the join's output, making use
!  * of data that was cached at the baserel level by set_rel_width(), and
!  * check whether the join's output contains whole-row Vars for later use
!  * in partitionwise joins.
   */
  static void
  build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
! 					RelOptInfo *input_rel, bool *has_wholerow)
  {
! 	Relids		relids = joinrel->relids;
  	ListCell   *vars;
  
  	foreach(vars, input_rel->reltarget->exprs)
  	{
  		Var		   *var = (Var *) lfirst(vars);
***************
*** 919,973 **** 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];
  		}
  	}
  }
--- 950,977 ----
  
  		/*
  		 * 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];
+ 
+ 			/* If it's a whole-row Var, set the flag */
+ 			if (var->varattno == InvalidAttrNumber)
+ 				*has_wholerow = true;
  		}
  	}
  }
***************
*** 1215,1220 **** fetch_upper_rel(PlannerInfo *root, UpperRelationKind kind, Relids relids)
--- 1219,1227 ----
  	upperrel->cheapest_unique_path = NULL;
  	upperrel->cheapest_parameterized_paths = NIL;
  
+ 	upperrel->consider_partitionwise_join = false;
+ 	upperrel->has_target_wrvs = false;
+ 
  	root->upper_rels[kind] = lappend(root->upper_rels[kind], upperrel);
  
  	return upperrel;
***************
*** 1768,1770 **** build_joinrel_partition_info(RelOptInfo *joinrel, RelOptInfo *outer_rel,
--- 1775,1893 ----
  		joinrel->nullable_partexprs[cnt] = nullable_partexpr;
  	}
  }
+ 
+ /*
+  * build_childrel_tlist
+  *	  Build the child's targetlist from the parent's with
+  *	  adjust_appendrel_attrs
+  */
+ void
+ build_childrel_tlist(PlannerInfo *root,
+ 					 RelOptInfo *parentrel,
+ 					 RelOptInfo *childrel,
+ 					 int nappinfos,
+ 					 AppendRelInfo **appinfos)
+ {
+ 	ListCell   *lc;
+ 
+ 	/*
+ 	 * If we don't consider partitionwise join paths, or if the parent's
+ 	 * targetlist doesn't contain any whole-row Vars, just apply
+ 	 * adjust_appendrel_attrs to the parent's targetlist.
+ 	 *
+ 	 * 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.
+ 	 */
+ 	if (!parentrel->consider_partitionwise_join ||
+ 		!parentrel->has_target_wrvs)
+ 	{
+ 		childrel->reltarget->exprs = (List *)
+ 			adjust_appendrel_attrs(root,
+ 								   (Node *) parentrel->reltarget->exprs,
+ 								   nappinfos, appinfos);
+ 		return;
+ 	}
+ 
+ 	/* The parent should be partitioned */
+ 	Assert(parentrel->part_scheme);
+ 
+ 	/*
+ 	 * We build the child's targetlist not to contain ConvertRowtypeExprs:
+ 	 * if the entry in the parent's targetlist is a whole-row Var for one of
+ 	 * the parent's rels, create a whole-row Var for the corresponding child
+ 	 * rel, instead of applying adjust_appendrel_attrs to the Var, and add it
+ 	 * to the child's targetlist; otherwise apply adjust_appendrel_attrs to
+ 	 * the entry in that targetlist.
+ 	 *
+ 	 * We also create an output that should be emitted by the child's rel for
+ 	 * the topmost parent's rel.
+ 	 */
+ 	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_target_wrvs = true;
+ 		}
+ 		else
+ 		{
+ 			childrel->reltarget->exprs =
+ 				lappend(childrel->reltarget->exprs,
+ 						adjust_appendrel_attrs(root, node,
+ 											   nappinfos,
+ 											   appinfos));
+ 		}
+ 	}
+ 	Assert(childrel->has_target_wrvs);
+ 
+ 	if (childrel->part_scheme)
+ 		childrel->consider_partitionwise_join = true;
+ }
+ 
+ /*
+  * 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
***************
*** 687,694 **** typedef struct RelOptInfo
  								 * involving this rel */
  	bool		has_eclass_joins;	/* T means joininfo is incomplete */
  
! 	/* used by "other" relations */
! 	Relids		top_parent_relids;	/* Relids of topmost parents */
  
  	/* used for partitioned relations */
  	PartitionScheme part_scheme;	/* Partitioning scheme. */
--- 687,701 ----
  								 * involving this rel */
  	bool		has_eclass_joins;	/* T means joininfo is incomplete */
  
! 	/* per-partitioned-relation planner control flags */
! 	bool		consider_partitionwise_join;	/* consider partitionwise
! 												 * join paths? */
! 
! 	/* used by partitionwise joins: */
! 	Oid			top_parent_rowtype; /* OID of topmost parent's rowtype */
! 	Relids		top_parent_relids;	/* Relids of topmost parent(s) */
! 	bool		has_target_wrvs;	/* T means reltarget->exprs contains
! 									 * whole-row Vars */
  
  	/* used for partitioned relations */
  	PartitionScheme part_scheme;	/* Partitioning scheme. */
*** a/src/include/optimizer/pathnode.h
--- b/src/include/optimizer/pathnode.h
***************
*** 297,301 **** extern RelOptInfo *build_child_join_rel(PlannerInfo *root,
--- 297,306 ----
  					 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
***************
*** 476,481 **** SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL
--- 476,554 ----
   550 |     | 
  (12 rows)
  
+ -- Check whole-row-Var handling in situations where the partitioned rel is not
+ -- the topmost scan/join relation
+ CREATE TABLE nonprt (a int, x text default 'testing');
+ INSERT INTO nonprt (a) SELECT i FROM generate_series(0, 599) i WHERE i % 6 = 0;
+ ANALYZE nonprt;
+ EXPLAIN (COSTS OFF)
+ SELECT t1, t3 FROM prt1 t1, nonprt t3 WHERE t1.a = t3.a AND t1.b = 0 ORDER BY t1.a;
+                     QUERY PLAN                    
+ --------------------------------------------------
+  Sort
+    Sort Key: t1.a
+    ->  Hash Join
+          Hash Cond: (t3.a = t1.a)
+          ->  Seq Scan on nonprt t3
+          ->  Hash
+                ->  Append
+                      ->  Seq Scan on prt1_p1 t1
+                            Filter: (b = 0)
+                      ->  Seq Scan on prt1_p2 t1_1
+                            Filter: (b = 0)
+                      ->  Seq Scan on prt1_p3 t1_2
+                            Filter: (b = 0)
+ (13 rows)
+ 
+ SELECT t1, t3 FROM prt1 t1, nonprt t3 WHERE t1.a = t3.a AND t1.b = 0 ORDER BY t1.a;
+       t1      |      t3       
+ --------------+---------------
+  (0,0,0000)   | (0,testing)
+  (150,0,0150) | (150,testing)
+  (300,0,0300) | (300,testing)
+  (450,0,0450) | (450,testing)
+ (4 rows)
+ 
+ EXPLAIN (COSTS OFF)
+ SELECT t1, t2, t3 FROM prt1 t1, prt2 t2, nonprt t3 WHERE t1.a = t2.b AND t1.a = t3.a AND t1.b = 0 ORDER BY t1.a;
+                           QUERY PLAN                          
+ --------------------------------------------------------------
+  Sort
+    Sort Key: t1.a
+    ->  Hash Join
+          Hash Cond: (t3.a = t1.a)
+          ->  Seq Scan on nonprt t3
+          ->  Hash
+                ->  Append
+                      ->  Hash Join
+                            Hash Cond: (t2.b = t1.a)
+                            ->  Seq Scan on prt2_p1 t2
+                            ->  Hash
+                                  ->  Seq Scan on prt1_p1 t1
+                                        Filter: (b = 0)
+                      ->  Hash Join
+                            Hash Cond: (t2_1.b = t1_1.a)
+                            ->  Seq Scan on prt2_p2 t2_1
+                            ->  Hash
+                                  ->  Seq Scan on prt1_p2 t1_1
+                                        Filter: (b = 0)
+                      ->  Hash Join
+                            Hash Cond: (t2_2.b = t1_2.a)
+                            ->  Seq Scan on prt2_p3 t2_2
+                            ->  Hash
+                                  ->  Seq Scan on prt1_p3 t1_2
+                                        Filter: (b = 0)
+ (25 rows)
+ 
+ SELECT t1, t2, t3 FROM prt1 t1, prt2 t2, nonprt t3 WHERE t1.a = t2.b AND t1.a = t3.a AND t1.b = 0 ORDER BY t1.a;
+       t1      |      t2      |      t3       
+ --------------+--------------+---------------
+  (0,0,0000)   | (0,0,0000)   | (0,testing)
+  (150,0,0150) | (0,150,0150) | (150,testing)
+  (300,0,0300) | (0,300,0300) | (300,testing)
+  (450,0,0450) | (0,450,0450) | (450,testing)
+ (4 rows)
+ 
  --
  -- partitioned by expression
  --
***************
*** 1042,1047 **** SELECT t1.a, t2.b FROM (SELECT * FROM prt1 WHERE a < 450) t1 LEFT JOIN (SELECT *
--- 1115,1163 ----
   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
***************
*** 89,94 **** SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL
--- 89,108 ----
  			  (SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.a) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
  			  ON t1.c = ss.t2c WHERE (t1.b + coalesce(ss.t2b, 0)) = 0 ORDER BY t1.a;
  
+ -- Check whole-row-Var handling in situations where the partitioned rel is not
+ -- the topmost scan/join relation
+ CREATE TABLE nonprt (a int, x text default 'testing');
+ INSERT INTO nonprt (a) SELECT i FROM generate_series(0, 599) i WHERE i % 6 = 0;
+ ANALYZE nonprt;
+ 
+ EXPLAIN (COSTS OFF)
+ SELECT t1, t3 FROM prt1 t1, nonprt t3 WHERE t1.a = t3.a AND t1.b = 0 ORDER BY t1.a;
+ SELECT t1, t3 FROM prt1 t1, nonprt t3 WHERE t1.a = t3.a AND t1.b = 0 ORDER BY t1.a;
+ 
+ EXPLAIN (COSTS OFF)
+ SELECT t1, t2, t3 FROM prt1 t1, prt2 t2, nonprt t3 WHERE t1.a = t2.b AND t1.a = t3.a AND t1.b = 0 ORDER BY t1.a;
+ SELECT t1, t2, t3 FROM prt1 t1, prt2 t2, nonprt t3 WHERE t1.a = t2.b AND t1.a = t3.a AND t1.b = 0 ORDER BY t1.a;
+ 
  --
  -- partitioned by expression
  --
***************
*** 160,165 **** EXPLAIN (COSTS OFF)
--- 174,184 ----
  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;
  
