diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 71e305804dbcd400c4333abe94b9abcebf100233..573ffe27dfc6886de28a1f5450b6149aaa777cae 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -1957,7 +1957,6 @@ _copyPlaceHolderInfo(const PlaceHolderInfo *from) COPY_NODE_FIELD(ph_var); COPY_BITMAPSET_FIELD(ph_eval_at); COPY_BITMAPSET_FIELD(ph_needed); - COPY_BITMAPSET_FIELD(ph_may_need); COPY_SCALAR_FIELD(ph_width); return newnode; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 3183ccff4d36ae12b17c9031ebf52ae0c86da8b3..72899fe0b3d12cc084b9b37e13f7ace2103f446e 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -822,7 +822,6 @@ _equalPlaceHolderInfo(const PlaceHolderInfo *a, const PlaceHolderInfo *b) COMPARE_NODE_FIELD(ph_var); COMPARE_BITMAPSET_FIELD(ph_eval_at); COMPARE_BITMAPSET_FIELD(ph_needed); - COMPARE_BITMAPSET_FIELD(ph_may_need); COMPARE_SCALAR_FIELD(ph_width); return true; diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index bad22399bf8320cc0a26f7dd54d014ff46568f9a..12932500b457609fa5c330fca0cbff18c5f84db0 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -1939,7 +1939,6 @@ _outPlaceHolderInfo(StringInfo str, const PlaceHolderInfo *node) WRITE_NODE_FIELD(ph_var); WRITE_BITMAPSET_FIELD(ph_eval_at); WRITE_BITMAPSET_FIELD(ph_needed); - WRITE_BITMAPSET_FIELD(ph_may_need); WRITE_INT_FIELD(ph_width); } diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c index a7db69c85bfabc95e37196398e4f18c57969fbe3..daab355b1d3e8454dc1ce019330c8cc7868bf1e3 100644 --- a/src/backend/optimizer/plan/analyzejoins.c +++ b/src/backend/optimizer/plan/analyzejoins.c @@ -388,8 +388,6 @@ remove_rel_from_query(PlannerInfo *root, int relid, Relids joinrelids) phinfo->ph_eval_at = bms_add_member(phinfo->ph_eval_at, relid); phinfo->ph_needed = bms_del_member(phinfo->ph_needed, relid); - /* ph_may_need probably isn't used after this, but fix it anyway */ - phinfo->ph_may_need = bms_del_member(phinfo->ph_may_need, relid); } /* diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 8efb94b44d448f9016cb80af7eb4e639feccc004..07c4dddd24ec040ec31241899d227aaef404df46 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -155,10 +155,9 @@ build_base_rel_tlists(PlannerInfo *root, List *final_tlist) * The list may also contain PlaceHolderVars. These don't necessarily * have a single owning relation; we keep their attr_needed info in * root->placeholder_list instead. If create_new_ph is true, it's OK - * to create new PlaceHolderInfos, and we also have to update ph_may_need; - * otherwise, the PlaceHolderInfos must already exist, and we should only - * update their ph_needed. (It should be true before deconstruct_jointree - * begins, and false after that.) + * to create new PlaceHolderInfos; otherwise, the PlaceHolderInfos must + * already exist, and we should only update their ph_needed. (This should + * be true before deconstruct_jointree begins, and false after that.) */ void add_vars_to_targetlist(PlannerInfo *root, List *vars, @@ -196,20 +195,8 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, PlaceHolderInfo *phinfo = find_placeholder_info(root, phv, create_new_ph); - /* Always adjust ph_needed */ phinfo->ph_needed = bms_add_members(phinfo->ph_needed, where_needed); - - /* - * If we are creating PlaceHolderInfos, mark them with the correct - * maybe-needed locations. Otherwise, it's too late to change - * that, so we'd better not have set ph_needed to more than - * ph_may_need. - */ - if (create_new_ph) - mark_placeholder_maybe_needed(root, phinfo, where_needed); - else - Assert(bms_is_subset(phinfo->ph_needed, phinfo->ph_may_need)); } else elog(ERROR, "unrecognized node type: %d", (int) nodeTag(node)); @@ -235,7 +222,7 @@ add_vars_to_targetlist(PlannerInfo *root, List *vars, * means setting suitable where_needed values for them. * * This has to run before deconstruct_jointree, since it might result in - * creation of PlaceHolderInfos or extension of their ph_may_need sets. + * creation of PlaceHolderInfos. */ void find_lateral_references(PlannerInfo *root) @@ -1005,11 +992,11 @@ make_outerjoininfo(PlannerInfo *root, /* * Examine PlaceHolderVars. If a PHV is supposed to be evaluated within - * this join's nullable side, and it may get used above this join, then - * ensure that min_righthand contains the full eval_at set of the PHV. - * This ensures that the PHV actually can be evaluated within the RHS. - * Note that this works only because we should already have determined the - * final eval_at level for any PHV syntactically within this join. + * this join's nullable side, then ensure that min_righthand contains the + * full eval_at set of the PHV. This ensures that the PHV actually can be + * evaluated within the RHS. Note that this works only because we should + * already have determined the final eval_at level for any PHV + * syntactically within this join. */ foreach(l, root->placeholder_list) { @@ -1020,11 +1007,6 @@ make_outerjoininfo(PlannerInfo *root, if (!bms_is_subset(ph_syn_level, right_rels)) continue; - /* We can also ignore it if it's certainly not used above this join */ - /* XXX this test is probably overly conservative */ - if (bms_is_subset(phinfo->ph_may_need, min_righthand)) - continue; - /* Else, prevent join from being formed before we eval the PHV */ min_righthand = bms_add_members(min_righthand, phinfo->ph_eval_at); } diff --git a/src/backend/optimizer/util/placeholder.c b/src/backend/optimizer/util/placeholder.c index c2ff2229e2619355019d4d4f9d03c9b0c5ead0f2..da92497689ba9a305e61e09b06446648c7f1a95c 100644 --- a/src/backend/optimizer/util/placeholder.c +++ b/src/backend/optimizer/util/placeholder.c @@ -23,9 +23,8 @@ #include "utils/lsyscache.h" /* Local functions */ -static Relids find_placeholders_recurse(PlannerInfo *root, Node *jtnode); -static void mark_placeholders_in_expr(PlannerInfo *root, Node *expr, - Relids relids); +static void find_placeholders_recurse(PlannerInfo *root, Node *jtnode); +static void find_placeholders_in_expr(PlannerInfo *root, Node *expr); /* @@ -90,16 +89,23 @@ find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv, phinfo->phid = phv->phid; phinfo->ph_var = copyObject(phv); + /* initialize ph_eval_at as the set of contained relids */ phinfo->ph_eval_at = pull_varnos((Node *) phv); /* ph_eval_at may change later, see update_placeholder_eval_levels */ phinfo->ph_needed = NULL; /* initially it's unused */ - phinfo->ph_may_need = NULL; /* for the moment, estimate width using just the datatype info */ phinfo->ph_width = get_typavgwidth(exprType((Node *) phv->phexpr), exprTypmod((Node *) phv->phexpr)); root->placeholder_list = lappend(root->placeholder_list, phinfo); + /* + * The PHV's contained expression may contain other, lower-level PHVs. We + * now know we need to get those into the PlaceHolderInfo list, too, so we + * may as well do that immediately. + */ + find_placeholders_in_expr(root, (Node *) phinfo->ph_var->phexpr); + return phinfo; } @@ -119,7 +125,7 @@ find_placeholders_in_jointree(PlannerInfo *root) /* Start recursion at top of jointree */ Assert(root->parse->jointree != NULL && IsA(root->parse->jointree, FromExpr)); - (void) find_placeholders_recurse(root, (Node *) root->parse->jointree); + find_placeholders_recurse(root, (Node *) root->parse->jointree); } } @@ -128,23 +134,15 @@ find_placeholders_in_jointree(PlannerInfo *root) * One recursion level of find_placeholders_in_jointree. * * jtnode is the current jointree node to examine. - * - * The result is the set of base Relids contained in or below jtnode. - * This is just an internal convenience, it's not used at the top level. */ -static Relids +static void find_placeholders_recurse(PlannerInfo *root, Node *jtnode) { - Relids jtrelids; - if (jtnode == NULL) - return NULL; + return; if (IsA(jtnode, RangeTblRef)) { - int varno = ((RangeTblRef *) jtnode)->rtindex; - - /* No quals to deal with, just return correct result */ - jtrelids = bms_make_singleton(varno); + /* No quals to deal with here */ } else if (IsA(jtnode, FromExpr)) { @@ -152,57 +150,43 @@ find_placeholders_recurse(PlannerInfo *root, Node *jtnode) ListCell *l; /* - * First, recurse to handle child joins, and form their relid set. + * First, recurse to handle child joins. */ - jtrelids = NULL; foreach(l, f->fromlist) { - Relids sub_relids; - - sub_relids = find_placeholders_recurse(root, lfirst(l)); - jtrelids = bms_join(jtrelids, sub_relids); + find_placeholders_recurse(root, lfirst(l)); } /* * Now process the top-level quals. */ - mark_placeholders_in_expr(root, f->quals, jtrelids); + find_placeholders_in_expr(root, f->quals); } else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; - Relids leftids, - rightids; /* - * First, recurse to handle child joins, and form their relid set. + * First, recurse to handle child joins. */ - leftids = find_placeholders_recurse(root, j->larg); - rightids = find_placeholders_recurse(root, j->rarg); - jtrelids = bms_join(leftids, rightids); + find_placeholders_recurse(root, j->larg); + find_placeholders_recurse(root, j->rarg); /* Process the qual clauses */ - mark_placeholders_in_expr(root, j->quals, jtrelids); + find_placeholders_in_expr(root, j->quals); } else - { elog(ERROR, "unrecognized node type: %d", (int) nodeTag(jtnode)); - jtrelids = NULL; /* keep compiler quiet */ - } - return jtrelids; } /* - * mark_placeholders_in_expr - * Find all PlaceHolderVars in the given expression, and mark them - * as possibly needed at the specified join level. - * - * relids is the syntactic join level to mark as the "maybe needed" level - * for each PlaceHolderVar found in the expression. + * find_placeholders_in_expr + * Find all PlaceHolderVars in the given expression, and create + * PlaceHolderInfo entries for them. */ static void -mark_placeholders_in_expr(PlannerInfo *root, Node *expr, Relids relids) +find_placeholders_in_expr(PlannerInfo *root, Node *expr) { List *vars; ListCell *vl; @@ -217,63 +201,17 @@ mark_placeholders_in_expr(PlannerInfo *root, Node *expr, Relids relids) foreach(vl, vars) { PlaceHolderVar *phv = (PlaceHolderVar *) lfirst(vl); - PlaceHolderInfo *phinfo; /* Ignore any plain Vars */ if (!IsA(phv, PlaceHolderVar)) continue; /* Create a PlaceHolderInfo entry if there's not one already */ - phinfo = find_placeholder_info(root, phv, true); - - /* Mark it, and recursively process any contained placeholders */ - mark_placeholder_maybe_needed(root, phinfo, relids); + (void) find_placeholder_info(root, phv, true); } list_free(vars); } -/* - * mark_placeholder_maybe_needed - * Mark a placeholder as possibly needed at the specified join level. - * - * relids is the syntactic join level to mark as the "maybe needed" level - * for the placeholder. - * - * This is called during an initial scan of the query's targetlist and quals - * before we begin deconstruct_jointree. Once we begin deconstruct_jointree, - * all active placeholders must be present in root->placeholder_list with - * their correct ph_may_need values, because make_outerjoininfo and - * update_placeholder_eval_levels require this info to be available while - * we crawl up the join tree. - */ -void -mark_placeholder_maybe_needed(PlannerInfo *root, PlaceHolderInfo *phinfo, - Relids relids) -{ - Relids est_eval_level; - - /* Mark the PHV as possibly needed at the given syntactic level */ - phinfo->ph_may_need = bms_add_members(phinfo->ph_may_need, relids); - - /* - * This is a bit tricky: the PHV's contained expression may contain other, - * lower-level PHVs. We need to get those into the PlaceHolderInfo list, - * but they aren't going to be needed where the outer PHV is referenced. - * Rather, they'll be needed where the outer PHV is evaluated. We can - * estimate that conservatively as the syntactic location of the PHV's - * expression, but not less than the level of any Vars it contains. - * (Normally the Vars would come from below the syntactic location anyway, - * but this might not be true if the PHV contains any LATERAL references.) - */ - est_eval_level = bms_union(phinfo->ph_var->phrels, phinfo->ph_eval_at); - - /* Now recurse to take care of any such PHVs */ - mark_placeholders_in_expr(root, (Node *) phinfo->ph_var->phexpr, - est_eval_level); - - bms_free(est_eval_level); -} - /* * update_placeholder_eval_levels * Adjust the target evaluation levels for placeholders diff --git a/src/include/nodes/relation.h b/src/include/nodes/relation.h index c0a636b9d7c9f6dc4f4367188741af0d07cef3c3..b611e0b905a9b7edef67f6e11aa763bc91dfc3a4 100644 --- a/src/include/nodes/relation.h +++ b/src/include/nodes/relation.h @@ -1465,18 +1465,9 @@ typedef struct AppendRelInfo * then allow it to bubble up like a Var until the ph_needed join level. * ph_needed has the same definition as attr_needed for a regular Var. * - * ph_may_need is an initial estimate of ph_needed, formed using the - * syntactic locations of references to the PHV. We need this in order to - * determine whether the PHV reference forces a join ordering constraint: - * if the PHV has to be evaluated below the nullable side of an outer join, - * and then used above that outer join, we must constrain join order to ensure - * there's a valid place to evaluate the PHV below the join. The final - * actual ph_needed level might be lower than ph_may_need, but we can't - * determine that until later on. Fortunately this doesn't matter for what - * we need ph_may_need for: if there's a PHV reference syntactically - * above the outer join, it's not going to be allowed to drop below the outer - * join, so we would come to the same conclusions about join order even if - * we had the final ph_needed value to compare to. + * Notice that when ph_eval_at is a join rather than a single baserel, the + * PlaceHolderInfo may create constraints on join order: the ph_eval_at join + * has to be formed below any outer joins that should null the PlaceHolderVar. * * We create a PlaceHolderInfo only after determining that the PlaceHolderVar * is actually referenced in the plan tree, so that unreferenced placeholders @@ -1491,7 +1482,6 @@ typedef struct PlaceHolderInfo PlaceHolderVar *ph_var; /* copy of PlaceHolderVar tree */ Relids ph_eval_at; /* lowest level we can evaluate value at */ Relids ph_needed; /* highest level the value is needed at */ - Relids ph_may_need; /* highest level it might be needed at */ int32 ph_width; /* estimated attribute width */ } PlaceHolderInfo; diff --git a/src/include/optimizer/placeholder.h b/src/include/optimizer/placeholder.h index c99ab0f610463f2849c1c0485be11af93814940e..a45b17d64c6a5b8c998dfec1c2ab0df07ac266fd 100644 --- a/src/include/optimizer/placeholder.h +++ b/src/include/optimizer/placeholder.h @@ -22,8 +22,6 @@ extern PlaceHolderVar *make_placeholder_expr(PlannerInfo *root, Expr *expr, extern PlaceHolderInfo *find_placeholder_info(PlannerInfo *root, PlaceHolderVar *phv, bool create_new_ph); extern void find_placeholders_in_jointree(PlannerInfo *root); -extern void mark_placeholder_maybe_needed(PlannerInfo *root, - PlaceHolderInfo *phinfo, Relids relids); extern void update_placeholder_eval_levels(PlannerInfo *root, SpecialJoinInfo *new_sjinfo); extern void fix_placeholder_input_needed_levels(PlannerInfo *root); diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 31c2a320a6d1aa47ef355c6901266d7a1aeb518b..814ddd8046913393e6031610fbd4e43e2bc9a871 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -3617,6 +3617,58 @@ select * from Output: (COALESCE((COALESCE(b.q2, 42::bigint)), d.q2)) (26 rows) +-- case that breaks the old ph_may_need optimization +explain (verbose, costs off) +select c.*,a.*,ss1.q1,ss2.q1,ss3.* from + int8_tbl c left join ( + int8_tbl a left join + (select q1, coalesce(q2,f1) as x from int8_tbl b, int4_tbl b2 + where q1 < f1) ss1 + on a.q2 = ss1.q1 + cross join + lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2 + ) on c.q2 = ss2.q1, + lateral (select * from int4_tbl i where ss2.y > f1) ss3; + QUERY PLAN +------------------------------------------------------------------------------------------- + Hash Right Join + Output: c.q1, c.q2, a.q1, a.q2, b.q1, d.q1, i.f1 + Hash Cond: (d.q1 = c.q2) + Filter: ((COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2)) > i.f1) + -> Nested Loop + Output: a.q1, a.q2, b.q1, d.q1, COALESCE((COALESCE(b.q2, (b2.f1)::bigint)), d.q2) + -> Hash Right Join + Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, (b2.f1)::bigint)) + Hash Cond: (b.q1 = a.q2) + -> Nested Loop + Output: b.q1, COALESCE(b.q2, (b2.f1)::bigint) + Join Filter: (b.q1 < b2.f1) + -> Seq Scan on public.int8_tbl b + Output: b.q1, b.q2 + -> Materialize + Output: b2.f1 + -> Seq Scan on public.int4_tbl b2 + Output: b2.f1 + -> Hash + Output: a.q1, a.q2 + -> Seq Scan on public.int8_tbl a + Output: a.q1, a.q2 + -> Materialize + Output: d.q1, d.q2 + -> Seq Scan on public.int8_tbl d + Output: d.q1, d.q2 + -> Hash + Output: c.q1, c.q2, i.f1 + -> Nested Loop + Output: c.q1, c.q2, i.f1 + -> Seq Scan on public.int8_tbl c + Output: c.q1, c.q2 + -> Materialize + Output: i.f1 + -> Seq Scan on public.int4_tbl i + Output: i.f1 +(36 rows) + -- test some error cases where LATERAL should have been used but wasn't select f1,g from int4_tbl a, (select f1 as g) ss; ERROR: column "f1" does not exist diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 656766acd3fbb499ef41da99e63bc79fcea0b108..7ec2cbeea4b5aae66d381a3ea771d54003b5e663 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -1006,6 +1006,19 @@ select * from ) on c.q2 = ss2.q1, lateral (select ss2.y) ss3; +-- case that breaks the old ph_may_need optimization +explain (verbose, costs off) +select c.*,a.*,ss1.q1,ss2.q1,ss3.* from + int8_tbl c left join ( + int8_tbl a left join + (select q1, coalesce(q2,f1) as x from int8_tbl b, int4_tbl b2 + where q1 < f1) ss1 + on a.q2 = ss1.q1 + cross join + lateral (select q1, coalesce(ss1.x,q2) as y from int8_tbl d) ss2 + ) on c.q2 = ss2.q1, + lateral (select * from int4_tbl i where ss2.y > f1) ss3; + -- test some error cases where LATERAL should have been used but wasn't select f1,g from int4_tbl a, (select f1 as g) ss; select f1,g from int4_tbl a, (select a.f1 as g) ss;