diff --git a/doc/src/sgml/keywords.sgml b/doc/src/sgml/keywords.sgml
index 767ed811a9cd4c0d421535e375c668e83e9bcf4f..0e7b32285145f8c16aafbed481341cfb59bf6008 100644
--- a/doc/src/sgml/keywords.sgml
+++ b/doc/src/sgml/keywords.sgml
@@ -2444,7 +2444,7 @@
    </row>
    <row>
     <entry><token>LATERAL</token></entry>
-    <entry></entry>
+    <entry>reserved</entry>
     <entry>reserved</entry>
     <entry>reserved</entry>
     <entry></entry>
diff --git a/doc/src/sgml/queries.sgml b/doc/src/sgml/queries.sgml
index a3dadbef891df46e23231e88608e42bfc4a6101e..2d9531f08d0402b2d52ec448fc79cda872fa0564 100644
--- a/doc/src/sgml/queries.sgml
+++ b/doc/src/sgml/queries.sgml
@@ -590,7 +590,7 @@ SELECT a.* FROM (my_table AS a JOIN your_table AS b ON ...) AS c
     <para>
      Subqueries specifying a derived table must be enclosed in
      parentheses and <emphasis>must</emphasis> be assigned a table
-     alias name.  (See <xref linkend="queries-table-aliases">.)  For
+     alias name (as in <xref linkend="queries-table-aliases">).  For
      example:
 <programlisting>
 FROM (SELECT * FROM table1) AS alias_name
@@ -697,6 +697,87 @@ SELECT *
      expand to.
     </para>
    </sect3>
+
+   <sect3 id="queries-lateral">
+    <title><literal>LATERAL</> Subqueries</title>
+
+    <indexterm zone="queries-lateral">
+     <primary>LATERAL</>
+     <secondary>in the FROM clause</>
+    </indexterm>
+
+    <para>
+     Subqueries and table functions appearing in <literal>FROM</> can be
+     preceded by the key word <literal>LATERAL</>.  This allows them to
+     reference columns provided by preceding <literal>FROM</> items.
+     (Without <literal>LATERAL</literal>, each <literal>FROM</> item is
+     evaluated independently and so cannot cross-reference any other
+     <literal>FROM</> item.)
+     A <literal>LATERAL</literal> item can appear at top level in the
+     <literal>FROM</> list, or within a <literal>JOIN</> tree; in the latter
+     case it can also refer to any items that are on the left-hand side of a
+     <literal>JOIN</> that it is on the right-hand side of.
+    </para>
+
+    <para>
+     When a <literal>FROM</> item contains <literal>LATERAL</literal>
+     cross-references, evaluation proceeds as follows: for each row of the
+     <literal>FROM</> item providing the cross-referenced column(s), or
+     set of rows of multiple <literal>FROM</> items providing the
+     columns, the <literal>LATERAL</literal> item is evaluated using that
+     row or row set's values of the columns.  The resulting row(s) are
+     joined as usual with the rows they were computed from.  This is
+     repeated for each row or set of rows from the column source table(s).
+    </para>
+
+    <para>
+     A trivial example of <literal>LATERAL</literal> is
+<programlisting>
+SELECT * FROM foo, LATERAL (SELECT * FROM bar WHERE bar.id = foo.bar_id) ss;
+</programlisting>
+     This is not especially useful since it has exactly the same result as
+     the more conventional
+<programlisting>
+SELECT * FROM foo, bar WHERE bar.id = foo.bar_id;
+</programlisting>
+     <literal>LATERAL</literal> is primarily useful when the cross-referenced
+     column is necessary for computing the row(s) to be joined.  A common
+     application is providing an argument value for a set-returning function.
+     For example, supposing that <function>vertices(polygon)</> returns the
+     set of vertices of a polygon, we could identify close-together vertices
+     of polygons stored in a table with:
+<programlisting>
+SELECT p1.id, p2.id, v1, v2
+FROM polygons p1, polygons p2,
+     LATERAL vertices(p1.poly) v1,
+     LATERAL vertices(p2.poly) v2
+WHERE (v1 &lt;-&gt; v2) &lt; 10 AND p1.id != p2.id;
+</programlisting>
+     This query could also be written
+<programlisting>
+SELECT p1.id, p2.id, v1, v2
+FROM polygons p1 CROSS JOIN LATERAL vertices(p1.poly) v1,
+     polygons p2 CROSS JOIN LATERAL vertices(p2.poly) v2
+WHERE (v1 &lt;-&gt; v2) &lt; 10 AND p1.id != p2.id;
+</programlisting>
+     or in several other equivalent formulations.
+    </para>
+
+    <para>
+     It is often particularly handy to <literal>LEFT JOIN</> to a
+     <literal>LATERAL</literal> subquery, so that source rows will appear in
+     the result even if the <literal>LATERAL</literal> subquery produces no
+     rows for them.  For example, if <function>get_product_names()</> returns
+     the names of products made by a manufacturer, but some manufacturers in
+     our table currently produce no products, we could find out which ones
+     those are like this:
+<programlisting>
+SELECT m.name
+FROM manufacturers m LEFT JOIN LATERAL get_product_names(m.id) pname ON true
+WHERE pname IS NULL;
+</programlisting>
+    </para>
+   </sect3>
   </sect2>
 
   <sect2 id="queries-where">
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index 7e989242587cbb897cf22ff05a565e77c0246957..0ac37a394f178e02f73f69093972b55748595511 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -50,10 +50,10 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
 <phrase>where <replaceable class="parameter">from_item</replaceable> can be one of:</phrase>
 
     [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ] [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
-    ( <replaceable class="parameter">select</replaceable> ) [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ]
+    [ LATERAL ] ( <replaceable class="parameter">select</replaceable> ) [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ]
     <replaceable class="parameter">with_query_name</replaceable> [ [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] ) ] ]
-    <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] | <replaceable class="parameter">column_definition</replaceable> [, ...] ) ]
-    <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] )
+    [ LATERAL ] <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) [ AS ] <replaceable class="parameter">alias</replaceable> [ ( <replaceable class="parameter">column_alias</replaceable> [, ...] | <replaceable class="parameter">column_definition</replaceable> [, ...] ) ]
+    [ LATERAL ] <replaceable class="parameter">function_name</replaceable> ( [ <replaceable class="parameter">argument</replaceable> [, ...] ] ) AS ( <replaceable class="parameter">column_definition</replaceable> [, ...] )
     <replaceable class="parameter">from_item</replaceable> [ NATURAL ] <replaceable class="parameter">join_type</replaceable> <replaceable class="parameter">from_item</replaceable> [ ON <replaceable class="parameter">join_condition</replaceable> | USING ( <replaceable class="parameter">join_column</replaceable> [, ...] ) ]
 
 <phrase>and <replaceable class="parameter">with_query</replaceable> is:</phrase>
@@ -284,8 +284,8 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
     The <literal>FROM</literal> clause specifies one or more source
     tables for the <command>SELECT</command>.  If multiple sources are
     specified, the result is the Cartesian product (cross join) of all
-    the sources.  But usually qualification conditions
-    are added to restrict the returned rows to a small subset of the
+    the sources.  But usually qualification conditions are added (via
+    <literal>WHERE</>) to restrict the returned rows to a small subset of the
     Cartesian product.
    </para>
 
@@ -414,17 +414,18 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
        </para>
 
        <para>
-        A <literal>JOIN</literal> clause combines two
-        <literal>FROM</> items.  Use parentheses if necessary to
-        determine the order of nesting.  In the absence of parentheses,
-        <literal>JOIN</literal>s nest left-to-right.  In any case
-        <literal>JOIN</literal> binds more tightly than the commas
-        separating <literal>FROM</> items.
+        A <literal>JOIN</literal> clause combines two <literal>FROM</>
+        items, which for convenience we will refer to as <quote>tables</>,
+        though in reality they can be any type of <literal>FROM</> item.
+        Use parentheses if necessary to determine the order of nesting.
+        In the absence of parentheses, <literal>JOIN</literal>s nest
+        left-to-right.  In any case <literal>JOIN</literal> binds more
+        tightly than the commas separating <literal>FROM</>-list items.
        </para>
 
        <para><literal>CROSS JOIN</> and <literal>INNER JOIN</literal>
         produce a simple Cartesian product, the same result as you get from
-        listing the two items at the top level of <literal>FROM</>,
+        listing the two tables at the top level of <literal>FROM</>,
         but restricted by the join condition (if any).
         <literal>CROSS JOIN</> is equivalent to <literal>INNER JOIN ON
         (TRUE)</>, that is, no rows are removed by qualification.
@@ -449,7 +450,7 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
         joined rows, plus one row for each unmatched right-hand row
         (extended with nulls on the left).  This is just a notational
         convenience, since you could convert it to a <literal>LEFT
-        OUTER JOIN</> by switching the left and right inputs.
+        OUTER JOIN</> by switching the left and right tables.
        </para>
 
        <para><literal>FULL OUTER JOIN</> returns all the joined rows, plus
@@ -495,6 +496,47 @@ TABLE [ ONLY ] <replaceable class="parameter">table_name</replaceable> [ * ]
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry>
+      <term><literal>LATERAL</literal></term>
+      <listitem>
+       <para>The <literal>LATERAL</literal> key word can precede a
+        sub-<command>SELECT</command> or function-call <literal>FROM</>
+        item.  This allows the sub-<command>SELECT</command> or function
+        expression to refer to columns of <literal>FROM</> items that appear
+        before it in the <literal>FROM</> list.  (Without
+        <literal>LATERAL</literal>, each <literal>FROM</> item is evaluated
+        independently and so cannot cross-reference any other
+        <literal>FROM</> item.)  A <literal>LATERAL</literal> item can
+        appear at top level in the <literal>FROM</> list, or within a
+        <literal>JOIN</> tree; in the latter case it can also refer to any
+        items that are on the left-hand side of a <literal>JOIN</> that it is
+        on the right-hand side of.
+       </para>
+
+       <para>
+        When a <literal>FROM</> item contains <literal>LATERAL</literal>
+        cross-references, evaluation proceeds as follows: for each row of the
+        <literal>FROM</> item providing the cross-referenced column(s), or
+        set of rows of multiple <literal>FROM</> items providing the
+        columns, the <literal>LATERAL</literal> item is evaluated using that
+        row or row set's values of the columns.  The resulting row(s) are
+        joined as usual with the rows they were computed from.  This is
+        repeated for each row or set of rows from the column source table(s).
+       </para>
+
+       <para>
+        The column source table(s) must be <literal>INNER</> or
+        <literal>LEFT</> joined to the <literal>LATERAL</literal> item, else
+        there would not be a well-defined set of rows from which to compute
+        each set of rows for the <literal>LATERAL</literal> item.  Thus,
+        although a construct such as <literal><replaceable>X</> RIGHT JOIN
+        LATERAL <replaceable>Y</></literal> is syntactically valid, it is
+        not actually allowed for <replaceable>Y</> to reference
+        <replaceable>X</>.
+       </para>
+      </listitem>
+     </varlistentry>
     </variablelist>
    </para>
   </refsect2>
@@ -1532,6 +1574,26 @@ SELECT distance, employee_name FROM employee_recursive;
    else the query will loop indefinitely.  (See <xref linkend="queries-with">
    for more examples.)
   </para>
+
+  <para>
+   This example uses <literal>LATERAL</> to apply a set-returning function
+   <function>get_product_names()</> for each row of the
+   <structname>manufacturers</> table:
+
+<programlisting>
+SELECT m.name AS mname, pname
+FROM manufacturers m, LATERAL get_product_names(m.id) pname;
+</programlisting>
+
+    Manufacturers not currently having any products would not appear in the
+    result, since it is an inner join.  If we wished to include the names of
+    such manufacturers in the result, we could do:
+
+<programlisting>
+SELECT m.name AS mname, pname
+FROM manufacturers m LEFT JOIN LATERAL get_product_names(m.id) pname ON true;
+</programlisting>
+  </para>
  </refsect1>
 
  <refsect1>
@@ -1611,6 +1673,20 @@ SELECT distributors.* WHERE distributors.name = 'Westward';
    </para>
   </refsect2>
 
+  <refsect2>
+   <title>Function Calls in <literal>FROM</literal></title>
+
+   <para>
+    <productname>PostgreSQL</productname> allows a function call to be
+    written directly as a member of the <literal>FROM</> list.  In the SQL
+    standard it would be necessary to wrap such a function call in a
+    sub-<command>SELECT</command>; that is, the syntax
+    <literal>FROM <replaceable>func</>(...) <replaceable>alias</></literal>
+    is approximately equivalent to
+    <literal>FROM (SELECT <replaceable>func</>(...)) <replaceable>alias</></literal>.
+   </para>
+  </refsect2>
+
   <refsect2>
    <title>Namespace Available to <literal>GROUP BY</literal> and <literal>ORDER BY</literal></title>
 
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 799930ad611b6e09403a3d14c5738f47cb2f3bab..71d53234bc3fbfc1afa85a4db8e53eb5525d2006 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1973,6 +1973,7 @@ _copyRangeTblEntry(const RangeTblEntry *from)
 	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);
