diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c index 35b2dc10347..7fa6f17382e 100644 --- a/src/backend/optimizer/plan/initsplan.c +++ b/src/backend/optimizer/plan/initsplan.c @@ -62,6 +62,8 @@ typedef struct JoinTreeItem /* Fields filled during deconstruct_recurse: */ Node *jtnode; /* jointree node to examine */ JoinDomain *jdomain; /* join domain for its ON/WHERE clauses */ + struct JoinTreeItem *jti_parent; /* JoinTreeItem for this node's + * parent, or NULL if it's the top */ Relids qualscope; /* base+OJ Relids syntactically included in * this jointree node */ Relids inner_join_rels; /* base+OJ Relids syntactically included @@ -74,26 +76,20 @@ typedef struct JoinTreeItem /* Fields filled during deconstruct_distribute: */ SpecialJoinInfo *sjinfo; /* if outer join, its SpecialJoinInfo */ List *oj_joinclauses; /* outer join quals not yet distributed */ + List *lateral_clauses; /* quals postponed from children due to + * lateral references */ } JoinTreeItem; -/* Elements of the postponed_qual_list used during deconstruct_distribute */ -typedef struct PostponedQual -{ - Node *qual; /* a qual clause waiting to be processed */ - Relids relids; /* the set of baserels it references */ -} PostponedQual; - static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel, Index rtindex); static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode, JoinDomain *parent_domain, + JoinTreeItem *parent_jtitem, List **item_list); -static void deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem, - List **postponed_qual_list); +static void deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem); static void process_security_barrier_quals(PlannerInfo *root, - int rti, Relids qualscope, - JoinDomain *jdomain); + int rti, JoinTreeItem *jtitem); static void mark_rels_nulled_by_join(PlannerInfo *root, Index ojrelid, Relids lower_rels); static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root, @@ -107,7 +103,7 @@ static void deconstruct_distribute_oj_quals(PlannerInfo *root, List *jtitems, JoinTreeItem *jtitem); static void distribute_quals_to_rels(PlannerInfo *root, List *clauses, - JoinDomain *jdomain, + JoinTreeItem *jtitem, SpecialJoinInfo *sjinfo, Index security_level, Relids qualscope, @@ -116,10 +112,9 @@ static void distribute_quals_to_rels(PlannerInfo *root, List *clauses, bool allow_equivalence, bool has_clone, bool is_clone, - List **postponed_qual_list, List **postponed_oj_qual_list); static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, - JoinDomain *jdomain, + JoinTreeItem *jtitem, SpecialJoinInfo *sjinfo, Index security_level, Relids qualscope, @@ -128,7 +123,6 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, bool allow_equivalence, bool has_clone, bool is_clone, - List **postponed_qual_list, List **postponed_oj_qual_list); static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause); static void check_mergejoinable(RestrictInfo *restrictinfo); @@ -742,7 +736,6 @@ deconstruct_jointree(PlannerInfo *root) List *result; JoinDomain *top_jdomain; List *item_list = NIL; - List *postponed_qual_list = NIL; ListCell *lc; /* @@ -766,7 +759,7 @@ deconstruct_jointree(PlannerInfo *root) /* Perform the initial scan of the jointree */ result = deconstruct_recurse(root, (Node *) root->parse->jointree, - top_jdomain, + top_jdomain, NULL, &item_list); /* Now we can form the value of all_query_rels, too */ @@ -780,16 +773,12 @@ deconstruct_jointree(PlannerInfo *root) { JoinTreeItem *jtitem = (JoinTreeItem *) lfirst(lc); - deconstruct_distribute(root, jtitem, - &postponed_qual_list); + deconstruct_distribute(root, jtitem); } - /* Shouldn't be any leftover postponed quals */ - Assert(postponed_qual_list == NIL); - /* - * However, if there were any special joins then we may have some - * postponed LEFT JOIN clauses to deal with. + * If there were any special joins then we may have some postponed LEFT + * JOIN clauses to deal with. */ if (root->join_info_list) { @@ -814,7 +803,8 @@ deconstruct_jointree(PlannerInfo *root) * * jtnode is the jointree node to examine, and parent_domain is the * enclosing join domain. (We must add all base+OJ relids appearing - * here or below to parent_domain.) + * here or below to parent_domain.) parent_jtitem is the JoinTreeItem + * for the parent jointree node, or NULL at the top of the recursion. * * item_list is an in/out parameter: we add a JoinTreeItem struct to * that list for each jointree node, in depth-first traversal order. @@ -825,6 +815,7 @@ deconstruct_jointree(PlannerInfo *root) static List * deconstruct_recurse(PlannerInfo *root, Node *jtnode, JoinDomain *parent_domain, + JoinTreeItem *parent_jtitem, List **item_list) { List *joinlist; @@ -835,6 +826,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, /* Make the new JoinTreeItem, but don't add it to item_list yet */ jtitem = palloc0_object(JoinTreeItem); jtitem->jtnode = jtnode; + jtitem->jti_parent = parent_jtitem; if (IsA(jtnode, RangeTblRef)) { @@ -880,6 +872,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, sub_joinlist = deconstruct_recurse(root, lfirst(l), parent_domain, + jtitem, item_list); sub_item = (JoinTreeItem *) llast(*item_list); jtitem->qualscope = bms_add_members(jtitem->qualscope, @@ -922,10 +915,12 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, /* Recurse */ leftjoinlist = deconstruct_recurse(root, j->larg, parent_domain, + jtitem, item_list); left_item = (JoinTreeItem *) llast(*item_list); rightjoinlist = deconstruct_recurse(root, j->rarg, parent_domain, + jtitem, item_list); right_item = (JoinTreeItem *) llast(*item_list); /* Compute qualscope etc */ @@ -947,10 +942,12 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, /* Recurse */ leftjoinlist = deconstruct_recurse(root, j->larg, parent_domain, + jtitem, item_list); left_item = (JoinTreeItem *) llast(*item_list); rightjoinlist = deconstruct_recurse(root, j->rarg, child_domain, + jtitem, item_list); right_item = (JoinTreeItem *) llast(*item_list); /* Compute join domain contents, qualscope etc */ @@ -984,10 +981,12 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, /* Recurse */ leftjoinlist = deconstruct_recurse(root, j->larg, parent_domain, + jtitem, item_list); left_item = (JoinTreeItem *) llast(*item_list); rightjoinlist = deconstruct_recurse(root, j->rarg, parent_domain, + jtitem, item_list); right_item = (JoinTreeItem *) llast(*item_list); /* Compute qualscope etc */ @@ -1013,6 +1012,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, root->join_domains = lappend(root->join_domains, child_domain); leftjoinlist = deconstruct_recurse(root, j->larg, child_domain, + jtitem, item_list); left_item = (JoinTreeItem *) llast(*item_list); fj_domain->jd_relids = bms_copy(child_domain->jd_relids); @@ -1021,6 +1021,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, root->join_domains = lappend(root->join_domains, child_domain); rightjoinlist = deconstruct_recurse(root, j->rarg, child_domain, + jtitem, item_list); right_item = (JoinTreeItem *) llast(*item_list); /* Compute qualscope etc */ @@ -1108,20 +1109,9 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode, * * Distribute quals of the node to appropriate restriction and join lists. * In addition, entries will be added to root->join_info_list for outer joins. - * - * Inputs: - * jtitem is the JoinTreeItem to examine - * Input/Outputs: - * *postponed_qual_list is a list of PostponedQual structs - * - * On entry, *postponed_qual_list contains any quals that had to be postponed - * out of lower join levels (because they contain lateral references). - * On exit, *postponed_qual_list contains quals that can't be processed yet - * (because their lateral references are still unsatisfied). */ static void -deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem, - List **postponed_qual_list) +deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem) { Node *jtnode = jtitem->jtnode; @@ -1133,82 +1123,51 @@ deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem, if (root->qual_security_level > 0) process_security_barrier_quals(root, varno, - jtitem->qualscope, - jtitem->jdomain); + jtitem); } else if (IsA(jtnode, FromExpr)) { FromExpr *f = (FromExpr *) jtnode; - List *new_postponed_quals = NIL; - ListCell *l; /* - * Try to process any quals postponed by children. If they need - * further postponement, add them to my output postponed_qual_list. + * Process any lateral-referencing quals that were postponed to this + * level by children. */ - foreach(l, *postponed_qual_list) - { - PostponedQual *pq = (PostponedQual *) lfirst(l); - - if (bms_is_subset(pq->relids, jtitem->qualscope)) - distribute_qual_to_rels(root, pq->qual, - jtitem->jdomain, - NULL, - root->qual_security_level, - jtitem->qualscope, NULL, NULL, - true, false, false, - NULL, NULL); - else - new_postponed_quals = lappend(new_postponed_quals, pq); - } - *postponed_qual_list = new_postponed_quals; + distribute_quals_to_rels(root, jtitem->lateral_clauses, + jtitem, + NULL, + root->qual_security_level, + jtitem->qualscope, NULL, NULL, + true, false, false, + NULL); /* * Now process the top-level quals. */ distribute_quals_to_rels(root, (List *) f->quals, - jtitem->jdomain, + jtitem, NULL, root->qual_security_level, jtitem->qualscope, NULL, NULL, true, false, false, - postponed_qual_list, NULL); + NULL); } else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; - List *new_postponed_quals = NIL; Relids ojscope; List *my_quals; SpecialJoinInfo *sjinfo; List **postponed_oj_qual_list; - ListCell *l; /* - * Try to process any quals postponed by children. If they need - * further postponement, add them to my output postponed_qual_list. - * Quals that can be processed now must be included in my_quals, so - * that they'll be handled properly in make_outerjoininfo. + * Include lateral-referencing quals postponed from children in + * my_quals, so that they'll be handled properly in + * make_outerjoininfo. (This is destructive to + * jtitem->lateral_clauses, but we won't use that again.) */ - my_quals = NIL; - foreach(l, *postponed_qual_list) - { - PostponedQual *pq = (PostponedQual *) lfirst(l); - - if (bms_is_subset(pq->relids, jtitem->qualscope)) - my_quals = lappend(my_quals, pq->qual); - else - { - /* - * We should not be postponing any quals past an outer join. - * If this Assert fires, pull_up_subqueries() messed up. - */ - Assert(j->jointype == JOIN_INNER); - new_postponed_quals = lappend(new_postponed_quals, pq); - } - } - *postponed_qual_list = new_postponed_quals; - my_quals = list_concat(my_quals, (List *) j->quals); + my_quals = list_concat(jtitem->lateral_clauses, + (List *) j->quals); /* * For an OJ, form the SpecialJoinInfo now, so that we can pass it to @@ -1268,14 +1227,13 @@ deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem, /* Process the JOIN's qual clauses */ distribute_quals_to_rels(root, my_quals, - jtitem->jdomain, + jtitem, sjinfo, root->qual_security_level, jtitem->qualscope, ojscope, jtitem->nonnullable_rels, true, /* allow_equivalence */ false, false, /* not clones */ - postponed_qual_list, postponed_oj_qual_list); /* And add the SpecialJoinInfo to join_info_list */ @@ -1304,8 +1262,7 @@ deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem, */ static void process_security_barrier_quals(PlannerInfo *root, - int rti, Relids qualscope, - JoinDomain *jdomain) + int rti, JoinTreeItem *jtitem) { RangeTblEntry *rte = root->simple_rte_array[rti]; Index security_level = 0; @@ -1328,15 +1285,14 @@ process_security_barrier_quals(PlannerInfo *root, * pushed up to top of tree, which we don't want. */ distribute_quals_to_rels(root, qualset, - jdomain, + jtitem, NULL, security_level, - qualscope, - qualscope, + jtitem->qualscope, + jtitem->qualscope, NULL, true, false, false, /* not clones */ - NULL, NULL); security_level++; } @@ -2038,7 +1994,7 @@ deconstruct_distribute_oj_quals(PlannerInfo *root, is_clone = !has_clone; distribute_quals_to_rels(root, quals, - otherjtitem->jdomain, + otherjtitem, sjinfo, root->qual_security_level, this_qualscope, @@ -2046,7 +2002,7 @@ deconstruct_distribute_oj_quals(PlannerInfo *root, allow_equivalence, has_clone, is_clone, - NULL, NULL); /* no more postponement */ + NULL); /* no more postponement */ /* * Adjust qual nulling bits for next level up, if needed. We @@ -2067,14 +2023,14 @@ deconstruct_distribute_oj_quals(PlannerInfo *root, { /* No commutation possible, just process the postponed clauses */ distribute_quals_to_rels(root, jtitem->oj_joinclauses, - jtitem->jdomain, + jtitem, sjinfo, root->qual_security_level, qualscope, ojscope, nonnullable_rels, true, /* allow_equivalence */ false, false, /* not clones */ - NULL, NULL); /* no more postponement */ + NULL); /* no more postponement */ } } @@ -2092,7 +2048,7 @@ deconstruct_distribute_oj_quals(PlannerInfo *root, */ static void distribute_quals_to_rels(PlannerInfo *root, List *clauses, - JoinDomain *jdomain, + JoinTreeItem *jtitem, SpecialJoinInfo *sjinfo, Index security_level, Relids qualscope, @@ -2101,7 +2057,6 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses, bool allow_equivalence, bool has_clone, bool is_clone, - List **postponed_qual_list, List **postponed_oj_qual_list) { ListCell *lc; @@ -2111,7 +2066,7 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses, Node *clause = (Node *) lfirst(lc); distribute_qual_to_rels(root, clause, - jdomain, + jtitem, sjinfo, security_level, qualscope, @@ -2120,7 +2075,6 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses, allow_equivalence, has_clone, is_clone, - postponed_qual_list, postponed_oj_qual_list); } } @@ -2134,12 +2088,12 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses, * mergejoinable operator, enter its left- and right-side expressions into * the query's EquivalenceClasses. * - * In some cases, quals will be added to postponed_qual_list or - * postponed_oj_qual_list instead of being processed right away. - * These will be dealt with in later steps of deconstruct_jointree. + * In some cases, quals will be added to parent jtitems' lateral_clauses + * or to postponed_oj_qual_list instead of being processed right away. + * These will be dealt with in later calls of deconstruct_distribute. * * 'clause': the qual clause to be distributed - * 'jdomain': the join domain containing the clause + * 'jtitem': the JoinTreeItem for the containing jointree node * 'sjinfo': join's SpecialJoinInfo (NULL for an inner join or WHERE clause) * 'security_level': security_level to assign to the qual * 'qualscope': set of base+OJ rels the qual's syntactic scope covers @@ -2153,9 +2107,6 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses, * EquivalenceClass * 'has_clone': has_clone property to assign to the qual * 'is_clone': is_clone property to assign to the qual - * 'postponed_qual_list': list of PostponedQual structs, which we can add - * this qual to if it turns out to belong to a higher join level. - * Can be NULL if caller knows postponement is impossible. * 'postponed_oj_qual_list': if not NULL, non-degenerate outer join clauses * should be added to this list instead of being processed (list entries * are just the bare clauses) @@ -2170,7 +2121,7 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses, */ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, - JoinDomain *jdomain, + JoinTreeItem *jtitem, SpecialJoinInfo *sjinfo, Index security_level, Relids qualscope, @@ -2179,7 +2130,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, bool allow_equivalence, bool has_clone, bool is_clone, - List **postponed_qual_list, List **postponed_oj_qual_list) { Relids relids; @@ -2202,19 +2152,32 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, * level that includes every rel they reference. Although we could make * pull_up_subqueries() place such quals correctly to begin with, it's * easier to handle it here. When we find a clause that contains Vars - * outside its syntactic scope, we add it to the postponed-quals list, and - * process it once we've recursed back up to the appropriate join level. + * outside its syntactic scope, locate the nearest parent join level that + * includes all the required rels and add the clause to that level's + * lateral_clauses list. We'll process it when we reach that join level. */ if (!bms_is_subset(relids, qualscope)) { - PostponedQual *pq = (PostponedQual *) palloc(sizeof(PostponedQual)); + JoinTreeItem *pitem; Assert(root->hasLateralRTEs); /* shouldn't happen otherwise */ Assert(sjinfo == NULL); /* mustn't postpone past outer join */ - pq->qual = clause; - pq->relids = relids; - *postponed_qual_list = lappend(*postponed_qual_list, pq); - return; + for (pitem = jtitem->jti_parent; pitem; pitem = pitem->jti_parent) + { + if (bms_is_subset(relids, pitem->qualscope)) + { + pitem->lateral_clauses = lappend(pitem->lateral_clauses, + clause); + return; + } + + /* + * We should not be postponing any quals past an outer join. If + * this Assert fires, pull_up_subqueries() messed up. + */ + Assert(pitem->sjinfo == NULL); + } + elog(ERROR, "failed to postpone qual containing lateral reference"); } /* @@ -2262,7 +2225,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, else { /* eval at join domain level */ - relids = bms_copy(jdomain->jd_relids); + relids = bms_copy(jtitem->jdomain->jd_relids); /* mark as gating qual */ pseudoconstant = true; /* tell createplan.c to check for gating quals */ @@ -2458,7 +2421,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause, { if (maybe_equivalence) { - if (process_equivalence(root, &restrictinfo, jdomain)) + if (process_equivalence(root, &restrictinfo, jtitem->jdomain)) return; /* EC rejected it, so set left_ec/right_ec the hard way ... */ if (restrictinfo->mergeopfamilies) /* EC might have changed this */ diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index e73ba93ca3c..8d7bd937a77 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -6203,6 +6203,28 @@ select * from Output: 3 (11 rows) +-- a new postponed-quals issue (bug #17768) +explain (costs off) +select * from int4_tbl t1, + lateral (select * from int4_tbl t2 inner join int4_tbl t3 on t1.f1 = 1 + inner join (int4_tbl t4 left join int4_tbl t5 on true) on true) ss; + QUERY PLAN +------------------------------------------------- + Nested Loop Left Join + -> Nested Loop + -> Nested Loop + -> Nested Loop + -> Seq Scan on int4_tbl t1 + Filter: (f1 = 1) + -> Seq Scan on int4_tbl t2 + -> Materialize + -> Seq Scan on int4_tbl t3 + -> Materialize + -> Seq Scan on int4_tbl t4 + -> Materialize + -> Seq Scan on int4_tbl t5 +(13 rows) + -- check dummy rels with lateral references (bug #15694) explain (verbose, costs off) select * from int8_tbl i8 left join lateral diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index ed26ffd8a86..9f55147be78 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -2159,6 +2159,12 @@ select * from select * from (select 3 as z offset 0) z where z.z = x.x ) zz on zz.z = y.y; +-- a new postponed-quals issue (bug #17768) +explain (costs off) +select * from int4_tbl t1, + lateral (select * from int4_tbl t2 inner join int4_tbl t3 on t1.f1 = 1 + inner join (int4_tbl t4 left join int4_tbl t5 on true) on true) ss; + -- check dummy rels with lateral references (bug #15694) explain (verbose, costs off) select * from int8_tbl i8 left join lateral