summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2016-04-21 20:05:58 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2016-04-21 20:05:58 -0400
commit6882dbd342d197e5f0bcc59059005780bc5c49a9 (patch)
tree1ea3d30c1127fcfd67b38b1e822734cad6400d0e
parent9028f404ef9605b585c176f7b0924069884cabfe (diff)
downloadpostgresql-6882dbd342d197e5f0bcc59059005780bc5c49a9.tar.gz
Fix planner failure with full join in RHS of left join.
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.
-rw-r--r--src/backend/optimizer/plan/initsplan.c25
-rw-r--r--src/test/regress/expected/join.out31
-rw-r--r--src/test/regress/sql/join.sql17
3 files changed, 72 insertions, 1 deletions
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 6dc01812da..cd132ccfce 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -648,9 +648,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
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index e69d5cccbc..d3c20d2e95 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -3049,6 +3049,37 @@ select * from
(2 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
--
explain (costs off)
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index fdeca0ba71..8ec74d9b4a 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -852,6 +852,23 @@ select * from
on i8.q1 = i4.f1;
--
+-- 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
--