@@ -2250,6 +2251,7 @@ _copyRangeSubselect(const RangeSubselect *from)
 {
 	RangeSubselect *newnode = makeNode(RangeSubselect);
 
+	COPY_SCALAR_FIELD(lateral);
 	COPY_NODE_FIELD(subquery);
 	COPY_NODE_FIELD(alias);
 
@@ -2261,6 +2263,7 @@ _copyRangeFunction(const RangeFunction *from)
 {
 	RangeFunction *newnode = makeNode(RangeFunction);
 
+	COPY_SCALAR_FIELD(lateral);
 	COPY_NODE_FIELD(funccallnode);
 	COPY_NODE_FIELD(alias);
 	COPY_NODE_FIELD(coldeflist);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 802b0636714d2c34013275132c531ebc7ae1bdd6..d690ca77a5ad038b2320f4d63d0575b5de51410d 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2161,6 +2161,7 @@ _equalWindowDef(const WindowDef *a, const WindowDef *b)
 static bool
 _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b)
 {
+	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_NODE_FIELD(subquery);
 	COMPARE_NODE_FIELD(alias);
 
@@ -2170,6 +2171,7 @@ _equalRangeSubselect(const RangeSubselect *a, const RangeSubselect *b)
 static bool
 _equalRangeFunction(const RangeFunction *a, const RangeFunction *b)
 {
+	COMPARE_SCALAR_FIELD(lateral);
 	COMPARE_NODE_FIELD(funccallnode);
 	COMPARE_NODE_FIELD(alias);
 	COMPARE_NODE_FIELD(coldeflist);
@@ -2287,6 +2289,7 @@ _equalRangeTblEntry(const RangeTblEntry *a, const RangeTblEntry *b)
 	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..9dee0414f361dc17763295bd8d2638d7b2b6e1a7 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2362,6 +2362,7 @@ _outRangeTblEntry(StringInfo str, const RangeTblEntry *node)
 			break;
 	}
 
+	WRITE_BOOL_FIELD(lateral);
 	WRITE_BOOL_FIELD(inh);
 	WRITE_BOOL_FIELD(inFromCl);
 	WRITE_UINT_FIELD(requiredPerms);
@@ -2565,6 +2566,7 @@ _outRangeSubselect(StringInfo str, const RangeSubselect *node)
 {
 	WRITE_NODE_TYPE("RANGESUBSELECT");
 
+	WRITE_BOOL_FIELD(lateral);
 	WRITE_NODE_FIELD(subquery);
 	WRITE_NODE_FIELD(alias);
 }
@@ -2574,6 +2576,7 @@ _outRangeFunction(StringInfo str, const RangeFunction *node)
 {
 	WRITE_NODE_TYPE("RANGEFUNCTION");
 
+	WRITE_BOOL_FIELD(lateral);
 	WRITE_NODE_FIELD(funccallnode);
 	WRITE_NODE_FIELD(alias);
 	WRITE_NODE_FIELD(coldeflist);
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
@@ -1222,6 +1222,7 @@ _readRangeTblEntry(void)
 			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..57eb2c39a4f315dbc8df8595c8690cadf9b6653c 100644
--- a/src/backend/optimizer/geqo/geqo_eval.c
+++ b/src/backend/optimizer/geqo/geqo_eval.c
@@ -56,6 +56,7 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
 	MemoryContext mycontext;
 	MemoryContext oldcxt;
 	RelOptInfo *joinrel;
+	Path	   *best_path;
 	Cost		fitness;
 	int			savelength;
 	struct HTAB *savehash;
@@ -99,6 +100,14 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
 
 	/* construct the best path for the given combination of relations */
 	joinrel = gimme_tree(root, tour, num_gene);
+	best_path = joinrel->cheapest_total_path;
+
+	/*
+	 * If no unparameterized path, use the cheapest parameterized path for
+	 * costing purposes.  XXX revisit this after LATERAL dust settles
+	 */
+	if (!best_path)
+		best_path = linitial(joinrel->cheapest_parameterized_paths);
 
 	/*
 	 * compute fitness
@@ -106,7 +115,7 @@ geqo_eval(PlannerInfo *root, Gene *tour, int num_gene)
 	 * XXX geqo does not currently support optimization for partial result
 	 * retrieval --- how to fix?
 	 */
-	fitness = joinrel->cheapest_total_path->total_cost;
+	fitness = best_path->total_cost;
 
 	/*
 	 * Restore join_rel_list to its former state, and put back original
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index f02954982a7e41619a0282f2ce095282b30d9cd8..bfda05394d6492c623fa57c521278227bc889705 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -253,8 +253,9 @@ set_rel_size(PlannerInfo *root, RelOptInfo *rel,
 			case RTE_SUBQUERY:
 
 				/*
-				 * Subqueries don't support parameterized paths, so just go
-				 * ahead and build their paths immediately.
+				 * Subqueries don't support making a choice between
+				 * parameterized and unparameterized paths, so just go ahead
+				 * and build their paths immediately.
 				 */
 				set_subquery_pathlist(root, rel, rti, rte);
 				break;
@@ -698,6 +699,10 @@ set_append_rel_pathlist(PlannerInfo *root, RelOptInfo *rel,
 		if (IS_DUMMY_REL(childrel))
 			continue;
 
+		/* XXX need to figure out what to do for LATERAL */
+		if (childrel->cheapest_total_path == NULL)
+			elog(ERROR, "LATERAL within an append relation is not supported yet");
+
 		/*
 		 * Child is live, so add its cheapest access path to the Append path
 		 * we are constructing for the parent.
@@ -906,6 +911,10 @@ generate_mergeappend_paths(PlannerInfo *root, RelOptInfo *rel,
 			 */
 			if (cheapest_startup == NULL || cheapest_total == NULL)
 			{
+				/* XXX need to figure out what to do for LATERAL */
+				if (childrel->cheapest_total_path == NULL)
+					elog(ERROR, "LATERAL within an append relation is not supported yet");
+
 				cheapest_startup = cheapest_total =
 					childrel->cheapest_total_path;
 				Assert(cheapest_total != NULL);
@@ -1012,8 +1021,13 @@ has_multiple_baserels(PlannerInfo *root)
  * set_subquery_pathlist
  *		Build the (single) access path for a subquery RTE
  *
- * There's no need for a separate set_subquery_size phase, since we don't
- * support parameterized paths for subqueries.
+ * We don't currently support generating parameterized paths for subqueries
+ * by pushing join clauses down into them; it seems too expensive to re-plan
+ * the subquery multiple times to consider different alternatives.	So the
+ * subquery will have exactly one path.  (The path will be parameterized
+ * if the subquery contains LATERAL references, otherwise not.)  Since there's
+ * no freedom of action here, there's no need for a separate set_subquery_size
+ * phase: we just make the path right away.
  */
 static void
 set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
@@ -1021,6 +1035,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 {
 	Query	   *parse = root->parse;
 	Query	   *subquery = rte->subquery;
+	Relids		required_outer;
 	bool	   *differentTypes;
 	double		tuple_fraction;
 	PlannerInfo *subroot;
@@ -1033,6 +1048,20 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	 */
 	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.
+	 */
+	if (rte->lateral)
+	{
+		required_outer = pull_varnos_of_level((Node *) subquery, 1);
+		/* Enforce convention that empty required_outer is exactly NULL */
+		if (bms_is_empty(required_outer))
+			required_outer = NULL;
+	}
+	else
+		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));
@@ -1051,10 +1080,9 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	 * pseudoconstant clauses; better to have the gating node above the
 	 * subquery.
 	 *
-	 * Also, if the sub-query has "security_barrier" flag, it means the
+	 * Also, if the sub-query has the "security_barrier" flag, it means the
 	 * sub-query originated from a view that must enforce row-level security.
-	 * We must not push down quals in order to avoid information leaks, either
-	 * via side-effects or error output.
+	 * Then we must not push down quals that contain leaky functions.
 	 *
 	 * Non-pushed-down clauses will get evaluated as qpquals of the
 	 * SubqueryScan node.
@@ -1134,7 +1162,7 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 	pathkeys = convert_subquery_pathkeys(root, rel, subroot->query_pathkeys);
 
 	/* Generate appropriate path */
-	add_path(rel, create_subqueryscan_path(root, rel, pathkeys, NULL));
+	add_path(rel, create_subqueryscan_path(root, rel, pathkeys, required_outer));
 
 	/* Select cheapest path (pretty easy in this case...) */
 	set_cheapest(rel);
@@ -1143,12 +1171,32 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 /*
  * set_function_pathlist
  *		Build the (single) access path for a function RTE
+ *
+ * As with subqueries, a function RTE's path might be parameterized due to
+ * LATERAL references, but that's inherent in the function expression and
+ * not a result of pushing down join quals.
  */
 static void
 set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 {
+	Relids		required_outer;
+
+	/*
+	 * If it's a LATERAL function, it might contain some Vars of the current
+	 * query level, requiring it to be treated as parameterized.
+	 */
+	if (rte->lateral)
+	{
+		required_outer = pull_varnos_of_level(rte->funcexpr, 0);
+		/* Enforce convention that empty required_outer is exactly NULL */
+		if (bms_is_empty(required_outer))
+			required_outer = NULL;
+	}
+	else
+		required_outer = NULL;
+
 	/* Generate appropriate path */
-	add_path(rel, create_functionscan_path(root, rel));
+	add_path(rel, create_functionscan_path(root, rel, required_outer));
 
 	/* Select cheapest path (pretty easy in this case...) */
 	set_cheapest(rel);
@@ -1157,6 +1205,10 @@ set_function_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 /*
  * set_values_pathlist
  *		Build the (single) access path for a VALUES RTE
+ *
+ * There can be no need for a parameterized path here.	(Although the SQL
+ * spec does allow LATERAL (VALUES (x)), the parser will transform that
+ * into a subquery, so it doesn't end up here.)
  */
 static void
 set_values_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
@@ -1988,10 +2040,16 @@ debug_print_rel(PlannerInfo *root, RelOptInfo *rel)
 	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);
+	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/costsize.c b/src/backend/optimizer/path/costsize.c
index 875c611ab505115e6af3093ee5477ccf89cb196d..d3f04eea4b1b19404f297cdf954ebda83c4219ea 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -989,12 +989,17 @@ cost_subqueryscan(Path *path, PlannerInfo *root,
 /*
  * cost_functionscan
  *	  Determines and returns the cost of scanning a function RTE.
+ *
+ * 'baserel' is the relation to be scanned
+ * 'param_info' is the ParamPathInfo if this is a parameterized path, else NULL
  */
 void
-cost_functionscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
+cost_functionscan(Path *path, PlannerInfo *root,
+				  RelOptInfo *baserel, ParamPathInfo *param_info)
 {
 	Cost		startup_cost = 0;
 	Cost		run_cost = 0;
+	QualCost	qpqual_cost;
 	Cost		cpu_per_tuple;
 	RangeTblEntry *rte;
 	QualCost	exprcost;
@@ -1004,8 +1009,11 @@ cost_functionscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
 	rte = planner_rt_fetch(baserel->relid, root);
 	Assert(rte->rtekind == RTE_FUNCTION);
 
-	/* functionscans are never parameterized */
-	path->rows = baserel->rows;
+	/* Mark the path with the correct row estimate */
+	if (param_info)
+		path->rows = param_info->ppi_rows;
+	else
+		path->rows = baserel->rows;
 
 	/*
 	 * Estimate costs of executing the function expression.
@@ -1025,8 +1033,10 @@ cost_functionscan(Path *path, PlannerInfo *root, RelOptInfo *baserel)
 	startup_cost += exprcost.startup + exprcost.per_tuple;
 
 	/* Add scanning CPU costs */
-	startup_cost += baserel->baserestrictcost.startup;
-	cpu_per_tuple = cpu_tuple_cost + baserel->baserestrictcost.per_tuple;
+	get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
+
+	startup_cost += qpqual_cost.startup;
+	cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
 	run_cost += cpu_per_tuple * baserel->tuples;
 
 	path->startup_cost = startup_cost;
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 65f86194e155508baa2e7c690a08be7d0380299a..fe0e4d7c2010e24f548c3c4ad11e2384aa756936 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -491,12 +491,18 @@ sort_inner_and_outer(PlannerInfo *root,
 	 * 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;
+
+	/* 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,
@@ -696,6 +702,10 @@ match_unsorted_outer(PlannerInfo *root,
 	 */
 	if (save_jointype == JOIN_UNIQUE_INNER)
 	{
+		/* XXX for the moment, don't crash on LATERAL --- rethink this */
+		if (inner_cheapest_total == NULL)
+			return;
+
 		inner_cheapest_total = (Path *)
 			create_unique_path(root, innerrel, inner_cheapest_total, sjinfo);
 		Assert(inner_cheapest_total);
@@ -707,7 +717,7 @@ match_unsorted_outer(PlannerInfo *root,
 		 * enable_material is off or the path in question materializes its
 		 * output anyway.
 		 */
-		if (enable_material &&
+		if (enable_material && inner_cheapest_total != NULL &&
 			!ExecMaterializesOutput(inner_cheapest_total->pathtype))
 			matpath = (Path *)
 				create_material_path(innerrel, inner_cheapest_total);
@@ -735,6 +745,8 @@ match_unsorted_outer(PlannerInfo *root,
 		 * 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 does nothing for LATERAL, rethink
 		 */
 		if (save_jointype == JOIN_UNIQUE_OUTER)
 		{
@@ -814,6 +826,10 @@ match_unsorted_outer(PlannerInfo *root,
 		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,
@@ -1092,6 +1108,12 @@ hash_inner_and_outer(PlannerInfo *root,
 		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/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 414406bb8a13422dae87086aa1114b72683da0f1..6bb821fb385da06cd745fe394bc5328afd1b4c3b 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -84,6 +84,7 @@ static HashJoin *create_hashjoin_plan(PlannerInfo *root, HashPath *best_path,
 					 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);
@@ -1640,6 +1641,7 @@ create_subqueryscan_plan(PlannerInfo *root, Path *best_path,
 	{
 		scan_clauses = (List *)
 			replace_nestloop_params(root, (Node *) scan_clauses);
+		identify_nestloop_extparams(root, best_path->parent->subplan);
 	}
 
 	scan_plan = make_subqueryscan(tlist,
@@ -1664,11 +1666,13 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
 	FunctionScan *scan_plan;
 	Index		scan_relid = best_path->parent->relid;
 	RangeTblEntry *rte;
+	Node	   *funcexpr;
 
 	/* it should be a function base rel... */
 	Assert(scan_relid > 0);
 	rte = planner_rt_fetch(scan_relid, root);
 	Assert(rte->rtekind == RTE_FUNCTION);
+	funcexpr = rte->funcexpr;
 
 	/* Sort clauses into best execution order */
 	scan_clauses = order_qual_clauses(root, scan_clauses);
@@ -1676,8 +1680,17 @@ create_functionscan_plan(PlannerInfo *root, Path *best_path,
 	/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
 	scan_clauses = extract_actual_clauses(scan_clauses, false);
 
+	/* Replace any outer-relation variables with nestloop params */
+	if (best_path->param_info)
+	{
+		scan_clauses = (List *)
+			replace_nestloop_params(root, (Node *) scan_clauses);
+		/* The func expression itself could contain nestloop params, too */
+		funcexpr = replace_nestloop_params(root, funcexpr);
+	}
+
 	scan_plan = make_functionscan(tlist, scan_clauses, scan_relid,
-								  rte->funcexpr,
+								  funcexpr,
 								  rte->eref->colnames,
 								  rte->funccoltypes,
 								  rte->funccoltypmods,
@@ -2559,6 +2572,102 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
 								   (void *) root);
 }
 
+/*
+ * 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
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 3c7fa632b8ebe26c1d91e83e0ba1fa9c03b9e919..4481db5c341469f3485b56533a524f498de37ce5 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -204,6 +204,64 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars,
 	}
 }
 
+/*
+ * 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];
+	List	   *vars;
+	List	   *newvars;
+	Relids		where_needed;
+	ListCell   *lc;
+
+	/* No cross-references are possible if it's not LATERAL */
+	if (!rte->lateral)
+		return;
+
+	/* Fetch the appropriate variables */
+	if (rte->rtekind == RTE_SUBQUERY)
+		vars = pull_vars_of_level((Node *) rte->subquery, 1);
+	else if (rte->rtekind == RTE_FUNCTION)
+		vars = pull_vars_of_level(rte->funcexpr, 0);
+	else
+		return;
+
+	/* 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);
+}
+
 
 /*****************************************************************************
  *
@@ -286,7 +344,9 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, bool below_outer_join,
 	{
 		int			varno = ((RangeTblRef *) jtnode)->rtindex;
 
-		/* No quals to deal with, just return correct result */
+		/* 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..26b5dbb559dffac48f1f0279f6632cbe0cd2f66b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -1817,7 +1817,7 @@ grouping_planner(PlannerInfo *root, double tuple_fraction)
  *
  * Once grouping_planner() has applied a general tlist to the topmost
  * scan/join plan node, any tlist eval cost for added-on nodes should be
- * accounted for as we create those nodes.  Presently, of the node types we
+ * accounted for as we create those nodes.	Presently, of the node types we
  * can add on later, only Agg, WindowAgg, and Group project new tlists (the
  * rest just copy their input tuples) --- so make_agg(), make_windowagg() and
  * make_group() are responsible for calling this function to account for their
@@ -3257,6 +3257,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
 	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
@@ -1231,6 +1231,7 @@ convert_ANY_sublink_to_join(PlannerInfo *root, SubLink *sublink,
 	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
@@ -1175,6 +1175,13 @@ is_simple_subquery(Query *subquery)
 		subquery->cteList)
 		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
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 00052f5c846ebe87ade0c19da26655b47c64cf73..11de5c70d808138ae121b51e0748e19251751687 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -193,7 +193,7 @@ compare_path_costs_fuzzily(Path *path1, Path *path2, double fuzz_factor)
  * 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.
+ * 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.
@@ -250,15 +250,18 @@ set_cheapest(RelOptInfo *parent_rel)
 			cheapest_total_path = path;
 	}
 
-	if (cheapest_total_path == NULL)
+	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 */
-	parent_rel->cheapest_parameterized_paths = list_make1(cheapest_total_path);
+	/* 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)
@@ -1131,6 +1134,13 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
 	int			numCols;
 	ListCell   *lc;
 
+	/* XXX temporary band-aid to not crash on LATERAL queries */
+	if (subpath == NULL)
+	{
+		Assert(subpath == rel->cheapest_total_path);
+		return NULL;
+	}
+
 	/* Caller made a mistake if subpath isn't cheapest_total ... */
 	Assert(subpath == rel->cheapest_total_path);
 	Assert(subpath->parent == rel);
@@ -1657,16 +1667,18 @@ create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
  *	  returning the pathnode.
  */
 Path *
-create_functionscan_path(PlannerInfo *root, RelOptInfo *rel)
+create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
+						 Relids required_outer)
 {
 	Path	   *pathnode = makeNode(Path);
 
 	pathnode->pathtype = T_FunctionScan;
 	pathnode->parent = rel;
-	pathnode->param_info = NULL;	/* never parameterized at present */
+	pathnode->param_info = get_baserel_parampathinfo(root, rel,
+													 required_outer);
 	pathnode->pathkeys = NIL;	/* for now, assume unordered result */
 
-	cost_functionscan(pathnode, root, rel);
+	cost_functionscan(pathnode, root, rel, pathnode->param_info);
 
 	return pathnode;
 }
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index 9bc90c253139deb43415683800681f48fe0c8436..81332ff1cd183fd101f3ca884fbf406941af7523 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -42,16 +42,15 @@ typedef struct
 
 typedef struct
 {
-	int			var_location;
+	List	   *vars;
 	int			sublevels_up;
-} locate_var_of_level_context;
+} pull_vars_context;
 
 typedef struct
 {
 	int			var_location;
-	int			relid;
 	int			sublevels_up;
-} locate_var_of_relation_context;
+} locate_var_of_level_context;
 
 typedef struct
 {
@@ -77,12 +76,11 @@ typedef struct
 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,
 						   locate_var_of_level_context *context);
-static bool locate_var_of_relation_walker(Node *node,
-							  locate_var_of_relation_context *context);
 static bool find_minimum_var_level_walker(Node *node,
 							  find_minimum_var_level_context *context);
 static bool pull_var_clause_walker(Node *node,
@@ -122,6 +120,31 @@ pull_varnos(Node *node)
 	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)
 {
@@ -230,6 +253,66 @@ pull_varattnos_walker(Node *node, pull_varattnos_context *context)
 }
 
 
+/*
+ * 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
@@ -405,76 +488,6 @@ locate_var_of_level_walker(Node *node,
 }
 
 
-/*
- * locate_var_of_relation
- *	  Find the parse location of any Var of the specified relation.
- *
- * Returns -1 if no such Var is in the querytree, or if they all have
- * unknown parse location.
- *
- * Will recurse into sublinks.	Also, may be invoked directly on a Query.
- */
-int
-locate_var_of_relation(Node *node, int relid, int levelsup)
-{
-	locate_var_of_relation_context context;
-
-	context.var_location = -1;	/* in case we find nothing */
-	context.relid = relid;
-	context.sublevels_up = levelsup;
-
-	(void) query_or_expression_tree_walker(node,
-										   locate_var_of_relation_walker,
-										   (void *) &context,
-										   0);
-
-	return context.var_location;
-}
-
-static bool
-locate_var_of_relation_walker(Node *node,
-							  locate_var_of_relation_context *context)
-{
-	if (node == NULL)
-		return false;
-	if (IsA(node, Var))
-	{
-		Var		   *var = (Var *) node;
-
-		if (var->varno == context->relid &&
-			var->varlevelsup == context->sublevels_up &&
-			var->location >= 0)
-		{
-			context->var_location = var->location;
-			return true;		/* abort tree traversal and return true */
-		}
-		return false;
-	}
-	if (IsA(node, CurrentOfExpr))
-	{
-		/* since CurrentOfExpr doesn't carry location, nothing we can do */
-		return false;
-	}
-	/* No extra code needed for PlaceHolderVar; just look in contained expr */
-	if (IsA(node, Query))
-	{
-		/* Recurse into subselects */
-		bool		result;
-
-		context->sublevels_up++;
-		result = query_tree_walker((Query *) node,
-								   locate_var_of_relation_walker,
-								   (void *) context,
-								   0);
-		context->sublevels_up--;
-		return result;
-	}
-	return expression_tree_walker(node,
-								  locate_var_of_relation_walker,
-								  (void *) context);
-}
-
-
 /*
  * find_minimum_var_level
  *	  Recursively scan a clause to find the lowest variable level it
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 263edb5a7a61d3a3c2ea5f1a5f9a255024d86bad..1a112cd9a4e07d8e2dbf64fc769bd2c0af08f0ca 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -533,6 +533,7 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		rte = addRangeTableEntryForSubquery(pstate,
 											selectQuery,
 											makeAlias("*SELECT*", NIL),
+											false,
 											false);
 		rtr = makeNode(RangeTblRef);
 		/* assume new rte is at end */
@@ -651,18 +652,6 @@ transformInsertStmt(ParseState *pstate, InsertStmt *stmt)
 		for (i = 0; i < sublist_length; i++)
 			collations = lappend_oid(collations, InvalidOid);
 
-		/*
-		 * There mustn't have been any table references in the expressions,
-		 * else strange things would happen, like Cartesian products of those
-		 * tables with the VALUES list ...
-		 */
-		if (pstate->p_joinlist != NIL)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("VALUES must not contain table references"),
-					 parser_errposition(pstate,
-							  locate_var_of_level((Node *) exprsLists, 0))));
-
 		/*
 		 * Another thing we can't currently support is NEW/OLD references in
 		 * rules --- seems we'd need something like SQL99's LATERAL construct
@@ -1067,7 +1056,7 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	List	  **colexprs = NULL;
 	int			sublist_length = -1;
 	RangeTblEntry *rte;
-	RangeTblRef *rtr;
+	int			rtindex;
 	ListCell   *lc;
 	ListCell   *lc2;
 	int			i;
@@ -1215,19 +1204,17 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 	 */
 	rte = addRangeTableEntryForValues(pstate, exprsLists, collations,
 									  NULL, true);
-	rtr = makeNode(RangeTblRef);
+	addRTEtoQuery(pstate, rte, true, true, true);
+
 	/* assume new rte is at end */
-	rtr->rtindex = list_length(pstate->p_rtable);
-	Assert(rte == rt_fetch(rtr->rtindex, pstate->p_rtable));
-	pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
-	pstate->p_relnamespace = lappend(pstate->p_relnamespace, rte);
-	pstate->p_varnamespace = lappend(pstate->p_varnamespace, rte);
+	rtindex = list_length(pstate->p_rtable);
+	Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
 
 	/*
 	 * Generate a targetlist as though expanding "*"
 	 */
 	Assert(pstate->p_next_resno == 1);
-	qry->targetList = expandRelAttrs(pstate, rte, rtr->rtindex, 0, -1);
+	qry->targetList = expandRelAttrs(pstate, rte, rtindex, 0, -1);
 
 	/*
 	 * The grammar allows attaching ORDER BY, LIMIT, and FOR UPDATE to a
@@ -1249,19 +1236,6 @@ transformValuesClause(ParseState *pstate, SelectStmt *stmt)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 			 errmsg("SELECT FOR UPDATE/SHARE cannot be applied to VALUES")));
 
-	/*
-	 * There mustn't have been any table references in the expressions, else
-	 * strange things would happen, like Cartesian products of those tables
-	 * with the VALUES list.  We have to check this after parsing ORDER BY et
-	 * al since those could insert more junk.
-	 */
-	if (list_length(pstate->p_joinlist) != 1)
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("VALUES must not contain table references"),
-				 parser_errposition(pstate,
-							  locate_var_of_level((Node *) exprsLists, 0))));
-
 	/*
 	 * Another thing we can't currently support is NEW/OLD references in rules
 	 * --- seems we'd need something like SQL99's LATERAL construct to ensure
@@ -1477,10 +1451,12 @@ transformSetOperationStmt(ParseState *pstate, SelectStmt *stmt)
 									 false);
 
 	sv_relnamespace = pstate->p_relnamespace;
-	pstate->p_relnamespace = NIL;		/* no qualified names allowed */
-
 	sv_varnamespace = pstate->p_varnamespace;
-	pstate->p_varnamespace = list_make1(jrte);
+	pstate->p_relnamespace = NIL;
+	pstate->p_varnamespace = NIL;
+
+	/* add jrte to varnamespace only */
+	addRTEtoQuery(pstate, jrte, false, false, true);
 
 	/*
 	 * For now, we don't support resjunk sort clauses on the output of a
@@ -1577,7 +1553,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 	/*
 	 * If an internal node of a set-op tree has ORDER BY, LIMIT, FOR UPDATE,
 	 * or WITH clauses attached, we need to treat it like a leaf node to
-	 * generate an independent sub-Query tree.  Otherwise, it can be
+	 * generate an independent sub-Query tree.	Otherwise, it can be
 	 * represented by a SetOperationStmt node underneath the parent Query.
 	 */
 	if (stmt->op == SETOP_NONE)
@@ -1652,6 +1628,7 @@ transformSetOperationTree(ParseState *pstate, SelectStmt *stmt,
 		rte = addRangeTableEntryForSubquery(pstate,
 											selectQuery,
 											makeAlias(selectName, NIL),
+											false,
 											false);
 
 		/*
@@ -2074,7 +2051,6 @@ transformReturningList(ParseState *pstate, List *returningList)
 	int			save_next_resno;
 	bool		save_hasAggs;
 	bool		save_hasWindowFuncs;
-	int			length_rtable;
 
 	if (returningList == NIL)
 		return NIL;				/* nothing to do */
@@ -2092,7 +2068,6 @@ transformReturningList(ParseState *pstate, List *returningList)
 	pstate->p_hasAggs = false;
 	save_hasWindowFuncs = pstate->p_hasWindowFuncs;
 	pstate->p_hasWindowFuncs = false;
-	length_rtable = list_length(pstate->p_rtable);
 
 	/* transform RETURNING identically to a SELECT targetlist */
 	rlist = transformTargetList(pstate, returningList);
@@ -2113,25 +2088,6 @@ transformReturningList(ParseState *pstate, List *returningList)
 				 parser_errposition(pstate,
 									locate_windowfunc((Node *) rlist))));
 
-	/* no new relation references please */
-	if (list_length(pstate->p_rtable) != length_rtable)
-	{
-		int			vlocation = -1;
-		int			relid;
-
-		/* try to locate such a reference to point to */
-		for (relid = length_rtable + 1; relid <= list_length(pstate->p_rtable); relid++)
-		{
-			vlocation = locate_var_of_relation((Node *) rlist, relid, 0);
-			if (vlocation >= 0)
-				break;
-		}
-		ereport(ERROR,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			errmsg("RETURNING cannot contain references to other relations"),
-				 parser_errposition(pstate, vlocation)));
-	}
-
 	/* mark column origins */
 	markTargetListOrigins(pstate, rlist);
 
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 6b6901197db61ea2a560bd4bd4d186e78c8ddf84..90ea1f9f004f49dc8a52bc2c11db191b6ff8c074 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -396,7 +396,8 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
 %type <node>	ctext_expr
 %type <value>	NumericOnly
 %type <list>	NumericOnly_list
-%type <alias>	alias_clause
+%type <alias>	alias_clause opt_alias_clause
+%type <list>	func_alias_clause
 %type <sortby>	sortby
 %type <ielem>	index_elem
 %type <node>	table_ref
@@ -532,9 +533,9 @@ static void processCASbits(int cas_bits, int location, const char *constrType,
 
 	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
+	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
 
@@ -9309,65 +9310,37 @@ from_list:
 		;
 
 /*
- * table_ref is where an alias clause can be attached.	Note we cannot make
- * alias_clause have an empty production because that causes parse conflicts
- * between table_ref := '(' joined_table ')' alias_clause
- * and joined_table := '(' joined_table ')'.  So, we must have the
- * redundant-looking productions here instead.
+ * table_ref is where an alias clause can be attached.
  */
-table_ref:	relation_expr
-				{
-					$$ = (Node *) $1;
-				}
-			| relation_expr alias_clause
+table_ref:	relation_expr opt_alias_clause
 				{
 					$1->alias = $2;
 					$$ = (Node *) $1;
 				}
-			| func_table
-				{
-					RangeFunction *n = makeNode(RangeFunction);
-					n->funccallnode = $1;
-					n->coldeflist = NIL;
-					$$ = (Node *) n;
-				}
-			| func_table alias_clause
-				{
-					RangeFunction *n = makeNode(RangeFunction);
-					n->funccallnode = $1;
-					n->alias = $2;
-					n->coldeflist = NIL;
-					$$ = (Node *) n;
-				}
-			| func_table AS '(' TableFuncElementList ')'
-				{
-					RangeFunction *n = makeNode(RangeFunction);
-					n->funccallnode = $1;
-					n->coldeflist = $4;
-					$$ = (Node *) n;
-				}
-			| func_table AS ColId '(' TableFuncElementList ')'
+			| func_table func_alias_clause
 				{
 					RangeFunction *n = makeNode(RangeFunction);
-					Alias *a = makeNode(Alias);
+					n->lateral = false;
 					n->funccallnode = $1;
-					a->aliasname = $3;
-					n->alias = a;
-					n->coldeflist = $5;
+					n->alias = linitial($2);
+					n->coldeflist = lsecond($2);
 					$$ = (Node *) n;
 				}
-			| func_table ColId '(' TableFuncElementList ')'
+			| LATERAL_P func_table func_alias_clause
 				{
 					RangeFunction *n = makeNode(RangeFunction);
-					Alias *a = makeNode(Alias);
-					n->funccallnode = $1;
-					a->aliasname = $2;
-					n->alias = a;
-					n->coldeflist = $4;
+					n->lateral = true;
+					n->funccallnode = $2;
+					n->alias = linitial($3);
+					n->coldeflist = lsecond($3);
 					$$ = (Node *) n;
 				}
-			| select_with_parens
+			| select_with_parens opt_alias_clause
 				{
+					RangeSubselect *n = makeNode(RangeSubselect);
+					n->lateral = false;
+					n->subquery = $1;
+					n->alias = $2;
 					/*
 					 * The SQL spec does not permit a subselect
 					 * (<derived_table>) without an alias clause,
@@ -9379,26 +9352,47 @@ table_ref:	relation_expr
 					 * However, it does seem like a good idea to emit
 					 * an error message that's better than "syntax error".
 					 */
-					if (IsA($1, SelectStmt) &&
-						((SelectStmt *) $1)->valuesLists)
-						ereport(ERROR,
-								(errcode(ERRCODE_SYNTAX_ERROR),
-								 errmsg("VALUES in FROM must have an alias"),
-								 errhint("For example, FROM (VALUES ...) [AS] foo."),
-								 parser_errposition(@1)));
-					else
-						ereport(ERROR,
-								(errcode(ERRCODE_SYNTAX_ERROR),
-								 errmsg("subquery in FROM must have an alias"),
-								 errhint("For example, FROM (SELECT ...) [AS] foo."),
-								 parser_errposition(@1)));
-					$$ = NULL;
+					if ($2 == NULL)
+					{
+						if (IsA($1, SelectStmt) &&
+							((SelectStmt *) $1)->valuesLists)
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("VALUES in FROM must have an alias"),
+									 errhint("For example, FROM (VALUES ...) [AS] foo."),
+									 parser_errposition(@1)));
+						else
+							ereport(ERROR,
+									(errcode(ERRCODE_SYNTAX_ERROR),
+									 errmsg("subquery in FROM must have an alias"),
+									 errhint("For example, FROM (SELECT ...) [AS] foo."),
+									 parser_errposition(@1)));
+					}
+					$$ = (Node *) n;
 				}
-			| select_with_parens alias_clause
+			| LATERAL_P select_with_parens opt_alias_clause
 				{
 					RangeSubselect *n = makeNode(RangeSubselect);
-					n->subquery = $1;
-					n->alias = $2;
+					n->lateral = true;
+					n->subquery = $2;
+					n->alias = $3;
+					/* same coment as above */
+					if ($3 == NULL)
+					{
+						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)));
+					}
 					$$ = (Node *) n;
 				}
 			| joined_table
@@ -9524,6 +9518,41 @@ alias_clause:
 				}
 		;
 
+opt_alias_clause: alias_clause						{ $$ = $1; }
+			| /*EMPTY*/								{ $$ = NULL; }
+		;
+
+/*
+ * func_alias_clause can include both an Alias and a coldeflist, so we make it
+ * return a 2-element list that gets disassembled by calling production.
+ */
+func_alias_clause:
+			alias_clause
+				{
+					$$ = list_make2($1, NIL);
+				}
+			| AS '(' TableFuncElementList ')'
+				{
+					$$ = list_make2(NULL, $3);
+				}
+			| AS ColId '(' TableFuncElementList ')'
+				{
+					Alias *a = makeNode(Alias);
+					a->aliasname = $2;
+					$$ = list_make2(a, $4);
+				}
+			| ColId '(' TableFuncElementList ')'
+				{
+					Alias *a = makeNode(Alias);
+					a->aliasname = $1;
+					$$ = list_make2(a, $3);
+				}
+			| /*EMPTY*/
+				{
+					$$ = list_make2(NULL, NIL);
+				}
+		;
+
 join_type:	FULL join_outer							{ $$ = JOIN_FULL; }
 			| LEFT join_outer						{ $$ = JOIN_LEFT; }
 			| RIGHT join_outer						{ $$ = JOIN_RIGHT; }
@@ -12736,6 +12765,7 @@ reserved_keyword:
 			| INITIALLY
 			| INTERSECT
 			| INTO
+			| LATERAL_P
 			| LEADING
 			| LIMIT
 			| LOCALTIME
diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c
index 380d9d356057f973194fdc05cdb84653651c45c9..5854f81005d70837dda5b8cb05225e48a6255b94 100644
--- a/src/backend/parser/parse_agg.c
+++ b/src/backend/parser/parse_agg.c
@@ -181,6 +181,16 @@ transformAggregateCall(ParseState *pstate, Aggref *agg,
 	while (min_varlevel-- > 0)
 		pstate = pstate->parentParseState;
 	pstate->p_hasAggs = true;
+
+	/*
+	 * Complain if we are inside a LATERAL subquery of the aggregation query.
+	 * We must be in its FROM clause, so the aggregate is misplaced.
+	 */
+	if (pstate->p_lateral_active)
+		ereport(ERROR,
+				(errcode(ERRCODE_GROUPING_ERROR),
+				 errmsg("aggregates not allowed in FROM clause"),
+				 parser_errposition(pstate, agg->location)));
 }
 
 /*
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 97ab9d5581a2ad379a2b5c7928dbc7c7e5bcbcb8..f9faa11b2e9c5a7480596b7aaf2a4eaaf42257f0 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -58,8 +58,7 @@ static Node *transformJoinUsingClause(ParseState *pstate,
 static Node *transformJoinOnClause(ParseState *pstate, JoinExpr *j,
 					  RangeTblEntry *l_rte,
 					  RangeTblEntry *r_rte,
-					  List *relnamespace,
-					  Relids containedRels);
+					  List *relnamespace);
 static RangeTblEntry *transformTableEntry(ParseState *pstate, RangeVar *r);
 static RangeTblEntry *transformCTEReference(ParseState *pstate, RangeVar *r,
 					  CommonTableExpr *cte, Index levelsup);
@@ -69,10 +68,13 @@ static RangeTblEntry *transformRangeFunction(ParseState *pstate,
 					   RangeFunction *r);
 static Node *transformFromClauseItem(ParseState *pstate, Node *n,
 						RangeTblEntry **top_rte, int *top_rti,
-						List **relnamespace,
-						Relids *containedRels);
+						List **relnamespace);
 static Node *buildMergedJoinVar(ParseState *pstate, JoinType jointype,
 				   Var *l_colvar, Var *r_colvar);
+static ParseNamespaceItem *makeNamespaceItem(RangeTblEntry *rte,
+				  bool lateral_only, bool lateral_ok);
+static void setNamespaceLateralState(List *namespace,
+						 bool lateral_only, bool lateral_ok);
 static void checkExprIsVarFree(ParseState *pstate, Node *n,
 				   const char *constructName);
 static TargetEntry *findTargetlistEntrySQL92(ParseState *pstate, Node *node,
@@ -101,11 +103,6 @@ static Node *transformFrameOffset(ParseState *pstate, int frameOptions,
  * p_varnamespace lists were initialized to NIL when the pstate was created.
  * We will add onto any entries already present --- this is needed for rule
  * processing, as well as for UPDATE and DELETE.
- *
- * The range table may grow still further when we transform the expressions
- * in the query's quals and target list. (This is possible because in
- * POSTQUEL, we allowed references to relations not specified in the
- * from-clause.  PostgreSQL keeps this extension to standard SQL.)
  */
 void
 transformFromClause(ParseState *pstate, List *frmList)
@@ -117,6 +114,9 @@ transformFromClause(ParseState *pstate, List *frmList)
 	 * RangeFunctions, and/or JoinExprs. Transform each one (possibly adding
 	 * entries to the rtable), check for duplicate refnames, and then add it
 	 * to the joinlist and namespaces.
+	 *
+	 * Note we must process the items left-to-right for proper handling of
+	 * LATERAL references.
 	 */
 	foreach(fl, frmList)
 	{
@@ -124,20 +124,31 @@ transformFromClause(ParseState *pstate, List *frmList)
 		RangeTblEntry *rte;
 		int			rtindex;
 		List	   *relnamespace;
-		Relids		containedRels;
 
 		n = transformFromClauseItem(pstate, n,
 									&rte,
 									&rtindex,
-									&relnamespace,
-									&containedRels);
+									&relnamespace);
+		/* Mark the new relnamespace items as visible to LATERAL */
+		setNamespaceLateralState(relnamespace, true, true);
+
 		checkNameSpaceConflicts(pstate, pstate->p_relnamespace, relnamespace);
+
 		pstate->p_joinlist = lappend(pstate->p_joinlist, n);
 		pstate->p_relnamespace = list_concat(pstate->p_relnamespace,
 											 relnamespace);
-		pstate->p_varnamespace = lappend(pstate->p_varnamespace, rte);
-		bms_free(containedRels);
+		pstate->p_varnamespace = lappend(pstate->p_varnamespace,
+										 makeNamespaceItem(rte, true, true));
 	}
+
+	/*
+	 * We're done parsing the FROM list, so make all namespace items
+	 * unconditionally visible.  Note that this will also reset lateral_only
+	 * for any namespace items that were already present when we were called;
+	 * but those should have been that way already.
+	 */
+	setNamespaceLateralState(pstate->p_relnamespace, false, true);
+	setNamespaceLateralState(pstate->p_varnamespace, false, true);
 }
 
 /*
@@ -375,55 +386,34 @@ static Node *
 transformJoinOnClause(ParseState *pstate, JoinExpr *j,
 					  RangeTblEntry *l_rte,
 					  RangeTblEntry *r_rte,
-					  List *relnamespace,
-					  Relids containedRels)
+					  List *relnamespace)
 {
 	Node	   *result;
 	List	   *save_relnamespace;
 	List	   *save_varnamespace;
-	Relids		clause_varnos;
-	int			varno;
 
 	/*
-	 * This is a tad tricky, for two reasons.  First, the namespace that the
-	 * join expression should see is just the two subtrees of the JOIN plus
-	 * any outer references from upper pstate levels.  So, temporarily set
-	 * this pstate's namespace accordingly.  (We need not check for refname
-	 * conflicts, because transformFromClauseItem() already did.) NOTE: this
-	 * code is OK only because the ON clause can't legally alter the namespace
-	 * by causing implicit relation refs to be added.
+	 * The namespace that the join expression should see is just the two
+	 * subtrees of the JOIN plus any outer references from upper pstate
+	 * levels.	Temporarily set this pstate's namespace accordingly.  (We need
+	 * not check for refname conflicts, because transformFromClauseItem()
+	 * already did.)  All namespace items are marked visible regardless of
+	 * LATERAL state.
 	 */
 	save_relnamespace = pstate->p_relnamespace;
 	save_varnamespace = pstate->p_varnamespace;
 
