diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 1c93e0c5ac30aa70694308d8bc799c01b0e88360..e911e067948674afd78bac753d229ac08f22eeae 100644
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
*************** postgresGetForeignPaths(PlannerInfo *roo
*** 592,599 ****
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(lc);
  
  		if (bms_is_member(baserel->relid, ljinfo->lateral_lhs))
! 			lateral_referencers = bms_add_member(lateral_referencers,
! 												 ljinfo->lateral_rhs);
  	}
  
  	/* Scan the rel's join clauses */
--- 592,599 ----
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(lc);
  
  		if (bms_is_member(baserel->relid, ljinfo->lateral_lhs))
! 			lateral_referencers = bms_add_members(lateral_referencers,
! 												  ljinfo->lateral_rhs);
  	}
  
  	/* Scan the rel's join clauses */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index b5b8d63cff79247ed1340b228fd5a10887a5a1bf..705f14a3884f85d132e5b233027ceaaf1c4c7bcc 100644
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyLateralJoinInfo(const LateralJoinIn
*** 1917,1924 ****
  {
  	LateralJoinInfo *newnode = makeNode(LateralJoinInfo);
  
- 	COPY_SCALAR_FIELD(lateral_rhs);
  	COPY_BITMAPSET_FIELD(lateral_lhs);
  
  	return newnode;
  }
--- 1917,1924 ----
  {
  	LateralJoinInfo *newnode = makeNode(LateralJoinInfo);
  
  	COPY_BITMAPSET_FIELD(lateral_lhs);
+ 	COPY_BITMAPSET_FIELD(lateral_rhs);
  
  	return newnode;
  }
*************** _copyPlaceHolderInfo(const PlaceHolderIn
*** 1952,1957 ****
--- 1952,1958 ----
  	COPY_SCALAR_FIELD(phid);
  	COPY_NODE_FIELD(ph_var);
  	COPY_BITMAPSET_FIELD(ph_eval_at);
+ 	COPY_BITMAPSET_FIELD(ph_lateral);
  	COPY_BITMAPSET_FIELD(ph_needed);
  	COPY_BITMAPSET_FIELD(ph_may_need);
  	COPY_SCALAR_FIELD(ph_width);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 3f96595e8eb041bcebe38639e075db7eb8a77fd2..41b75044a001f8affa557984fc672b7b310f1934 100644
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalPlaceHolderVar(const PlaceHolderVa
*** 761,775 ****
  	/*
  	 * We intentionally do not compare phexpr.	Two PlaceHolderVars with the
  	 * same ID and levelsup should be considered equal even if the contained
! 	 * expressions have managed to mutate to different states.	One way in
! 	 * which that can happen is that initplan sublinks would get replaced by
! 	 * differently-numbered Params when sublink folding is done.  (The end
! 	 * result of such a situation would be some unreferenced initplans, which
! 	 * is annoying but not really a problem.)
  	 *
  	 * COMPARE_NODE_FIELD(phexpr);
  	 */
- 	COMPARE_BITMAPSET_FIELD(phrels);
  	COMPARE_SCALAR_FIELD(phid);
  	COMPARE_SCALAR_FIELD(phlevelsup);
  
--- 761,779 ----
  	/*
  	 * We intentionally do not compare phexpr.	Two PlaceHolderVars with the
  	 * same ID and levelsup should be considered equal even if the contained
! 	 * expressions have managed to mutate to different states.	This will
! 	 * happen during final plan construction when there are nested PHVs, since
! 	 * the inner PHV will get replaced by a Param in some copies of the outer
! 	 * PHV.  Another way in which it can happen is that initplan sublinks
! 	 * could get replaced by differently-numbered Params when sublink folding
! 	 * is done.  (The end result of such a situation would be some
! 	 * unreferenced initplans, which is annoying but not really a problem.) On
! 	 * the same reasoning, there is no need to examine phrels.
  	 *
  	 * COMPARE_NODE_FIELD(phexpr);
+ 	 *
+ 	 * COMPARE_BITMAPSET_FIELD(phrels);
  	 */
  	COMPARE_SCALAR_FIELD(phid);
  	COMPARE_SCALAR_FIELD(phlevelsup);
  
*************** _equalSpecialJoinInfo(const SpecialJoinI
*** 794,801 ****
  static bool
  _equalLateralJoinInfo(const LateralJoinInfo *a, const LateralJoinInfo *b)
  {
- 	COMPARE_SCALAR_FIELD(lateral_rhs);
  	COMPARE_BITMAPSET_FIELD(lateral_lhs);
  
  	return true;
  }
--- 798,805 ----
  static bool
  _equalLateralJoinInfo(const LateralJoinInfo *a, const LateralJoinInfo *b)
  {
  	COMPARE_BITMAPSET_FIELD(lateral_lhs);
+ 	COMPARE_BITMAPSET_FIELD(lateral_rhs);
  
  	return true;
  }
*************** static bool
*** 817,824 ****
  _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
  {
  	COMPARE_SCALAR_FIELD(phid);
! 	COMPARE_NODE_FIELD(ph_var);
  	COMPARE_BITMAPSET_FIELD(ph_eval_at);
  	COMPARE_BITMAPSET_FIELD(ph_needed);
  	COMPARE_BITMAPSET_FIELD(ph_may_need);
  	COMPARE_SCALAR_FIELD(ph_width);
--- 821,829 ----
  _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b)
  {
  	COMPARE_SCALAR_FIELD(phid);
! 	COMPARE_NODE_FIELD(ph_var); /* should be redundant */
  	COMPARE_BITMAPSET_FIELD(ph_eval_at);
+ 	COMPARE_BITMAPSET_FIELD(ph_lateral);
  	COMPARE_BITMAPSET_FIELD(ph_needed);
  	COMPARE_BITMAPSET_FIELD(ph_may_need);
  	COMPARE_SCALAR_FIELD(ph_width);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b2183f42137bd474925df18d96349ac99d5f0a80..752d2ca3444cd1a9e24121e4e8559d4acbc211c2 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outLateralJoinInfo(StringInfo str, cons
*** 1909,1916 ****
  {
  	WRITE_NODE_TYPE("LATERALJOININFO");
  
- 	WRITE_UINT_FIELD(lateral_rhs);
  	WRITE_BITMAPSET_FIELD(lateral_lhs);
  }
  
  static void
--- 1909,1916 ----
  {
  	WRITE_NODE_TYPE("LATERALJOININFO");
  
  	WRITE_BITMAPSET_FIELD(lateral_lhs);
+ 	WRITE_BITMAPSET_FIELD(lateral_rhs);
  }
  
  static void
*************** _outPlaceHolderInfo(StringInfo str, cons
*** 1934,1939 ****
--- 1934,1940 ----
  	WRITE_UINT_FIELD(phid);
  	WRITE_NODE_FIELD(ph_var);
  	WRITE_BITMAPSET_FIELD(ph_eval_at);
+ 	WRITE_BITMAPSET_FIELD(ph_lateral);
  	WRITE_BITMAPSET_FIELD(ph_needed);
  	WRITE_BITMAPSET_FIELD(ph_may_need);
  	WRITE_INT_FIELD(ph_width);
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 65eb344cde449b9cfeab3da87672ff06734b7d1b..109e14cd7368ed453cd311a197642a9cee228095 100644
*** a/src/backend/optimizer/path/indxpath.c
--- b/src/backend/optimizer/path/indxpath.c
*************** create_index_paths(PlannerInfo *root, Re
*** 259,266 ****
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(lc);
  
  		if (bms_is_member(rel->relid, ljinfo->lateral_lhs))
! 			lateral_referencers = bms_add_member(lateral_referencers,
! 												 ljinfo->lateral_rhs);
  	}
  
  	/* Bitmap paths are collected and then dealt with at the end */
--- 259,266 ----
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(lc);
  
  		if (bms_is_member(rel->relid, ljinfo->lateral_lhs))
! 			lateral_referencers = bms_add_members(lateral_referencers,
! 												  ljinfo->lateral_rhs);
  	}
  
  	/* Bitmap paths are collected and then dealt with at the end */
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index d6050a616c73093b0b10c9c454ddd85829353b6d..5b477e52d3fc72bcffe0d7cfb4f62f03610756c9 100644
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** static void sort_inner_and_outer(Planner
*** 29,47 ****
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
  					 List *restrictlist, List *mergeclause_list,
  					 JoinType jointype, SpecialJoinInfo *sjinfo,
! 					 Relids param_source_rels);
  static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
  					 List *restrictlist, List *mergeclause_list,
  					 JoinType jointype, SpecialJoinInfo *sjinfo,
  					 SemiAntiJoinFactors *semifactors,
! 					 Relids param_source_rels);
  static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
  					 List *restrictlist,
  					 JoinType jointype, SpecialJoinInfo *sjinfo,
  					 SemiAntiJoinFactors *semifactors,
! 					 Relids param_source_rels);
  static List *select_mergejoin_clauses(PlannerInfo *root,
  						 RelOptInfo *joinrel,
  						 RelOptInfo *outerrel,
--- 29,47 ----
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
  					 List *restrictlist, List *mergeclause_list,
  					 JoinType jointype, SpecialJoinInfo *sjinfo,
! 					 Relids param_source_rels, Relids extra_lateral_rels);
  static void match_unsorted_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
  					 List *restrictlist, List *mergeclause_list,
  					 JoinType jointype, SpecialJoinInfo *sjinfo,
  					 SemiAntiJoinFactors *semifactors,
! 					 Relids param_source_rels, Relids extra_lateral_rels);
  static void hash_inner_and_outer(PlannerInfo *root, RelOptInfo *joinrel,
  					 RelOptInfo *outerrel, RelOptInfo *innerrel,
  					 List *restrictlist,
  					 JoinType jointype, SpecialJoinInfo *sjinfo,
  					 SemiAntiJoinFactors *semifactors,
! 					 Relids param_source_rels, Relids extra_lateral_rels);
  static List *select_mergejoin_clauses(PlannerInfo *root,
  						 RelOptInfo *joinrel,
  						 RelOptInfo *outerrel,
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 87,92 ****
--- 87,93 ----
  	bool		mergejoin_allowed = true;
  	SemiAntiJoinFactors semifactors;
  	Relids		param_source_rels = NULL;
+ 	Relids		extra_lateral_rels = NULL;
  	ListCell   *lc;
  
  	/*
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 162,181 ****
  	{
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(lc);
  
! 		if (bms_is_member(ljinfo->lateral_rhs, joinrel->relids))
  			param_source_rels = bms_join(param_source_rels,
  										 bms_difference(ljinfo->lateral_lhs,
  														joinrel->relids));
  	}
  
  	/*
  	 * 1. Consider mergejoin paths where both relations must be explicitly
  	 * sorted.	Skip this if we can't mergejoin.
  	 */
  	if (mergejoin_allowed)
  		sort_inner_and_outer(root, joinrel, outerrel, innerrel,
  							 restrictlist, mergeclause_list, jointype,
! 							 sjinfo, param_source_rels);
  
  	/*
  	 * 2. Consider paths where the outer relation need not be explicitly
--- 163,220 ----
  	{
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(lc);
  
! 		if (bms_is_subset(ljinfo->lateral_rhs, joinrel->relids))
  			param_source_rels = bms_join(param_source_rels,
  										 bms_difference(ljinfo->lateral_lhs,
  														joinrel->relids));
  	}
  
  	/*
+ 	 * Another issue created by LATERAL references is that PlaceHolderVars
+ 	 * that need to be computed at this join level might contain lateral
+ 	 * references to rels not in the join, meaning that the paths for the join
+ 	 * would need to be marked as parameterized by those rels, independently
+ 	 * of all other considerations.  Set extra_lateral_rels to the set of such
+ 	 * rels.  This will not affect our decisions as to which paths to
+ 	 * generate; we merely add these rels to their required_outer sets.
+ 	 */
+ 	foreach(lc, root->placeholder_list)
+ 	{
+ 		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
+ 
+ 		/* PHVs without lateral refs can be skipped over quickly */
+ 		if (phinfo->ph_lateral == NULL)
+ 			continue;
+ 		/* Is it due to be evaluated at this join, and not in either input? */
+ 		if (bms_is_subset(phinfo->ph_eval_at, joinrel->relids) &&
+ 			!bms_is_subset(phinfo->ph_eval_at, outerrel->relids) &&
+ 			!bms_is_subset(phinfo->ph_eval_at, innerrel->relids))
+ 		{
+ 			/* Yes, remember its lateral rels */
+ 			extra_lateral_rels = bms_add_members(extra_lateral_rels,
+ 												 phinfo->ph_lateral);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Make sure extra_lateral_rels doesn't list anything within the join, and
+ 	 * that it's NULL if empty.  (This allows us to use bms_add_members to add
+ 	 * it to required_outer below, while preserving the property that
+ 	 * required_outer is exactly NULL if empty.)
+ 	 */
+ 	extra_lateral_rels = bms_del_members(extra_lateral_rels, joinrel->relids);
+ 	if (bms_is_empty(extra_lateral_rels))
+ 		extra_lateral_rels = NULL;
+ 
+ 	/*
  	 * 1. Consider mergejoin paths where both relations must be explicitly
  	 * sorted.	Skip this if we can't mergejoin.
  	 */
  	if (mergejoin_allowed)
  		sort_inner_and_outer(root, joinrel, outerrel, innerrel,
  							 restrictlist, mergeclause_list, jointype,
! 							 sjinfo,
! 							 param_source_rels, extra_lateral_rels);
  
  	/*
  	 * 2. Consider paths where the outer relation need not be explicitly
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 187,193 ****
  	if (mergejoin_allowed)
  		match_unsorted_outer(root, joinrel, outerrel, innerrel,
  							 restrictlist, mergeclause_list, jointype,
! 							 sjinfo, &semifactors, param_source_rels);
  
  #ifdef NOT_USED
  
--- 226,233 ----
  	if (mergejoin_allowed)
  		match_unsorted_outer(root, joinrel, outerrel, innerrel,
  							 restrictlist, mergeclause_list, jointype,
! 							 sjinfo, &semifactors,
! 							 param_source_rels, extra_lateral_rels);
  
  #ifdef NOT_USED
  
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 205,211 ****
  	if (mergejoin_allowed)
  		match_unsorted_inner(root, joinrel, outerrel, innerrel,
  							 restrictlist, mergeclause_list, jointype,
! 							 sjinfo, &semifactors, param_source_rels);
  #endif
  
  	/*
--- 245,252 ----
  	if (mergejoin_allowed)
  		match_unsorted_inner(root, joinrel, outerrel, innerrel,
  							 restrictlist, mergeclause_list, jointype,
! 							 sjinfo, &semifactors,
! 							 param_source_rels, extra_lateral_rels);
  #endif
  
  	/*
*************** add_paths_to_joinrel(PlannerInfo *root,
*** 216,222 ****
  	if (enable_hashjoin || jointype == JOIN_FULL)
  		hash_inner_and_outer(root, joinrel, outerrel, innerrel,
  							 restrictlist, jointype,
! 							 sjinfo, &semifactors, param_source_rels);
  }
  
  /*
--- 257,264 ----
  	if (enable_hashjoin || jointype == JOIN_FULL)
  		hash_inner_and_outer(root, joinrel, outerrel, innerrel,
  							 restrictlist, jointype,
! 							 sjinfo, &semifactors,
! 							 param_source_rels, extra_lateral_rels);
  }
  
  /*
*************** try_nestloop_path(PlannerInfo *root,
*** 231,236 ****
--- 273,279 ----
  				  SpecialJoinInfo *sjinfo,
  				  SemiAntiJoinFactors *semifactors,
  				  Relids param_source_rels,
+ 				  Relids extra_lateral_rels,
  				  Path *outer_path,
  				  Path *inner_path,
  				  List *restrict_clauses,
*************** try_nestloop_path(PlannerInfo *root,
*** 254,259 ****
--- 297,308 ----
  	}
  
  	/*
+ 	 * Independently of that, add parameterization needed for any
+ 	 * PlaceHolderVars that need to be computed at the join.
+ 	 */
+ 	required_outer = bms_add_members(required_outer, extra_lateral_rels);
+ 
+ 	/*
  	 * Do a precheck to quickly eliminate obviously-inferior paths.  We
  	 * calculate a cheap lower bound on the path's cost and then use
  	 * add_path_precheck() to see if the path is clearly going to be dominated
*************** try_mergejoin_path(PlannerInfo *root,
*** 301,306 ****
--- 350,356 ----
  				   JoinType jointype,
  				   SpecialJoinInfo *sjinfo,
  				   Relids param_source_rels,
+ 				   Relids extra_lateral_rels,
  				   Path *outer_path,
  				   Path *inner_path,
  				   List *restrict_clauses,
*************** try_mergejoin_path(PlannerInfo *root,
*** 327,332 ****
--- 377,388 ----
  	}
  
  	/*
+ 	 * Independently of that, add parameterization needed for any
+ 	 * PlaceHolderVars that need to be computed at the join.
+ 	 */
+ 	required_outer = bms_add_members(required_outer, extra_lateral_rels);
+ 
+ 	/*
  	 * If the given paths are already well enough ordered, we can skip doing
  	 * an explicit sort.
  	 */
*************** try_hashjoin_path(PlannerInfo *root,
*** 383,388 ****
--- 439,445 ----
  				  SpecialJoinInfo *sjinfo,
  				  SemiAntiJoinFactors *semifactors,
  				  Relids param_source_rels,
+ 				  Relids extra_lateral_rels,
  				  Path *outer_path,
  				  Path *inner_path,
  				  List *restrict_clauses,
*************** try_hashjoin_path(PlannerInfo *root,
*** 406,411 ****
--- 463,474 ----
  	}
  
  	/*
+ 	 * Independently of that, add parameterization needed for any
+ 	 * PlaceHolderVars that need to be computed at the join.
+ 	 */
+ 	required_outer = bms_add_members(required_outer, extra_lateral_rels);
+ 
+ 	/*
  	 * See comments in try_nestloop_path().  Also note that hashjoin paths
  	 * never have any output pathkeys, per comments in create_hashjoin_path.
  	 */
*************** clause_sides_match_join(RestrictInfo *ri
*** 483,488 ****
--- 546,552 ----
   * 'jointype' is the type of join to do
   * 'sjinfo' is extra info about the join for selectivity estimation
   * 'param_source_rels' are OK targets for parameterization of result paths
+  * 'extra_lateral_rels' are additional parameterization for result paths
   */
  static void
  sort_inner_and_outer(PlannerInfo *root,
*************** sort_inner_and_outer(PlannerInfo *root,
*** 493,499 ****
  					 List *mergeclause_list,
  					 JoinType jointype,
  					 SpecialJoinInfo *sjinfo,
! 					 Relids param_source_rels)
  {
  	Path	   *outer_path;
  	Path	   *inner_path;
--- 557,564 ----
  					 List *mergeclause_list,
  					 JoinType jointype,
  					 SpecialJoinInfo *sjinfo,
! 					 Relids param_source_rels,
! 					 Relids extra_lateral_rels)
  {
  	Path	   *outer_path;
  	Path	   *inner_path;
*************** sort_inner_and_outer(PlannerInfo *root,
*** 623,628 ****
--- 688,694 ----
  						   jointype,
  						   sjinfo,
  						   param_source_rels,
+ 						   extra_lateral_rels,
  						   outer_path,
  						   inner_path,
  						   restrictlist,
*************** sort_inner_and_outer(PlannerInfo *root,
*** 668,673 ****
--- 734,740 ----
   * 'sjinfo' is extra info about the join for selectivity estimation
   * 'semifactors' contains valid data if jointype is SEMI or ANTI
   * 'param_source_rels' are OK targets for parameterization of result paths
+  * 'extra_lateral_rels' are additional parameterization for result paths
   */
  static void
  match_unsorted_outer(PlannerInfo *root,
*************** match_unsorted_outer(PlannerInfo *root,
*** 679,685 ****
  					 JoinType jointype,
  					 SpecialJoinInfo *sjinfo,
  					 SemiAntiJoinFactors *semifactors,
! 					 Relids param_source_rels)
  {
  	JoinType	save_jointype = jointype;
  	bool		nestjoinOK;
--- 746,753 ----
  					 JoinType jointype,
  					 SpecialJoinInfo *sjinfo,
  					 SemiAntiJoinFactors *semifactors,
! 					 Relids param_source_rels,
! 					 Relids extra_lateral_rels)
  {
  	JoinType	save_jointype = jointype;
  	bool		nestjoinOK;
*************** match_unsorted_outer(PlannerInfo *root,
*** 809,814 ****
--- 877,883 ----
  							  sjinfo,
  							  semifactors,
  							  param_source_rels,
+ 							  extra_lateral_rels,
  							  outerpath,
  							  inner_cheapest_total,
  							  restrictlist,
*************** match_unsorted_outer(PlannerInfo *root,
*** 834,839 ****
--- 903,909 ----
  								  sjinfo,
  								  semifactors,
  								  param_source_rels,
+ 								  extra_lateral_rels,
  								  outerpath,
  								  innerpath,
  								  restrictlist,
*************** match_unsorted_outer(PlannerInfo *root,
*** 848,853 ****
--- 918,924 ----
  								  sjinfo,
  								  semifactors,
  								  param_source_rels,
+ 								  extra_lateral_rels,
  								  outerpath,
  								  matpath,
  								  restrictlist,
*************** match_unsorted_outer(PlannerInfo *root,
*** 903,908 ****
--- 974,980 ----
  						   jointype,
  						   sjinfo,
  						   param_source_rels,
+ 						   extra_lateral_rels,
  						   outerpath,
  						   inner_cheapest_total,
  						   restrictlist,
*************** match_unsorted_outer(PlannerInfo *root,
*** 1001,1006 ****
--- 1073,1079 ----
  								   jointype,
  								   sjinfo,
  								   param_source_rels,
+ 								   extra_lateral_rels,
  								   outerpath,
  								   innerpath,
  								   restrictlist,
*************** match_unsorted_outer(PlannerInfo *root,
*** 1046,1051 ****
--- 1119,1125 ----
  									   jointype,
  									   sjinfo,
  									   param_source_rels,
+ 									   extra_lateral_rels,
  									   outerpath,
  									   innerpath,
  									   restrictlist,
*************** match_unsorted_outer(PlannerInfo *root,
*** 1080,1085 ****
--- 1154,1160 ----
   * 'sjinfo' is extra info about the join for selectivity estimation
   * 'semifactors' contains valid data if jointype is SEMI or ANTI
   * 'param_source_rels' are OK targets for parameterization of result paths
+  * 'extra_lateral_rels' are additional parameterization for result paths
   */
  static void
  hash_inner_and_outer(PlannerInfo *root,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1090,1096 ****
  					 JoinType jointype,
  					 SpecialJoinInfo *sjinfo,
  					 SemiAntiJoinFactors *semifactors,
! 					 Relids param_source_rels)
  {
  	bool		isouterjoin = IS_OUTER_JOIN(jointype);
  	List	   *hashclauses;
--- 1165,1172 ----
  					 JoinType jointype,
  					 SpecialJoinInfo *sjinfo,
  					 SemiAntiJoinFactors *semifactors,
! 					 Relids param_source_rels,
! 					 Relids extra_lateral_rels)
  {
  	bool		isouterjoin = IS_OUTER_JOIN(jointype);
  	List	   *hashclauses;
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1164,1169 ****
--- 1240,1246 ----
  							  sjinfo,
  							  semifactors,
  							  param_source_rels,
+ 							  extra_lateral_rels,
  							  cheapest_total_outer,
  							  cheapest_total_inner,
  							  restrictlist,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1183,1188 ****
--- 1260,1266 ----
  							  sjinfo,
  							  semifactors,
  							  param_source_rels,
+ 							  extra_lateral_rels,
  							  cheapest_total_outer,
  							  cheapest_total_inner,
  							  restrictlist,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1195,1200 ****
--- 1273,1279 ----
  								  sjinfo,
  								  semifactors,
  								  param_source_rels,
+ 								  extra_lateral_rels,
  								  cheapest_startup_outer,
  								  cheapest_total_inner,
  								  restrictlist,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1219,1224 ****
--- 1298,1304 ----
  								  sjinfo,
  								  semifactors,
  								  param_source_rels,
+ 								  extra_lateral_rels,
  								  cheapest_startup_outer,
  								  cheapest_total_inner,
  								  restrictlist,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1256,1261 ****
--- 1336,1342 ----
  									  sjinfo,
  									  semifactors,
  									  param_source_rels,
+ 									  extra_lateral_rels,
  									  outerpath,
  									  innerpath,
  									  restrictlist,
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 819498a4281d191f4ecb927fe821e027cc55e8b0..d627f9e130c00e1a40e88294ccfb11d70fbeb380 100644
*** a/src/backend/optimizer/path/joinrels.c
--- b/src/backend/optimizer/path/joinrels.c
*************** join_is_legal(PlannerInfo *root, RelOptI
*** 526,532 ****
  	{
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
  
! 		if (bms_is_member(ljinfo->lateral_rhs, rel2->relids) &&
  			bms_overlap(ljinfo->lateral_lhs, rel1->relids))
  		{
  			/* has to be implemented as nestloop with rel1 on left */
--- 526,532 ----
  	{
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
  
! 		if (bms_is_subset(ljinfo->lateral_rhs, rel2->relids) &&
  			bms_overlap(ljinfo->lateral_lhs, rel1->relids))
  		{
  			/* has to be implemented as nestloop with rel1 on left */
*************** join_is_legal(PlannerInfo *root, RelOptI
*** 539,545 ****
  				(reversed || match_sjinfo->jointype == JOIN_FULL))
  				return false;	/* not implementable as nestloop */
  		}
! 		if (bms_is_member(ljinfo->lateral_rhs, rel1->relids) &&
  			bms_overlap(ljinfo->lateral_lhs, rel2->relids))
  		{
  			/* has to be implemented as nestloop with rel2 on left */
--- 539,545 ----
  				(reversed || match_sjinfo->jointype == JOIN_FULL))
  				return false;	/* not implementable as nestloop */
  		}
! 		if (bms_is_subset(ljinfo->lateral_rhs, rel1->relids) &&
  			bms_overlap(ljinfo->lateral_lhs, rel2->relids))
  		{
  			/* has to be implemented as nestloop with rel2 on left */
*************** have_join_order_restriction(PlannerInfo 
*** 829,838 ****
  	{
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
  
! 		if (bms_is_member(ljinfo->lateral_rhs, rel2->relids) &&
  			bms_overlap(ljinfo->lateral_lhs, rel1->relids))
  			return true;
! 		if (bms_is_member(ljinfo->lateral_rhs, rel1->relids) &&
  			bms_overlap(ljinfo->lateral_lhs, rel2->relids))
  			return true;
  	}
--- 829,838 ----
  	{
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
  
! 		if (bms_is_subset(ljinfo->lateral_rhs, rel2->relids) &&
  			bms_overlap(ljinfo->lateral_lhs, rel1->relids))
  			return true;
! 		if (bms_is_subset(ljinfo->lateral_rhs, rel1->relids) &&
  			bms_overlap(ljinfo->lateral_lhs, rel2->relids))
  			return true;
  	}
*************** has_join_restriction(PlannerInfo *root, 
*** 928,934 ****
  	{
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
  
! 		if (bms_is_member(ljinfo->lateral_rhs, rel->relids) ||
  			bms_overlap(ljinfo->lateral_lhs, rel->relids))
  			return true;
  	}
--- 928,934 ----
  	{
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
  
! 		if (bms_is_subset(ljinfo->lateral_rhs, rel->relids) ||
  			bms_overlap(ljinfo->lateral_lhs, rel->relids))
  			return true;
  	}
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index a7db69c85bfabc95e37196398e4f18c57969fbe3..795f2286b1ebf768c95c25706309fadbcc8246ff 100644
*** a/src/backend/optimizer/plan/analyzejoins.c
--- b/src/backend/optimizer/plan/analyzejoins.c
*************** join_is_removable(PlannerInfo *root, Spe
*** 202,208 ****
  	 * that will be used above the join.  We only need to fail if such a PHV
  	 * actually references some inner-rel attributes; but the correct check
  	 * for that is relatively expensive, so we first check against ph_eval_at,
! 	 * which must mention the inner rel if the PHV uses any inner-rel attrs.
  	 */
  	foreach(l, root->placeholder_list)
  	{
--- 202,210 ----
  	 * that will be used above the join.  We only need to fail if such a PHV
  	 * actually references some inner-rel attributes; but the correct check
  	 * for that is relatively expensive, so we first check against ph_eval_at,
! 	 * which must mention the inner rel if the PHV uses any inner-rel attrs as
! 	 * non-lateral references.	Note also that if the PHV's syntactic scope is
! 	 * just the inner rel, we can't drop it even if it's variable-free.
  	 */
  	foreach(l, root->placeholder_list)
  	{
*************** join_is_removable(PlannerInfo *root, Spe
*** 210,218 ****
  
  		if (bms_is_subset(phinfo->ph_needed, joinrelids))
  			continue;			/* PHV is not used above the join */
  		if (!bms_overlap(phinfo->ph_eval_at, innerrel->relids))
  			continue;			/* it definitely doesn't reference innerrel */
! 		if (bms_overlap(pull_varnos((Node *) phinfo->ph_var),
  						innerrel->relids))
  			return false;		/* it does reference innerrel */
  	}
--- 212,224 ----
  
  		if (bms_is_subset(phinfo->ph_needed, joinrelids))
  			continue;			/* PHV is not used above the join */
+ 		if (bms_overlap(phinfo->ph_lateral, innerrel->relids))
+ 			return false;		/* it references innerrel laterally */
  		if (!bms_overlap(phinfo->ph_eval_at, innerrel->relids))
  			continue;			/* it definitely doesn't reference innerrel */
! 		if (bms_is_subset(phinfo->ph_eval_at, innerrel->relids))
! 			return false;		/* there isn't any other place to eval PHV */
! 		if (bms_overlap(pull_varnos((Node *) phinfo->ph_var->phexpr),
  						innerrel->relids))
  			return false;		/* it does reference innerrel */
  	}
*************** remove_rel_from_query(PlannerInfo *root,
*** 355,361 ****
  	 * Likewise remove references from LateralJoinInfo data structures.
  	 *
  	 * If we are deleting a LATERAL subquery, we can forget its
! 	 * LateralJoinInfo altogether.	Otherwise, make sure the target is not
  	 * included in any lateral_lhs set.  (It probably can't be, since that
  	 * should have precluded deciding to remove it; but let's cope anyway.)
  	 */
--- 361,367 ----
  	 * Likewise remove references from LateralJoinInfo data structures.
  	 *
  	 * If we are deleting a LATERAL subquery, we can forget its
! 	 * LateralJoinInfos altogether.  Otherwise, make sure the target is not
  	 * included in any lateral_lhs set.  (It probably can't be, since that
  	 * should have precluded deciding to remove it; but let's cope anyway.)
  	 */
*************** remove_rel_from_query(PlannerInfo *root,
*** 364,392 ****
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
  
  		nextl = lnext(l);
! 		if (ljinfo->lateral_rhs == relid)
  			root->lateral_info_list = list_delete_ptr(root->lateral_info_list,
  													  ljinfo);
  		else
  			ljinfo->lateral_lhs = bms_del_member(ljinfo->lateral_lhs, relid);
  	}
  
  	/*
  	 * Likewise remove references from PlaceHolderVar data structures.
- 	 *
- 	 * Here we have a special case: if a PHV's eval_at set is just the target
- 	 * relid, we want to leave it that way instead of reducing it to the empty
- 	 * set.  An empty eval_at set would confuse later processing since it
- 	 * would match every possible eval placement.
  	 */
  	foreach(l, root->placeholder_list)
  	{
  		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(l);
  
  		phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at, relid);
! 		if (bms_is_empty(phinfo->ph_eval_at))	/* oops, belay that */
! 			phinfo->ph_eval_at = bms_add_member(phinfo->ph_eval_at, relid);
! 
  		phinfo->ph_needed = bms_del_member(phinfo->ph_needed, relid);
  		/* ph_may_need probably isn't used after this, but fix it anyway */
  		phinfo->ph_may_need = bms_del_member(phinfo->ph_may_need, relid);
--- 370,396 ----
  		LateralJoinInfo *ljinfo = (LateralJoinInfo *) lfirst(l);
  
  		nextl = lnext(l);
! 		ljinfo->lateral_rhs = bms_del_member(ljinfo->lateral_rhs, relid);
! 		if (bms_is_empty(ljinfo->lateral_rhs))
  			root->lateral_info_list = list_delete_ptr(root->lateral_info_list,
  													  ljinfo);
  		else
+ 		{
  			ljinfo->lateral_lhs = bms_del_member(ljinfo->lateral_lhs, relid);
+ 			Assert(!bms_is_empty(ljinfo->lateral_lhs));
+ 		}
  	}
  
  	/*
  	 * Likewise remove references from PlaceHolderVar data structures.
  	 */
  	foreach(l, root->placeholder_list)
  	{
  		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(l);
  
  		phinfo->ph_eval_at = bms_del_member(phinfo->ph_eval_at, relid);
! 		Assert(!bms_is_empty(phinfo->ph_eval_at));
! 		Assert(!bms_is_member(relid, phinfo->ph_lateral));
  		phinfo->ph_needed = bms_del_member(phinfo->ph_needed, relid);
  		/* ph_may_need probably isn't used after this, but fix it anyway */
  		phinfo->ph_may_need = bms_del_member(phinfo->ph_may_need, relid);
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 52bab79007e66d80205a07da08bc4437c424232c..c501737a2671e8c0f6a0fc21157e7e125a7b1a34 100644
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 44,52 ****
  
  static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
  static Plan *create_scan_plan(PlannerInfo *root, Path *best_path);
! static List *build_relation_tlist(RelOptInfo *rel);
  static bool use_physical_tlist(PlannerInfo *root, RelOptInfo *rel);
! static void disuse_physical_tlist(Plan *plan, Path *path);
  static Plan *create_gating_plan(PlannerInfo *root, Plan *plan, List *quals);
  static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
  static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path);
--- 44,52 ----
  
  static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
  static Plan *create_scan_plan(PlannerInfo *root, Path *best_path);
! static List *build_path_tlist(PlannerInfo *root, Path *path);
  static bool use_physical_tlist(PlannerInfo *root, RelOptInfo *rel);
! static void disuse_physical_tlist(PlannerInfo *root, Plan *plan, Path *path);
  static Plan *create_gating_plan(PlannerInfo *root, Plan *plan, List *quals);
  static Plan *create_join_plan(PlannerInfo *root, JoinPath *best_path);
  static Plan *create_append_plan(PlannerInfo *root, AppendPath *best_path);
*************** create_scan_plan(PlannerInfo *root, Path
*** 305,325 ****
  			tlist = build_physical_tlist(root, rel);
  			/* if fail because of dropped cols, use regular method */
  			if (tlist == NIL)
! 				tlist = build_relation_tlist(rel);
  		}
  	}
  	else
  	{
! 		tlist = build_relation_tlist(rel);
! 
! 		/*
! 		 * If it's a parameterized otherrel, there might be lateral references
! 		 * in the tlist, which need to be replaced with Params.  This cannot
! 		 * happen for regular baserels, though.  Note use_physical_tlist()
! 		 * always fails for otherrels, so we don't need to check this above.
! 		 */
! 		if (rel->reloptkind != RELOPT_BASEREL && best_path->param_info)
! 			tlist = (List *) replace_nestloop_params(root, (Node *) tlist);
  	}
  
  	/*
--- 305,316 ----
  			tlist = build_physical_tlist(root, rel);
  			/* if fail because of dropped cols, use regular method */
  			if (tlist == NIL)
! 				tlist = build_path_tlist(root, best_path);
  		}
  	}
  	else
  	{
! 		tlist = build_path_tlist(root, best_path);
  	}
  
  	/*
*************** create_scan_plan(PlannerInfo *root, Path
*** 439,449 ****
  }
  
  /*
!  * Build a target list (ie, a list of TargetEntry) for a relation.
   */
  static List *
! build_relation_tlist(RelOptInfo *rel)
  {
  	List	   *tlist = NIL;
  	int			resno = 1;
  	ListCell   *v;
--- 430,441 ----
  }
  
  /*
!  * Build a target list (ie, a list of TargetEntry) for the Path's output.
   */
  static List *
! build_path_tlist(PlannerInfo *root, Path *path)
  {
+ 	RelOptInfo *rel = path->parent;
  	List	   *tlist = NIL;
  	int			resno = 1;
  	ListCell   *v;
*************** build_relation_tlist(RelOptInfo *rel)
*** 453,458 ****
--- 445,459 ----
  		/* Do we really need to copy here?	Not sure */
  		Node	   *node = (Node *) copyObject(lfirst(v));
  
+ 		/*
+ 		 * If it's a parameterized path, there might be lateral references in
+ 		 * the tlist, which need to be replaced with Params.  There's no need
+ 		 * to remake the TargetEntry nodes, so apply this to each list item
+ 		 * separately.
+ 		 */
+ 		if (path->param_info)
+ 			node = replace_nestloop_params(root, node);
+ 
  		tlist = lappend(tlist, makeTargetEntry((Expr *) node,
  											   resno,
  											   NULL,
*************** use_physical_tlist(PlannerInfo *root, Re
*** 528,534 ****
   * and Material nodes want this, so they don't have to store useless columns.
   */
  static void
! disuse_physical_tlist(Plan *plan, Path *path)
  {
  	/* Only need to undo it for path types handled by create_scan_plan() */
  	switch (path->pathtype)
--- 529,535 ----
   * and Material nodes want this, so they don't have to store useless columns.
   */
  static void
! disuse_physical_tlist(PlannerInfo *root, Plan *plan, Path *path)
  {
  	/* Only need to undo it for path types handled by create_scan_plan() */
  	switch (path->pathtype)
*************** disuse_physical_tlist(Plan *plan, Path *
*** 544,550 ****
  		case T_CteScan:
  		case T_WorkTableScan:
  		case T_ForeignScan:
! 			plan->targetlist = build_relation_tlist(path->parent);
  			break;
  		default:
  			break;
--- 545,551 ----
  		case T_CteScan:
  		case T_WorkTableScan:
  		case T_ForeignScan:
! 			plan->targetlist = build_path_tlist(root, path);
  			break;
  		default:
  			break;
*************** static Plan *
*** 678,684 ****
  create_append_plan(PlannerInfo *root, AppendPath *best_path)
  {
  	Append	   *plan;
! 	List	   *tlist = build_relation_tlist(best_path->path.parent);
  	List	   *subplans = NIL;
  	ListCell   *subpaths;
  
--- 679,685 ----
  create_append_plan(PlannerInfo *root, AppendPath *best_path)
  {
  	Append	   *plan;
! 	List	   *tlist = build_path_tlist(root, &best_path->path);
  	List	   *subplans = NIL;
  	ListCell   *subpaths;
  
*************** create_merge_append_plan(PlannerInfo *ro
*** 733,739 ****
  {
  	MergeAppend *node = makeNode(MergeAppend);
  	Plan	   *plan = &node->plan;
! 	List	   *tlist = build_relation_tlist(best_path->path.parent);
  	List	   *pathkeys = best_path->path.pathkeys;
  	List	   *subplans = NIL;
  	ListCell   *subpaths;
--- 734,740 ----
  {
  	MergeAppend *node = makeNode(MergeAppend);
  	Plan	   *plan = &node->plan;
! 	List	   *tlist = build_path_tlist(root, &best_path->path);
  	List	   *pathkeys = best_path->path.pathkeys;
  	List	   *subplans = NIL;
  	ListCell   *subpaths;
*************** create_material_plan(PlannerInfo *root, 
*** 862,868 ****
  	subplan = create_plan_recurse(root, best_path->subpath);
  
  	/* We don't want any excess columns in the materialized tuples */
! 	disuse_physical_tlist(subplan, best_path->subpath);
  
  	plan = make_material(subplan);
  
--- 863,869 ----
  	subplan = create_plan_recurse(root, best_path->subpath);
  
  	/* We don't want any excess columns in the materialized tuples */
! 	disuse_physical_tlist(root, subplan, best_path->subpath);
  
  	plan = make_material(subplan);
  
*************** create_unique_plan(PlannerInfo *root, Un
*** 911,917 ****
  	 * should be left as-is if we don't need to add any expressions; but if we
  	 * do have to add expressions, then a projection step will be needed at
  	 * runtime anyway, so we may as well remove unneeded items. Therefore
! 	 * newtlist starts from build_relation_tlist() not just a copy of the
  	 * subplan's tlist; and we don't install it into the subplan unless we are
  	 * sorting or stuff has to be added.
  	 */
--- 912,918 ----
  	 * should be left as-is if we don't need to add any expressions; but if we
  	 * do have to add expressions, then a projection step will be needed at
  	 * runtime anyway, so we may as well remove unneeded items. Therefore
! 	 * newtlist starts from build_path_tlist() not just a copy of the
  	 * subplan's tlist; and we don't install it into the subplan unless we are
  	 * sorting or stuff has to be added.
  	 */
*************** create_unique_plan(PlannerInfo *root, Un
*** 919,925 ****
  	uniq_exprs = best_path->uniq_exprs;
  
  	/* initialize modified subplan tlist as just the "required" vars */
! 	newtlist = build_relation_tlist(best_path->path.parent);
  	nextresno = list_length(newtlist) + 1;
  	newitems = false;
  
--- 920,926 ----
  	uniq_exprs = best_path->uniq_exprs;
  
  	/* initialize modified subplan tlist as just the "required" vars */
! 	newtlist = build_path_tlist(root, &best_path->path);
  	nextresno = list_length(newtlist) + 1;
  	newitems = false;
  
*************** create_unique_plan(PlannerInfo *root, Un
*** 1009,1015 ****
  		 * subplan tlist.
  		 */
  		plan = (Plan *) make_agg(root,
! 								 build_relation_tlist(best_path->path.parent),
  								 NIL,
  								 AGG_HASHED,
  								 NULL,
--- 1010,1016 ----
  		 * subplan tlist.
  		 */
  		plan = (Plan *) make_agg(root,
! 								 build_path_tlist(root, &best_path->path),
  								 NIL,
  								 AGG_HASHED,
  								 NULL,
*************** create_nestloop_plan(PlannerInfo *root,
*** 2028,2034 ****
  					 Plan *inner_plan)
  {
  	NestLoop   *join_plan;
! 	List	   *tlist = build_relation_tlist(best_path->path.parent);
  	List	   *joinrestrictclauses = best_path->joinrestrictinfo;
  	List	   *joinclauses;
  	List	   *otherclauses;
--- 2029,2035 ----
  					 Plan *inner_plan)
  {
  	NestLoop   *join_plan;
! 	List	   *tlist = build_path_tlist(root, &best_path->path);
  	List	   *joinrestrictclauses = best_path->joinrestrictinfo;
  	List	   *joinclauses;
  	List	   *otherclauses;
*************** create_mergejoin_plan(PlannerInfo *root,
*** 2118,2124 ****
  					  Plan *outer_plan,
  					  Plan *inner_plan)
  {
! 	List	   *tlist = build_relation_tlist(best_path->jpath.path.parent);
  	List	   *joinclauses;
  	List	   *otherclauses;
  	List	   *mergeclauses;
--- 2119,2125 ----
  					  Plan *outer_plan,
  					  Plan *inner_plan)
  {
! 	List	   *tlist = build_path_tlist(root, &best_path->jpath.path);
  	List	   *joinclauses;
  	List	   *otherclauses;
  	List	   *mergeclauses;
*************** create_mergejoin_plan(PlannerInfo *root,
*** 2186,2192 ****
  	 */
  	if (best_path->outersortkeys)
  	{
! 		disuse_physical_tlist(outer_plan, best_path->jpath.outerjoinpath);
  		outer_plan = (Plan *)
  			make_sort_from_pathkeys(root,
  									outer_plan,
--- 2187,2193 ----
  	 */
  	if (best_path->outersortkeys)
  	{
! 		disuse_physical_tlist(root, outer_plan, best_path->jpath.outerjoinpath);
  		outer_plan = (Plan *)
  			make_sort_from_pathkeys(root,
  									outer_plan,
*************** create_mergejoin_plan(PlannerInfo *root,
*** 2199,2205 ****
  
  	if (best_path->innersortkeys)
  	{
! 		disuse_physical_tlist(inner_plan, best_path->jpath.innerjoinpath);
  		inner_plan = (Plan *)
  			make_sort_from_pathkeys(root,
  									inner_plan,
--- 2200,2206 ----
  
  	if (best_path->innersortkeys)
  	{
! 		disuse_physical_tlist(root, inner_plan, best_path->jpath.innerjoinpath);
  		inner_plan = (Plan *)
  			make_sort_from_pathkeys(root,
  									inner_plan,
*************** create_hashjoin_plan(PlannerInfo *root,
*** 2413,2419 ****
  					 Plan *outer_plan,
  					 Plan *inner_plan)
  {
! 	List	   *tlist = build_relation_tlist(best_path->jpath.path.parent);
  	List	   *joinclauses;
  	List	   *otherclauses;
  	List	   *hashclauses;
--- 2414,2420 ----
  					 Plan *outer_plan,
  					 Plan *inner_plan)
  {
! 	List	   *tlist = build_path_tlist(root, &best_path->jpath.path);
  	List	   *joinclauses;
  	List	   *otherclauses;
  	List	   *hashclauses;
*************** create_hashjoin_plan(PlannerInfo *root,
*** 2470,2480 ****
  							 best_path->jpath.outerjoinpath->parent->relids);
  
  	/* We don't want any excess columns in the hashed tuples */
! 	disuse_physical_tlist(inner_plan, best_path->jpath.innerjoinpath);
  
  	/* If we expect batching, suppress excess columns in outer tuples too */
  	if (best_path->num_batches > 1)
! 		disuse_physical_tlist(outer_plan, best_path->jpath.outerjoinpath);
  
  	/*
  	 * If there is a single join clause and we can identify the outer variable
--- 2471,2481 ----
  							 best_path->jpath.outerjoinpath->parent->relids);
  
  	/* We don't want any excess columns in the hashed tuples */
! 	disuse_physical_tlist(root, inner_plan, best_path->jpath.innerjoinpath);
  
  	/* If we expect batching, suppress excess columns in outer tuples too */
  	if (best_path->num_batches > 1)
! 		disuse_physical_tlist(root, outer_plan, best_path->jpath.outerjoinpath);
  
  	/*
  	 * If there is a single join clause and we can identify the outer variable
*************** replace_nestloop_params_mutator(Node *no
*** 2604,2619 ****
  		Assert(phv->phlevelsup == 0);
  
  		/*
! 		 * If not to be replaced, just return the PlaceHolderVar unmodified.
! 		 * We use bms_overlap as a cheap/quick test to see if the PHV might be
! 		 * evaluated in the outer rels, and then grab its PlaceHolderInfo to
! 		 * tell for sure.
  		 */
! 		if (!bms_overlap(phv->phrels, root->curOuterRels))
! 			return node;
! 		if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
! 						   root->curOuterRels))
! 			return node;
  		/* Create a Param representing the PlaceHolderVar */
  		param = assign_nestloop_param_placeholdervar(root, phv);
  		/* Is this param already listed in root->curOuterParams? */
--- 2605,2641 ----
  		Assert(phv->phlevelsup == 0);
  
  		/*
! 		 * Check whether we need to replace the PHV.  We use bms_overlap as a
! 		 * cheap/quick test to see if the PHV might be evaluated in the outer
! 		 * rels, and then grab its PlaceHolderInfo to tell for sure.
  		 */
! 		if (!bms_overlap(phv->phrels, root->curOuterRels) ||
! 		  !bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
! 						 root->curOuterRels))
! 		{
! 			/*
! 			 * We can't replace the whole PHV, but we might still need to
! 			 * replace Vars or PHVs within its expression, in case it ends up
! 			 * actually getting evaluated here.  (It might get evaluated in
! 			 * this plan node, or some child node; in the latter case we don't
! 			 * really need to process the expression here, but we haven't got
! 			 * enough info to tell if that's the case.)  Flat-copy the PHV
! 			 * node and then recurse on its expression.
! 			 *
! 			 * Note that after doing this, we might have different
! 			 * representations of the contents of the same PHV in different
! 			 * parts of the plan tree.	This is OK because equal() will just
! 			 * match on phid/phlevelsup, so setrefs.c will still recognize an
! 			 * upper-level reference to a lower-level copy of the same PHV.
! 			 */
! 			PlaceHolderVar *newphv = makeNode(PlaceHolderVar);
! 
! 			memcpy(newphv, phv, sizeof(PlaceHolderVar));
! 			newphv->phexpr = (Expr *)
! 				replace_nestloop_params_mutator((Node *) phv->phexpr,
! 												root);
! 			return (Node *) newphv;
! 		}
  		/* Create a Param representing the PlaceHolderVar */
  		param = assign_nestloop_param_placeholdervar(root, phv);
  		/* Is this param already listed in root->curOuterParams? */
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 839ed9dde4049a7bff81d6909776f0ba3e4550a9..0f2c6752c445dece8214fe19ac20d2c4da07edb9 100644
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
*************** int			join_collapse_limit;
*** 37,43 ****
  
  static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
  						   Index rtindex);
! static void add_lateral_info(PlannerInfo *root, Index rhs, Relids lhs);
  static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
  					bool below_outer_join,
  					Relids *qualscope, Relids *inner_join_rels);
--- 37,43 ----
  
  static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
  						   Index rtindex);
! static void add_lateral_info(PlannerInfo *root, Relids lhs, Relids rhs);
  static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
  					bool below_outer_join,
  					Relids *qualscope, Relids *inner_join_rels);
*************** add_vars_to_targetlist(PlannerInfo *root
*** 177,182 ****
--- 177,184 ----
  			RelOptInfo *rel = find_base_rel(root, var->varno);
  			int			attno = var->varattno;
  
+ 			if (bms_is_subset(where_needed, rel->relids))
+ 				continue;
  			Assert(attno >= rel->min_attr && attno <= rel->max_attr);
  			attno -= rel->min_attr;
  			if (rel->attr_needed[attno] == NULL)
*************** extract_lateral_references(PlannerInfo *
*** 369,382 ****
   * create_lateral_join_info
   *	  For each LATERAL subquery, create LateralJoinInfo(s) and add them to
   *	  root->lateral_info_list, and fill in the per-rel lateral_relids sets.
   *
   * This has to run after deconstruct_jointree, because we need to know the
!  * final ph_eval_at values for referenced PlaceHolderVars.
   */
  void
  create_lateral_join_info(PlannerInfo *root)
  {
  	Index		rti;
  
  	/* We need do nothing if the query contains no LATERAL RTEs */
  	if (!root->hasLateralRTEs)
--- 371,387 ----
   * create_lateral_join_info
   *	  For each LATERAL subquery, create LateralJoinInfo(s) and add them to
   *	  root->lateral_info_list, and fill in the per-rel lateral_relids sets.
+  *	  Also generate LateralJoinInfo(s) to represent any lateral references
+  *	  within PlaceHolderVars.
   *
   * This has to run after deconstruct_jointree, because we need to know the
!  * final ph_eval_at values for PlaceHolderVars.
   */
  void
  create_lateral_join_info(PlannerInfo *root)
  {
  	Index		rti;
+ 	ListCell   *lc;
  
  	/* We need do nothing if the query contains no LATERAL RTEs */
  	if (!root->hasLateralRTEs)
*************** create_lateral_join_info(PlannerInfo *ro
*** 389,395 ****
  	{
  		RelOptInfo *brel = root->simple_rel_array[rti];
  		Relids		lateral_relids;
- 		ListCell   *lc;
  
  		/* there may be empty slots corresponding to non-baserel RTEs */
  		if (brel == NULL)
--- 394,399 ----
*************** create_lateral_join_info(PlannerInfo *ro
*** 412,418 ****
  			{
  				Var		   *var = (Var *) node;
  
! 				add_lateral_info(root, rti, bms_make_singleton(var->varno));
  				lateral_relids = bms_add_member(lateral_relids,
  												var->varno);
  			}
--- 416,423 ----
  			{
  				Var		   *var = (Var *) node;
  
! 				add_lateral_info(root, bms_make_singleton(var->varno),
! 								 brel->relids);
  				lateral_relids = bms_add_member(lateral_relids,
  												var->varno);
  			}
*************** create_lateral_join_info(PlannerInfo *ro
*** 422,428 ****
  				PlaceHolderInfo *phinfo = find_placeholder_info(root, phv,
  																false);
  
! 				add_lateral_info(root, rti, bms_copy(phinfo->ph_eval_at));
  				lateral_relids = bms_add_members(lateral_relids,
  												 phinfo->ph_eval_at);
  			}
--- 427,433 ----
  				PlaceHolderInfo *phinfo = find_placeholder_info(root, phv,
  																false);
  
! 				add_lateral_info(root, phinfo->ph_eval_at, brel->relids);
  				lateral_relids = bms_add_members(lateral_relids,
  												 phinfo->ph_eval_at);
  			}
*************** create_lateral_join_info(PlannerInfo *ro
*** 460,503 ****
  			}
  		}
  	}
  }
  
  /*
   * add_lateral_info
   *		Add a LateralJoinInfo to root->lateral_info_list, if needed
   *
!  * We suppress redundant list entries.	The passed lhs set must be freshly
!  * made; we free it if not used in a new list entry.
   */
  static void
! add_lateral_info(PlannerInfo *root, Index rhs, Relids lhs)
  {
  	LateralJoinInfo *ljinfo;
! 	ListCell   *l;
  
! 	Assert(!bms_is_member(rhs, lhs));
  
  	/*
! 	 * If an existing list member has the same RHS and an LHS that is a subset
! 	 * of the new one, it's redundant, but we don't trouble to get rid of it.
! 	 * The only case that is really worth worrying about is identical entries,
! 	 * and we handle that well enough with this simple logic.
  	 */
! 	foreach(l, root->lateral_info_list)
  	{
! 		ljinfo = (LateralJoinInfo *) lfirst(l);
! 		if (rhs == ljinfo->lateral_rhs &&
  			bms_is_subset(lhs, ljinfo->lateral_lhs))
- 		{
- 			bms_free(lhs);
  			return;
- 		}
  	}
  
  	/* Not there, so make a new entry */
  	ljinfo = makeNode(LateralJoinInfo);
! 	ljinfo->lateral_rhs = rhs;
! 	ljinfo->lateral_lhs = lhs;
  	root->lateral_info_list = lappend(root->lateral_info_list, ljinfo);
  }
  
--- 465,558 ----
  			}
  		}
  	}
+ 
+ 	/*
+ 	 * Now check for lateral references within PlaceHolderVars.  Unlike the
+ 	 * case for unflattened LATERAL RTEs, the referencing location could be a
+ 	 * join.
+ 	 */
+ 	foreach(lc, root->placeholder_list)
+ 	{
+ 		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
+ 		Relids		eval_at = phinfo->ph_eval_at;
+ 
+ 		/* if there are lateral refs in it, add them to lateral_vars */
+ 		if (phinfo->ph_lateral != NULL)
+ 		{
+ 			List	   *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
+ 											   PVC_RECURSE_AGGREGATES,
+ 											   PVC_INCLUDE_PLACEHOLDERS);
+ 			ListCell   *lc2;
+ 
+ 			foreach(lc2, vars)
+ 			{
+ 				Node	   *node = (Node *) lfirst(lc2);
+ 
+ 				if (IsA(node, Var))
+ 				{
+ 					Var		   *var = (Var *) node;
+ 
+ 					if (!bms_is_member(var->varno, eval_at))
+ 						add_lateral_info(root,
+ 										 bms_make_singleton(var->varno),
+ 										 eval_at);
+ 				}
+ 				else if (IsA(node, PlaceHolderVar))
+ 				{
+ 					PlaceHolderVar *other_phv = (PlaceHolderVar *) node;
+ 					PlaceHolderInfo *other_phi;
+ 
+ 					other_phi = find_placeholder_info(root, other_phv,
+ 													  false);
+ 					if (!bms_is_subset(other_phi->ph_eval_at, eval_at))
+ 						add_lateral_info(root, other_phi->ph_eval_at, eval_at);
+ 				}
+ 				else
+ 					Assert(false);
+ 			}
+ 
+ 			list_free(vars);
+ 		}
+ 	}
  }
  
  /*
   * add_lateral_info
   *		Add a LateralJoinInfo to root->lateral_info_list, if needed
   *
!  * We suppress redundant list entries.	The passed Relids are copied if saved.
   */
  static void
! add_lateral_info(PlannerInfo *root, Relids lhs, Relids rhs)
  {
  	LateralJoinInfo *ljinfo;
! 	ListCell   *lc;
  
! 	/* Sanity-check the input */
! 	Assert(!bms_is_empty(lhs));
! 	Assert(!bms_is_empty(rhs));
! 	Assert(!bms_overlap(lhs, rhs));
  
  	/*
! 	 * The input is redundant if it has the same RHS and an LHS that is a
! 	 * subset of an existing entry's.  If an existing entry has the same RHS
! 	 * and an LHS that is a subset of the new one, it's redundant, but we
! 	 * don't trouble to get rid of it.  The only case that is really worth
! 	 * worrying about is identical entries, and we handle that well enough
! 	 * with this simple logic.
  	 */
! 	foreach(lc, root->lateral_info_list)
  	{
! 		ljinfo = (LateralJoinInfo *) lfirst(lc);
! 		if (bms_equal(rhs, ljinfo->lateral_rhs) &&
  			bms_is_subset(lhs, ljinfo->lateral_lhs))
  			return;
  	}
  
  	/* Not there, so make a new entry */
  	ljinfo = makeNode(LateralJoinInfo);
! 	ljinfo->lateral_lhs = bms_copy(lhs);
! 	ljinfo->lateral_rhs = bms_copy(rhs);
  	root->lateral_info_list = lappend(root->lateral_info_list, ljinfo);
  }
  
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 42a98945a38d07972ee2e8b5de16b3639196eebd..284929f125e9d8c6eef502d0e112321e65cf5bbf 100644
*** a/src/backend/optimizer/plan/planmain.c
--- b/src/backend/optimizer/plan/planmain.c
*************** query_planner(PlannerInfo *root, List *t
*** 176,187 ****
  	joinlist = deconstruct_jointree(root);
  
  	/*
- 	 * Create the LateralJoinInfo list now that we have finalized
- 	 * PlaceHolderVar eval levels.
- 	 */
- 	create_lateral_join_info(root);
- 
- 	/*
  	 * Reconsider any postponed outer-join quals now that we have built up
  	 * equivalence classes.  (This could result in further additions or
  	 * mergings of classes.)
--- 176,181 ----
*************** query_planner(PlannerInfo *root, List *t
*** 226,231 ****
--- 220,232 ----
  	add_placeholders_to_base_rels(root);
  
  	/*
+ 	 * Create the LateralJoinInfo list now that we have finalized
+ 	 * PlaceHolderVar eval levels and made any necessary additions to the
+ 	 * lateral_vars lists for lateral references within PlaceHolderVars.
+ 	 */
+ 	create_lateral_join_info(root);
+ 
+ 	/*
  	 * We should now have size estimates for every actual table involved in
  	 * the query, and we also know which if any have been deleted from the
  	 * query by join removal; so we can compute total_table_pages.
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 52842931ec5552c04fdec41e013c36e1897fd4e4..1178b0fc99680d7677efdb8c9a7c0d3521772fe7 100644
*** a/src/backend/optimizer/prep/prepjointree.c
--- b/src/backend/optimizer/prep/prepjointree.c
*************** typedef struct pullup_replace_vars_conte
*** 41,46 ****
--- 41,48 ----
  	PlannerInfo *root;
  	List	   *targetlist;		/* tlist of subquery being pulled up */
  	RangeTblEntry *target_rte;	/* RTE of subquery */
+ 	Relids		relids;			/* relids within subquery, as numbered after
+ 								 * pullup (set only if target_rte->lateral) */
  	bool	   *outer_hasSubLinks;		/* -> outer query's hasSubLinks */
  	int			varno;			/* varno of subquery */
  	bool		need_phvs;		/* do we need PlaceHolderVars? */
*************** pull_up_simple_subquery(PlannerInfo *roo
*** 884,897 ****
  	/*
  	 * The subquery's targetlist items are now in the appropriate form to
  	 * insert into the top query, but if we are under an outer join then
! 	 * non-nullable items may have to be turned into PlaceHolderVars.  If we
! 	 * are dealing with an appendrel member then anything that's not a simple
! 	 * Var has to be turned into a PlaceHolderVar.	Set up appropriate context
! 	 * data for pullup_replace_vars.
  	 */
  	rvcontext.root = root;
  	rvcontext.targetlist = subquery->targetList;
  	rvcontext.target_rte = rte;
  	rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
  	rvcontext.varno = varno;
  	rvcontext.need_phvs = (lowest_nulling_outer_join != NULL ||
--- 886,904 ----
  	/*
  	 * The subquery's targetlist items are now in the appropriate form to
  	 * insert into the top query, but if we are under an outer join then
! 	 * non-nullable items and lateral references may have to be turned into
! 	 * PlaceHolderVars.  If we are dealing with an appendrel member then
! 	 * anything that's not a simple Var has to be turned into a
! 	 * PlaceHolderVar.	Set up required context data for pullup_replace_vars.
  	 */
  	rvcontext.root = root;
  	rvcontext.targetlist = subquery->targetList;
  	rvcontext.target_rte = rte;
+ 	if (rte->lateral)
+ 		rvcontext.relids = get_relids_in_jointree((Node *) subquery->jointree,
+ 												  true);
+ 	else	/* won't need relids */
+ 		rvcontext.relids = NULL;
  	rvcontext.outer_hasSubLinks = &parse->hasSubLinks;
  	rvcontext.varno = varno;
  	rvcontext.need_phvs = (lowest_nulling_outer_join != NULL ||
*************** pullup_replace_vars_callback(Var *var,
*** 1674,1681 ****
  			if (newnode && IsA(newnode, Var) &&
  				((Var *) newnode)->varlevelsup == 0)
  			{
! 				/* Simple Vars always escape being wrapped */
! 				wrap = false;
  			}
  			else if (newnode && IsA(newnode, PlaceHolderVar) &&
  					 ((PlaceHolderVar *) newnode)->phlevelsup == 0)
--- 1681,1698 ----
  			if (newnode && IsA(newnode, Var) &&
  				((Var *) newnode)->varlevelsup == 0)
  			{
! 				/*
! 				 * Simple Vars always escape being wrapped, unless they are
! 				 * lateral references to something outside the subquery being
! 				 * pulled up.  (Even then, we could omit the PlaceHolderVar if
! 				 * the referenced rel is under the same lowest outer join, but
! 				 * it doesn't seem worth the trouble to check that.)
! 				 */
! 				if (rcon->target_rte->lateral &&
! 					!bms_is_member(((Var *) newnode)->varno, rcon->relids))
! 					wrap = true;
! 				else
! 					wrap = false;
  			}
  			else if (newnode && IsA(newnode, PlaceHolderVar) &&
  					 ((PlaceHolderVar *) newnode)->phlevelsup == 0)
*************** pullup_replace_vars_callback(Var *var,
*** 1691,1699 ****
  			else
  			{
  				/*
! 				 * If it contains a Var of current level, and does not contain
! 				 * any non-strict constructs, then it's certainly nullable so
! 				 * we don't need to insert a PlaceHolderVar.
  				 *
  				 * This analysis could be tighter: in particular, a non-strict
  				 * construct hidden within a lower-level PlaceHolderVar is not
--- 1708,1717 ----
  			else
  			{
  				/*
! 				 * If it contains a Var of the subquery being pulled up, and
! 				 * does not contain any non-strict constructs, then it's
! 				 * certainly nullable so we don't need to insert a
! 				 * PlaceHolderVar.
  				 *
  				 * This analysis could be tighter: in particular, a non-strict
  				 * construct hidden within a lower-level PlaceHolderVar is not
*************** pullup_replace_vars_callback(Var *var,
*** 1702,1709 ****
  				 *
  				 * Note: in future maybe we should insert a PlaceHolderVar
  				 * anyway, if the tlist item is expensive to evaluate?
  				 */
! 				if (contain_vars_of_level((Node *) newnode, 0) &&
  					!contain_nonstrict_functions((Node *) newnode))
  				{
  					/* No wrap needed */
--- 1720,1733 ----
  				 *
  				 * Note: in future maybe we should insert a PlaceHolderVar
  				 * anyway, if the tlist item is expensive to evaluate?
+ 				 *
+ 				 * For a LATERAL subquery, we have to check the actual var
+ 				 * membership of the node, but if it's non-lateral then any
+ 				 * level-zero var must belong to the subquery.
  				 */
! 				if ((rcon->target_rte->lateral ?
! 				   bms_overlap(pull_varnos((Node *) newnode), rcon->relids) :
! 					 contain_vars_of_level((Node *) newnode, 0)) &&
  					!contain_nonstrict_functions((Node *) newnode))
  				{
  					/* No wrap needed */
diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c
index c2ff2229e2619355019d4d4f9d03c9b0c5ead0f2..b6f198b712137045a8fb88fa895609983987deca 100644
*** a/src/backend/optimizer/util/placeholder.c
--- b/src/backend/optimizer/util/placeholder.c
*************** find_placeholder_info(PlannerInfo *root,
*** 70,75 ****
--- 70,76 ----
  					  bool create_new_ph)
  {
  	PlaceHolderInfo *phinfo;
+ 	Relids		rels_used;
  	ListCell   *lc;
  
  	/* if this ever isn't true, we'd need to be able to look in parent lists */
*************** find_placeholder_info(PlannerInfo *root,
*** 90,96 ****
  
  	phinfo->phid = phv->phid;
  	phinfo->ph_var = copyObject(phv);
! 	phinfo->ph_eval_at = pull_varnos((Node *) phv);
  	/* ph_eval_at may change later, see update_placeholder_eval_levels */
  	phinfo->ph_needed = NULL;	/* initially it's unused */
  	phinfo->ph_may_need = NULL;
--- 91,114 ----
  
  	phinfo->phid = phv->phid;
  	phinfo->ph_var = copyObject(phv);
! 
! 	/*
! 	 * Any referenced rels that are outside the PHV's syntactic scope are
! 	 * LATERAL references, which should be included in ph_lateral but not in
! 	 * ph_eval_at.	If no referenced rels are within the syntactic scope,
! 	 * force evaluation at the syntactic location.
! 	 */
! 	rels_used = pull_varnos((Node *) phv->phexpr);
! 	phinfo->ph_lateral = bms_difference(rels_used, phv->phrels);
! 	if (bms_is_empty(phinfo->ph_lateral))
! 		phinfo->ph_lateral = NULL;		/* make it exactly NULL if empty */
! 	phinfo->ph_eval_at = bms_int_members(rels_used, phv->phrels);
! 	/* If no contained vars, force evaluation at syntactic location */
! 	if (bms_is_empty(phinfo->ph_eval_at))
! 	{
! 		phinfo->ph_eval_at = bms_copy(phv->phrels);
! 		Assert(!bms_is_empty(phinfo->ph_eval_at));
! 	}
  	/* ph_eval_at may change later, see update_placeholder_eval_levels */
  	phinfo->ph_needed = NULL;	/* initially it's unused */
  	phinfo->ph_may_need = NULL;
*************** void
*** 250,257 ****
  mark_placeholder_maybe_needed(PlannerInfo *root, PlaceHolderInfo *phinfo,
  							  Relids relids)
  {
- 	Relids		est_eval_level;
- 
  	/* Mark the PHV as possibly needed at the given syntactic level */
  	phinfo->ph_may_need = bms_add_members(phinfo->ph_may_need, relids);
  
--- 268,273 ----
*************** mark_placeholder_maybe_needed(PlannerInf
*** 260,277 ****
  	 * lower-level PHVs.  We need to get those into the PlaceHolderInfo list,
  	 * but they aren't going to be needed where the outer PHV is referenced.
  	 * Rather, they'll be needed where the outer PHV is evaluated.  We can
! 	 * estimate that conservatively as the syntactic location of the PHV's
! 	 * expression, but not less than the level of any Vars it contains.
! 	 * (Normally the Vars would come from below the syntactic location anyway,
! 	 * but this might not be true if the PHV contains any LATERAL references.)
  	 */
- 	est_eval_level = bms_union(phinfo->ph_var->phrels, phinfo->ph_eval_at);
- 
- 	/* Now recurse to take care of any such PHVs */
  	mark_placeholders_in_expr(root, (Node *) phinfo->ph_var->phexpr,
! 							  est_eval_level);
! 
! 	bms_free(est_eval_level);
  }
  
  /*
--- 276,286 ----
  	 * lower-level PHVs.  We need to get those into the PlaceHolderInfo list,
  	 * but they aren't going to be needed where the outer PHV is referenced.
  	 * Rather, they'll be needed where the outer PHV is evaluated.  We can
! 	 * estimate that (conservatively) as the syntactic location of the PHV's
! 	 * expression.	Recurse to take care of any such PHVs.
  	 */
  	mark_placeholders_in_expr(root, (Node *) phinfo->ph_var->phexpr,
! 							  phinfo->ph_var->phrels);
  }
  
  /*
*************** update_placeholder_eval_levels(PlannerIn
*** 361,366 ****
--- 370,378 ----
  			}
  		} while (found_some);
  
+ 		/* Can't move the PHV's eval_at level above its syntactic level */
+ 		Assert(bms_is_subset(eval_at, syn_level));
+ 
  		phinfo->ph_eval_at = eval_at;
  	}
  }
*************** update_placeholder_eval_levels(PlannerIn
*** 371,381 ****
   *
   * This is called after we've finished determining the eval_at levels for
   * all placeholders.  We need to make sure that all vars and placeholders
!  * needed to evaluate each placeholder will be available at the join level
!  * where the evaluation will be done.  Note that this loop can have
!  * side-effects on the ph_needed sets of other PlaceHolderInfos; that's okay
!  * because we don't examine ph_needed here, so there are no ordering issues
!  * to worry about.
   */
  void
  fix_placeholder_input_needed_levels(PlannerInfo *root)
--- 383,396 ----
   *
   * This is called after we've finished determining the eval_at levels for
   * all placeholders.  We need to make sure that all vars and placeholders
!  * needed to evaluate each placeholder will be available at the scan or join
!  * level where the evaluation will be done.  (It might seem that scan-level
!  * evaluations aren't interesting, but that's not so: a LATERAL reference
!  * within a placeholder's expression needs to cause the referenced var or
!  * placeholder to be marked as needed in the scan where it's evaluated.)
!  * Note that this loop can have side-effects on the ph_needed sets of other
!  * PlaceHolderInfos; that's okay because we don't examine ph_needed here, so
!  * there are no ordering issues to worry about.
   */
  void
  fix_placeholder_input_needed_levels(PlannerInfo *root)
*************** fix_placeholder_input_needed_levels(Plan
*** 385,411 ****
  	foreach(lc, root->placeholder_list)
  	{
  		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
! 		Relids		eval_at = phinfo->ph_eval_at;
! 
! 		/* No work unless it'll be evaluated above baserel level */
! 		if (bms_membership(eval_at) == BMS_MULTIPLE)
! 		{
! 			List	   *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
! 											   PVC_RECURSE_AGGREGATES,
! 											   PVC_INCLUDE_PLACEHOLDERS);
  
! 			add_vars_to_targetlist(root, vars, eval_at, false);
! 			list_free(vars);
! 		}
  	}
  }
  
  /*
   * add_placeholders_to_base_rels
!  *		Add any required PlaceHolderVars to base rels' targetlists.
   *
   * If any placeholder can be computed at a base rel and is needed above it,
!  * add it to that rel's targetlist.  This might look like it could be merged
   * with fix_placeholder_input_needed_levels, but it must be separate because
   * join removal happens in between, and can change the ph_eval_at sets.  There
   * is essentially the same logic in add_placeholders_to_joinrel, but we can't
--- 400,422 ----
  	foreach(lc, root->placeholder_list)
  	{
  		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
! 		List	   *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
! 										   PVC_RECURSE_AGGREGATES,
! 										   PVC_INCLUDE_PLACEHOLDERS);
  
! 		add_vars_to_targetlist(root, vars, phinfo->ph_eval_at, false);
! 		list_free(vars);
  	}
  }
  
  /*
   * add_placeholders_to_base_rels
!  *		Add any required PlaceHolderVars to base rels' targetlists, and
!  *		update lateral_vars lists for lateral references contained in them.
   *
   * If any placeholder can be computed at a base rel and is needed above it,
!  * add it to that rel's targetlist, and add any lateral references it requires
!  * to the rel's lateral_vars list.  This might look like it could be merged
   * with fix_placeholder_input_needed_levels, but it must be separate because
   * join removal happens in between, and can change the ph_eval_at sets.  There
   * is essentially the same logic in add_placeholders_to_joinrel, but we can't
*************** add_placeholders_to_base_rels(PlannerInf
*** 421,434 ****
  		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
  		Relids		eval_at = phinfo->ph_eval_at;
  
! 		if (bms_membership(eval_at) == BMS_SINGLETON &&
! 			bms_nonempty_difference(phinfo->ph_needed, eval_at))
  		{
  			int			varno = bms_singleton_member(eval_at);
  			RelOptInfo *rel = find_base_rel(root, varno);
  
! 			rel->reltargetlist = lappend(rel->reltargetlist,
! 										 copyObject(phinfo->ph_var));
  		}
  	}
  }
--- 432,483 ----
  		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
  		Relids		eval_at = phinfo->ph_eval_at;
  
! 		if (bms_membership(eval_at) == BMS_SINGLETON)
  		{
  			int			varno = bms_singleton_member(eval_at);
  			RelOptInfo *rel = find_base_rel(root, varno);
  
! 			/* add it to reltargetlist if needed above the rel scan level */
! 			if (bms_nonempty_difference(phinfo->ph_needed, eval_at))
! 				rel->reltargetlist = lappend(rel->reltargetlist,
! 											 copyObject(phinfo->ph_var));
! 			/* if there are lateral refs in it, add them to lateral_vars */
! 			if (phinfo->ph_lateral != NULL)
! 			{
! 				List	   *vars = pull_var_clause((Node *) phinfo->ph_var->phexpr,
! 												   PVC_RECURSE_AGGREGATES,
! 												   PVC_INCLUDE_PLACEHOLDERS);
! 				ListCell   *lc2;
! 
! 				foreach(lc2, vars)
! 				{
! 					Node	   *node = (Node *) lfirst(lc2);
! 
! 					if (IsA(node, Var))
! 					{
! 						Var		   *var = (Var *) node;
! 
! 						if (var->varno != varno)
! 							rel->lateral_vars = lappend(rel->lateral_vars,
! 														var);
! 					}
! 					else if (IsA(node, PlaceHolderVar))
! 					{
! 						PlaceHolderVar *other_phv = (PlaceHolderVar *) node;
! 						PlaceHolderInfo *other_phi;
! 
! 						other_phi = find_placeholder_info(root, other_phv,
! 														  false);
! 						if (!bms_is_subset(other_phi->ph_eval_at, eval_at))
! 							rel->lateral_vars = lappend(rel->lateral_vars,
! 														other_phv);
! 					}
! 					else
! 						Assert(false);
! 				}
! 
! 				list_free(vars);
! 			}
  		}
  	}
  }
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 7eaf8d27bf08bf5dd1776d203876adb8396c73b3..adde10a85ab05f031e0a792f0c27cebdeabea9d2 100644
*** a/src/backend/optimizer/util/var.c
--- b/src/backend/optimizer/util/var.c
*************** pull_varnos_walker(Node *node, pull_varn
*** 161,168 ****
  	if (IsA(node, PlaceHolderVar))
  	{
  		/*
! 		 * Normally, we can just take the varnos in the contained expression.
! 		 * But if it is variable-free, use the PHV's syntactic relids.
  		 */
  		PlaceHolderVar *phv = (PlaceHolderVar *) node;
  		pull_varnos_context subcontext;
--- 161,173 ----
  	if (IsA(node, PlaceHolderVar))
  	{
  		/*
! 		 * A PlaceHolderVar acts as a variable of its syntactic scope, or
! 		 * lower than that if it references only a subset of the rels in its
! 		 * syntactic scope.  It might also contain lateral references, but we
! 		 * should ignore such references when computing the set of varnos in
! 		 * an expression tree.	Also, if the PHV contains no variables within
! 		 * its syntactic scope, it will be forced to be evaluated exactly at
! 		 * the syntactic scope, so take that as the relid set.
  		 */
  		PlaceHolderVar *phv = (PlaceHolderVar *) node;
  		pull_varnos_context subcontext;
*************** pull_varnos_walker(Node *node, pull_varn
*** 170,181 ****
  		subcontext.varnos = NULL;
  		subcontext.sublevels_up = context->sublevels_up;
  		(void) pull_varnos_walker((Node *) phv->phexpr, &subcontext);
! 
! 		if (bms_is_empty(subcontext.varnos) &&
! 			phv->phlevelsup == context->sublevels_up)
! 			context->varnos = bms_add_members(context->varnos, phv->phrels);
! 		else
! 			context->varnos = bms_join(context->varnos, subcontext.varnos);
  		return false;
  	}
  	if (IsA(node, Query))
--- 175,189 ----
  		subcontext.varnos = NULL;
  		subcontext.sublevels_up = context->sublevels_up;
  		(void) pull_varnos_walker((Node *) phv->phexpr, &subcontext);
! 		if (phv->phlevelsup == context->sublevels_up)
! 		{
! 			subcontext.varnos = bms_int_members(subcontext.varnos,
! 												phv->phrels);
! 			if (bms_is_empty(subcontext.varnos))
! 				context->varnos = bms_add_members(context->varnos,
! 												  phv->phrels);
! 		}
! 		context->varnos = bms_join(context->varnos, subcontext.varnos);
  		return false;
  	}
  	if (IsA(node, Query))
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index c0a636b9d7c9f6dc4f4367188741af0d07cef3c3..7a868079d87e257cd17a21c83280406dacdd789e 100644
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct SpecialJoinInfo
*** 1344,1373 ****
  /*
   * "Lateral join" info.
   *
!  * Lateral references in subqueries constrain the join order in a way that's
!  * somewhat like outer joins, though different in detail.  We construct one or
!  * more LateralJoinInfos for each RTE with lateral references, and add them to
!  * the PlannerInfo node's lateral_info_list.
   *
!  * lateral_rhs is the relid of a baserel with lateral references, and
!  * lateral_lhs is a set of relids of baserels it references, all of which
!  * must be present on the LHS to compute a parameter needed by the RHS.
!  * Typically, lateral_lhs is a singleton, but it can include multiple rels
!  * if the RHS references a PlaceHolderVar with a multi-rel ph_eval_at level.
!  * We disallow joining to only part of the LHS in such cases, since that would
!  * result in a join tree with no convenient place to compute the PHV.
   *
   * When an appendrel contains lateral references (eg "LATERAL (SELECT x.col1
   * UNION ALL SELECT y.col2)"), the LateralJoinInfos reference the parent
   * baserel not the member otherrels, since it is the parent relid that is
   * considered for joining purposes.
   */
  
  typedef struct LateralJoinInfo
  {
  	NodeTag		type;
! 	Index		lateral_rhs;	/* a baserel containing lateral refs */
! 	Relids		lateral_lhs;	/* some base relids it references */
  } LateralJoinInfo;
  
  /*
--- 1344,1381 ----
  /*
   * "Lateral join" info.
   *
!  * Lateral references constrain the join order in a way that's somewhat like
!  * outer joins, though different in detail.  We construct a LateralJoinInfo
!  * for each lateral cross-reference, placing them in the PlannerInfo node's
!  * lateral_info_list.
   *
!  * For unflattened LATERAL RTEs, we generate LateralJoinInfo(s) in which
!  * lateral_rhs is the relid of the LATERAL baserel, and lateral_lhs is a set
!  * of relids of baserels it references, all of which must be present on the
!  * LHS to compute a parameter needed by the RHS.  Typically, lateral_lhs is
!  * a singleton, but it can include multiple rels if the RHS references a
!  * PlaceHolderVar with a multi-rel ph_eval_at level.  We disallow joining to
!  * only part of the LHS in such cases, since that would result in a join tree
!  * with no convenient place to compute the PHV.
   *
   * When an appendrel contains lateral references (eg "LATERAL (SELECT x.col1
   * UNION ALL SELECT y.col2)"), the LateralJoinInfos reference the parent
   * baserel not the member otherrels, since it is the parent relid that is
   * considered for joining purposes.
+  *
+  * If any LATERAL RTEs were flattened into the parent query, it is possible
+  * that the query now contains PlaceHolderVars containing lateral references,
+  * representing expressions that need to be evaluated at particular spots in
+  * the jointree but contain lateral references to Vars from elsewhere.	These
+  * give rise to LateralJoinInfos in which lateral_rhs is the evaluation point
+  * of a PlaceHolderVar and lateral_lhs is the set of lateral rels it needs.
   */
  
  typedef struct LateralJoinInfo
  {
  	NodeTag		type;
! 	Relids		lateral_lhs;	/* rels needed to compute a lateral value */
! 	Relids		lateral_rhs;	/* rel where lateral value is needed */
  } LateralJoinInfo;
  
  /*
*************** typedef struct AppendRelInfo
*** 1465,1470 ****
--- 1473,1482 ----
   * then allow it to bubble up like a Var until the ph_needed join level.
   * ph_needed has the same definition as attr_needed for a regular Var.
   *
+  * The PlaceHolderVar's expression might contain LATERAL references to vars
+  * coming from outside its syntactic scope.  If so, those rels are *not*
+  * included in ph_eval_at, but they are recorded in ph_lateral.
+  *
   * ph_may_need is an initial estimate of ph_needed, formed using the
   * syntactic locations of references to the PHV.  We need this in order to
   * determine whether the PHV reference forces a join ordering constraint:
*************** typedef struct PlaceHolderInfo
*** 1490,1495 ****
--- 1502,1508 ----
  	Index		phid;			/* ID for PH (unique within planner run) */
  	PlaceHolderVar *ph_var;		/* copy of PlaceHolderVar tree */
  	Relids		ph_eval_at;		/* lowest level we can evaluate value at */
+ 	Relids		ph_lateral;		/* relids of contained lateral refs, if any */
  	Relids		ph_needed;		/* highest level the value is needed at */
  	Relids		ph_may_need;	/* highest level it might be needed at */
  	int32		ph_width;		/* estimated attribute width */
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index 31c2a320a6d1aa47ef355c6901266d7a1aeb518b..afa2a13326408a7b10619408f713c58f7cc748c2 100644
*** a/src/test/regress/expected/join.out
--- b/src/test/regress/expected/join.out
*************** select v.* from
*** 3577,3582 ****
--- 3577,3721 ----
   -4567890123456789 |                  
  (20 rows)
  
+ explain (verbose, costs off)
+ select * from
+   int8_tbl a left join
+   lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;
+                 QUERY PLAN                
+ ------------------------------------------
+  Nested Loop Left Join
+    Output: a.q1, a.q2, b.q1, b.q2, (a.q2)
+    ->  Seq Scan on public.int8_tbl a
+          Output: a.q1, a.q2
+    ->  Seq Scan on public.int8_tbl b
+          Output: b.q1, b.q2, a.q2
+          Filter: (a.q2 = b.q1)
+ (7 rows)
+ 
+ select * from
+   int8_tbl a left join
+   lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;
+         q1        |        q2         |        q1        |        q2         |        x         
+ ------------------+-------------------+------------------+-------------------+------------------
+               123 |               456 |                  |                   |                 
+               123 |  4567890123456789 | 4567890123456789 |               123 | 4567890123456789
+               123 |  4567890123456789 | 4567890123456789 |  4567890123456789 | 4567890123456789
+               123 |  4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789
+  4567890123456789 |               123 |              123 |               456 |              123
+  4567890123456789 |               123 |              123 |  4567890123456789 |              123
+  4567890123456789 |  4567890123456789 | 4567890123456789 |               123 | 4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 |  4567890123456789 | 4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789
+  4567890123456789 | -4567890123456789 |                  |                   |                 
+ (10 rows)
+ 
+ explain (verbose, costs off)
+ select * from
+   int8_tbl a left join
+   lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;
+                            QUERY PLAN                           
+ ----------------------------------------------------------------
+  Nested Loop Left Join
+    Output: a.q1, a.q2, b.q1, b.q2, (COALESCE(a.q2, 42::bigint))
+    ->  Seq Scan on public.int8_tbl a
+          Output: a.q1, a.q2
+    ->  Seq Scan on public.int8_tbl b
+          Output: b.q1, b.q2, COALESCE(a.q2, 42::bigint)
+          Filter: (a.q2 = b.q1)
+ (7 rows)
+ 
+ select * from
+   int8_tbl a left join
+   lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;
+         q1        |        q2         |        q1        |        q2         |        x         
+ ------------------+-------------------+------------------+-------------------+------------------
+               123 |               456 |                  |                   |                 
+               123 |  4567890123456789 | 4567890123456789 |               123 | 4567890123456789
+               123 |  4567890123456789 | 4567890123456789 |  4567890123456789 | 4567890123456789
+               123 |  4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789
+  4567890123456789 |               123 |              123 |               456 |              123
+  4567890123456789 |               123 |              123 |  4567890123456789 |              123
+  4567890123456789 |  4567890123456789 | 4567890123456789 |               123 | 4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 |  4567890123456789 | 4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789
+  4567890123456789 | -4567890123456789 |                  |                   |                 
+ (10 rows)
+ 
+ -- lateral reference in a PlaceHolderVar evaluated at join level
+ explain (verbose, costs off)
+ select * from
+   int8_tbl a left join lateral
+   (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from
+    int8_tbl b cross join int8_tbl c) ss
+   on a.q2 = ss.bq1;
+                          QUERY PLAN                          
+ -------------------------------------------------------------
+  Nested Loop Left Join
+    Output: a.q1, a.q2, b.q1, c.q1, (LEAST(a.q1, b.q1, c.q1))
+    ->  Seq Scan on public.int8_tbl a
+          Output: a.q1, a.q2
+    ->  Nested Loop
+          Output: b.q1, c.q1, LEAST(a.q1, b.q1, c.q1)
+          Join Filter: (a.q2 = b.q1)
+          ->  Seq Scan on public.int8_tbl b
+                Output: b.q1, b.q2
+          ->  Materialize
+                Output: c.q1
+                ->  Seq Scan on public.int8_tbl c
+                      Output: c.q1
+ (13 rows)
+ 
+ select * from
+   int8_tbl a left join lateral
+   (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from
+    int8_tbl b cross join int8_tbl c) ss
+   on a.q2 = ss.bq1;
+         q1        |        q2         |       bq1        |       cq1        |      least       
+ ------------------+-------------------+------------------+------------------+------------------
+               123 |               456 |                  |                  |                 
+               123 |  4567890123456789 | 4567890123456789 |              123 |              123
+               123 |  4567890123456789 | 4567890123456789 |              123 |              123
+               123 |  4567890123456789 | 4567890123456789 | 4567890123456789 |              123
+               123 |  4567890123456789 | 4567890123456789 | 4567890123456789 |              123
+               123 |  4567890123456789 | 4567890123456789 | 4567890123456789 |              123
+               123 |  4567890123456789 | 4567890123456789 |              123 |              123
+               123 |  4567890123456789 | 4567890123456789 |              123 |              123
+               123 |  4567890123456789 | 4567890123456789 | 4567890123456789 |              123
+               123 |  4567890123456789 | 4567890123456789 | 4567890123456789 |              123
+               123 |  4567890123456789 | 4567890123456789 | 4567890123456789 |              123
+               123 |  4567890123456789 | 4567890123456789 |              123 |              123
+               123 |  4567890123456789 | 4567890123456789 |              123 |              123
+               123 |  4567890123456789 | 4567890123456789 | 4567890123456789 |              123
+               123 |  4567890123456789 | 4567890123456789 | 4567890123456789 |              123
+               123 |  4567890123456789 | 4567890123456789 | 4567890123456789 |              123
+  4567890123456789 |               123 |              123 |              123 |              123
+  4567890123456789 |               123 |              123 |              123 |              123
+  4567890123456789 |               123 |              123 | 4567890123456789 |              123
+  4567890123456789 |               123 |              123 | 4567890123456789 |              123
+  4567890123456789 |               123 |              123 | 4567890123456789 |              123
+  4567890123456789 |               123 |              123 |              123 |              123
+  4567890123456789 |               123 |              123 |              123 |              123
+  4567890123456789 |               123 |              123 | 4567890123456789 |              123
+  4567890123456789 |               123 |              123 | 4567890123456789 |              123
+  4567890123456789 |               123 |              123 | 4567890123456789 |              123
+  4567890123456789 |  4567890123456789 | 4567890123456789 |              123 |              123
+  4567890123456789 |  4567890123456789 | 4567890123456789 |              123 |              123
+  4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 |              123 |              123
+  4567890123456789 |  4567890123456789 | 4567890123456789 |              123 |              123
+  4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 |              123 |              123
+  4567890123456789 |  4567890123456789 | 4567890123456789 |              123 |              123
+  4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+  4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789
+  4567890123456789 | -4567890123456789 |                  |                  |                 
+ (42 rows)
+ 
  -- case requiring nested PlaceHolderVars
  explain (verbose, costs off)
  select * from
*************** select * from
*** 3595,3601 ****
           Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, 42::bigint)), (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2))
           Hash Cond: (d.q1 = c.q2)
           ->  Nested Loop
!                Output: a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, 42::bigint)), COALESCE((COALESCE(b.q2, 42::bigint)), d.q2)
                 ->  Hash Left Join
                       Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, 42::bigint))
                       Hash Cond: (a.q2 = b.q1)
--- 3734,3740 ----
           Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, 42::bigint)), (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2))
           Hash Cond: (d.q1 = c.q2)
           ->  Nested Loop
!                Output: a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, 42::bigint)), (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2))
                 ->  Hash Left Join
                       Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, 42::bigint))
                       Hash Cond: (a.q2 = b.q1)
*************** select * from
*** 3605,3621 ****
                             Output: b.q1, (COALESCE(b.q2, 42::bigint))
                             ->  Seq Scan on public.int8_tbl b
                                   Output: b.q1, COALESCE(b.q2, 42::bigint)
!                ->  Materialize
!                      Output: d.q1, d.q2
!                      ->  Seq Scan on public.int8_tbl d
!                            Output: d.q1, d.q2
           ->  Hash
                 Output: c.q1, c.q2
                 ->  Seq Scan on public.int8_tbl c
                       Output: c.q1, c.q2
     ->  Result
           Output: (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2))
! (26 rows)
  
  -- test some error cases where LATERAL should have been used but wasn't
  select f1,g from int4_tbl a, (select f1 as g) ss;
--- 3744,3758 ----
                             Output: b.q1, (COALESCE(b.q2, 42::bigint))
                             ->  Seq Scan on public.int8_tbl b
                                   Output: b.q1, COALESCE(b.q2, 42::bigint)
!                ->  Seq Scan on public.int8_tbl d
!                      Output: d.q1, COALESCE((COALESCE(b.q2, 42::bigint)), d.q2)
           ->  Hash
                 Output: c.q1, c.q2
                 ->  Seq Scan on public.int8_tbl c
                       Output: c.q1, c.q2
     ->  Result
           Output: (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2))
! (24 rows)
  
  -- test some error cases where LATERAL should have been used but wasn't
  select f1,g from int4_tbl a, (select f1 as g) ss;
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index 656766acd3fbb499ef41da99e63bc79fcea0b108..f6009141d2437341d7a9c42d896977acf1f88743 100644
*** a/src/test/regress/sql/join.sql
--- b/src/test/regress/sql/join.sql
*************** select v.* from
*** 995,1000 ****
--- 995,1028 ----
    left join int4_tbl z on z.f1 = x.q2,
    lateral (select x.q1,y.q1 from dual union all select x.q2,y.q2 from dual) v(vx,vy);
  
+ explain (verbose, costs off)
+ select * from
+   int8_tbl a left join
+   lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;
+ select * from
+   int8_tbl a left join
+   lateral (select *, a.q2 as x from int8_tbl b) ss on a.q2 = ss.q1;
+ explain (verbose, costs off)
+ select * from
+   int8_tbl a left join
+   lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;
+ select * from
+   int8_tbl a left join
+   lateral (select *, coalesce(a.q2, 42) as x from int8_tbl b) ss on a.q2 = ss.q1;
+ 
+ -- lateral reference in a PlaceHolderVar evaluated at join level
+ explain (verbose, costs off)
+ select * from
+   int8_tbl a left join lateral
+   (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from
+    int8_tbl b cross join int8_tbl c) ss
+   on a.q2 = ss.bq1;
+ select * from
+   int8_tbl a left join lateral
+   (select b.q1 as bq1, c.q1 as cq1, least(a.q1,b.q1,c.q1) from
+    int8_tbl b cross join int8_tbl c) ss
+   on a.q2 = ss.bq1;
+ 
  -- case requiring nested PlaceHolderVars
  explain (verbose, costs off)
  select * from
