Rethink treatment of "postponed" quals in deconstruct_jointree().

After pulling up LATERAL subqueries, we may have qual clauses that
refer to relations outside their syntactic scope.  Before doing any
such pullup, prepjointree.c checks to make sure that it wouldn't
create a semantically-invalid situation; but we leave it to
deconstruct_jointree() to actually move these quals up the join
tree to a place where they can be evaluated.  In commit 2489d76c4,
I (tgl) refactored deconstruct_jointree() in a way that caused
assertion failures while moving such quals, because the new logic
failed to distinguish "this jointree node is a parent of the source
one" from "this jointree node is processed after the source
one in depth-first order".

Fix this, and at the same time reduce the overhead a bit, by
getting rid of the common PostponedQual list and instead making each
JoinTreeItem contain a list of quals that needed to be postponed to
its level.  We can help distribute_qual_to_rels find the appropriate
JoinTreeItem efficiently by adding parent-item links to the
JoinTreeItem data structure.  This ends up being the same number
of relid subset checks as the original (pre-bug) logic, but less
list manipulation is required during multi-level postponements.

Richard Guo and Tom Lane, per bug #17768 from Robins Tharakan.

Discussion: https://postgr.es/m/17768-5ac8730ece54478f@postgresql.org
This commit is contained in:
Tom Lane 2023-02-04 12:45:53 -05:00
parent faff8f8e47
commit 5840c20272
3 changed files with 111 additions and 120 deletions

View File