+	setNamespaceLateralState(relnamespace, false, true);
 	pstate->p_relnamespace = relnamespace;
-	pstate->p_varnamespace = list_make2(l_rte, r_rte);
+
+	pstate->p_varnamespace = list_make2(makeNamespaceItem(l_rte, false, true),
+									  makeNamespaceItem(r_rte, false, true));
 
 	result = transformWhereClause(pstate, j->quals, "JOIN/ON");
 
 	pstate->p_relnamespace = save_relnamespace;
 	pstate->p_varnamespace = save_varnamespace;
 
-	/*
-	 * Second, we need to check that the ON condition doesn't refer to any
-	 * rels outside the input subtrees of the JOIN.  It could do that despite
-	 * our hack on the namespace if it uses fully-qualified names. So, grovel
-	 * through the transformed clause and make sure there are no bogus
-	 * references.	(Outer references are OK, and are ignored here.)
-	 */
-	clause_varnos = pull_varnos(result);
-	clause_varnos = bms_del_members(clause_varnos, containedRels);
-	if ((varno = bms_first_member(clause_varnos)) >= 0)
-	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
-		 errmsg("JOIN/ON clause refers to \"%s\", which is not part of JOIN",
-				rt_fetch(varno, pstate->p_rtable)->eref->aliasname),
-				 parser_errposition(pstate,
-								 locate_var_of_relation(result, varno, 0))));
-	}
-	bms_free(clause_varnos);
-
 	return result;
 }
 
