diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 2bdeddd453a..9c42616f024 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -3365,7 +3365,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 d4c5738e965..27ccdfe4f0d 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -616,6 +616,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); @@ -643,6 +646,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 fa7a66c8e69..5150f6173a2 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -5624,6 +5624,108 @@ where ss1.c2 = 0; ----+----+----+----+----+---- (0 rows) +-- +-- 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 5d8709f929e..19ded2a58fc 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -1986,6 +1986,50 @@ select ss2.* from lateral (select i41.*, i8.*, ss1.* from text_tbl limit 1) ss2 where ss1.c2 = 0; +-- +-- 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) --