@ -62,6 +62,8 @@ typedef struct JoinTreeItem
/* Fields filled during deconstruct_recurse: */ /* Fields filled during deconstruct_recurse: */
Node *jtnode; /* jointree node to examine */ Node *jtnode; /* jointree node to examine */
JoinDomain *jdomain; /* join domain for its ON/WHERE clauses */ 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 Relids qualscope; /* base+OJ Relids syntactically included in
* this jointree node */ * this jointree node */
Relids inner_join_rels; /* base+OJ Relids syntactically included Relids inner_join_rels; /* base+OJ Relids syntactically included
@ -74,26 +76,20 @@ typedef struct JoinTreeItem
/* Fields filled during deconstruct_distribute: */ /* Fields filled during deconstruct_distribute: */
SpecialJoinInfo *sjinfo; /* if outer join, its SpecialJoinInfo */ SpecialJoinInfo *sjinfo; /* if outer join, its SpecialJoinInfo */
List *oj_joinclauses; /* outer join quals not yet distributed */ List *oj_joinclauses; /* outer join quals not yet distributed */
List *lateral_clauses; /* quals postponed from children due to
* lateral references */
} JoinTreeItem; } 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, static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
Index rtindex); Index rtindex);
static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode, static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
JoinDomain *parent_domain, JoinDomain *parent_domain,
JoinTreeItem *parent_jtitem,
List **item_list); List **item_list);
static void deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem, static void deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem);
List **postponed_qual_list);
static void process_security_barrier_quals(PlannerInfo *root, static void process_security_barrier_quals(PlannerInfo *root,
int rti, Relids qualscope, int rti, JoinTreeItem *jtitem);
JoinDomain *jdomain);
static void mark_rels_nulled_by_join(PlannerInfo *root, Index ojrelid, static void mark_rels_nulled_by_join(PlannerInfo *root, Index ojrelid,
Relids lower_rels); Relids lower_rels);
static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root, static SpecialJoinInfo *make_outerjoininfo(PlannerInfo *root,
@ -107,7 +103,7 @@ static void deconstruct_distribute_oj_quals(PlannerInfo *root,
List *jtitems, List *jtitems,
JoinTreeItem *jtitem); JoinTreeItem *jtitem);
static void distribute_quals_to_rels(PlannerInfo *root, List *clauses, static void distribute_quals_to_rels(PlannerInfo *root, List *clauses,
JoinDomain *jdomain, JoinTreeItem *jtitem,
SpecialJoinInfo *sjinfo, SpecialJoinInfo *sjinfo,
Index security_level, Index security_level,
Relids qualscope, Relids qualscope,
@ -116,10 +112,9 @@ static void distribute_quals_to_rels(PlannerInfo *root, List *clauses,
bool allow_equivalence, bool allow_equivalence,
bool has_clone, bool has_clone,
bool is_clone, bool is_clone,
List **postponed_qual_list,
List **postponed_oj_qual_list); List **postponed_oj_qual_list);
static void distribute_qual_to_rels(PlannerInfo *root, Node *clause, static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
JoinDomain *jdomain, JoinTreeItem *jtitem,
SpecialJoinInfo *sjinfo, SpecialJoinInfo *sjinfo,
Index security_level, Index security_level,
Relids qualscope, Relids qualscope,
@ -128,7 +123,6 @@ static void distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool allow_equivalence, bool allow_equivalence,
bool has_clone, bool has_clone,
bool is_clone, bool is_clone,
List **postponed_qual_list,
List **postponed_oj_qual_list); List **postponed_oj_qual_list);
static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause); static bool check_redundant_nullability_qual(PlannerInfo *root, Node *clause);
static void check_mergejoinable(RestrictInfo *restrictinfo); static void check_mergejoinable(RestrictInfo *restrictinfo);
@ -742,7 +736,6 @@ deconstruct_jointree(PlannerInfo *root)
List *result; List *result;
JoinDomain *top_jdomain; JoinDomain *top_jdomain;
List *item_list = NIL; List *item_list = NIL;
List *postponed_qual_list = NIL;
ListCell *lc; ListCell *lc;
/* /*
@ -766,7 +759,7 @@ deconstruct_jointree(PlannerInfo *root)
/* Perform the initial scan of the jointree */ /* Perform the initial scan of the jointree */
result = deconstruct_recurse(root, (Node *) root->parse->jointree, result = deconstruct_recurse(root, (Node *) root->parse->jointree,
top_jdomain, top_jdomain, NULL,
&item_list); &item_list);
/* Now we can form the value of all_query_rels, too */ /* Now we can form the value of all_query_rels, too */
@ -780,16 +773,12 @@ deconstruct_jointree(PlannerInfo *root)
{ {
JoinTreeItem *jtitem = (JoinTreeItem *) lfirst(lc); JoinTreeItem *jtitem = (JoinTreeItem *) lfirst(lc);
deconstruct_distribute(root, jtitem, deconstruct_distribute(root, jtitem);
&postponed_qual_list);
} }
/* Shouldn't be any leftover postponed quals */
Assert(postponed_qual_list == NIL);
/* /*
* However, if there were any special joins then we may have some * If there were any special joins then we may have some postponed LEFT
* postponed LEFT JOIN clauses to deal with. * JOIN clauses to deal with.
*/ */
if (root->join_info_list) 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 * jtnode is the jointree node to examine, and parent_domain is the
* enclosing join domain. (We must add all base+OJ relids appearing * 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 * item_list is an in/out parameter: we add a JoinTreeItem struct to
* that list for each jointree node, in depth-first traversal order. * that list for each jointree node, in depth-first traversal order.
@ -825,6 +815,7 @@ deconstruct_jointree(PlannerInfo *root)
static List * static List *
deconstruct_recurse(PlannerInfo *root, Node *jtnode, deconstruct_recurse(PlannerInfo *root, Node *jtnode,
JoinDomain *parent_domain, JoinDomain *parent_domain,
JoinTreeItem *parent_jtitem,
List **item_list) List **item_list)
{ {
List *joinlist; 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 */ /* Make the new JoinTreeItem, but don't add it to item_list yet */
jtitem = palloc0_object(JoinTreeItem); jtitem = palloc0_object(JoinTreeItem);
jtitem->jtnode = jtnode; jtitem->jtnode = jtnode;
jtitem->jti_parent = parent_jtitem;
if (IsA(jtnode, RangeTblRef)) if (IsA(jtnode, RangeTblRef))
{ {
@ -880,6 +872,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
sub_joinlist = deconstruct_recurse(root, lfirst(l), sub_joinlist = deconstruct_recurse(root, lfirst(l),
parent_domain, parent_domain,
jtitem,
item_list); item_list);
sub_item = (JoinTreeItem *) llast(*item_list); sub_item = (JoinTreeItem *) llast(*item_list);
jtitem->qualscope = bms_add_members(jtitem->qualscope, jtitem->qualscope = bms_add_members(jtitem->qualscope,
@ -922,10 +915,12 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
/* Recurse */ /* Recurse */
leftjoinlist = deconstruct_recurse(root, j->larg, leftjoinlist = deconstruct_recurse(root, j->larg,
parent_domain, parent_domain,
jtitem,
item_list); item_list);
left_item = (JoinTreeItem *) llast(*item_list); left_item = (JoinTreeItem *) llast(*item_list);
rightjoinlist = deconstruct_recurse(root, j->rarg, rightjoinlist = deconstruct_recurse(root, j->rarg,
parent_domain, parent_domain,
jtitem,
item_list); item_list);
right_item = (JoinTreeItem *) llast(*item_list); right_item = (JoinTreeItem *) llast(*item_list);
/* Compute qualscope etc */ /* Compute qualscope etc */
@ -947,10 +942,12 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
/* Recurse */ /* Recurse */
leftjoinlist = deconstruct_recurse(root, j->larg, leftjoinlist = deconstruct_recurse(root, j->larg,
parent_domain, parent_domain,
jtitem,
item_list); item_list);
left_item = (JoinTreeItem *) llast(*item_list); left_item = (JoinTreeItem *) llast(*item_list);
rightjoinlist = deconstruct_recurse(root, j->rarg, rightjoinlist = deconstruct_recurse(root, j->rarg,
child_domain, child_domain,
jtitem,
item_list); item_list);
right_item = (JoinTreeItem *) llast(*item_list); right_item = (JoinTreeItem *) llast(*item_list);
/* Compute join domain contents, qualscope etc */ /* Compute join domain contents, qualscope etc */
@ -984,10 +981,12 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
/* Recurse */ /* Recurse */
leftjoinlist = deconstruct_recurse(root, j->larg, leftjoinlist = deconstruct_recurse(root, j->larg,
parent_domain, parent_domain,
jtitem,
item_list); item_list);
left_item = (JoinTreeItem *) llast(*item_list); left_item = (JoinTreeItem *) llast(*item_list);
rightjoinlist = deconstruct_recurse(root, j->rarg, rightjoinlist = deconstruct_recurse(root, j->rarg,
parent_domain, parent_domain,
jtitem,
item_list); item_list);
right_item = (JoinTreeItem *) llast(*item_list); right_item = (JoinTreeItem *) llast(*item_list);
/* Compute qualscope etc */ /* Compute qualscope etc */
@ -1013,6 +1012,7 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
root->join_domains = lappend(root->join_domains, child_domain); root->join_domains = lappend(root->join_domains, child_domain);
leftjoinlist = deconstruct_recurse(root, j->larg, leftjoinlist = deconstruct_recurse(root, j->larg,
child_domain, child_domain,
jtitem,
item_list); item_list);
left_item = (JoinTreeItem *) llast(*item_list); left_item = (JoinTreeItem *) llast(*item_list);
fj_domain->jd_relids = bms_copy(child_domain->jd_relids); 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); root->join_domains = lappend(root->join_domains, child_domain);
rightjoinlist = deconstruct_recurse(root, j->rarg, rightjoinlist = deconstruct_recurse(root, j->rarg,
child_domain, child_domain,
jtitem,
item_list); item_list);
right_item = (JoinTreeItem *) llast(*item_list); right_item = (JoinTreeItem *) llast(*item_list);
/* Compute qualscope etc */ /* Compute qualscope etc */
@ -1108,20 +1109,9 @@ deconstruct_recurse(PlannerInfo *root, Node *jtnode,
* *
* Distribute quals of the node to appropriate restriction and join lists. * 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. * 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 static void
deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem, deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem)
List **postponed_qual_list)
{ {
Node *jtnode = jtitem->jtnode; Node *jtnode = jtitem->jtnode;
@ -1133,82 +1123,51 @@ deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem,
if (root->qual_security_level > 0) if (root->qual_security_level > 0)
process_security_barrier_quals(root, process_security_barrier_quals(root,
varno, varno,
jtitem->qualscope, jtitem);
jtitem->jdomain);
} }
else if (IsA(jtnode, FromExpr)) else if (IsA(jtnode, FromExpr))
{ {
FromExpr *f = (FromExpr *) jtnode; FromExpr *f = (FromExpr *) jtnode;
List *new_postponed_quals = NIL;
ListCell *l;
/* /*
* Try to process any quals postponed by children. If they need * Process any lateral-referencing quals that were postponed to this
* further postponement, add them to my output postponed_qual_list. * level by children.
*/ */
foreach(l, *postponed_qual_list) distribute_quals_to_rels(root, jtitem->lateral_clauses,
{ jtitem,
PostponedQual *pq = (PostponedQual *) lfirst(l);
if (bms_is_subset(pq->relids, jtitem->qualscope))
distribute_qual_to_rels(root, pq->qual,
jtitem->jdomain,
NULL, NULL,
root->qual_security_level, root->qual_security_level,
jtitem->qualscope, NULL, NULL, jtitem->qualscope, NULL, NULL,
true, false, false, true, false, false,
NULL, NULL); NULL);
else
new_postponed_quals = lappend(new_postponed_quals, pq);
}
*postponed_qual_list = new_postponed_quals;
/* /*
* Now process the top-level quals. * Now process the top-level quals.
*/ */
distribute_quals_to_rels(root, (List *) f->quals, distribute_quals_to_rels(root, (List *) f->quals,
jtitem->jdomain, jtitem,
NULL, NULL,
root->qual_security_level, root->qual_security_level,
jtitem->qualscope, NULL, NULL, jtitem->qualscope, NULL, NULL,
true, false, false, true, false, false,
postponed_qual_list, NULL); NULL);
} }
else if (IsA(jtnode, JoinExpr)) else if (IsA(jtnode, JoinExpr))
{ {
JoinExpr *j = (JoinExpr *) jtnode; JoinExpr *j = (JoinExpr *) jtnode;
List *new_postponed_quals = NIL;
Relids ojscope; Relids ojscope;
List *my_quals; List *my_quals;
SpecialJoinInfo *sjinfo; SpecialJoinInfo *sjinfo;
List **postponed_oj_qual_list; List **postponed_oj_qual_list;
ListCell *l;
/* /*
* Try to process any quals postponed by children. If they need * Include lateral-referencing quals postponed from children in
* further postponement, add them to my output postponed_qual_list. * my_quals, so that they'll be handled properly in
* Quals that can be processed now must be included in my_quals, so * make_outerjoininfo. (This is destructive to
* that they'll be handled properly in make_outerjoininfo. * jtitem->lateral_clauses, but we won't use that again.)
*/ */
my_quals = NIL; my_quals = list_concat(jtitem->lateral_clauses,
foreach(l, *postponed_qual_list) (List *) j->quals);
{
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);
/* /*
* For an OJ, form the SpecialJoinInfo now, so that we can pass it to * 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 */ /* Process the JOIN's qual clauses */
distribute_quals_to_rels(root, my_quals, distribute_quals_to_rels(root, my_quals,
jtitem->jdomain, jtitem,
sjinfo, sjinfo,
root->qual_security_level, root->qual_security_level,
jtitem->qualscope, jtitem->qualscope,
ojscope, jtitem->nonnullable_rels, ojscope, jtitem->nonnullable_rels,
true, /* allow_equivalence */ true, /* allow_equivalence */
false, false, /* not clones */ false, false, /* not clones */
postponed_qual_list,
postponed_oj_qual_list); postponed_oj_qual_list);
/* And add the SpecialJoinInfo to join_info_list */ /* And add the SpecialJoinInfo to join_info_list */
@ -1304,8 +1262,7 @@ deconstruct_distribute(PlannerInfo *root, JoinTreeItem *jtitem,
*/ */
static void static void
process_security_barrier_quals(PlannerInfo *root, process_security_barrier_quals(PlannerInfo *root,
int rti, Relids qualscope, int rti, JoinTreeItem *jtitem)
JoinDomain *jdomain)
{ {
RangeTblEntry *rte = root->simple_rte_array[rti]; RangeTblEntry *rte = root->simple_rte_array[rti];
Index security_level = 0; 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. * pushed up to top of tree, which we don't want.
*/ */
distribute_quals_to_rels(root, qualset, distribute_quals_to_rels(root, qualset,
jdomain, jtitem,
NULL, NULL,
security_level, security_level,
qualscope, jtitem->qualscope,
qualscope, jtitem->qualscope,
NULL, NULL,
true, true,
false, false, /* not clones */ false, false, /* not clones */
NULL,
NULL); NULL);
security_level++; security_level++;
} }
@ -2038,7 +1994,7 @@ deconstruct_distribute_oj_quals(PlannerInfo *root,
is_clone = !has_clone; is_clone = !has_clone;
distribute_quals_to_rels(root, quals, distribute_quals_to_rels(root, quals,
otherjtitem->jdomain, otherjtitem,
sjinfo, sjinfo,
root->qual_security_level, root->qual_security_level,
this_qualscope, this_qualscope,
@ -2046,7 +2002,7 @@ deconstruct_distribute_oj_quals(PlannerInfo *root,
allow_equivalence, allow_equivalence,
has_clone, has_clone,
is_clone, is_clone,
NULL, NULL); /* no more postponement */ NULL); /* no more postponement */
/* /*
* Adjust qual nulling bits for next level up, if needed. We * 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 */ /* No commutation possible, just process the postponed clauses */
distribute_quals_to_rels(root, jtitem->oj_joinclauses, distribute_quals_to_rels(root, jtitem->oj_joinclauses,
jtitem->jdomain, jtitem,
sjinfo, sjinfo,
root->qual_security_level, root->qual_security_level,
qualscope, qualscope,
ojscope, nonnullable_rels, ojscope, nonnullable_rels,
true, /* allow_equivalence */ true, /* allow_equivalence */
false, false, /* not clones */ 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 static void
distribute_quals_to_rels(PlannerInfo *root, List *clauses, distribute_quals_to_rels(PlannerInfo *root, List *clauses,
JoinDomain *jdomain, JoinTreeItem *jtitem,
SpecialJoinInfo *sjinfo, SpecialJoinInfo *sjinfo,
Index security_level, Index security_level,
Relids qualscope, Relids qualscope,
@ -2101,7 +2057,6 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses,
bool allow_equivalence, bool allow_equivalence,
bool has_clone, bool has_clone,
bool is_clone, bool is_clone,
List **postponed_qual_list,
List **postponed_oj_qual_list) List **postponed_oj_qual_list)
{ {
ListCell *lc; ListCell *lc;
@ -2111,7 +2066,7 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses,
Node *clause = (Node *) lfirst(lc); Node *clause = (Node *) lfirst(lc);
distribute_qual_to_rels(root, clause, distribute_qual_to_rels(root, clause,
jdomain, jtitem,
sjinfo, sjinfo,
security_level, security_level,
qualscope, qualscope,
@ -2120,7 +2075,6 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses,
allow_equivalence, allow_equivalence,
has_clone, has_clone,
is_clone, is_clone,
postponed_qual_list,
postponed_oj_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 * mergejoinable operator, enter its left- and right-side expressions into
* the query's EquivalenceClasses. * the query's EquivalenceClasses.
* *
* In some cases, quals will be added to postponed_qual_list or * In some cases, quals will be added to parent jtitems' lateral_clauses
* postponed_oj_qual_list instead of being processed right away. * or to postponed_oj_qual_list instead of being processed right away.
* These will be dealt with in later steps of deconstruct_jointree. * These will be dealt with in later calls of deconstruct_distribute.
* *
* 'clause': the qual clause to be distributed * '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) * 'sjinfo': join's SpecialJoinInfo (NULL for an inner join or WHERE clause)
* 'security_level': security_level to assign to the qual * 'security_level': security_level to assign to the qual
* 'qualscope': set of base+OJ rels the qual's syntactic scope covers * '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 * EquivalenceClass
* 'has_clone': has_clone property to assign to the qual * 'has_clone': has_clone property to assign to the qual
* 'is_clone': is_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 * 'postponed_oj_qual_list': if not NULL, non-degenerate outer join clauses
* should be added to this list instead of being processed (list entries * should be added to this list instead of being processed (list entries
* are just the bare clauses) * are just the bare clauses)
@ -2170,7 +2121,7 @@ distribute_quals_to_rels(PlannerInfo *root, List *clauses,
*/ */
static void static void
distribute_qual_to_rels(PlannerInfo *root, Node *clause, distribute_qual_to_rels(PlannerInfo *root, Node *clause,
JoinDomain *jdomain, JoinTreeItem *jtitem,
SpecialJoinInfo *sjinfo, SpecialJoinInfo *sjinfo,
Index security_level, Index security_level,
Relids qualscope, Relids qualscope,
@ -2179,7 +2130,6 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
bool allow_equivalence, bool allow_equivalence,
bool has_clone, bool has_clone,
bool is_clone, bool is_clone,
List **postponed_qual_list,
List **postponed_oj_qual_list) List **postponed_oj_qual_list)
{ {
Relids relids; Relids relids;
@ -2202,21 +2152,34 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
* level that includes every rel they reference. Although we could make * level that includes every rel they reference. Although we could make
* pull_up_subqueries() place such quals correctly to begin with, it's * 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 * 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 * outside its syntactic scope, locate the nearest parent join level that
* process it once we've recursed back up to the appropriate join level. * 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)) if (!bms_is_subset(relids, qualscope))
{ {
PostponedQual *pq = (PostponedQual *) palloc(sizeof(PostponedQual)); JoinTreeItem *pitem;
Assert(root->hasLateralRTEs); /* shouldn't happen otherwise */ Assert(root->hasLateralRTEs); /* shouldn't happen otherwise */
Assert(sjinfo == NULL); /* mustn't postpone past outer join */ Assert(sjinfo == NULL); /* mustn't postpone past outer join */
pq->qual = clause; for (pitem = jtitem->jti_parent; pitem; pitem = pitem->jti_parent)
pq->relids = relids; {
*postponed_qual_list = lappend(*postponed_qual_list, pq); if (bms_is_subset(relids, pitem->qualscope))
{
pitem->lateral_clauses = lappend(pitem->lateral_clauses,
clause);
return; 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");
}
/* /*
* If it's an outer-join clause, also check that relids is a subset of * If it's an outer-join clause, also check that relids is a subset of
* ojscope. (This should not fail if the syntactic scope check passed.) * ojscope. (This should not fail if the syntactic scope check passed.)
@ -2262,7 +2225,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
else else
{ {
/* eval at join domain level */ /* eval at join domain level */
relids = bms_copy(jdomain->jd_relids); relids = bms_copy(jtitem->jdomain->jd_relids);
/* mark as gating qual */ /* mark as gating qual */
pseudoconstant = true; pseudoconstant = true;
/* tell createplan.c to check for gating quals */ /* tell createplan.c to check for gating quals */
@ -2458,7 +2421,7 @@ distribute_qual_to_rels(PlannerInfo *root, Node *clause,
{ {
if (maybe_equivalence) if (maybe_equivalence)
{ {
if (process_equivalence(root, &restrictinfo, jdomain)) if (process_equivalence(root, &restrictinfo, jtitem->jdomain))
return; return;
/* EC rejected it, so set left_ec/right_ec the hard way ... */ /* EC rejected it, so set left_ec/right_ec the hard way ... */
if (restrictinfo->mergeopfamilies) /* EC might have changed this */ if (restrictinfo->mergeopfamilies) /* EC might have changed this */

View File

@ -6203,6 +6203,28 @@ select * from
Output: 3 Output: 3
(11 rows) (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) -- check dummy rels with lateral references (bug #15694)
explain (verbose, costs off) explain (verbose, costs off)
select * from int8_tbl i8 left join lateral select * from int8_tbl i8 left join lateral

View File

@ -2159,6 +2159,12 @@ select * from
select * from (select 3 as z offset 0) z where z.z = x.x select * from (select 3 as z offset 0) z where z.z = x.x
) zz on zz.z = y.y; ) 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) -- check dummy rels with lateral references (bug #15694)
explain (verbose, costs off) explain (verbose, costs off)
select * from int8_tbl i8 left join lateral select * from int8_tbl i8 left join lateral