Fix planner failure with full join in RHS of left join.
authorTom Lane
Fri, 22 Apr 2016 00:05:58 +0000 (20:05 -0400)
committerTom Lane
Fri, 22 Apr 2016 00:05:58 +0000 (20:05 -0400)
Given a left join containing a full join in its righthand side, with
the left join's joinclause referencing only one side of the full join
(in a non-strict fashion, so that the full join doesn't get simplified),
the planner could fail with "failed to build any N-way joins" or related
errors.  This happened because the full join was seen as overlapping the
left join's RHS, and then recent changes within join_is_legal() caused
that function to conclude that the full join couldn't validly be formed.
Rather than try to rejigger join_is_legal() yet more to allow this,
I think it's better to fix initsplan.c so that the required join order
is explicit in the SpecialJoinInfo data structure.  The previous coding
there essentially ignored full joins, relying on the fact that we don't
flatten them in the joinlist data structure to preserve their ordering.
That's sufficient to prevent a wrong plan from being formed, but as this
example shows, it's not sufficient to ensure that the right plan will
be formed.  We need to work a bit harder to ensure that the right plan
looks sane according to the SpecialJoinInfos.

Per bug #14105 from Vojtech Rylko.  This was apparently induced by
commit 8703059c6 (though now that I've seen it, I wonder whether there
are related cases that could have failed before that); so back-patch
to all active branches.  Unfortunately, that patch also went into 9.0,
so this bug is a regression that won't be fixed in that branch.

src/backend/optimizer/plan/initsplan.c
src/test/regress/expected/join.out
src/test/regress/sql/join.sql

index f2dce6a296926ecc9edbebf11b25074a781e2d0f..de292e7da4f993e69a3ca9c5aaa3b0113155953d 100644 (file)
@@ -1156,9 +1156,32 @@ make_outerjoininfo(PlannerInfo *root,
    {
        SpecialJoinInfo *otherinfo = (SpecialJoinInfo *) lfirst(l);
 
-       /* ignore full joins --- other mechanisms preserve their ordering */
+       /*
+        * A full join is an optimization barrier: we can't associate into or
+        * out of it.  Hence, if it overlaps either LHS or RHS of the current
+        * rel, expand that side's min relset to cover the whole full join.
+        */
        if (otherinfo->jointype == JOIN_FULL)
+       {
+           if (bms_overlap(left_rels, otherinfo->syn_lefthand) ||
+               bms_overlap(left_rels, otherinfo->syn_righthand))
+           {
+               min_lefthand = bms_add_members(min_lefthand,
+                                              otherinfo->syn_lefthand);
+               min_lefthand = bms_add_members(min_lefthand,
+                                              otherinfo->syn_righthand);
+           }
+           if (bms_overlap(right_rels, otherinfo->syn_lefthand) ||
+               bms_overlap(right_rels, otherinfo->syn_righthand))
+           {
+               min_righthand = bms_add_members(min_righthand,
+                                               otherinfo->syn_lefthand);
+               min_righthand = bms_add_members(min_righthand,
+                                               otherinfo->syn_righthand);
+           }
+           /* Needn't do anything else with the full join */
            continue;
+       }
 
        /*
         * For a lower OJ in our LHS, if our join condition uses the lower
index bade392394644ed5057a7c2c7d445e85415b4154..8676bc9f1d364f4eb58b6c80e3e0590baf69f6e3 100644 (file)
@@ -3801,6 +3801,37 @@ where ss1.c2 = 0;
 ----+----+----+----+----+----
 (0 rows)
 
+--
+-- test successful handling of full join underneath left join (bug #14105)
+--
+explain (costs off)
+select * from
+  (select 1 as id) as xx
+  left join
+    (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id))
+  on (xx.id = coalesce(yy.id));
+              QUERY PLAN               
+---------------------------------------
+ Nested Loop Left Join
+   Join Filter: ((1) = COALESCE((1)))
+   ->  Result
+   ->  Hash Full Join
+         Hash Cond: (a1.unique1 = (1))
+         ->  Seq Scan on tenk1 a1
+         ->  Hash
+               ->  Result
+(8 rows)
+
+select * from
+  (select 1 as id) as xx
+  left join
+    (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id))
+  on (xx.id = coalesce(yy.id));
+ id | unique1 | unique2 | two | four | ten | twenty | hundred | thousand | twothousand | fivethous | tenthous | odd | even | stringu1 | stringu2 | string4 | id 
+----+---------+---------+-----+------+-----+--------+---------+----------+-------------+-----------+----------+-----+------+----------+----------+---------+----
+  1 |       1 |    2838 |   1 |    1 |   1 |      1 |       1 |        1 |           1 |         1 |        1 |   2 |    3 | BAAAAA   | EFEAAA   | OOOOxx  |  1
+(1 row)
+
 --
 -- test ability to push constants through outer join clauses
 --
index c3902d9e9fc6a337623f488e00a16c4b7ab604a0..67c4d3956cc9b01db54c3f9e6df97593418c29f2 100644 (file)
@@ -1209,6 +1209,23 @@ select ss2.* from
   lateral (select i41.*, i8.*, ss1.* from text_tbl limit 1) ss2
 where ss1.c2 = 0;
 
+--
+-- test successful handling of full join underneath left join (bug #14105)
+--
+
+explain (costs off)
+select * from
+  (select 1 as id) as xx
+  left join
+    (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id))
+  on (xx.id = coalesce(yy.id));
+
+select * from
+  (select 1 as id) as xx
+  left join
+    (tenk1 as a1 full join (select 1 as id) as yy on (a1.unique1 = yy.id))
+  on (xx.id = coalesce(yy.id));
+
 --
 -- test ability to push constants through outer join clauses
 --