From: Tom Lane Date: Sat, 18 Aug 2012 18:10:17 +0000 (-0400) Subject: Another round of planner fixes for LATERAL. X-Git-Tag: REL9_3_BETA1~1062 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=084a29c94f94b5a08aec9f68f3cfaf252f4fa17c;p=postgresql Another round of planner fixes for LATERAL. Formerly, subquery pullup had no need to examine other entries in the range table, since they could not contain any references to the subquery being pulled up. That's no longer true with LATERAL, so now we need to be able to visit rangetable subexpressions to replace Vars referencing the pulled-up subquery. Also, this means that extract_lateral_references must be unsurprised at encountering lateral PlaceHolderVars, since such might be created when pulling up a subquery that's underneath an outer join with respect to the lateral reference. --- diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 996f91d309..81f7cf2e9d 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -237,14 +237,30 @@ extract_lateral_references(PlannerInfo *root, int rtindex) else return; - /* Copy each Var and adjust it to match our level */ + /* Copy each Var (or PlaceHolderVar) and adjust it to match our level */ newvars = NIL; foreach(lc, vars) { - Var *var = (Var *) lfirst(lc); + Node *var = (Node *) lfirst(lc); var = copyObject(var); - var->varlevelsup = 0; + if (IsA(var, Var)) + { + ((Var *) var)->varlevelsup = 0; + } + else if (IsA(var, PlaceHolderVar)) + { + /* + * It's sufficient to set phlevelsup = 0, because we call + * add_vars_to_targetlist with create_new_ph = false (as we must, + * because deconstruct_jointree has already started); therefore + * nobody is going to look at the contained expression to notice + * whether its Vars have the right level. + */ + ((PlaceHolderVar *) var)->phlevelsup = 0; + } + else + Assert(false); newvars = lappend(newvars, var); } diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c index f5deed4bc8..d72c78f8db 100644 --- a/src/backend/optimizer/prep/prepjointree.c +++ b/src/backend/optimizer/prep/prepjointree.c @@ -89,6 +89,8 @@ static Node *pullup_replace_vars(Node *expr, pullup_replace_vars_context *context); static Node *pullup_replace_vars_callback(Var *var, replace_rte_variables_context *context); +static Query *pullup_replace_vars_subquery(Query *query, + pullup_replace_vars_context *context); static reduce_outer_joins_state *reduce_outer_joins_pass1(Node *jtnode); static void reduce_outer_joins_pass2(Node *jtnode, reduce_outer_joins_state *state, @@ -1472,7 +1474,50 @@ replace_vars_in_jointree(Node *jtnode, return; if (IsA(jtnode, RangeTblRef)) { - /* nothing to do here */ + /* + * If the RangeTblRef refers to a LATERAL subquery (that isn't the + * same subquery we're pulling up), it might contain references to the + * target subquery, which we must replace. We drive this from the + * jointree scan, rather than a scan of the rtable, for a couple of + * reasons: we can avoid processing no-longer-referenced RTEs, and we + * can use the appropriate setting of need_phvs depending on whether + * the RTE is above possibly-nulling outer joins or not. + */ + int varno = ((RangeTblRef *) jtnode)->rtindex; + + if (varno != context->varno) /* ignore target subquery itself */ + { + RangeTblEntry *rte = rt_fetch(varno, context->root->parse->rtable); + + Assert(rte != context->target_rte); + if (rte->lateral) + { + switch (rte->rtekind) + { + case RTE_SUBQUERY: + rte->subquery = + pullup_replace_vars_subquery(rte->subquery, + context); + break; + case RTE_FUNCTION: + rte->funcexpr = + pullup_replace_vars(rte->funcexpr, + context); + break; + case RTE_VALUES: + rte->values_lists = (List *) + pullup_replace_vars((Node *) rte->values_lists, + context); + break; + case RTE_RELATION: + case RTE_JOIN: + case RTE_CTE: + /* these shouldn't be marked LATERAL */ + Assert(false); + break; + } + } + } } else if (IsA(jtnode, FromExpr)) { @@ -1695,6 +1740,25 @@ pullup_replace_vars_callback(Var *var, return newnode; } +/* + * Apply pullup variable replacement to a subquery + * + * This needs to be different from pullup_replace_vars() because + * replace_rte_variables will think that it shouldn't increment sublevels_up + * before entering the Query; so we need to call it with sublevels_up == 1. + */ +static Query * +pullup_replace_vars_subquery(Query *query, + pullup_replace_vars_context *context) +{ + Assert(IsA(query, Query)); + return (Query *) replace_rte_variables((Node *) query, + context->varno, 1, + pullup_replace_vars_callback, + (void *) context, + NULL); +} + /* * flatten_simple_union_all diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c index 1d88a77820..21b7753f05 100644 --- a/src/backend/optimizer/util/var.c +++ b/src/backend/optimizer/util/var.c @@ -247,11 +247,8 @@ 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. + * Create a list of all Vars (and PlaceHolderVars) referencing the + * specified query level in the given parsetree. * * Caution: the Vars are not copied, only linked into the list. */ @@ -288,7 +285,15 @@ pull_vars_walker(Node *node, pull_vars_context *context) context->vars = lappend(context->vars, var); return false; } - Assert(!IsA(node, PlaceHolderVar)); + if (IsA(node, PlaceHolderVar)) + { + PlaceHolderVar *phv = (PlaceHolderVar *) node; + + if (phv->phlevelsup == context->sublevels_up) + context->vars = lappend(context->vars, phv); + /* we don't want to look into the contained expression */ + return false; + } if (IsA(node, Query)) { /* Recurse into RTE subquery or not-yet-planned sublink subquery */ diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 51aeb8de7b..0856b457bf 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -3242,6 +3242,100 @@ select * from int8_tbl a, 4567890123456789 | -4567890123456789 | 4567890123456789 | -4567890123456789 | (57 rows) +-- lateral references requiring pullup +select * from (values(1)) x(lb), + lateral generate_series(lb,4) x4; + lb | x4 +----+---- + 1 | 1 + 1 | 2 + 1 | 3 + 1 | 4 +(4 rows) + +select * from (select f1/1000000000 from int4_tbl) x(lb), + lateral generate_series(lb,4) x4; + lb | x4 +----+---- + 0 | 0 + 0 | 1 + 0 | 2 + 0 | 3 + 0 | 4 + 0 | 0 + 0 | 1 + 0 | 2 + 0 | 3 + 0 | 4 + 0 | 0 + 0 | 1 + 0 | 2 + 0 | 3 + 0 | 4 + 2 | 2 + 2 | 3 + 2 | 4 + -2 | -2 + -2 | -1 + -2 | 0 + -2 | 1 + -2 | 2 + -2 | 3 + -2 | 4 +(25 rows) + +select * from (values(1)) x(lb), + lateral (values(lb)) y(lbcopy); + lb | lbcopy +----+-------- + 1 | 1 +(1 row) + +select * from (values(1)) x(lb), + lateral (select lb from int4_tbl) y(lbcopy); + lb | lbcopy +----+-------- + 1 | 1 + 1 | 1 + 1 | 1 + 1 | 1 + 1 | 1 +(5 rows) + +select * from + int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1, + lateral (values(x.q1,y.q1,y.q2)) v(xq1,yq1,yq2); + q1 | q2 | q1 | q2 | xq1 | yq1 | yq2 +------------------+-------------------+------------------+-------------------+------------------+------------------+------------------- + 123 | 456 | | | 123 | | + 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789 + 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 + 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123 + 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 + 4567890123456789 | 123 | 123 | 456 | 4567890123456789 | 123 | 456 + 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 + 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 + 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 123 + 4567890123456789 | -4567890123456789 | | | 4567890123456789 | | +(10 rows) + +select * from + int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1, + lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2); + q1 | q2 | q1 | q2 | xq1 | yq1 | yq2 +------------------+-------------------+------------------+-------------------+------------------+------------------+------------------- + 123 | 456 | | | 123 | | + 123 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 123 | 4567890123456789 | -4567890123456789 + 123 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 + 123 | 4567890123456789 | 4567890123456789 | 123 | 123 | 4567890123456789 | 123 + 4567890123456789 | 123 | 123 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 + 4567890123456789 | 123 | 123 | 456 | 4567890123456789 | 123 | 456 + 4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789 + 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 | 4567890123456789 + 4567890123456789 | 4567890123456789 | 4567890123456789 | 123 | 4567890123456789 | 4567890123456789 | 123 + 4567890123456789 | -4567890123456789 | | | 4567890123456789 | | +(10 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 diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 30ea48cb92..3c8ed5027e 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -901,6 +901,22 @@ select * from int8_tbl a, int8_tbl x left join lateral (select a.q1 from int4_tbl y) ss(z) on x.q2 = ss.z; +-- lateral references requiring pullup +select * from (values(1)) x(lb), + lateral generate_series(lb,4) x4; +select * from (select f1/1000000000 from int4_tbl) x(lb), + lateral generate_series(lb,4) x4; +select * from (values(1)) x(lb), + lateral (values(lb)) y(lbcopy); +select * from (values(1)) x(lb), + lateral (select lb from int4_tbl) y(lbcopy); +select * from + int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1, + lateral (values(x.q1,y.q1,y.q2)) v(xq1,yq1,yq2); +select * from + int8_tbl x left join (select q1,coalesce(q2,0) q2 from int8_tbl) y on x.q2 = y.q1, + lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2); + -- 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;