@@ -435,13 +425,7 @@ transformTableEntry(ParseState *pstate, RangeVar *r)
 {
 	RangeTblEntry *rte;
 
-	/*
-	 * mark this entry to indicate it comes from the FROM clause. In SQL, the
-	 * target list can only refer to range variables specified in the from
-	 * clause but we follow the more powerful POSTQUEL semantics and
-	 * automatically generate the range variable if not specified. However
-	 * there are times we need to know whether the entries are legitimate.
-	 */
+	/* We need only build a range table entry */
 	rte = addRangeTableEntry(pstate, r, r->alias,
 							 interpretInhOption(r->inhOpt), true);
 
@@ -476,17 +460,28 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 	 * We require user to supply an alias for a subselect, per SQL92. To relax
 	 * this, we'd have to be prepared to gin up a unique alias for an
 	 * unlabeled subselect.  (This is just elog, not ereport, because the
-	 * grammar should have enforced it already.)
+	 * grammar should have enforced it already.  It'd probably be better to
+	 * report the error here, but we don't have a good error location here.)
 	 */
 	if (r->alias == NULL)
 		elog(ERROR, "subquery in FROM must have an alias");
 
+	/*
+	 * If the subselect is LATERAL, make lateral_only names of this level
+	 * visible to it.  (LATERAL can't nest within a single pstate level, so we
+	 * don't need save/restore logic here.)
+	 */
+	Assert(!pstate->p_lateral_active);
+	pstate->p_lateral_active = r->lateral;
+
 	/*
 	 * Analyze and transform the subquery.
 	 */
 	query = parse_sub_analyze(r->subquery, pstate, NULL,
 							  isLockedRefname(pstate, r->alias->aliasname));
 
+	pstate->p_lateral_active = false;
+
 	/*
 	 * Check that we got something reasonable.	Many of these conditions are
 	 * impossible given restrictions of the grammar, but check 'em anyway.
@@ -496,33 +491,14 @@ transformRangeSubselect(ParseState *pstate, RangeSubselect *r)
 		query->utilityStmt != NULL)
 		elog(ERROR, "unexpected non-SELECT command in subquery in FROM");
 
-	/*
-	 * The subquery cannot make use of any variables from FROM items created
-	 * earlier in the current query.  Per SQL92, the scope of a FROM item does
-	 * not include other FROM items.  Formerly we hacked the namespace so that
-	 * 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,
-					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
-					 errmsg("subquery in FROM cannot refer to other relations of same query level"),
-					 parser_errposition(pstate,
-								   locate_var_of_level((Node *) query, 1))));
-	}
-
 	/*
 	 * OK, build an RTE for the subquery.
 	 */
-	rte = addRangeTableEntryForSubquery(pstate, query, r->alias, true);
+	rte = addRangeTableEntryForSubquery(pstate,
+										query,
+										r->alias,
+										r->lateral,
+										true);
 
 	return rte;
 }
