Fix oversight in outer join removal.
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 8 Jun 2023 21:10:04 +0000 (17:10 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 8 Jun 2023 21:10:04 +0000 (17:10 -0400)
A placeholder that references the outer join's relid in ph_eval_at
is logically "above" the join, and therefore we can't remove its
PlaceHolderInfo: it might still be used somewhere in the query.

This was not an issue pre-v16 because we failed to remove the join
at all in such cases.  The new outer-join-aware-Var infrastructure
permits deducing that it's okay to remove the join, but then we
have to clean up correctly afterwards.

Report and fix by Richard Guo

Discussion: https://postgr.es/m/CAMbWs4_tuVn9EwwMcggGiZJWWstdXX_ci8FeEU17vs+4nLgw3w@mail.gmail.com

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

index 6f7e657f05610667719476544520ee70c15f5764..6476e55e56886d564a075477b88e5ad3539bf854 100644 (file)
@@ -425,7 +425,8 @@ remove_rel_from_query(PlannerInfo *root, int relid, SpecialJoinInfo *sjinfo)
 
                Assert(!bms_is_member(relid, phinfo->ph_lateral));
                if (bms_is_subset(phinfo->ph_needed, joinrelids) &&
-                       bms_is_member(relid, phinfo->ph_eval_at))
+                       bms_is_member(relid, phinfo->ph_eval_at) &&
+                       !bms_is_member(ojrelid, phinfo->ph_eval_at))
                {
                        root->placeholder_list = foreach_delete_current(root->placeholder_list,
                                                                                                                        l);
index 4c278b2fa3953a19f5e4a5664b84536c2723c079..d04648df3fde4fa04a72d1915463cc98bfb7e51e 100644 (file)
@@ -5842,6 +5842,63 @@ where ss.stringu2 !~* ss.case1;
  doh!
 (1 row)
 
+rollback;
+-- test cases where we can remove a join, but not a PHV computed at it
+begin;
+create temp table t (a int unique, b int);
+insert into t values (1,1), (2,2);
+explain (costs off)
+select 1
+from t t1
+  left join (select t2.a, 1 as c
+             from t t2 left join t t3 on t2.a = t3.a) s
+  on true
+  left join t t4 on true
+where s.a < s.c;
+             QUERY PLAN              
+-------------------------------------
+ Nested Loop Left Join
+   ->  Nested Loop
+         ->  Seq Scan on t t1
+         ->  Materialize
+               ->  Seq Scan on t t2
+                     Filter: (a < 1)
+   ->  Materialize
+         ->  Seq Scan on t t4
+(8 rows)
+
+explain (costs off)
+select t1.a, s.*
+from t t1
+  left join lateral (select t2.a, coalesce(t1.a, 1) as c
+                     from t t2 left join t t3 on t2.a = t3.a) s
+  on true
+  left join t t4 on true
+where s.a < s.c;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Nested Loop Left Join
+   ->  Nested Loop
+         ->  Seq Scan on t t1
+         ->  Seq Scan on t t2
+               Filter: (a < COALESCE(t1.a, 1))
+   ->  Materialize
+         ->  Seq Scan on t t4
+(7 rows)
+
+select t1.a, s.*
+from t t1
+  left join lateral (select t2.a, coalesce(t1.a, 1) as c
+                     from t t2 left join t t3 on t2.a = t3.a) s
+  on true
+  left join t t4 on true
+where s.a < s.c;
+ a | a | c 
+---+---+---
+ 2 | 1 | 2
+ 2 | 1 | 2
+(2 rows)
+
 rollback;
 -- test case to expose miscomputation of required relid set for a PHV
 explain (verbose, costs off)
index 4baf5ebc138f7946ef89f260478e7c0f3be01087..0308258a917e417db8e0972d922ab789d7902a2f 100644 (file)
@@ -2153,6 +2153,40 @@ where ss.stringu2 !~* ss.case1;
 
 rollback;
 
+-- test cases where we can remove a join, but not a PHV computed at it
+begin;
+
+create temp table t (a int unique, b int);
+insert into t values (1,1), (2,2);
+
+explain (costs off)
+select 1
+from t t1
+  left join (select t2.a, 1 as c
+             from t t2 left join t t3 on t2.a = t3.a) s
+  on true
+  left join t t4 on true
+where s.a < s.c;
+
+explain (costs off)
+select t1.a, s.*
+from t t1
+  left join lateral (select t2.a, coalesce(t1.a, 1) as c
+                     from t t2 left join t t3 on t2.a = t3.a) s
+  on true
+  left join t t4 on true
+where s.a < s.c;
+
+select t1.a, s.*
+from t t1
+  left join lateral (select t2.a, coalesce(t1.a, 1) as c
+                     from t t2 left join t t3 on t2.a = t3.a) s
+  on true
+  left join t t4 on true
+where s.a < s.c;
+
+rollback;
+
 -- test case to expose miscomputation of required relid set for a PHV
 explain (verbose, costs off)
 select i8.*, ss.v, t.unique2