aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/optimizer/plan/initsplan.c203
-rw-r--r--src/test/regress/expected/join.out22
-rw-r--r--src/test/regress/sql/join.sql6
3 files changed, 111 insertions, 120 deletions
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