@@ -546,35 +522,26 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
 	 */
 	funcname = FigureColname(r->funccallnode);
 
+	/*
+	 * If the function is LATERAL, make lateral_only names of this level
+	 * visible to it.  (LATERAL can't nest within a single pstate level, so we
+	 * don't need save/restore logic here.)
+	 */
+	Assert(!pstate->p_lateral_active);
+	pstate->p_lateral_active = r->lateral;
+
 	/*
 	 * Transform the raw expression.
 	 */
 	funcexpr = transformExpr(pstate, r->funccallnode);
 
+	pstate->p_lateral_active = false;
+
 	/*
 	 * We must assign collations now so that we can fill funccolcollations.
 	 */
 	assign_expr_collations(pstate, funcexpr);
 
-	/*
-	 * The function parameters cannot make use of any variables from other
-	 * FROM items.	(Compare to transformRangeSubselect(); the coding is
-	 * different though because we didn't parse as a sub-select with its own
-	 * level of namespace.)
-	 *
-	 * XXX this will need further work to support SQL99's LATERAL() feature,
-	 * wherein such references would indeed be legal.
-	 */
-	if (pstate->p_relnamespace || pstate->p_varnamespace)
-	{
-		if (contain_vars_of_level(funcexpr, 0))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
-					 errmsg("function expression in FROM cannot refer to other relations of same query level"),
-					 parser_errposition(pstate,
-										locate_var_of_level(funcexpr, 0))));
-	}
-
 	/*
 	 * Disallow aggregate functions in the expression.	(No reason to postpone
 	 * this check until parseCheckAggregates.)
@@ -598,7 +565,7 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
 	 * OK, build an RTE for the function.
 	 */
 	rte = addRangeTableEntryForFunction(pstate, funcname, funcexpr,
-										r, true);
+										r, r->lateral, true);
 
 	/*
 	 * If a coldeflist was supplied, ensure it defines a legal set of names
@@ -637,12 +604,9 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
  *
  * *top_rti: receives the rangetable index of top_rte.	(Ditto.)
  *
- * *relnamespace: receives a List of the RTEs exposed as relation names
- * by this item.
- *
- * *containedRels: receives a bitmap set of the rangetable indexes
- * of all the base and join relations represented in this jointree item.
- * This is needed for checking JOIN/ON conditions in higher levels.
+ * *relnamespace: receives a List of ParseNamespaceItems for the RTEs exposed
+ * as relation names by this item.	(The lateral_only flags in these items
+ * are indeterminate and should be explicitly set by the caller before use.)
  *
  * We do not need to pass back an explicit varnamespace value, because
  * in all cases the varnamespace contribution is exactly top_rte.
@@ -650,8 +614,7 @@ transformRangeFunction(ParseState *pstate, RangeFunction *r)
 static Node *
 transformFromClauseItem(ParseState *pstate, Node *n,
 						RangeTblEntry **top_rte, int *top_rti,
-						List **relnamespace,
-						Relids *containedRels)
+						List **relnamespace)
 {
 	if (IsA(n, RangeVar))
 	{
@@ -681,8 +644,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
 		*top_rte = rte;
 		*top_rti = rtindex;
-		*relnamespace = list_make1(rte);
-		*containedRels = bms_make_singleton(rtindex);
+		*relnamespace = list_make1(makeNamespaceItem(rte, false, true));
 		rtr = makeNode(RangeTblRef);
 		rtr->rtindex = rtindex;
 		return (Node *) rtr;
@@ -700,8 +662,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
 		*top_rte = rte;
 		*top_rti = rtindex;
-		*relnamespace = list_make1(rte);
-		*containedRels = bms_make_singleton(rtindex);
+		*relnamespace = list_make1(makeNamespaceItem(rte, false, true));
 		rtr = makeNode(RangeTblRef);
 		rtr->rtindex = rtindex;
 		return (Node *) rtr;
@@ -719,8 +680,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));
 		*top_rte = rte;
 		*top_rti = rtindex;
-		*relnamespace = list_make1(rte);
-		*containedRels = bms_make_singleton(rtindex);
+		*relnamespace = list_make1(makeNamespaceItem(rte, false, true));
 		rtr = makeNode(RangeTblRef);
 		rtr->rtindex = rtindex;
 		return (Node *) rtr;
@@ -733,9 +693,6 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		RangeTblEntry *r_rte;
 		int			l_rtindex;
 		int			r_rtindex;
-		Relids		l_containedRels,
-					r_containedRels,
-					my_containedRels;
 		List	   *l_relnamespace,
 				   *r_relnamespace,
 				   *my_relnamespace,
@@ -745,38 +702,66 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 				   *l_colvars,
 				   *r_colvars,
 				   *res_colvars;
+		bool		lateral_ok;
+		int			sv_relnamespace_length,
+					sv_varnamespace_length;
 		RangeTblEntry *rte;
 		int			k;
 
 		/*
-		 * Recursively process the left and right subtrees
+		 * Recursively process the left subtree, then the right.  We must do
+		 * it in this order for correct visibility of LATERAL references.
 		 */
 		j->larg = transformFromClauseItem(pstate, j->larg,
 										  &l_rte,
 										  &l_rtindex,
-										  &l_relnamespace,
-										  &l_containedRels);
+										  &l_relnamespace);
+
+		/*
+		 * Make the left-side RTEs available for LATERAL access within the
+		 * right side, by temporarily adding them to the pstate's namespace
+		 * lists.  Per SQL:2008, if the join type is not INNER or LEFT then
+		 * the left-side names must still be exposed, but it's an error to
+		 * reference them.	(Stupid design, but that's what it says.)  Hence,
+		 * we always push them into the namespaces, but mark them as not
+		 * lateral_ok if the jointype is wrong.
+		 *
+		 * NB: this coding relies on the fact that list_concat is not
+		 * destructive to its second argument.
+		 */
+		lateral_ok = (j->jointype == JOIN_INNER || j->jointype == JOIN_LEFT);
+		setNamespaceLateralState(l_relnamespace, true, lateral_ok);
+		checkNameSpaceConflicts(pstate, pstate->p_relnamespace, l_relnamespace);
+		sv_relnamespace_length = list_length(pstate->p_relnamespace);
+		pstate->p_relnamespace = list_concat(pstate->p_relnamespace,
+											 l_relnamespace);
+		sv_varnamespace_length = list_length(pstate->p_varnamespace);
+		pstate->p_varnamespace = lappend(pstate->p_varnamespace,
+								 makeNamespaceItem(l_rte, true, lateral_ok));
+
+		/* And now we can process the RHS */
 		j->rarg = transformFromClauseItem(pstate, j->rarg,
 										  &r_rte,
 										  &r_rtindex,
-										  &r_relnamespace,
-										  &r_containedRels);
+										  &r_relnamespace);
+
+		/* Remove the left-side RTEs from the namespace lists again */
+		pstate->p_relnamespace = list_truncate(pstate->p_relnamespace,
+											   sv_relnamespace_length);
+		pstate->p_varnamespace = list_truncate(pstate->p_varnamespace,
+											   sv_varnamespace_length);
 
 		/*
 		 * Check for conflicting refnames in left and right subtrees. Must do
 		 * this because higher levels will assume I hand back a self-
-		 * consistent namespace subtree.
+		 * consistent namespace list.
 		 */
 		checkNameSpaceConflicts(pstate, l_relnamespace, r_relnamespace);
 
 		/*
-		 * Generate combined relation membership info for possible use by
-		 * transformJoinOnClause below.
+		 * Generate combined relnamespace info for possible use below.
 		 */
 		my_relnamespace = list_concat(l_relnamespace, r_relnamespace);
