Fix improper interaction of FULL JOINs with lateral references.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 8 Apr 2019 20:09:07 +0000 (16:09 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 8 Apr 2019 20:09:29 +0000 (16:09 -0400)
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
src/test/regress/expected/rangefuncs.out
src/test/regress/sql/rangefuncs.sql

index e16855924c995c680c78b8b6d17e973a549e0f10..148879a63963f2cfad6bac811a57ddc7e36ebc3c 100644 (file)
@@ -611,6 +611,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))
                                        {
@@ -618,15 +622,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))
index 5c82614e0515e58484ab5d162008812fcefb8dad..046cc4a13a799be0b591cfbe83d104dd2f892c88 100644 (file)
@@ -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 foo_sql(int,int);
 DROP FUNCTION foo_mat(int,int);
 DROP SEQUENCE foo_rescan_seq1;
index 442d397d4a61ece0fddb714e3f14165472eaba30..668b1c40c442c8fb163b24697665d1c6884a868f 100644 (file)
@@ -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 foo_sql(int,int);
 DROP FUNCTION foo_mat(int,int);
 DROP SEQUENCE foo_rescan_seq1;