diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 799930ad611b6e09403a3d14c5738f47cb2f3bab..f72f62b4c391e6ad42744c9c54be02fa6af716a9 100644
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
*************** _copyRangeTblEntry(const RangeTblEntry *
*** 1973,1978 ****
--- 1973,1979 ----
  	COPY_NODE_FIELD(ctecolcollations);
  	COPY_NODE_FIELD(alias);
  	COPY_NODE_FIELD(eref);
+ 	COPY_SCALAR_FIELD(lateral);
  	COPY_SCALAR_FIELD(inh);
  	COPY_SCALAR_FIELD(inFromCl);
  	COPY_SCALAR_FIELD(requiredPerms);
*************** _copyRangeSubselect(const RangeSubselect
*** 2250,2255 ****
--- 2251,2257 ----
  {
  	RangeSubselect *newnode = makeNode(RangeSubselect);
  
+ 	COPY_SCALAR_FIELD(lateral);
  	COPY_NODE_FIELD(subquery);
  	COPY_NODE_FIELD(alias);
  
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 802b0636714d2c34013275132c531ebc7ae1bdd6..cb6e272a22f9a13bdc5bb85ef99d7933b775a1f9 100644
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
*************** _equalWindowDef(const WindowDef *a, cons
*** 2161,2166 ****
--- 2161,2167 ----
  static bool
  _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b)
  {
+ 	COMPARE_SCALAR_FIELD(lateral);
  	COMPARE_NODE_FIELD(subquery);
  	COMPARE_NODE_FIELD(alias);
  
*************** _equalRangeTblEntry(const RangeTblEntry 
*** 2287,2292 ****
--- 2288,2294 ----
  	COMPARE_NODE_FIELD(ctecolcollations);
  	COMPARE_NODE_FIELD(alias);
  	COMPARE_NODE_FIELD(eref);
+ 	COMPARE_SCALAR_FIELD(lateral);
  	COMPARE_SCALAR_FIELD(inh);
  	COMPARE_SCALAR_FIELD(inFromCl);
  	COMPARE_SCALAR_FIELD(requiredPerms);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index b83bd1c9fdb132d8b76e65759deade49b73fb4eb..b8219af16d16f93b498f3d1e37b7a4e6e43cb934 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outRangeTblEntry(StringInfo str, const 
*** 2362,2367 ****
--- 2362,2368 ----
  			break;
  	}
  
+ 	WRITE_BOOL_FIELD(lateral);
  	WRITE_BOOL_FIELD(inh);
  	WRITE_BOOL_FIELD(inFromCl);
  	WRITE_UINT_FIELD(requiredPerms);
*************** _outRangeSubselect(StringInfo str, const
*** 2565,2570 ****
--- 2566,2572 ----
  {
  	WRITE_NODE_TYPE("RANGESUBSELECT");
  
+ 	WRITE_BOOL_FIELD(lateral);
  	WRITE_NODE_FIELD(subquery);
  	WRITE_NODE_FIELD(alias);
  }
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index ff77cefd07be34dc0a3363b7b1c8db85a4691e38..1eb7582914e75276967da46bb6db45fe3648167c 100644
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
*************** _readRangeTblEntry(void)
*** 1222,1227 ****
--- 1222,1228 ----
  			break;
  	}
  
+ 	READ_BOOL_FIELD(lateral);
  	READ_BOOL_FIELD(inh);
  	READ_BOOL_FIELD(inFromCl);
  	READ_UINT_FIELD(requiredPerms);
diff --git a/src/backend/optimizer/geqo/geqo_eval.c b/src/backend/optimizer/geqo/geqo_eval.c
index 1e352fd3b5ea7e9961a6befe0399697a0c863e54..8a1ca78ee66f60547f6c4b4db84b0dd2a034b999 100644
*** a/src/backend/optimizer/geqo/geqo_eval.c
--- b/src/backend/optimizer/geqo/geqo_eval.c
*************** geqo_eval(PlannerInfo *root, Gene *tour,
*** 105,110 ****
--- 105,112 ----
  	 *
  	 * XXX geqo does not currently support optimization for partial result
  	 * retrieval --- how to fix?
+ 	 *
+ 	 * XXX what happens in LATERAL cases?
  	 */
  	fitness = joinrel->cheapest_total_path->total_cost;
  
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index f02954982a7e41619a0282f2ce095282b30d9cd8..e911b6c9849094dbc5828d42b77db614223ba0e3 100644
*** a/src/backend/optimizer/path/allpaths.c
--- b/src/backend/optimizer/path/allpaths.c
*************** set_append_rel_pathlist(PlannerInfo *roo
*** 701,706 ****
--- 701,708 ----
  		/*
  		 * Child is live, so add its cheapest access path to the Append path
  		 * we are constructing for the parent.
+ 		 *
+ 		 * XXX what if child is LATERAL?
  		 */
  		subpaths = accumulate_append_subpath(subpaths,
  											 childrel->cheapest_total_path);
*************** generate_mergeappend_paths(PlannerInfo *
*** 903,908 ****
--- 905,912 ----
  			/*
  			 * If we can't find any paths with the right order just use the
  			 * cheapest-total path; we'll have to sort it later.
+ 			 *
+ 			 * XXX what if child is LATERAL?
  			 */
  			if (cheapest_startup == NULL || cheapest_total == NULL)
  			{
*************** set_subquery_pathlist(PlannerInfo *root,
*** 1021,1026 ****
--- 1025,1031 ----
  {
  	Query	   *parse = root->parse;
  	Query	   *subquery = rte->subquery;
+ 	Relids		required_outer;
  	bool	   *differentTypes;
  	double		tuple_fraction;
  	PlannerInfo *subroot;
*************** set_subquery_pathlist(PlannerInfo *root,
*** 1033,1038 ****
--- 1038,1053 ----
  	 */
  	subquery = copyObject(subquery);
  
+ 	/*
+ 	 * If it's a LATERAL subquery, it might contain some Vars of the current
+ 	 * query level, requiring it to be treated as parameterized.
+ 	 */
+ 	required_outer = pull_varnos_of_level((Node *) subquery, 1);
+ 
+ 	/* Enforce convention that empty required_outer sets are exactly NULL */
+ 	if (bms_is_empty(required_outer))
+ 		required_outer = NULL;
+ 
  	/* We need a workspace for keeping track of set-op type coercions */
  	differentTypes = (bool *)
  		palloc0((list_length(subquery->targetList) + 1) * sizeof(bool));
*************** set_subquery_pathlist(PlannerInfo *root,
*** 1134,1140 ****
  	pathkeys = convert_subquery_pathkeys(root, rel, subroot->query_pathkeys);
  
  	/* Generate appropriate path */
! 	add_path(rel, create_subqueryscan_path(root, rel, pathkeys, NULL));
  
  	/* Select cheapest path (pretty easy in this case...) */
  	set_cheapest(rel);
--- 1149,1155 ----
  	pathkeys = convert_subquery_pathkeys(root, rel, subroot->query_pathkeys);
  
  	/* Generate appropriate path */
! 	add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
  
  	/* Select cheapest path (pretty easy in this case...) */
  	set_cheapest(rel);
*************** debug_print_rel(PlannerInfo *root, RelOp
*** 1988,1997 ****
  	printf("\tpath list:\n");
  	foreach(l, rel->pathlist)
  		print_path(root, lfirst(l), 1);
! 	printf("\n\tcheapest startup path:\n");
! 	print_path(root, rel->cheapest_startup_path, 1);
! 	printf("\n\tcheapest total path:\n");
! 	print_path(root, rel->cheapest_total_path, 1);
  	printf("\n");
  	fflush(stdout);
  }
--- 2003,2018 ----
  	printf("\tpath list:\n");
  	foreach(l, rel->pathlist)
  		print_path(root, lfirst(l), 1);
! 	if (rel->cheapest_startup_path)
! 	{
! 		printf("\n\tcheapest startup path:\n");
! 		print_path(root, rel->cheapest_startup_path, 1);
! 	}
! 	if (rel->cheapest_total_path)
! 	{
! 		printf("\n\tcheapest total path:\n");
! 		print_path(root, rel->cheapest_total_path, 1);
! 	}
  	printf("\n");
  	fflush(stdout);
  }
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 65f86194e155508baa2e7c690a08be7d0380299a..3be0e11a9492d297d530c4ada73bfd233b35d3e5 100644
*** a/src/backend/optimizer/path/joinpath.c
--- b/src/backend/optimizer/path/joinpath.c
*************** sort_inner_and_outer(PlannerInfo *root,
*** 491,502 ****
  	 * explosion of mergejoin paths of dubious value.  This interacts with
  	 * decisions elsewhere that also discriminate against mergejoins with
  	 * parameterized inputs; see comments in src/backend/optimizer/README.
- 	 *
- 	 * If unique-ification is requested, do it and then handle as a plain
- 	 * inner join.
  	 */
  	outer_path = outerrel->cheapest_total_path;
  	inner_path = innerrel->cheapest_total_path;
  	if (jointype == JOIN_UNIQUE_OUTER)
  	{
  		outer_path = (Path *) create_unique_path(root, outerrel,
--- 491,508 ----
  	 * explosion of mergejoin paths of dubious value.  This interacts with
  	 * decisions elsewhere that also discriminate against mergejoins with
  	 * parameterized inputs; see comments in src/backend/optimizer/README.
  	 */
  	outer_path = outerrel->cheapest_total_path;
  	inner_path = innerrel->cheapest_total_path;
+ 
+ 	/* Punt if either rel has only parameterized paths */
+ 	if (!outer_path || !inner_path)
+ 		return;
+ 
+ 	/*
+ 	 * If unique-ification is requested, do it and then handle as a plain
+ 	 * inner join.
+ 	 */
  	if (jointype == JOIN_UNIQUE_OUTER)
  	{
  		outer_path = (Path *) create_unique_path(root, outerrel,
*************** match_unsorted_outer(PlannerInfo *root,
*** 692,698 ****
  
  	/*
  	 * If we need to unique-ify the inner path, we will consider only the
! 	 * cheapest-total inner.
  	 */
  	if (save_jointype == JOIN_UNIQUE_INNER)
  	{
--- 698,704 ----
  
  	/*
  	 * If we need to unique-ify the inner path, we will consider only the
! 	 * cheapest-total inner.  XXX what about LATERAL?
  	 */
  	if (save_jointype == JOIN_UNIQUE_INNER)
  	{
*************** match_unsorted_outer(PlannerInfo *root,
*** 707,713 ****
  		 * enable_material is off or the path in question materializes its
  		 * output anyway.
  		 */
! 		if (enable_material &&
  			!ExecMaterializesOutput(inner_cheapest_total->pathtype))
  			matpath = (Path *)
  				create_material_path(innerrel, inner_cheapest_total);
--- 713,719 ----
  		 * enable_material is off or the path in question materializes its
  		 * output anyway.
  		 */
! 		if (enable_material && inner_cheapest_total != NULL &&
  			!ExecMaterializesOutput(inner_cheapest_total->pathtype))
  			matpath = (Path *)
  				create_material_path(innerrel, inner_cheapest_total);
*************** match_unsorted_outer(PlannerInfo *root,
*** 735,740 ****
--- 741,748 ----
  		 * If we need to unique-ify the outer path, it's pointless to consider
  		 * any but the cheapest outer.	(XXX we don't consider parameterized
  		 * outers, nor inners, for unique-ified cases.	Should we?)
+ 		 *
+ 		 * XXX wrong for LATERAL?
  		 */
  		if (save_jointype == JOIN_UNIQUE_OUTER)
  		{
*************** match_unsorted_outer(PlannerInfo *root,
*** 814,819 ****
--- 822,831 ----
  		if (save_jointype == JOIN_UNIQUE_OUTER)
  			continue;
  
+ 		/* Can't do anything else if inner has no unparameterized paths */
+ 		if (!inner_cheapest_total)
+ 			continue;
+ 
  		/* Look for useful mergeclauses (if any) */
  		mergeclauses = find_mergeclauses_for_pathkeys(root,
  													  outerpath->pathkeys,
*************** hash_inner_and_outer(PlannerInfo *root,
*** 1092,1097 ****
--- 1104,1115 ----
  		Path	   *cheapest_total_outer = outerrel->cheapest_total_path;
  		Path	   *cheapest_total_inner = innerrel->cheapest_total_path;
  
+ 		/* Punt if either rel has only parameterized paths */
+ 		if (!cheapest_startup_outer ||
+ 			!cheapest_total_outer ||
+ 			!cheapest_total_inner)
+ 			return;
+ 
  		/* Unique-ify if need be; we ignore parameterized possibilities */
  		if (jointype == JOIN_UNIQUE_OUTER)
  		{
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index e6a0f8dab6db311b816b2c139eb5dbc343e181ee..d7887da7a2a73ee282481a424b261359409de342 100644
*** a/src/backend/optimizer/path/joinrels.c
--- b/src/backend/optimizer/path/joinrels.c
*************** join_is_legal(PlannerInfo *root, RelOptI
*** 383,388 ****
--- 383,389 ----
  		}
  		else if (sjinfo->jointype == JOIN_SEMI &&
  				 bms_equal(sjinfo->syn_righthand, rel2->relids) &&
+ 				 rel2->cheapest_total_path != NULL &&
  				 create_unique_path(root, rel2, rel2->cheapest_total_path,
  									sjinfo) != NULL)
  		{
*************** join_is_legal(PlannerInfo *root, RelOptI
*** 416,421 ****
--- 417,423 ----
  		}
  		else if (sjinfo->jointype == JOIN_SEMI &&
  				 bms_equal(sjinfo->syn_righthand, rel1->relids) &&
+ 				 rel1->cheapest_total_path != NULL &&
  				 create_unique_path(root, rel1, rel1->cheapest_total_path,
  									sjinfo) != NULL)
  		{
*************** make_join_rel(PlannerInfo *root, RelOptI
*** 672,677 ****
--- 674,680 ----
  			 * anyway to be sure.)
  			 */
  			if (bms_equal(sjinfo->syn_righthand, rel2->relids) &&
+ 				rel2->cheapest_total_path != NULL &&
  				create_unique_path(root, rel2, rel2->cheapest_total_path,
  								   sjinfo) != NULL)
  			{
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 414406bb8a13422dae87086aa1114b72683da0f1..572711c887bd62bd303bb98191d77ef7555c3167 100644
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
*************** static HashJoin *create_hashjoin_plan(Pl
*** 84,89 ****
--- 84,90 ----
  					 Plan *outer_plan, Plan *inner_plan);
  static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
  static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
+ static void identify_nestloop_extparams(PlannerInfo *root, Plan *subplan);
  static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path);
  static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path);
  static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol);
*************** create_subqueryscan_plan(PlannerInfo *ro
*** 1640,1645 ****
--- 1641,1647 ----
  	{
  		scan_clauses = (List *)
  			replace_nestloop_params(root, (Node *) scan_clauses);
+ 		identify_nestloop_extparams(root, best_path->parent->subplan);
  	}
  
  	scan_plan = make_subqueryscan(tlist,
*************** replace_nestloop_params_mutator(Node *no
*** 2560,2565 ****
--- 2562,2663 ----
  }
  
  /*
+  * identify_nestloop_extparams
+  *	  Identify extParams of a parameterized subquery that need to be fed
+  *	  from an outer nestloop.
+  *
+  * The subplan's references to the outer variables are already represented
+  * as PARAM_EXEC Params, so we need not modify the subplan here.  What we
+  * do need to do is add entries to root->curOuterParams to signal the parent
+  * nestloop plan node that it must provide these values.
+  */
+ static void
+ identify_nestloop_extparams(PlannerInfo *root, Plan *subplan)
+ {
+ 	Bitmapset  *tmpset;
+ 	int			paramid;
+ 
+ 	/* Examine each extParam of the subquery's plan */
+ 	tmpset = bms_copy(subplan->extParam);
+ 	while ((paramid = bms_first_member(tmpset)) >= 0)
+ 	{
+ 		PlannerParamItem *pitem = list_nth(root->glob->paramlist, paramid);
+ 
+ 		/* Ignore anything coming from an upper query level */
+ 		if (pitem->abslevel != root->query_level)
+ 			continue;
+ 
+ 		if (IsA(pitem->item, Var))
+ 		{
+ 			Var		   *var = (Var *) pitem->item;
+ 			NestLoopParam *nlp;
+ 			ListCell   *lc;
+ 
+ 			/* If not from a nestloop outer rel, nothing to do */
+ 			if (!bms_is_member(var->varno, root->curOuterRels))
+ 				continue;
+ 			/* Is this param already listed in root->curOuterParams? */
+ 			foreach(lc, root->curOuterParams)
+ 			{
+ 				nlp = (NestLoopParam *) lfirst(lc);
+ 				if (nlp->paramno == paramid)
+ 				{
+ 					Assert(equal(var, nlp->paramval));
+ 					/* Present, so nothing to do */
+ 					break;
+ 				}
+ 			}
+ 			if (lc == NULL)
+ 			{
+ 				/* No, so add it */
+ 				nlp = makeNode(NestLoopParam);
+ 				nlp->paramno = paramid;
+ 				nlp->paramval = copyObject(var);
+ 				root->curOuterParams = lappend(root->curOuterParams, nlp);
+ 			}
+ 		}
+ 		else if (IsA(pitem->item, PlaceHolderVar))
+ 		{
+ 			PlaceHolderVar *phv = (PlaceHolderVar *) pitem->item;
+ 			NestLoopParam *nlp;
+ 			ListCell   *lc;
+ 
+ 			/*
+ 			 * If not from a nestloop outer rel, nothing to do.  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))
+ 				continue;
+ 			if (!bms_is_subset(find_placeholder_info(root, phv, false)->ph_eval_at,
+ 							   root->curOuterRels))
+ 				continue;
+ 			/* Is this param already listed in root->curOuterParams? */
+ 			foreach(lc, root->curOuterParams)
+ 			{
+ 				nlp = (NestLoopParam *) lfirst(lc);
+ 				if (nlp->paramno == paramid)
+ 				{
+ 					Assert(equal(phv, nlp->paramval));
+ 					/* Present, so nothing to do */
+ 					break;
+ 				}
+ 			}
+ 			if (lc == NULL)
+ 			{
+ 				/* No, so add it */
+ 				nlp = makeNode(NestLoopParam);
+ 				nlp->paramno = paramid;
+ 				nlp->paramval = copyObject(phv);
+ 				root->curOuterParams = lappend(root->curOuterParams, nlp);
+ 			}
+ 		}
+ 	}
+ 	bms_free(tmpset);
+ }
+ 
+ /*
   * fix_indexqual_references
   *	  Adjust indexqual clauses to the form the executor's indexqual
   *	  machinery needs.
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 3c7fa632b8ebe26c1d91e83e0ba1fa9c03b9e919..1ab9f1b78a80d63cd5f4347c5193bd360db4e6a8 100644
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
*************** add_vars_to_targetlist(PlannerInfo *root
*** 204,209 ****
--- 204,262 ----
  	}
  }
  
+ /*
+  * extract_lateral_references
+  *	  If the specified RTE is a LATERAL subquery, extract all its references
+  *	  to Vars of the current query level, and make sure those Vars will be
+  *	  available for evaluation of the RTE.
+  *
+  * XXX this is rather duplicative of processing that has to happen elsewhere.
+  * Maybe it'd be a good idea to do this type of extraction further upstream
+  * and save the results?
+  */
+ static void
+ extract_lateral_references(PlannerInfo *root, int rtindex)
+ {
+ 	RangeTblEntry *rte = root->simple_rte_array[rtindex];
+ 
+ 	/* No cross-references are possible if it's not LATERAL */
+ 	if (!rte->lateral)
+ 		return;
+ 	if (rte->rtekind == RTE_SUBQUERY)
+ 	{
+ 		List	   *vars = pull_vars_of_level((Node *) rte->subquery, 1);
+ 		List	   *newvars;
+ 		Relids		where_needed;
+ 		ListCell   *lc;
+ 
+ 		/* Copy each Var and adjust it to match our level */
+ 		newvars = NIL;
+ 		foreach(lc, vars)
+ 		{
+ 			Var	   *var = (Var *) lfirst(lc);
+ 
+ 			var = copyObject(var);
+ 			var->varlevelsup = 0;
+ 			newvars = lappend(newvars, var);
+ 		}
+ 
+ 		/*
+ 		 * We mark the Vars as being "needed" at the LATERAL RTE.  This is a
+ 		 * bit of a cheat: a more formal approach would be to mark each one as
+ 		 * needed at the join of the LATERAL RTE with its source RTE.  But it
+ 		 * will work, and it's much less tedious than computing a separate
+ 		 * where_needed for each Var.
+ 		 */
+ 		where_needed = bms_make_singleton(rtindex);
+ 
+ 		/* Push the Vars into their source relations' targetlists */
+ 		add_vars_to_targetlist(root, newvars, where_needed, false);
+ 
+ 		list_free(newvars);
+ 		list_free(vars);
+ 	}
+ }
+ 
  
  /*****************************************************************************
   *
*************** deconstruct_recurse(PlannerInfo *root, N
*** 286,292 ****
  	{
  		int			varno = ((RangeTblRef *) jtnode)->rtindex;
  
! 		/* No quals to deal with, just return correct result */
  		*qualscope = bms_make_singleton(varno);
  		/* A single baserel does not create an inner join */
  		*inner_join_rels = NULL;
--- 339,347 ----
  	{
  		int			varno = ((RangeTblRef *) jtnode)->rtindex;
  
! 		/* No quals to deal with, but do check for LATERAL subqueries */
! 		extract_lateral_references(root, varno);
! 		/* Result qualscope is just the one Relid */
  		*qualscope = bms_make_singleton(varno);
  		/* A single baserel does not create an inner join */
  		*inner_join_rels = NULL;
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 31fe55707238de952e5eeae4736ea6d10789b0da..d184f49097d6964e45e2f0edac1fb1bf7a1bded3 100644
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** plan_cluster_use_sort(Oid tableOid, Oid 
*** 3257,3262 ****
--- 3257,3263 ----
  	rte->rtekind = RTE_RELATION;
  	rte->relid = tableOid;
  	rte->relkind = RELKIND_RELATION;
+ 	rte->lateral = false;
  	rte->inh = false;
  	rte->inFromCl = true;
  	query->rtable = list_make1(rte);
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 8ce6bee8561074e164fd42f4ea8ca8fd62861051..863c943f2a0ac06585443782e27cc525c311a054 100644
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
*************** convert_ANY_sublink_to_join(PlannerInfo 
*** 1231,1236 ****
--- 1231,1237 ----
  	rte = addRangeTableEntryForSubquery(NULL,
  										subselect,
  										makeAlias("ANY_subquery", NIL),
+ 										false,
  										false);
  	parse->rtable = lappend(parse->rtable, rte);
  	rtindex = list_length(parse->rtable);
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index be1219eb3d12373cf166ab56697951839f437b95..06dbe84540444fa4d72b771b0d08137f3c1b4587 100644
*** a/src/backend/optimizer/prep/prepjointree.c
--- b/src/backend/optimizer/prep/prepjointree.c
*************** is_simple_subquery(Query *subquery)
*** 1176,1181 ****
--- 1176,1188 ----
  		return false;
  
  	/*
+ 	 * Don't pull up a LATERAL subquery (hopefully, this is just a temporary
+ 	 * implementation restriction).
+ 	 */
+ 	if (contain_vars_of_level((Node *) subquery, 1))
+ 		return false;
+ 
+ 	/*
  	 * Don't pull up a subquery that has any set-returning functions in its
  	 * targetlist.	Otherwise we might well wind up inserting set-returning
  	 * functions into places where they mustn't go, such as quals of higher
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 00052f5c846ebe87ade0c19da26655b47c64cf73..9d5c97b7a422272177855cc7957269d9e9538e21 100644
*** a/src/backend/optimizer/util/pathnode.c
--- b/src/backend/optimizer/util/pathnode.c
*************** compare_path_costs_fuzzily(Path *path1, 
*** 193,199 ****
   * and cheapest_total.	The cheapest_parameterized_paths list collects paths
   * that are cheapest-total for their parameterization (i.e., there is no
   * cheaper path with the same or weaker parameterization).	This list always
!  * includes the unparameterized cheapest-total path, too.
   *
   * This is normally called only after we've finished constructing the path
   * list for the rel node.
--- 193,199 ----
   * and cheapest_total.	The cheapest_parameterized_paths list collects paths
   * that are cheapest-total for their parameterization (i.e., there is no
   * cheaper path with the same or weaker parameterization).	This list always
!  * includes the unparameterized cheapest-total path, too, if there is one.
   *
   * This is normally called only after we've finished constructing the path
   * list for the rel node.
*************** set_cheapest(RelOptInfo *parent_rel)
*** 250,264 ****
  			cheapest_total_path = path;
  	}
  
! 	if (cheapest_total_path == NULL)
  		elog(ERROR, "could not devise a query plan for the given query");
  
  	parent_rel->cheapest_startup_path = cheapest_startup_path;
  	parent_rel->cheapest_total_path = cheapest_total_path;
  	parent_rel->cheapest_unique_path = NULL;	/* computed only if needed */
  
! 	/* Seed the parameterized-paths list with the cheapest total */
! 	parent_rel->cheapest_parameterized_paths = list_make1(cheapest_total_path);
  
  	/* And, if there are any parameterized paths, add them in one at a time */
  	if (have_parameterized_paths)
--- 250,267 ----
  			cheapest_total_path = path;
  	}
  
! 	if (cheapest_total_path == NULL && !have_parameterized_paths)
  		elog(ERROR, "could not devise a query plan for the given query");
  
  	parent_rel->cheapest_startup_path = cheapest_startup_path;
  	parent_rel->cheapest_total_path = cheapest_total_path;
  	parent_rel->cheapest_unique_path = NULL;	/* computed only if needed */
  
! 	/* Seed the parameterized-paths list with the cheapest total, if any */
! 	if (cheapest_total_path)
! 		parent_rel->cheapest_parameterized_paths = list_make1(cheapest_total_path);
! 	else
! 		parent_rel->cheapest_parameterized_paths = NIL;
  
  	/* And, if there are any parameterized paths, add them in one at a time */
  	if (have_parameterized_paths)
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 9bc90c253139deb43415683800681f48fe0c8436..0e66720c60fb32571f9a348df852aebc1319595e 100644
*** a/src/backend/optimizer/util/var.c
--- b/src/backend/optimizer/util/var.c
*************** typedef struct
*** 42,47 ****
--- 42,53 ----
  
  typedef struct
  {
+ 	List	   *vars;
+ 	int			sublevels_up;
+ } pull_vars_context;
+ 
+ typedef struct
+ {
  	int			var_location;
  	int			sublevels_up;
  } locate_var_of_level_context;
*************** typedef struct
*** 77,82 ****
--- 83,89 ----
  static bool pull_varnos_walker(Node *node,
  				   pull_varnos_context *context);
  static bool pull_varattnos_walker(Node *node, pull_varattnos_context *context);
+ static bool pull_vars_walker(Node *node, pull_vars_context *context);
  static bool contain_var_clause_walker(Node *node, void *context);
  static bool contain_vars_of_level_walker(Node *node, int *sublevels_up);
  static bool locate_var_of_level_walker(Node *node,
*************** pull_varnos(Node *node)
*** 122,127 ****
--- 129,159 ----
  	return context.varnos;
  }
  
+ /*
+  * pull_varnos_of_level
+  *		Create a set of all the distinct varnos present in a parsetree.
+  *		Only Vars of the specified level are considered.
+  */
+ Relids
+ pull_varnos_of_level(Node *node, int levelsup)
+ {
+ 	pull_varnos_context context;
+ 
+ 	context.varnos = NULL;
+ 	context.sublevels_up = levelsup;
+ 
+ 	/*
+ 	 * Must be prepared to start with a Query or a bare expression tree; if
+ 	 * it's a Query, we don't want to increment sublevels_up.
+ 	 */
+ 	query_or_expression_tree_walker(node,
+ 									pull_varnos_walker,
+ 									(void *) &context,
+ 									0);
+ 
+ 	return context.varnos;
+ }
+ 
  static bool
  pull_varnos_walker(Node *node, pull_varnos_context *context)
  {
*************** pull_varattnos_walker(Node *node, pull_v
*** 231,236 ****
--- 263,328 ----
  
  
  /*
+  * pull_vars_of_level
+  *		Create a list of all Vars referencing the specified query level
+  *		in the given parsetree.
+  *
+  * This is used on unplanned parsetrees, so we don't expect to see any
+  * PlaceHolderVars.
+  *
+  * Caution: the Vars are not copied, only linked into the list.
+  */
+ List *
+ pull_vars_of_level(Node *node, int levelsup)
+ {
+ 	pull_vars_context context;
+ 
+ 	context.vars = NIL;
+ 	context.sublevels_up = levelsup;
+ 
+ 	/*
+ 	 * Must be prepared to start with a Query or a bare expression tree; if
+ 	 * it's a Query, we don't want to increment sublevels_up.
+ 	 */
+ 	query_or_expression_tree_walker(node,
+ 									pull_vars_walker,
+ 									(void *) &context,
+ 									0);
+ 
+ 	return context.vars;
+ }
+ 
+ static bool
+ pull_vars_walker(Node *node, pull_vars_context *context)
+ {
+ 	if (node == NULL)
+ 		return false;
+ 	if (IsA(node, Var))
+ 	{
+ 		Var		   *var = (Var *) node;
+ 
+ 		if (var->varlevelsup == context->sublevels_up)
+ 			context->vars = lappend(context->vars, var);
+ 		return false;
+ 	}
+ 	Assert(!IsA(node, PlaceHolderVar));
+ 	if (IsA(node, Query))
+ 	{
+ 		/* Recurse into RTE subquery or not-yet-planned sublink subquery */
+ 		bool		result;
+ 
+ 		context->sublevels_up++;
+ 		result = query_tree_walker((Query *) node, pull_vars_walker,
+ 								   (void *) context, 0);
+ 		context->sublevels_up--;
+ 		return result;
+ 	}
+ 	return expression_tree_walker(node, pull_vars_walker,
+ 								  (void *) context);
+ }
+ 
+ 
+ /*
   * contain_var_clause
   *	  Recursively scan a clause to discover whether it contains any Var nodes
   *	  (of the current query level).
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 263edb5a7a61d3a3c2ea5f1a5f9a255024d86bad..0492394bc8378ffd68086f4bd72bba654690fd42 100644
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
*************** transformInsertStmt(ParseState *pstate, 
*** 533,538 ****
--- 533,539 ----
  		rte = addRangeTableEntryForSubquery(pstate,
  											selectQuery,
  											makeAlias("*SELECT*", NIL),
+ 											false,
  											false);
  		rtr = makeNode(RangeTblRef);
  		/* assume new rte is at end */
*************** transformSetOperationTree(ParseState *ps
*** 1652,1657 ****
--- 1653,1659 ----
  		rte = addRangeTableEntryForSubquery(pstate,
  											selectQuery,
  											makeAlias(selectName, NIL),
+ 											false,
  											false);
  
  		/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6b6901197db61ea2a560bd4bd4d186e78c8ddf84..11cf0addc236c832cb99a162f99433ae052594c5 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** static void processCASbits(int cas_bits,
*** 532,540 ****
  
  	KEY
  
! 	LABEL LANGUAGE LARGE_P LAST_P LC_COLLATE_P LC_CTYPE_P LEADING LEAKPROOF
! 	LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP
! 	LOCATION LOCK_P
  
  	MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
  
--- 532,540 ----
  
  	KEY
  
! 	LABEL LANGUAGE LARGE_P LAST_P LATERAL_P LC_COLLATE_P LC_CTYPE_P
! 	LEADING LEAKPROOF LEAST LEFT LEVEL LIKE LIMIT LISTEN LOAD LOCAL
! 	LOCALTIME LOCALTIMESTAMP LOCATION LOCK_P
  
  	MAPPING MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
  
*************** table_ref:	relation_expr
*** 9394,9406 ****
--- 9394,9433 ----
  								 parser_errposition(@1)));
  					$$ = NULL;
  				}
+ 			| LATERAL_P select_with_parens
+ 				{
+ 					/* As above, with LATERAL */
+ 					if (IsA($2, SelectStmt) &&
+ 						((SelectStmt *) $2)->valuesLists)
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_SYNTAX_ERROR),
+ 								 errmsg("VALUES in FROM must have an alias"),
+ 								 errhint("For example, FROM (VALUES ...) [AS] foo."),
+ 								 parser_errposition(@2)));
+ 					else
+ 						ereport(ERROR,
+ 								(errcode(ERRCODE_SYNTAX_ERROR),
+ 								 errmsg("subquery in FROM must have an alias"),
+ 								 errhint("For example, FROM (SELECT ...) [AS] foo."),
+ 								 parser_errposition(@2)));
+ 					$$ = NULL;
+ 				}
  			| select_with_parens alias_clause
  				{
  					RangeSubselect *n = makeNode(RangeSubselect);
+ 					n->lateral = false;
  					n->subquery = $1;
  					n->alias = $2;
  					$$ = (Node *) n;
  				}
+ 			| LATERAL_P select_with_parens alias_clause
+ 				{
+ 					RangeSubselect *n = makeNode(RangeSubselect);
+ 					n->lateral = true;
+ 					n->subquery = $2;
+ 					n->alias = $3;
+ 					$$ = (Node *) n;
+ 				}
  			| joined_table
  				{
  					$$ = (Node *) $1;
*************** col_name_keyword:
*** 12618,12623 ****
--- 12645,12651 ----
  			| INT_P
  			| INTEGER
  			| INTERVAL
+ 			| LATERAL_P
  			| LEAST
  			| NATIONAL
  			| NCHAR
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 97ab9d5581a2ad379a2b5c7928dbc7c7e5bcbcb8..9f0558ae18dbdc60a1b71e22df44894044a859af 100644
*** a/src/backend/parser/parse_clause.c
--- b/src/backend/parser/parse_clause.c
*************** transformRangeSubselect(ParseState *psta
*** 503,515 ****
  	 * the other variables weren't even visible, but it seems more useful to
  	 * leave them visible and give a specific error message.
  	 *
! 	 * XXX this will need further work to support SQL99's LATERAL() feature,
! 	 * wherein such references would indeed be legal.
  	 *
  	 * We can skip groveling through the subquery if there's not anything
  	 * visible in the current query.  Also note that outer references are OK.
  	 */
! 	if (pstate->p_relnamespace || pstate->p_varnamespace)
  	{
  		if (contain_vars_of_level((Node *) query, 1))
  			ereport(ERROR,
--- 503,516 ----
  	 * the other variables weren't even visible, but it seems more useful to
  	 * leave them visible and give a specific error message.
  	 *
! 	 * XXX this needs further work to support LATERAL subqueries.  For the
! 	 * moment, just assume any cross-reference in a LATERAL subquery is OK.
  	 *
  	 * We can skip groveling through the subquery if there's not anything
  	 * visible in the current query.  Also note that outer references are OK.
  	 */
! 	if (!r->lateral &&
! 		(pstate->p_relnamespace || pstate->p_varnamespace))
  	{
  		if (contain_vars_of_level((Node *) query, 1))
  			ereport(ERROR,
*************** transformRangeSubselect(ParseState *psta
*** 522,528 ****
  	/*
  	 * OK, build an RTE for the subquery.
  	 */
! 	rte = addRangeTableEntryForSubquery(pstate, query, r->alias, true);
  
  	return rte;
  }
--- 523,533 ----
  	/*
  	 * OK, build an RTE for the subquery.
  	 */
! 	rte = addRangeTableEntryForSubquery(pstate,
! 										query,
! 										r->alias,
! 										r->lateral,
! 										true);
  
  	return rte;
  }
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 30b307b191cc4e5304dbc99e0aea95046720bd46..2477d07aaf5c3212c39200101b23e2136b12988c 100644
*** a/src/backend/parser/parse_relation.c
--- b/src/backend/parser/parse_relation.c
*************** addRangeTableEntry(ParseState *pstate,
*** 917,932 ****
  	 */
  	heap_close(rel, NoLock);
  
! 	/*----------
! 	 * Flags:
! 	 * - this RTE should be expanded to include descendant tables,
! 	 * - this RTE is in the FROM clause,
! 	 * - this RTE should be checked for appropriate access rights.
  	 *
  	 * The initial default on access checks is always check-for-READ-access,
  	 * which is the right thing for all except target tables.
- 	 *----------
  	 */
  	rte->inh = inh;
  	rte->inFromCl = inFromCl;
  
--- 917,929 ----
  	 */
  	heap_close(rel, NoLock);
  
! 	/*
! 	 * Set flags and access permissions.
  	 *
  	 * The initial default on access checks is always check-for-READ-access,
  	 * which is the right thing for all except target tables.
  	 */
+ 	rte->lateral = false;
  	rte->inh = inh;
  	rte->inFromCl = inFromCl;
  
*************** addRangeTableEntryForRelation(ParseState
*** 973,988 ****
  	rte->eref = makeAlias(refname, NIL);
  	buildRelationAliases(rel->rd_att, alias, rte->eref);
  
! 	/*----------
! 	 * Flags:
! 	 * - this RTE should be expanded to include descendant tables,
! 	 * - this RTE is in the FROM clause,
! 	 * - this RTE should be checked for appropriate access rights.
  	 *
  	 * The initial default on access checks is always check-for-READ-access,
  	 * which is the right thing for all except target tables.
- 	 *----------
  	 */
  	rte->inh = inh;
  	rte->inFromCl = inFromCl;
  
--- 970,982 ----
  	rte->eref = makeAlias(refname, NIL);
  	buildRelationAliases(rel->rd_att, alias, rte->eref);
  
! 	/*
! 	 * Set flags and access permissions.
  	 *
  	 * The initial default on access checks is always check-for-READ-access,
  	 * which is the right thing for all except target tables.
  	 */
+ 	rte->lateral = false;
  	rte->inh = inh;
  	rte->inFromCl = inFromCl;
  
*************** RangeTblEntry *
*** 1011,1016 ****
--- 1005,1011 ----
  addRangeTableEntryForSubquery(ParseState *pstate,
  							  Query *subquery,
  							  Alias *alias,
+ 							  bool lateral,
  							  bool inFromCl)
  {
  	RangeTblEntry *rte = makeNode(RangeTblEntry);
*************** addRangeTableEntryForSubquery(ParseState
*** 1054,1068 ****
  
  	rte->eref = eref;
  
! 	/*----------
! 	 * Flags:
! 	 * - this RTE should be expanded to include descendant tables,
! 	 * - this RTE is in the FROM clause,
! 	 * - this RTE should be checked for appropriate access rights.
  	 *
  	 * Subqueries are never checked for access rights.
- 	 *----------
  	 */
  	rte->inh = false;			/* never true for subqueries */
  	rte->inFromCl = inFromCl;
  
--- 1049,1060 ----
  
  	rte->eref = eref;
  
! 	/*
! 	 * Set flags and access permissions.
  	 *
  	 * Subqueries are never checked for access rights.
  	 */
+ 	rte->lateral = lateral;
  	rte->inh = false;			/* never true for subqueries */
  	rte->inFromCl = inFromCl;
  
*************** addRangeTableEntryForFunction(ParseState
*** 1192,1207 ****
  					funcname, format_type_be(funcrettype)),
  				 parser_errposition(pstate, exprLocation(funcexpr))));
  
! 	/*----------
! 	 * Flags:
! 	 * - this RTE should be expanded to include descendant tables,
! 	 * - this RTE is in the FROM clause,
! 	 * - this RTE should be checked for appropriate access rights.
  	 *
! 	 * Functions are never checked for access rights (at least, not by
! 	 * the RTE permissions mechanism).
! 	 *----------
  	 */
  	rte->inh = false;			/* never true for functions */
  	rte->inFromCl = inFromCl;
  
--- 1184,1196 ----
  					funcname, format_type_be(funcrettype)),
  				 parser_errposition(pstate, exprLocation(funcexpr))));
  
! 	/*
! 	 * Set flags and access permissions.
  	 *
! 	 * Functions are never checked for access rights (at least, not by the RTE
! 	 * permissions mechanism).
  	 */
+ 	rte->lateral = false;
  	rte->inh = false;			/* never true for functions */
  	rte->inFromCl = inFromCl;
  
*************** addRangeTableEntryForValues(ParseState *
*** 1267,1281 ****
  
  	rte->eref = eref;
  
! 	/*----------
! 	 * Flags:
! 	 * - this RTE should be expanded to include descendant tables,
! 	 * - this RTE is in the FROM clause,
! 	 * - this RTE should be checked for appropriate access rights.
  	 *
  	 * Subqueries are never checked for access rights.
- 	 *----------
  	 */
  	rte->inh = false;			/* never true for values RTEs */
  	rte->inFromCl = inFromCl;
  
--- 1256,1267 ----
  
  	rte->eref = eref;
  
! 	/*
! 	 * Set flags and access permissions.
  	 *
  	 * Subqueries are never checked for access rights.
  	 */
+ 	rte->lateral = false;
  	rte->inh = false;			/* never true for values RTEs */
  	rte->inFromCl = inFromCl;
  
*************** addRangeTableEntryForJoin(ParseState *ps
*** 1338,1352 ****
  
  	rte->eref = eref;
  
! 	/*----------
! 	 * Flags:
! 	 * - this RTE should be expanded to include descendant tables,
! 	 * - this RTE is in the FROM clause,
! 	 * - this RTE should be checked for appropriate access rights.
  	 *
  	 * Joins are never checked for access rights.
- 	 *----------
  	 */
  	rte->inh = false;			/* never true for joins */
  	rte->inFromCl = inFromCl;
  
--- 1324,1335 ----
  
  	rte->eref = eref;
  
! 	/*
! 	 * Set flags and access permissions.
  	 *
  	 * Joins are never checked for access rights.
  	 */
+ 	rte->lateral = false;
  	rte->inh = false;			/* never true for joins */
  	rte->inFromCl = inFromCl;
  
*************** addRangeTableEntryForCTE(ParseState *pst
*** 1441,1455 ****
  
  	rte->eref = eref;
  
! 	/*----------
! 	 * Flags:
! 	 * - this RTE should be expanded to include descendant tables,
! 	 * - this RTE is in the FROM clause,
! 	 * - this RTE should be checked for appropriate access rights.
  	 *
  	 * Subqueries are never checked for access rights.
- 	 *----------
  	 */
  	rte->inh = false;			/* never true for subqueries */
  	rte->inFromCl = inFromCl;
  
--- 1424,1435 ----
  
  	rte->eref = eref;
  
! 	/*
! 	 * Set flags and access permissions.
  	 *
  	 * Subqueries are never checked for access rights.
  	 */
+ 	rte->lateral = false;
  	rte->inh = false;			/* never true for subqueries */
  	rte->inFromCl = inFromCl;
  
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 412dfe6f9aac5f49d618666866966809fb900e47..01030219619ee5f122dc91ee4ce4ab3444ee8999 100644
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
*************** pg_get_triggerdef_worker(Oid trigid, boo
*** 676,681 ****
--- 676,682 ----
  		oldrte->relid = trigrec->tgrelid;
  		oldrte->relkind = relkind;
  		oldrte->eref = makeAlias("old", NIL);
+ 		oldrte->lateral = false;
  		oldrte->inh = false;
  		oldrte->inFromCl = true;
  
*************** pg_get_triggerdef_worker(Oid trigid, boo
*** 684,689 ****
--- 685,691 ----
  		newrte->relid = trigrec->tgrelid;
  		newrte->relkind = relkind;
  		newrte->eref = makeAlias("new", NIL);
+ 		newrte->lateral = false;
  		newrte->inh = false;
  		newrte->inFromCl = true;
  
*************** deparse_context_for(const char *aliasnam
*** 2174,2179 ****
--- 2176,2182 ----
  	rte->relid = relid;
  	rte->relkind = RELKIND_RELATION;	/* no need for exactness here */
  	rte->eref = makeAlias(aliasname, NIL);
+ 	rte->lateral = false;
  	rte->inh = false;
  	rte->inFromCl = true;
  
*************** get_from_clause_item(Node *jtnode, Query
*** 6618,6623 ****
--- 6621,6629 ----
  		RangeTblEntry *rte = rt_fetch(varno, query->rtable);
  		bool		gavealias = false;
  
+ 		if (rte->lateral)
+ 			appendStringInfoString(buf, "LATERAL ");
+ 
  		switch (rte->rtekind)
  		{
  			case RTE_RELATION:
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 119e1ed2f6e18872cfd3758b9303fb2835e5695a..5d53060a70d06b56c3afb8fdf5db3fbfb091d53c 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct WindowDef
*** 451,456 ****
--- 451,457 ----
  typedef struct RangeSubselect
  {
  	NodeTag		type;
+ 	bool		lateral;		/* does it have LATERAL prefix? */
  	Node	   *subquery;		/* the untransformed sub-select clause */
  	Alias	   *alias;			/* table alias & optional column aliases */
  } RangeSubselect;
*************** typedef struct RangeTblEntry
*** 706,712 ****
  	 * Fields valid for a subquery RTE (else NULL):
  	 */
  	Query	   *subquery;		/* the sub-query */
! 	bool		security_barrier;		/* subquery from security_barrier view */
  
  	/*
  	 * Fields valid for a join RTE (else NULL/zero):
--- 707,713 ----
  	 * Fields valid for a subquery RTE (else NULL):
  	 */
  	Query	   *subquery;		/* the sub-query */
! 	bool		security_barrier;		/* is from security_barrier view? */
  
  	/*
  	 * Fields valid for a join RTE (else NULL/zero):
*************** typedef struct RangeTblEntry
*** 756,761 ****
--- 757,763 ----
  	 */
  	Alias	   *alias;			/* user-written alias clause, if any */
  	Alias	   *eref;			/* expanded reference names */
+ 	bool		lateral;		/* subquery or function is marked LATERAL? */
  	bool		inh;			/* inheritance requested? */
  	bool		inFromCl;		/* present in FROM clause? */
  	AclMode		requiredPerms;	/* bitmask of required access permissions */
diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h
index cf0bbd9f159e6fc168bce1a448527c3f901fff71..8238981c2894553a02d23745886f7675d77d7775 100644
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
*************** typedef struct PlannerInfo
*** 307,321 ****
   *		ppilist - ParamPathInfo nodes for parameterized Paths, if any
   *		cheapest_startup_path - the pathlist member with lowest startup cost
   *								(regardless of its ordering; but must be
!  *								 unparameterized)
   *		cheapest_total_path - the pathlist member with lowest total cost
   *							  (regardless of its ordering; but must be
!  *							   unparameterized)
   *		cheapest_unique_path - for caching cheapest path to produce unique
   *							   (no duplicates) output from relation
   *		cheapest_parameterized_paths - paths with cheapest total costs for
   *								 their parameterizations; always includes
!  *								 cheapest_total_path
   *
   * If the relation is a base relation it will have these fields set:
   *
--- 307,323 ----
   *		ppilist - ParamPathInfo nodes for parameterized Paths, if any
   *		cheapest_startup_path - the pathlist member with lowest startup cost
   *								(regardless of its ordering; but must be
!  *								 unparameterized; hence will be NULL for
!  *								 a LATERAL subquery)
   *		cheapest_total_path - the pathlist member with lowest total cost
   *							  (regardless of its ordering; but must be
!  *							   unparameterized; hence will be NULL for
!  *							   a LATERAL subquery)
   *		cheapest_unique_path - for caching cheapest path to produce unique
   *							   (no duplicates) output from relation
   *		cheapest_parameterized_paths - paths with cheapest total costs for
   *								 their parameterizations; always includes
!  *								 cheapest_total_path, if that exists
   *
   * If the relation is a base relation it will have these fields set:
   *
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
index f546362b680ca5e6f2f1a41883755b3cc6235e15..f10b838dc7f9cfdaf255cea2aa5c6c78977d4ffe 100644
*** a/src/include/optimizer/var.h
--- b/src/include/optimizer/var.h
*************** typedef enum
*** 31,37 ****
--- 31,39 ----
  } PVCPlaceHolderBehavior;
  
  extern Relids pull_varnos(Node *node);
+ extern Relids pull_varnos_of_level(Node *node, int levelsup);
  extern void pull_varattnos(Node *node, Index varno, Bitmapset **varattnos);
+ extern List *pull_vars_of_level(Node *node, int levelsup);
  extern bool contain_var_clause(Node *node);
  extern bool contain_vars_of_level(Node *node, int levelsup);
  extern int	locate_var_of_level(Node *node, int levelsup);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 7e55a92185b64e0eeac830c534c18b071bb4313a..3a0bc8f849e56b4a948ba3ab748ebaf28646c0c4 100644
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
*************** PG_KEYWORD("label", LABEL, UNRESERVED_KE
*** 213,218 ****
--- 213,219 ----
  PG_KEYWORD("language", LANGUAGE, UNRESERVED_KEYWORD)
  PG_KEYWORD("large", LARGE_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("last", LAST_P, UNRESERVED_KEYWORD)
+ PG_KEYWORD("lateral", LATERAL_P, COL_NAME_KEYWORD)
  PG_KEYWORD("lc_collate", LC_COLLATE_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("lc_ctype", LC_CTYPE_P, UNRESERVED_KEYWORD)
  PG_KEYWORD("leading", LEADING, RESERVED_KEYWORD)
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index ababd74f82f55cd80879a3b39a209417b2bae431..fa99066a4efb9b97cd350f9230387adb15a58232 100644
*** a/src/include/parser/parse_relation.h
--- b/src/include/parser/parse_relation.h
*************** extern RangeTblEntry *addRangeTableEntry
*** 55,60 ****
--- 55,61 ----
  extern RangeTblEntry *addRangeTableEntryForSubquery(ParseState *pstate,
  							  Query *subquery,
  							  Alias *alias,
+ 							  bool lateral,
  							  bool inFromCl);
  extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate,
  							  char *funcname,