-		my_containedRels = bms_join(l_containedRels, r_containedRels);
-
-		pfree(r_relnamespace);	/* free unneeded list header */
 
 		/*
 		 * Extract column name and var lists from both subtrees
@@ -941,8 +926,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 			/* User-written ON-condition; transform it */
 			j->quals = transformJoinOnClause(pstate, j,
 											 l_rte, r_rte,
-											 my_relnamespace,
-											 my_containedRels);
+											 my_relnamespace);
 		}
 		else
 		{
@@ -1006,18 +990,10 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 		 * relnamespace.
 		 */
 		if (j->alias)
-		{
-			*relnamespace = list_make1(rte);
-			list_free(my_relnamespace);
-		}
+			*relnamespace = list_make1(makeNamespaceItem(rte, false, true));
 		else
 			*relnamespace = my_relnamespace;
 
-		/*
-		 * Include join RTE in returned containedRels set
-		 */
-		*containedRels = bms_add_member(my_containedRels, j->rtindex);
-
 		return (Node *) j;
 	}
 	else
@@ -1144,6 +1120,40 @@ buildMergedJoinVar(ParseState *pstate, JoinType jointype,
 	return res_node;
 }
 
+/*
+ * makeNamespaceItem -
+ *	  Convenience subroutine to construct a ParseNamespaceItem.
+ */
+static ParseNamespaceItem *
+makeNamespaceItem(RangeTblEntry *rte, bool lateral_only, bool lateral_ok)
+{
+	ParseNamespaceItem *nsitem;
+
+	nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
+	nsitem->p_rte = rte;
+	nsitem->p_lateral_only = lateral_only;
+	nsitem->p_lateral_ok = lateral_ok;
+	return nsitem;
+}
+
+/*
+ * setNamespaceLateralState -
+ *	  Convenience subroutine to update LATERAL flags in a namespace list.
+ */
+static void
+setNamespaceLateralState(List *namespace, bool lateral_only, bool lateral_ok)
+{
+	ListCell   *lc;
+
+	foreach(lc, namespace)
+	{
+		ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(lc);
+
+		nsitem->p_lateral_only = lateral_only;
+		nsitem->p_lateral_ok = lateral_ok;
+	}
+}
+
 
 /*
  * transformWhereClause -
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index bb1ad9af96bb5df02b9bf2a06222dd7dca8ea926..385f8e767e48c2a38403ccc8fe1e134d47ca55ae 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -751,19 +751,7 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref)
 		switch (crerr)
 		{
 			case CRERR_NO_COLUMN:
-				if (relname)
-					ereport(ERROR,
-							(errcode(ERRCODE_UNDEFINED_COLUMN),
-							 errmsg("column %s.%s does not exist",
-									relname, colname),
-							 parser_errposition(pstate, cref->location)));
-
-				else
-					ereport(ERROR,
-							(errcode(ERRCODE_UNDEFINED_COLUMN),
-							 errmsg("column \"%s\" does not exist",
-									colname),
-							 parser_errposition(pstate, cref->location)));
+				errorMissingColumn(pstate, relname, colname, cref->location);
 				break;
 			case CRERR_NO_RTE:
 				errorMissingRTE(pstate, makeRangeVar(nspname, relname,
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 30b307b191cc4e5304dbc99e0aea95046720bd46..47686c8719c9e9b091e0442b153d84840b43003b 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -137,7 +137,12 @@ scanNameSpaceForRefname(ParseState *pstate, const char *refname, int location)
 
 	foreach(l, pstate->p_relnamespace)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l);
+		RangeTblEntry *rte = nsitem->p_rte;
+
+		/* If not inside LATERAL, ignore lateral-only items */
+		if (nsitem->p_lateral_only && !pstate->p_lateral_active)
+			continue;
 
 		if (strcmp(rte->eref->aliasname, refname) == 0)
 		{
@@ -147,6 +152,14 @@ scanNameSpaceForRefname(ParseState *pstate, const char *refname, int location)
 						 errmsg("table reference \"%s\" is ambiguous",
 								refname),
 						 parser_errposition(pstate, location)));
+			/* SQL:2008 demands this be an error, not an invisible item */
+			if (nsitem->p_lateral_only && !nsitem->p_lateral_ok)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+						 errmsg("invalid reference to FROM-clause entry for table \"%s\"",
+								refname),
+						 errdetail("The combining JOIN type must be INNER or LEFT for a LATERAL reference."),
+						 parser_errposition(pstate, location)));
 			result = rte;
 		}
 	}
@@ -170,7 +183,12 @@ scanNameSpaceForRelid(ParseState *pstate, Oid relid, int location)
 
 	foreach(l, pstate->p_relnamespace)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l);
+		RangeTblEntry *rte = nsitem->p_rte;
+
+		/* If not inside LATERAL, ignore lateral-only items */
+		if (nsitem->p_lateral_only && !pstate->p_lateral_active)
+			continue;
 
 		/* yes, the test for alias == NULL should be there... */
 		if (rte->rtekind == RTE_RELATION &&
@@ -183,6 +201,14 @@ scanNameSpaceForRelid(ParseState *pstate, Oid relid, int location)
 						 errmsg("table reference %u is ambiguous",
 								relid),
 						 parser_errposition(pstate, location)));
+			/* SQL:2008 demands this be an error, not an invisible item */
+			if (nsitem->p_lateral_only && !nsitem->p_lateral_ok)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+						 errmsg("invalid reference to FROM-clause entry for table \"%s\"",
+								rte->eref->aliasname),
+						 errdetail("The combining JOIN type must be INNER or LEFT for a LATERAL reference."),
+						 parser_errposition(pstate, location)));
 			result = rte;
 		}
 	}
@@ -245,7 +271,7 @@ isFutureCTE(ParseState *pstate, const char *refname)
 }
 
 /*
- * searchRangeTable
+ * searchRangeTableForRel
  *	  See if any RangeTblEntry could possibly match the RangeVar.
  *	  If so, return a pointer to the RangeTblEntry; else return NULL.
  *
@@ -260,7 +286,7 @@ isFutureCTE(ParseState *pstate, const char *refname)
  * and matches on alias.
  */
 static RangeTblEntry *
-searchRangeTable(ParseState *pstate, RangeVar *relation)
+searchRangeTableForRel(ParseState *pstate, RangeVar *relation)
 {
 	const char *refname = relation->relname;
 	Oid			relId = InvalidOid;
@@ -322,6 +348,9 @@ searchRangeTable(ParseState *pstate, RangeVar *relation)
  * Per SQL92, two alias-less plain relation RTEs do not conflict even if
  * they have the same eref->aliasname (ie, same relation name), if they
  * are for different relation OIDs (implying they are in different schemas).
+ *
+ * We ignore the lateral-only flags in the namespace items: the lists must
+ * not conflict, even when all items are considered visible.
  */
 void
 checkNameSpaceConflicts(ParseState *pstate, List *namespace1,
@@ -331,13 +360,15 @@ checkNameSpaceConflicts(ParseState *pstate, List *namespace1,
 
 	foreach(l1, namespace1)
 	{
-		RangeTblEntry *rte1 = (RangeTblEntry *) lfirst(l1);
+		ParseNamespaceItem *nsitem1 = (ParseNamespaceItem *) lfirst(l1);
+		RangeTblEntry *rte1 = nsitem1->p_rte;
 		const char *aliasname1 = rte1->eref->aliasname;
 		ListCell   *l2;
 
 		foreach(l2, namespace2)
 		{
-			RangeTblEntry *rte2 = (RangeTblEntry *) lfirst(l2);
+			ParseNamespaceItem *nsitem2 = (ParseNamespaceItem *) lfirst(l2);
+			RangeTblEntry *rte2 = nsitem2->p_rte;
 
 			if (strcmp(rte2->eref->aliasname, aliasname1) != 0)
 				continue;		/* definitely no conflict */
@@ -544,9 +575,14 @@ colNameToVar(ParseState *pstate, char *colname, bool localonly,
 
 		foreach(l, pstate->p_varnamespace)
 		{
-			RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+			ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l);
+			RangeTblEntry *rte = nsitem->p_rte;
 			Node	   *newresult;
 
+			/* If not inside LATERAL, ignore lateral-only items */
+			if (nsitem->p_lateral_only && !pstate->p_lateral_active)
+				continue;
+
 			/* use orig_pstate here to get the right sublevels_up */
 			newresult = scanRTEForColumn(orig_pstate, rte, colname, location);
 
@@ -558,6 +594,14 @@ colNameToVar(ParseState *pstate, char *colname, bool localonly,
 							 errmsg("column reference \"%s\" is ambiguous",
 									colname),
 							 parser_errposition(orig_pstate, location)));
+				/* SQL:2008 demands this be an error, not an invisible item */
+				if (nsitem->p_lateral_only && !nsitem->p_lateral_ok)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+							 errmsg("invalid reference to FROM-clause entry for table \"%s\"",
+									rte->eref->aliasname),
+							 errdetail("The combining JOIN type must be INNER or LEFT for a LATERAL reference."),
+							 parser_errposition(orig_pstate, location)));
 				result = newresult;
 			}
 		}
@@ -571,6 +615,40 @@ colNameToVar(ParseState *pstate, char *colname, bool localonly,
 	return result;
 }
 
+/*
+ * searchRangeTableForCol
+ *	  See if any RangeTblEntry could possibly provide the given column name.
+ *	  If so, return a pointer to the RangeTblEntry; else return NULL.
+ *
+ * This is different from colNameToVar in that it considers every entry in
+ * the ParseState's rangetable(s), not only those that are currently visible
+ * in the p_varnamespace lists.  This behavior is invalid per the SQL spec,
+ * and it may give ambiguous results (there might be multiple equally valid
+ * matches, but only one will be returned).  This must be used ONLY as a
+ * heuristic in giving suitable error messages.  See errorMissingColumn.
+ */
+static RangeTblEntry *
+searchRangeTableForCol(ParseState *pstate, char *colname, int location)
+{
+	ParseState *orig_pstate = pstate;
+
+	while (pstate != NULL)
+	{
+		ListCell   *l;
+
+		foreach(l, pstate->p_rtable)
+		{
+			RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+
+			if (scanRTEForColumn(orig_pstate, rte, colname, location))
+				return rte;
+		}
+
+		pstate = pstate->parentParseState;
+	}
+	return NULL;
+}
+
 /*
  * markRTEForSelectPriv
  *	   Mark the specified column of an RTE as requiring SELECT privilege
@@ -917,16 +995,13 @@ addRangeTableEntry(ParseState *pstate,
 	 */
 	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.
+	/*
+	 * 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;
 
@@ -973,16 +1048,13 @@ addRangeTableEntryForRelation(ParseState *pstate,
 	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.
+	/*
+	 * 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;
 
@@ -1011,6 +1083,7 @@ RangeTblEntry *
 addRangeTableEntryForSubquery(ParseState *pstate,
 							  Query *subquery,
 							  Alias *alias,
+							  bool lateral,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
@@ -1054,15 +1127,12 @@ addRangeTableEntryForSubquery(ParseState *pstate,
 
 	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.
+	/*
+	 * 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;
 
@@ -1091,6 +1161,7 @@ addRangeTableEntryForFunction(ParseState *pstate,
 							  char *funcname,
 							  Node *funcexpr,
 							  RangeFunction *rangefunc,
+							  bool lateral,
 							  bool inFromCl)
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
@@ -1192,16 +1263,13 @@ addRangeTableEntryForFunction(ParseState *pstate,
 					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.
+	/*
+	 * Set flags and access permissions.
 	 *
-	 * Functions are never checked for access rights (at least, not by
-	 * the RTE permissions mechanism).
-	 *----------
+	 * Functions are never checked for access rights (at least, not by the RTE
+	 * permissions mechanism).
 	 */
+	rte->lateral = lateral;
 	rte->inh = false;			/* never true for functions */
 	rte->inFromCl = inFromCl;
 
@@ -1267,15 +1335,12 @@ addRangeTableEntryForValues(ParseState *pstate,
 
 	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.
+	/*
+	 * 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;
 
@@ -1338,15 +1403,12 @@ addRangeTableEntryForJoin(ParseState *pstate,
 
 	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.
+	/*
+	 * 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;
 
@@ -1441,15 +1503,12 @@ addRangeTableEntryForCTE(ParseState *pstate,
 
 	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.
+	/*
+	 * 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;
 
@@ -1519,7 +1578,8 @@ isLockedRefname(ParseState *pstate, const char *refname)
 /*
  * Add the given RTE as a top-level entry in the pstate's join list
  * and/or name space lists.  (We assume caller has checked for any
- * namespace conflicts.)
+ * namespace conflicts.)  The RTE is always marked as unconditionally
+ * visible, that is, not LATERAL-only.
  */
 void
 addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
@@ -1534,10 +1594,19 @@ addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
 		rtr->rtindex = rtindex;
 		pstate->p_joinlist = lappend(pstate->p_joinlist, rtr);
 	}
-	if (addToRelNameSpace)
-		pstate->p_relnamespace = lappend(pstate->p_relnamespace, rte);
-	if (addToVarNameSpace)
-		pstate->p_varnamespace = lappend(pstate->p_varnamespace, rte);
+	if (addToRelNameSpace || addToVarNameSpace)
+	{
+		ParseNamespaceItem *nsitem;
+
+		nsitem = (ParseNamespaceItem *) palloc(sizeof(ParseNamespaceItem));
+		nsitem->p_rte = rte;
+		nsitem->p_lateral_only = false;
+		nsitem->p_lateral_ok = true;
+		if (addToRelNameSpace)
+			pstate->p_relnamespace = lappend(pstate->p_relnamespace, nsitem);
+		if (addToVarNameSpace)
+			pstate->p_varnamespace = lappend(pstate->p_varnamespace, nsitem);
+	}
 }
 
 /*
@@ -2453,7 +2522,7 @@ errorMissingRTE(ParseState *pstate, RangeVar *relation)
 	 * rangetable.	(Note: cases involving a bad schema name in the RangeVar
 	 * will throw error immediately here.  That seems OK.)
 	 */
