diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index b53722036cd..ec03bc0e853 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -3415,7 +3415,8 @@ make_rel_from_joinlist(PlannerInfo *root, List *joinlist) * using a plugin, GEQO, or the regular join search code. * * We put the initial_rels list into a PlannerInfo field because - * has_legal_joinclause() needs to look at it (ugly :-(). + * has_legal_joinclause() and join_is_legal() need to look at it (ugly + * :-(). */ root->initial_rels = initial_rels; diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index 0b558c4146e..733074b36f0 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -607,6 +607,9 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, * to find any plan at all. We have to consider not only rels that * are directly on the inner side of an OJ with the joinrel, but also * ones that are indirectly so, so search to find all such rels. + * Likewise, input rels of the current join search that laterally + * reference the joinrel or any rel found so far cannot supply a + * parameter to the joinrel, so include those too. */ join_lateral_rels = min_join_parameterization(root, joinrelids, rel1, rel2); @@ -634,6 +637,24 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, more = true; } } + + /* + * Add input rels that laterally reference any rel found so + * far. An input rel can be a sub-joinlist's joinrel, which + * must be taken as a whole. + */ + foreach(l, root->initial_rels) + { + RelOptInfo *irel = (RelOptInfo *) lfirst(l); + + if (bms_overlap(irel->lateral_relids, join_plus_rhs) && + !bms_is_subset(irel->relids, join_plus_rhs)) + { + join_plus_rhs = bms_add_members(join_plus_rhs, + irel->relids); + more = true; + } + } } while (more); if (bms_overlap(join_plus_rhs, join_lateral_rels)) return false; /* will not be able to join to some RHS rel */ diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 0f091c0b370..44adc7e464f 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -6105,6 +6105,108 @@ select count(*) from int4_tbl t1 left join 25 (1 row) +-- +-- check that a join is disallowed when its lateral reference can only be +-- satisfied by way of a rel that laterally references the join itself +-- +explain (costs off) +select 1 from int4_tbl t1 left join int4_tbl t2 on true, + lateral (select t2.f1 offset 0) s1 left join int8_tbl t3 on s1.f1 = t3.q1, + lateral (select t3.q2 offset 0) s2 +where s2.q2 > t1.f1; + QUERY PLAN +------------------------------------------------------- + Nested Loop + Join Filter: ((t3.q2) > t1.f1) + -> Hash Left Join + Hash Cond: ((t2.f1) = t3.q1) + -> Nested Loop + -> Nested Loop Left Join + -> Seq Scan on int4_tbl t1 + -> Materialize + -> Seq Scan on int4_tbl t2 + -> Result + -> Hash + -> Seq Scan on int8_tbl t3 + -> Result +(13 rows) + +select 1 from int4_tbl t1 left join int4_tbl t2 on true, + lateral (select t2.f1 offset 0) s1 left join int8_tbl t3 on s1.f1 = t3.q1, + lateral (select t3.q2 offset 0) s2 +where s2.q2 > t1.f1; + ?column? +---------- +(0 rows) + +-- likewise when the outer join above the lateral referencer is a full join +explain (costs off) +select count(*) from int4_tbl t1 left join int4_tbl t2 on true, + lateral (select t2.f1 offset 0) s1 full join int8_tbl t3 on s1.f1 = t3.q1, + lateral (select t3.q2 offset 0) s2 +where s2.q2 > t1.f1; + QUERY PLAN +------------------------------------------------------- + Aggregate + -> Nested Loop + Join Filter: ((t3.q2) > t1.f1) + -> Nested Loop + -> Nested Loop Left Join + -> Seq Scan on int4_tbl t1 + -> Materialize + -> Seq Scan on int4_tbl t2 + -> Hash Full Join + Hash Cond: (t3.q1 = (t2.f1)) + -> Seq Scan on int8_tbl t3 + -> Hash + -> Result + -> Result +(14 rows) + +select count(*) from int4_tbl t1 left join int4_tbl t2 on true, + lateral (select t2.f1 offset 0) s1 full join int8_tbl t3 on s1.f1 = t3.q1, + lateral (select t3.q2 offset 0) s2 +where s2.q2 > t1.f1; + count +------- + 80 +(1 row) + +-- likewise when the lateral referencer is part of a sub-joinlist +set from_collapse_limit to 4; +explain (costs off) +select count(*) from int4_tbl t1 left join int4_tbl t2 on true, + lateral (select t2.f1 offset 0) s1 join int8_tbl t3 on s1.f1 = t3.q1, + lateral (select t3.q2 offset 0) s2 +where s2.q2 > t1.f1; + QUERY PLAN +------------------------------------------------------- + Aggregate + -> Nested Loop + Join Filter: ((t3.q2) > t1.f1) + -> Nested Loop + -> Nested Loop Left Join + -> Seq Scan on int4_tbl t1 + -> Materialize + -> Seq Scan on int4_tbl t2 + -> Hash Join + Hash Cond: (t3.q1 = (t2.f1)) + -> Seq Scan on int8_tbl t3 + -> Hash + -> Result + -> Result +(14 rows) + +select count(*) from int4_tbl t1 left join int4_tbl t2 on true, + lateral (select t2.f1 offset 0) s1 join int8_tbl t3 on s1.f1 = t3.q1, + lateral (select t3.q2 offset 0) s2 +where s2.q2 > t1.f1; + count +------- + 0 +(1 row) + +reset from_collapse_limit; -- -- test successful handling of full join underneath left join (bug #14105) -- diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 229e3826878..560180fffa0 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -2167,6 +2167,50 @@ select count(*) from int4_tbl t1 left join (select c.f1 as cnt from int4_tbl c where c.f1 = t2.one offset 0) t3 on t2.bx = t3.cnt; +-- +-- check that a join is disallowed when its lateral reference can only be +-- satisfied by way of a rel that laterally references the join itself +-- + +explain (costs off) +select 1 from int4_tbl t1 left join int4_tbl t2 on true, + lateral (select t2.f1 offset 0) s1 left join int8_tbl t3 on s1.f1 = t3.q1, + lateral (select t3.q2 offset 0) s2 +where s2.q2 > t1.f1; + +select 1 from int4_tbl t1 left join int4_tbl t2 on true, + lateral (select t2.f1 offset 0) s1 left join int8_tbl t3 on s1.f1 = t3.q1, + lateral (select t3.q2 offset 0) s2 +where s2.q2 > t1.f1; + +-- likewise when the outer join above the lateral referencer is a full join +explain (costs off) +select count(*) from int4_tbl t1 left join int4_tbl t2 on true, + lateral (select t2.f1 offset 0) s1 full join int8_tbl t3 on s1.f1 = t3.q1, + lateral (select t3.q2 offset 0) s2 +where s2.q2 > t1.f1; + +select count(*) from int4_tbl t1 left join int4_tbl t2 on true, + lateral (select t2.f1 offset 0) s1 full join int8_tbl t3 on s1.f1 = t3.q1, + lateral (select t3.q2 offset 0) s2 +where s2.q2 > t1.f1; + +-- likewise when the lateral referencer is part of a sub-joinlist +set from_collapse_limit to 4; + +explain (costs off) +select count(*) from int4_tbl t1 left join int4_tbl t2 on true, + lateral (select t2.f1 offset 0) s1 join int8_tbl t3 on s1.f1 = t3.q1, + lateral (select t3.q2 offset 0) s2 +where s2.q2 > t1.f1; + +select count(*) from int4_tbl t1 left join int4_tbl t2 on true, + lateral (select t2.f1 offset 0) s1 join int8_tbl t3 on s1.f1 = t3.q1, + lateral (select t3.q2 offset 0) s2 +where s2.q2 > t1.f1; + +reset from_collapse_limit; + -- -- test successful handling of full join underneath left join (bug #14105) --