From 68e745ed0d230bde188dc4c9b528627d048144be Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 8 Apr 2019 16:09:07 -0400 Subject: [PATCH] Fix improper interaction of FULL JOINs with lateral references. join_is_legal() needs to reject forming certain outer joins in cases where that would lead the planner down a blind alley. However, it mistakenly supposed that the way to handle full joins was to treat them as applying the same constraints as for left joins, only to both sides. That doesn't work, as shown in bug #15741 from Anthony Skorski: given a lateral reference out of a join that's fully enclosed by a full join, the code would fail to believe that any join ordering is legal, resulting in errors like "failed to build any N-way joins". However, we don't really need to consider full joins at all for this purpose, because we effectively force them to be evaluated in syntactic order, and that order is always legal for lateral references. Hence, get rid of this broken logic for full joins and just ignore them instead. This seems to have been an oversight in commit 7e19db0c0. Back-patch to all supported branches, as that was. Discussion: https://postgr.es/m/15741-276f1f464b3f40eb@postgresql.org --- src/backend/optimizer/path/joinrels.c | 13 ++++-------- src/test/regress/expected/rangefuncs.out | 25 ++++++++++++++++++++++++ src/test/regress/sql/rangefuncs.sql | 15 ++++++++++++++ 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c index e2c20ded90..7079b6ac3f 100644 --- a/src/backend/optimizer/path/joinrels.c +++ b/src/backend/optimizer/path/joinrels.c @@ -621,6 +621,10 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, { SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(l); + /* ignore full joins --- their ordering is predetermined */ + if (sjinfo->jointype == JOIN_FULL) + continue; + if (bms_overlap(sjinfo->min_lefthand, join_plus_rhs) && !bms_is_subset(sjinfo->min_righthand, join_plus_rhs)) { @@ -628,15 +632,6 @@ join_is_legal(PlannerInfo *root, RelOptInfo *rel1, RelOptInfo *rel2, sjinfo->min_righthand); more = true; } - /* full joins constrain both sides symmetrically */ - if (sjinfo->jointype == JOIN_FULL && - bms_overlap(sjinfo->min_righthand, join_plus_rhs) && - !bms_is_subset(sjinfo->min_lefthand, join_plus_rhs)) - { - join_plus_rhs = bms_add_members(join_plus_rhs, - sjinfo->min_lefthand); - more = true; - } } } while (more); if (bms_overlap(join_plus_rhs, join_lateral_rels)) diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out index 34ca0ef890..36a5929113 100644 --- a/src/test/regress/expected/rangefuncs.out +++ b/src/test/regress/expected/rangefuncs.out @@ -1391,6 +1391,31 @@ SELECT * FROM (VALUES (1),(2),(3)) v1(r1), 3 | 3 | 30 | 8 (45 rows) +-- check handling of FULL JOIN with multiple lateral references (bug #15741) +SELECT * +FROM (VALUES (1),(2)) v1(r1) + LEFT JOIN LATERAL ( + SELECT * + FROM generate_series(1, v1.r1) AS gs1 + LEFT JOIN LATERAL ( + SELECT * + FROM generate_series(1, gs1) AS gs2 + LEFT JOIN generate_series(1, gs2) AS gs3 ON TRUE + ) AS ss1 ON TRUE + FULL JOIN generate_series(1, v1.r1) AS gs4 ON FALSE + ) AS ss0 ON TRUE; + r1 | gs1 | gs2 | gs3 | gs4 +----+-----+-----+-----+----- + 1 | | | | 1 + 1 | 1 | 1 | 1 | + 2 | | | | 1 + 2 | | | | 2 + 2 | 1 | 1 | 1 | + 2 | 2 | 1 | 1 | + 2 | 2 | 2 | 1 | + 2 | 2 | 2 | 2 | +(8 rows) + DROP FUNCTION rngfunc_sql(int,int); DROP FUNCTION rngfunc_mat(int,int); DROP SEQUENCE rngfunc_rescan_seq1; diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql index fc8ad9a158..5d29d2e401 100644 --- a/src/test/regress/sql/rangefuncs.sql +++ b/src/test/regress/sql/rangefuncs.sql @@ -319,6 +319,21 @@ SELECT * FROM (VALUES (1),(2),(3)) v1(r1), LATERAL (SELECT r1, * FROM (VALUES (10),(20),(30)) v2(r2) LEFT JOIN generate_series(r1,2+r2/5) f(i) ON ((r2+i)<100) OFFSET 0) s1; +-- check handling of FULL JOIN with multiple lateral references (bug #15741) + +SELECT * +FROM (VALUES (1),(2)) v1(r1) + LEFT JOIN LATERAL ( + SELECT * + FROM generate_series(1, v1.r1) AS gs1 + LEFT JOIN LATERAL ( + SELECT * + FROM generate_series(1, gs1) AS gs2 + LEFT JOIN generate_series(1, gs2) AS gs3 ON TRUE + ) AS ss1 ON TRUE + FULL JOIN generate_series(1, v1.r1) AS gs4 ON FALSE + ) AS ss0 ON TRUE; + DROP FUNCTION rngfunc_sql(int,int); DROP FUNCTION rngfunc_mat(int,int); DROP SEQUENCE rngfunc_rescan_seq1;