-	rte = searchRangeTable(pstate, relation);
+	rte = searchRangeTableForRel(pstate, relation);
 
 	/*
 	 * If we found a match that has an alias and the alias is visible in the
@@ -2490,3 +2559,43 @@ errorMissingRTE(ParseState *pstate, RangeVar *relation)
 						relation->relname),
 				 parser_errposition(pstate, relation->location)));
 }
+
+/*
+ * Generate a suitable error about a missing column.
+ *
+ * Since this is a very common type of error, we work rather hard to
+ * produce a helpful message.
+ */
+void
+errorMissingColumn(ParseState *pstate,
+				   char *relname, char *colname, int location)
+{
+	RangeTblEntry *rte;
+
+	/*
+	 * If relname was given, just play dumb and report it.	(In practice, a
+	 * bad qualification name should end up at errorMissingRTE, not here, so
+	 * no need to work hard on this case.)
+	 */
+	if (relname)
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_COLUMN),
+				 errmsg("column %s.%s does not exist", relname, colname),
+				 parser_errposition(pstate, location)));
+
+	/*
+	 * Otherwise, search the entire rtable looking for possible matches.  If
+	 * we find one, emit a hint about it.
+	 *
+	 * TODO: improve this code (and also errorMissingRTE) to mention using
+	 * LATERAL if appropriate.
+	 */
+	rte = searchRangeTableForCol(pstate, colname, location);
+
+	ereport(ERROR,
+			(errcode(ERRCODE_UNDEFINED_COLUMN),
+			 errmsg("column \"%s\" does not exist", colname),
+			 rte ? errhint("There is a column named \"%s\" in table \"%s\", but it cannot be referenced from this part of the query.",
+						   colname, rte->eref->aliasname) : 0,
+			 parser_errposition(pstate, location)));
+}
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3850a3bc646f3e32dc293240a78ef8ed72aa8500..4d9e6e610663afe59ba3a08fd25b7aae3ca3f612 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1129,9 +1129,13 @@ ExpandAllTables(ParseState *pstate, int location)
 
 	foreach(l, pstate->p_varnamespace)
 	{
-		RangeTblEntry *rte = (RangeTblEntry *) lfirst(l);
+		ParseNamespaceItem *nsitem = (ParseNamespaceItem *) lfirst(l);
+		RangeTblEntry *rte = nsitem->p_rte;
 		int			rtindex = RTERangeTablePosn(pstate, rte, NULL);
 
+		/* Should not have any lateral-only items when parsing targetlist */
+		Assert(!nsitem->p_lateral_only);
+
 		target = list_concat(target,
 							 expandRelAttrs(pstate, rte, rtindex, 0,
 											location));
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
@@ -676,6 +676,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 		oldrte->relid = trigrec->tgrelid;
 		oldrte->relkind = relkind;
 		oldrte->eref = makeAlias("old", NIL);
+		oldrte->lateral = false;
 		oldrte->inh = false;
 		oldrte->inFromCl = true;
 
@@ -684,6 +685,7 @@ pg_get_triggerdef_worker(Oid trigid, bool pretty)
 		newrte->relid = trigrec->tgrelid;
 		newrte->relkind = relkind;
 		newrte->eref = makeAlias("new", NIL);
+		newrte->lateral = false;
 		newrte->inh = false;
 		newrte->inFromCl = true;
 
@@ -2174,6 +2176,7 @@ deparse_context_for(const char *aliasname, Oid relid)
 	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;
 
@@ -6618,6 +6621,9 @@ get_from_clause_item(Node *jtnode, Query *query, deparse_context *context)
 		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/catalog/catversion.h b/src/include/catalog/catversion.h
index 4806b3133419f6a021515f92b4e8d3030b5dcd79..82b6c0cfc71e4ee5922feb8199bc2732effe7430 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */
 
 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201207201
+#define CATALOG_VERSION_NO	201208071
 
 #endif
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 119e1ed2f6e18872cfd3758b9303fb2835e5695a..f433166cc681431aa5832c0666bd3cc59afb4304 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -451,6 +451,7 @@ typedef struct WindowDef
 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;
@@ -461,6 +462,7 @@ typedef struct RangeSubselect
 typedef struct RangeFunction
 {
 	NodeTag		type;
+	bool		lateral;		/* does it have LATERAL prefix? */
 	Node	   *funccallnode;	/* untransformed function call tree */
 	Alias	   *alias;			/* table alias & optional column aliases */
 	List	   *coldeflist;		/* list of ColumnDef nodes to describe result
@@ -706,7 +708,7 @@ typedef struct RangeTblEntry
 	 * Fields valid for a subquery RTE (else NULL):
 	 */
 	Query	   *subquery;		/* the sub-query */
-	bool		security_barrier;		/* subquery from security_barrier view */
+	bool		security_barrier;		/* is from security_barrier view? */
 
 	/*
 	 * Fields valid for a join RTE (else NULL/zero):
@@ -756,6 +758,7 @@ typedef struct RangeTblEntry
 	 */
 	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 */
@@ -1752,7 +1755,7 @@ typedef struct AlterEventTrigStmt
 {
 	NodeTag		type;
 	char	   *trigname;		/* TRIGGER's name */
-	char        tgenabled;		/* trigger's firing configuration WRT
+	char		tgenabled;		/* trigger's firing configuration WRT
 								 * session_replication_role */
 } AlterEventTrigStmt;
 
@@ -2046,7 +2049,7 @@ typedef struct FetchStmt
  *
  * This represents creation of an index and/or an associated constraint.
  * If isconstraint is true, we should create a pg_constraint entry along
- * with the index.  But if indexOid isn't InvalidOid, we are not creating an
+ * with the index.	But if indexOid isn't InvalidOid, we are not creating an
  * index, just a UNIQUE/PKEY constraint using an existing index.  isconstraint
  * must always be true in this case, and the fields describing the index
  * properties are empty.
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
@@ -307,15 +307,17 @@ typedef struct PlannerInfo
  *		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)
+ *								 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)
+ *							   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
+ *								 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/cost.h b/src/include/optimizer/cost.h
index b2cdb3d62e96cb363118cebf0091f0c00a917730..e3d33d69ba357274cdeec62f3275ff4c2ae56fa1 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -81,7 +81,7 @@ extern void cost_tidscan(Path *path, PlannerInfo *root,
 extern void cost_subqueryscan(Path *path, PlannerInfo *root,
 				  RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_functionscan(Path *path, PlannerInfo *root,
-				  RelOptInfo *baserel);
+				  RelOptInfo *baserel, ParamPathInfo *param_info);
 extern void cost_valuesscan(Path *path, PlannerInfo *root,
 				RelOptInfo *baserel);
 extern void cost_ctescan(Path *path, PlannerInfo *root, RelOptInfo *baserel);
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 385bae6eb80e737741001d978139dcdeb15fdb1e..3af1172cbe5f1398fe4780561843cf147b693b96 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -69,7 +69,8 @@ extern UniquePath *create_unique_path(PlannerInfo *root, RelOptInfo *rel,
 				   Path *subpath, SpecialJoinInfo *sjinfo);
 extern Path *create_subqueryscan_path(PlannerInfo *root, RelOptInfo *rel,
 						 List *pathkeys, Relids required_outer);
-extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel);
+extern Path *create_functionscan_path(PlannerInfo *root, RelOptInfo *rel,
+						 Relids required_outer);
 extern Path *create_valuesscan_path(PlannerInfo *root, RelOptInfo *rel);
 extern Path *create_ctescan_path(PlannerInfo *root, RelOptInfo *rel);
 extern Path *create_worktablescan_path(PlannerInfo *root, RelOptInfo *rel);
diff --git a/src/include/optimizer/var.h b/src/include/optimizer/var.h
index f546362b680ca5e6f2f1a41883755b3cc6235e15..ec21df3a7e0e1119be5802c7ade25b642e405a42 100644
--- a/src/include/optimizer/var.h
+++ b/src/include/optimizer/var.h
@@ -31,11 +31,12 @@ typedef enum
 } 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);
-extern int	locate_var_of_relation(Node *node, int relid, int levelsup);
 extern int	find_minimum_var_level(Node *node);
 extern List *pull_var_clause(Node *node, PVCAggregateBehavior aggbehavior,
 				PVCPlaceHolderBehavior phbehavior);
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 7e55a92185b64e0eeac830c534c18b071bb4313a..af60dac08e5cd148d91763242019baeeafe62793 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -213,6 +213,7 @@ PG_KEYWORD("label", LABEL, UNRESERVED_KEYWORD)
 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, RESERVED_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_node.h b/src/include/parser/parse_node.h
index 670e084993686a55ee3f944d3312cdc8d6c76607..13f745f6fa611255c4ce1449170c19e711d7bc11 100644
--- a/src/include/parser/parse_node.h
+++ b/src/include/parser/parse_node.h
@@ -54,22 +54,25 @@ typedef Node *(*CoerceParamHook) (ParseState *pstate, Param *param,
  * p_joinlist: list of join items (RangeTblRef and JoinExpr nodes) that
  * will become the fromlist of the query's top-level FromExpr node.
  *
- * p_relnamespace: list of RTEs that represents the current namespace for
- * table lookup, ie, those RTEs that are accessible by qualified names.
- * This may be just a subset of the rtable + joinlist, and/or may contain
- * entries that are not yet added to the main joinlist.
- *
- * p_varnamespace: list of RTEs that represents the current namespace for
- * column lookup, ie, those RTEs that are accessible by unqualified names.
- * This is different from p_relnamespace because a JOIN without an alias does
- * not hide the contained tables (so they must still be in p_relnamespace)
- * but it does hide their columns (unqualified references to the columns must
- * refer to the JOIN, not the member tables).  Other special RTEs such as
- * NEW/OLD for rules may also appear in just one of these lists.
+ * p_relnamespace: list of ParseNamespaceItems that represents the current
+ * namespace for table lookup, ie, those RTEs that are accessible by
+ * qualified names.  (This may be just a subset of the whole rtable.)
+ *
+ * p_varnamespace: list of ParseNamespaceItems that represents the current
+ * namespace for column lookup, ie, those RTEs that are accessible by
+ * unqualified names.  This is different from p_relnamespace because a JOIN
+ * without an alias does not hide the contained tables (so they must be in
+ * p_relnamespace) but it does hide their columns (unqualified references to
+ * the columns must refer to the JOIN, not the member tables).	Other special
+ * RTEs such as NEW/OLD for rules may also appear in just one of these lists.
+ *
+ * p_lateral_active: TRUE if we are currently parsing a LATERAL subexpression
+ * of this parse level.  This makes p_lateral_only namespace items visible,
+ * whereas they are not visible when p_lateral_active is FALSE.
  *
  * p_ctenamespace: list of CommonTableExprs (WITH items) that are visible
- * at the moment.  This is different from p_relnamespace because you have
- * to make an RTE before you can access a CTE.
+ * at the moment.  This is entirely different from p_relnamespace because
+ * a CTE is not an RTE, rather "visibility" means you could make an RTE.
  *
  * p_future_ctes: list of CommonTableExprs (WITH items) that are not yet
  * visible due to scope rules.	This is used to help improve error messages.
@@ -93,6 +96,7 @@ struct ParseState
 								 * node's fromlist) */
 	List	   *p_relnamespace; /* current namespace for relations */
 	List	   *p_varnamespace; /* current namespace for columns */
+	bool		p_lateral_active;		/* p_lateral_only items visible? */
 	List	   *p_ctenamespace; /* current namespace for common table exprs */
 	List	   *p_future_ctes;	/* common table exprs not yet in namespace */
 	CommonTableExpr *p_parent_cte;		/* this query's containing CTE */
@@ -121,6 +125,14 @@ struct ParseState
 	void	   *p_ref_hook_state;		/* common passthrough link for above */
 };
 
