Fix miscomputation of direct_lateral_relids for join relations.
authorTom Lane <[email protected]>
Mon, 30 Nov 2020 17:22:43 +0000 (12:22 -0500)
committerTom Lane <[email protected]>
Mon, 30 Nov 2020 17:22:43 +0000 (12:22 -0500)
If a PlaceHolderVar is to be evaluated at a join relation, but
its value is only needed there and not at higher levels, we neglected
to update the joinrel's direct_lateral_relids to include the PHV's
source rel.  This causes problems because join_is_legal() then won't
allow joining the joinrel to the PHV's source rel at all, leading
to "failed to build any N-way joins" planner failures.

Per report from Andreas Seltenreich.  Back-patch to 9.5
where the problem originated.

Discussion: https://postgr.es/m/[email protected]

src/backend/optimizer/util/placeholder.c
src/test/regress/expected/join.out
src/test/regress/sql/join.sql

index 7fa93fb29ac1002cc5dfde29edc49576d6622ed3..aee8d2b475214620d25b3604c9ea4f8b3217eadd 100644 (file)
@@ -400,10 +400,10 @@ add_placeholders_to_base_rels(PlannerInfo *root)
  *     and if they contain lateral references, add those references to the
  *     joinrel's direct_lateral_relids.
  *
- * A join rel should emit a PlaceHolderVar if (a) the PHV is needed above
- * this join level and (b) the PHV can be computed at or below this level.
- * At this time we do not need to distinguish whether the PHV will be
- * computed here or copied up from below.
+ * A join rel should emit a PlaceHolderVar if (a) the PHV can be computed
+ * at or below this join level and (b) the PHV is needed above this level.
+ * However, condition (a) is sufficient to add to direct_lateral_relids,
+ * as explained below.
  */
 void
 add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel)
@@ -415,21 +415,36 @@ add_placeholders_to_joinrel(PlannerInfo *root, RelOptInfo *joinrel)
    {
        PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(lc);
 
-       /* Is it still needed above this joinrel? */
-       if (bms_nonempty_difference(phinfo->ph_needed, relids))
+       /* Is it computable here? */
+       if (bms_is_subset(phinfo->ph_eval_at, relids))
        {
-           /* Is it computable here? */
-           if (bms_is_subset(phinfo->ph_eval_at, relids))
+           /* Is it still needed above this joinrel? */
+           if (bms_nonempty_difference(phinfo->ph_needed, relids))
            {
                /* Yup, add it to the output */
                joinrel->reltargetlist = lappend(joinrel->reltargetlist,
                                                 phinfo->ph_var);
                joinrel->width += phinfo->ph_width;
-               /* Adjust joinrel's direct_lateral_relids as needed */
-               joinrel->direct_lateral_relids =
-                   bms_add_members(joinrel->direct_lateral_relids,
-                                   phinfo->ph_lateral);
            }
+
+           /*
+            * Also adjust joinrel's direct_lateral_relids to include the
+            * PHV's source rel(s).  We must do this even if we're not
+            * actually going to emit the PHV, otherwise join_is_legal() will
+            * reject valid join orderings.  (In principle maybe we could
+            * instead remove the joinrel's lateral_relids dependency; but
+            * that's complicated to get right, and cases where we're not
+            * going to emit the PHV are too rare to justify the work.)
+            *
+            * In principle we should only do this if the join doesn't yet
+            * include the PHV's source rel(s).  But our caller
+            * build_join_rel() will clean things up by removing the join's
+            * own relids from its direct_lateral_relids, so we needn't
+            * account for that here.
+            */
+           joinrel->direct_lateral_relids =
+               bms_add_members(joinrel->direct_lateral_relids,
+                               phinfo->ph_lateral);
        }
    }
 }
index 7ee0d122cbe41926bca3647f89a02ae88d6f8198..c901e4f0f2c795e711987f357b6bd6a618c972eb 100644 (file)
@@ -2972,6 +2972,70 @@ order by 1,2;
  4567890123456789 | 4567890123456789 | 4567890123456789 | 42
 (8 rows)
 
+--
+-- variant where a PlaceHolderVar is needed at a join, but not above the join
+--
+explain (costs off)
+select * from
+  int4_tbl as i41,
+  lateral
+    (select 1 as x from
+      (select i41.f1 as lat,
+              i42.f1 as loc from
+         int8_tbl as i81, int4_tbl as i42) as ss1
+      right join int4_tbl as i43 on (i43.f1 > 1)
+      where ss1.loc = ss1.lat) as ss2
+where i41.f1 > 0;
+                    QUERY PLAN                    
+--------------------------------------------------
+ Nested Loop
+   ->  Nested Loop
+         ->  Seq Scan on int4_tbl i41
+               Filter: (f1 > 0)
+         ->  Nested Loop
+               Join Filter: (i41.f1 = i42.f1)
+               ->  Seq Scan on int8_tbl i81
+               ->  Materialize
+                     ->  Seq Scan on int4_tbl i42
+   ->  Materialize
+         ->  Seq Scan on int4_tbl i43
+               Filter: (f1 > 1)
+(12 rows)
+
+select * from
+  int4_tbl as i41,
+  lateral
+    (select 1 as x from
+      (select i41.f1 as lat,
+              i42.f1 as loc from
+         int8_tbl as i81, int4_tbl as i42) as ss1
+      right join int4_tbl as i43 on (i43.f1 > 1)
+      where ss1.loc = ss1.lat) as ss2
+where i41.f1 > 0;
+     f1     | x 
+------------+---
+     123456 | 1
+     123456 | 1
+     123456 | 1
+     123456 | 1
+     123456 | 1
+     123456 | 1
+     123456 | 1
+     123456 | 1
+     123456 | 1
+     123456 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+ 2147483647 | 1
+(20 rows)
+
 --
 -- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE
 --
index b0b83893dc4484b36cb0ec7e63d3aed4e3637216..3199ad5b6556b36d3a7117416056307606636a1e 100644 (file)
@@ -882,6 +882,33 @@ where
   1 = (select 1 from int8_tbl t3 where ss.y is not null limit 1)
 order by 1,2;
 
+--
+-- variant where a PlaceHolderVar is needed at a join, but not above the join
+--
+
+explain (costs off)
+select * from
+  int4_tbl as i41,
+  lateral
+    (select 1 as x from
+      (select i41.f1 as lat,
+              i42.f1 as loc from
+         int8_tbl as i81, int4_tbl as i42) as ss1
+      right join int4_tbl as i43 on (i43.f1 > 1)
+      where ss1.loc = ss1.lat) as ss2
+where i41.f1 > 0;
+
+select * from
+  int4_tbl as i41,
+  lateral
+    (select 1 as x from
+      (select i41.f1 as lat,
+              i42.f1 as loc from
+         int8_tbl as i81, int4_tbl as i42) as ss1
+      right join int4_tbl as i43 on (i43.f1 > 1)
+      where ss1.loc = ss1.lat) as ss2
+where i41.f1 > 0;
+
 --
 -- test the corner cases FULL JOIN ON TRUE and FULL JOIN ON FALSE
 --