diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c index 687e923c46c..ef0172b2f82 100644 --- a/src/backend/optimizer/util/relnode.c +++ b/src/backend/optimizer/util/relnode.c @@ -1735,6 +1735,15 @@ get_baserel_parampathinfo(PlannerInfo *root, RelOptInfo *baserel, { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + /* + * A clone clause must not be enforced here if an outer join it is + * incompatible with has already been computed below the point of + * evaluation; some other clone is the right one to apply. + */ + if ((rinfo->has_clone || rinfo->is_clone) && + bms_overlap(rinfo->incompatible_relids, joinrelids)) + continue; + if (join_clause_is_movable_into(rinfo, baserel->relids, joinrelids)) @@ -1867,6 +1876,11 @@ get_joinrel_parampathinfo(PlannerInfo *root, RelOptInfo *joinrel, { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); + /* As above, reject clones incompatible with a computed outer join */ + if ((rinfo->has_clone || rinfo->is_clone) && + bms_overlap(rinfo->incompatible_relids, join_and_req)) + continue; + if (join_clause_is_movable_into(rinfo, joinrel->relids, join_and_req) && diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h index 27a2c6815b7..fd8de0022fc 100644 --- a/src/include/nodes/pathnodes.h +++ b/src/include/nodes/pathnodes.h @@ -2822,7 +2822,7 @@ typedef struct LimitPath * clause and null Vars that it uses. In practice we only bother to populate * it for "clone" clauses, as it's currently only needed to prevent multiple * clones of the same clause from being accepted for evaluation at the same - * join level. + * plan level, whether at a join or within a parameterized path. * * There is also an outer_relids field, which is NULL except for outer join * clauses; for those, it is the set of relids on the outer side of the diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index 5b666dcc697..4353f8cb9ec 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -5891,8 +5891,8 @@ select count(*) from int4_tbl t1 left join left join lateral (select c.f1 as cnt from int4_tbl c where c.f1 = t2.one offset 0) t3 on t2.bx = t3.cnt; - QUERY PLAN -------------------------------------------------------------------------- + QUERY PLAN +------------------------------------------------------------ Aggregate -> Nested Loop Left Join -> Seq Scan on int4_tbl t1 @@ -5904,7 +5904,7 @@ select count(*) from int4_tbl t1 left join -> Hash -> Seq Scan on int8_tbl b -> Subquery Scan on t3 - Filter: ((b.q1 = t3.cnt) AND (b.q1 = t3.cnt)) + Filter: (b.q1 = t3.cnt) -> Seq Scan on int4_tbl c Filter: (f1 = (1)) (14 rows) @@ -5920,6 +5920,51 @@ select count(*) from int4_tbl t1 left join 25 (1 row) +-- +-- check that a cloned outer-join qual is not enforced multiple times when +-- it is moved into a parameterized join +-- +explain (costs off) +select count(*) from int4_tbl t1 left join + (select b.q1 as bx, 1 as one from int4_tbl a left join int8_tbl b on a.f1 = b.q2) t2 + on true + left join + (lateral (select c.f1 as cnt from int4_tbl c where c.f1 = t2.one offset 0) t3 + join int4_tbl t4 on t3.cnt = t4.f1) + on t2.bx = t3.cnt + t4.f1; + QUERY PLAN +---------------------------------------------------------------- + Aggregate + -> Nested Loop Left Join + -> Seq Scan on int4_tbl t1 + -> Materialize + -> Nested Loop Left Join + -> Hash Left Join + Hash Cond: (a.f1 = b.q2) + -> Seq Scan on int4_tbl a + -> Hash + -> Seq Scan on int8_tbl b + -> Hash Join + Hash Cond: (t4.f1 = c.f1) + Join Filter: (b.q1 = (c.f1 + t4.f1)) + -> Seq Scan on int4_tbl t4 + -> Hash + -> Seq Scan on int4_tbl c + Filter: (f1 = (1)) +(17 rows) + +select count(*) from int4_tbl t1 left join + (select b.q1 as bx, 1 as one from int4_tbl a left join int8_tbl b on a.f1 = b.q2) t2 + on true + left join + (lateral (select c.f1 as cnt from int4_tbl c where c.f1 = t2.one offset 0) t3 + join int4_tbl t4 on t3.cnt = t4.f1) + on t2.bx = t3.cnt + t4.f1; + count +------- + 25 +(1 row) + -- -- 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 792637a7c8d..75e4d66453a 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -2113,6 +2113,28 @@ 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 cloned outer-join qual is not enforced multiple times when +-- it is moved into a parameterized join +-- + +explain (costs off) +select count(*) from int4_tbl t1 left join + (select b.q1 as bx, 1 as one from int4_tbl a left join int8_tbl b on a.f1 = b.q2) t2 + on true + left join + (lateral (select c.f1 as cnt from int4_tbl c where c.f1 = t2.one offset 0) t3 + join int4_tbl t4 on t3.cnt = t4.f1) + on t2.bx = t3.cnt + t4.f1; + +select count(*) from int4_tbl t1 left join + (select b.q1 as bx, 1 as one from int4_tbl a left join int8_tbl b on a.f1 = b.q2) t2 + on true + left join + (lateral (select c.f1 as cnt from int4_tbl c where c.f1 = t2.one offset 0) t3 + join int4_tbl t4 on t3.cnt = t4.f1) + on t2.bx = t3.cnt + t4.f1; + -- -- test successful handling of full join underneath left join (bug #14105) --