+/* An element of p_relnamespace or p_varnamespace */
+typedef struct ParseNamespaceItem
+{
+	RangeTblEntry *p_rte;		/* The relation's rangetable entry */
+	bool		p_lateral_only; /* Is only visible to LATERAL expressions? */
+	bool		p_lateral_ok;	/* If so, does join type allow use? */
+} ParseNamespaceItem;
+
 /* Support for parser_errposition_callback function */
 typedef struct ParseCallbackState
 {
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index ababd74f82f55cd80879a3b39a209417b2bae431..ba99fc2d8a38cdf213a15125c4bfe7dac401d925 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -55,11 +55,13 @@ extern RangeTblEntry *addRangeTableEntryForRelation(ParseState *pstate,
 extern RangeTblEntry *addRangeTableEntryForSubquery(ParseState *pstate,
 							  Query *subquery,
 							  Alias *alias,
+							  bool lateral,
 							  bool inFromCl);
 extern RangeTblEntry *addRangeTableEntryForFunction(ParseState *pstate,
 							  char *funcname,
 							  Node *funcexpr,
 							  RangeFunction *rangefunc,
+							  bool lateral,
 							  bool inFromCl);
 extern RangeTblEntry *addRangeTableEntryForValues(ParseState *pstate,
 							List *exprs,
@@ -82,6 +84,8 @@ extern void addRTEtoQuery(ParseState *pstate, RangeTblEntry *rte,
 			  bool addToJoinList,
 			  bool addToRelNameSpace, bool addToVarNameSpace);
 extern void errorMissingRTE(ParseState *pstate, RangeVar *relation);
+extern void errorMissingColumn(ParseState *pstate,
+				   char *relname, char *colname, int location);
 extern void expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 		  int location, bool include_dropped,
 		  List **colnames, List **colvars);
diff --git a/src/interfaces/ecpg/preproc/ecpg.addons b/src/interfaces/ecpg/preproc/ecpg.addons
index 5c5adf7699655fe09774f806499c9a156b963aff..aae3cc9d92d18fde7a6fc32dcc05f5781b89eb32 100644
--- a/src/interfaces/ecpg/preproc/ecpg.addons
+++ b/src/interfaces/ecpg/preproc/ecpg.addons
@@ -358,7 +358,11 @@ ECPG: into_clauseINTOOptTempTableName block
 						$$= cat2_str(mm_strdup("into"), $2);
 					}
 	| ecpg_into { $$ = EMPTY; }
-ECPG: table_refselect_with_parens addon
+ECPG: table_refselect_with_parensopt_alias_clause addon
+	if ($2 == NULL)
+		mmerror(PARSE_ERROR, ET_ERROR, "subquery in FROM must have an alias");
+ECPG: table_refLATERAL_Pselect_with_parensopt_alias_clause addon
+	if ($3 == NULL)
 		mmerror(PARSE_ERROR, ET_ERROR, "subquery in FROM must have an alias");
 ECPG: TypenameSimpleTypenameopt_array_bounds block
 	{	$$ = cat2_str($1, $2.str); }
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index c46d35df89e05a4b99a6105d7ab9cb5921a52e54..c5b92582b4cf29317ae8e391a6637d1d9c789a9c 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -2986,3 +2986,163 @@ SELECT * FROM
 (5 rows)
 
 rollback;
+--
+-- Test LATERAL
+--
+select unique2, x.*
+from tenk1 a, lateral (select * from int4_tbl b where f1 = a.unique1) x;
+ unique2 | f1 
+---------+----
+    9998 |  0
+(1 row)
+
+explain (costs off)
+  select unique2, x.*
+  from tenk1 a, lateral (select * from int4_tbl b where f1 = a.unique1) x;
+            QUERY PLAN            
+----------------------------------
+ Nested Loop
+   ->  Seq Scan on tenk1 a
+   ->  Seq Scan on int4_tbl b
+         Filter: (f1 = a.unique1)
+(4 rows)
+
+select unique2, x.*
+from int4_tbl x, lateral (select unique2 from tenk1 where f1 = unique1) ss;
+ unique2 | f1 
+---------+----
+    9998 |  0
+(1 row)
+
+explain (costs off)
+  select unique2, x.*
+  from int4_tbl x, lateral (select unique2 from tenk1 where f1 = unique1) ss;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Nested Loop
+   ->  Seq Scan on int4_tbl x
+   ->  Index Scan using tenk1_unique1 on tenk1
+         Index Cond: (x.f1 = unique1)
+(4 rows)
+
+explain (costs off)
+  select unique2, x.*
+  from int4_tbl x cross join lateral (select unique2 from tenk1 where f1 = unique1) ss;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Nested Loop
+   ->  Seq Scan on int4_tbl x
+   ->  Index Scan using tenk1_unique1 on tenk1
+         Index Cond: (x.f1 = unique1)
+(4 rows)
+
+select unique2, x.*
+from int4_tbl x left join lateral (select unique1, unique2 from tenk1 where f1 = unique1) ss on f1 = unique1;
+ unique2 |     f1      
+---------+-------------
+    9998 |           0
+         |      123456
+         |     -123456
+         |  2147483647
+         | -2147483647
+(5 rows)
+
+explain (costs off)
+  select unique2, x.*
+  from int4_tbl x left join lateral (select unique1, unique2 from tenk1 where f1 = unique1) ss on f1 = unique1;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Nested Loop Left Join
+   ->  Seq Scan on int4_tbl x
+   ->  Subquery Scan on ss
+         Filter: (x.f1 = ss.unique1)
+         ->  Index Scan using tenk1_unique1 on tenk1
+               Index Cond: (x.f1 = unique1)
+(6 rows)
+
+-- check scoping of lateral versus parent references
+-- the first of these should return int8_tbl.q2, the second int8_tbl.q1
+select *, (select r from (select q1 as q2) x, (select q2 as r) y) from int8_tbl;
+        q1        |        q2         |         r         
+------------------+-------------------+-------------------
+              123 |               456 |               456
+              123 |  4567890123456789 |  4567890123456789
+ 4567890123456789 |               123 |               123
+ 4567890123456789 |  4567890123456789 |  4567890123456789
+ 4567890123456789 | -4567890123456789 | -4567890123456789
+(5 rows)
+
+select *, (select r from (select q1 as q2) x, lateral (select q2 as r) y) from int8_tbl;
+        q1        |        q2         |        r         
+------------------+-------------------+------------------
+              123 |               456 |              123
+              123 |  4567890123456789 |              123
+ 4567890123456789 |               123 | 4567890123456789
+ 4567890123456789 |  4567890123456789 | 4567890123456789
+ 4567890123456789 | -4567890123456789 | 4567890123456789
+(5 rows)
+
+-- lateral SRF
+select count(*) from tenk1 a, lateral generate_series(1,two) g;
+ count 
+-------
+  5000
+(1 row)
+
+explain (costs off)
+  select count(*) from tenk1 a, lateral generate_series(1,two) g;
+                   QUERY PLAN                   
+------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         ->  Seq Scan on tenk1 a
+         ->  Function Scan on generate_series g
+(4 rows)
+
+explain (costs off)
+  select count(*) from tenk1 a cross join lateral generate_series(1,two) g;
+                   QUERY PLAN                   
+------------------------------------------------
+ Aggregate
+   ->  Nested Loop
+         ->  Seq Scan on tenk1 a
+         ->  Function Scan on generate_series g
+(4 rows)
+
+-- test some error cases where LATERAL should have been used but wasn't
+select f1,g from int4_tbl a, generate_series(0, f1) g;
+ERROR:  column "f1" does not exist
+LINE 1: select f1,g from int4_tbl a, generate_series(0, f1) g;
+                                                        ^
+HINT:  There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
+select f1,g from int4_tbl a, generate_series(0, a.f1) g;
+ERROR:  invalid reference to FROM-clause entry for table "a"
+LINE 1: select f1,g from int4_tbl a, generate_series(0, a.f1) g;
+                                                        ^
+HINT:  There is an entry for table "a", but it cannot be referenced from this part of the query.
+select f1,g from int4_tbl a cross join generate_series(0, f1) g;
+ERROR:  column "f1" does not exist
+LINE 1: ...ct f1,g from int4_tbl a cross join generate_series(0, f1) g;
+                                                                 ^
+HINT:  There is a column named "f1" in table "a", but it cannot be referenced from this part of the query.
+select f1,g from int4_tbl a cross join generate_series(0, a.f1) g;
+ERROR:  invalid reference to FROM-clause entry for table "a"
+LINE 1: ... f1,g from int4_tbl a cross join generate_series(0, a.f1) g;
+                                                               ^
+HINT:  There is an entry for table "a", but it cannot be referenced from this part of the query.
+-- SQL:2008 says the left table is in scope but illegal to access here
+select f1,g from int4_tbl a right join lateral generate_series(0, a.f1) g on true;
+ERROR:  invalid reference to FROM-clause entry for table "a"
+LINE 1: ... int4_tbl a right join lateral generate_series(0, a.f1) g on...
+                                                             ^
+DETAIL:  The combining JOIN type must be INNER or LEFT for a LATERAL reference.
+select f1,g from int4_tbl a full join lateral generate_series(0, a.f1) g on true;
+ERROR:  invalid reference to FROM-clause entry for table "a"
+LINE 1: ...m int4_tbl a full join lateral generate_series(0, a.f1) g on...
+                                                             ^
+DETAIL:  The combining JOIN type must be INNER or LEFT for a LATERAL reference.
+-- LATERAL can be used to put an aggregate into the FROM clause of its query
+select 1 from tenk1 a, lateral (select max(a.unique1) from int4_tbl b) ss;
+ERROR:  aggregates not allowed in FROM clause
+LINE 1: select 1 from tenk1 a, lateral (select max(a.unique1) from i...
+                                               ^
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 6710b9c15edd22018da7f3f1a26a18956c5a664e..0fe8ca4c4e927da12cfe9ae75c9479b3ed8c4c22 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -21,9 +21,10 @@ INSERT INTO foo2 VALUES(1, 111);
 CREATE FUNCTION foot(int) returns setof foo2 as 'SELECT * FROM foo2 WHERE fooid = $1;' LANGUAGE SQL;
 -- supposed to fail with ERROR
 select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
-ERROR:  function expression in FROM cannot refer to other relations of same query level
+ERROR:  invalid reference to FROM-clause entry for table "foo2"
 LINE 1: select * from foo2, foot(foo2.fooid) z where foo2.f2 = z.f2;
                                  ^
+HINT:  There is an entry for table "foo2", but it cannot be referenced from this part of the query.
 -- function in subselect
 select * from foo2 where f2 in (select f2 from foot(foo2.fooid) z where z.fooid = foo2.fooid) ORDER BY 1,2;
  fooid | f2  
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index f07f39534a5d5b0cc7267da3414571d29dd8ae7b..b041550f0064d81853b392241d2fd0511bec8b2e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1190,6 +1190,7 @@ do instead insert into foo2 values (f1);
 ERROR:  column "f1" does not exist
 LINE 2: do instead insert into foo2 values (f1);
                                             ^
+HINT:  There is a column named "f1" in table "old", but it cannot be referenced from this part of the query.
 -- this is the correct way:
 create rule foorule as on insert to foo where f1 < 100
 do instead insert into foo2 values (new.f1);
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 14028314e8e98241ee4eaf1893f408718a0d74ac..f78218822f507ab296a9827700f312cdfe9c3ed3 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -412,6 +412,7 @@ SELECT q1 FROM int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1;
 ERROR:  column "q2" does not exist
 LINE 1: ... int8_tbl EXCEPT SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1...
                                                              ^
+HINT:  There is a column named "q2" in table "*SELECT* 2", but it cannot be referenced from this part of the query.
 -- But this should work:
 SELECT q1 FROM int8_tbl EXCEPT (((SELECT q2 FROM int8_tbl ORDER BY q2 LIMIT 1)));
         q1        
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index 8676e2f76103a4ade2c130e59956e41245903dca..5de98dc0a722c1f855689105b3beacd6e5a810ee 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -840,3 +840,49 @@ SELECT * FROM
   ON true;
 
 rollback;
+
+--
+-- Test LATERAL
+--
+
+select unique2, x.*
+from tenk1 a, lateral (select * from int4_tbl b where f1 = a.unique1) x;
+explain (costs off)
+  select unique2, x.*
+  from tenk1 a, lateral (select * from int4_tbl b where f1 = a.unique1) x;
+select unique2, x.*
+from int4_tbl x, lateral (select unique2 from tenk1 where f1 = unique1) ss;
+explain (costs off)
+  select unique2, x.*
+  from int4_tbl x, lateral (select unique2 from tenk1 where f1 = unique1) ss;
+explain (costs off)
+  select unique2, x.*
+  from int4_tbl x cross join lateral (select unique2 from tenk1 where f1 = unique1) ss;
+select unique2, x.*
+from int4_tbl x left join lateral (select unique1, unique2 from tenk1 where f1 = unique1) ss on f1 = unique1;
+explain (costs off)
+  select unique2, x.*
+  from int4_tbl x left join lateral (select unique1, unique2 from tenk1 where f1 = unique1) ss on f1 = unique1;
+
+-- check scoping of lateral versus parent references
+-- the first of these should return int8_tbl.q2, the second int8_tbl.q1
+select *, (select r from (select q1 as q2) x, (select q2 as r) y) from int8_tbl;
+select *, (select r from (select q1 as q2) x, lateral (select q2 as r) y) from int8_tbl;
+
+-- lateral SRF
+select count(*) from tenk1 a, lateral generate_series(1,two) g;
+explain (costs off)
+  select count(*) from tenk1 a, lateral generate_series(1,two) g;
+explain (costs off)
+  select count(*) from tenk1 a cross join lateral generate_series(1,two) g;
+
+-- test some error cases where LATERAL should have been used but wasn't
+select f1,g from int4_tbl a, generate_series(0, f1) g;
+select f1,g from int4_tbl a, generate_series(0, a.f1) g;
+select f1,g from int4_tbl a cross join generate_series(0, f1) g;
+select f1,g from int4_tbl a cross join generate_series(0, a.f1) g;
+-- SQL:2008 says the left table is in scope but illegal to access here
+select f1,g from int4_tbl a right join lateral generate_series(0, a.f1) g on true;
+select f1,g from int4_tbl a full join lateral generate_series(0, a.f1) g on true;
+-- LATERAL can be used to put an aggregate into the FROM clause of its query
+select 1 from tenk1 a, lateral (select max(a.unique1) from int4_tbl b) ss;