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