Fix computation of varnullingrels when const-folding field selection.
authorTom Lane <[email protected]>
Thu, 9 Nov 2023 20:46:16 +0000 (15:46 -0500)
committerTom Lane <[email protected]>
Thu, 9 Nov 2023 20:46:16 +0000 (15:46 -0500)
We can simplify FieldSelect on a whole-row Var into a plain Var
for the selected field.  However, we should copy the whole-row Var's
varnullingrels when we do so, because the new Var is clearly nullable
by exactly the same rels as the original.  Failure to do this led to
errors like "wrong varnullingrels (b) (expected (b 3)) for Var 2/2".

Richard Guo, per bug #18184 from Marian Krucina.  Back- to
v16 where varnullingrels was introduced.

Discussion: https://postgr.es/m/18184-5868dd258782058e@postgresql.org

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

index da258968b8c30806afb8027b8d47e5a31bd738ef..948c2bf06db05f2f47d04aaa3dbe6ef518dcf2b9 100644 (file)
@@ -3296,12 +3296,19 @@ eval_const_expressions_mutator(Node *node,
                                                                                          fselect->resulttype,
                                                                                          fselect->resulttypmod,
                                                                                          fselect->resultcollid))
-                                               return (Node *) makeVar(((Var *) arg)->varno,
-                                                                                               fselect->fieldnum,
-                                                                                               fselect->resulttype,
-                                                                                               fselect->resulttypmod,
-                                                                                               fselect->resultcollid,
-                                                                                               ((Var *) arg)->varlevelsup);
+                                       {
+                                               Var                *newvar;
+
+                                               newvar = makeVar(((Var *) arg)->varno,
+                                                                                fselect->fieldnum,
+                                                                                fselect->resulttype,
+                                                                                fselect->resulttypmod,
+                                                                                fselect->resultcollid,
+                                                                                ((Var *) arg)->varlevelsup);
+                                               /* New Var is nullable by same rels as the old one */
+                                               newvar->varnullingrels = ((Var *) arg)->varnullingrels;
+                                               return (Node *) newvar;
+                                       }
                                }
                                if (arg && IsA(arg, RowExpr))
                                {
index ddc4e692329954216023a687ae9fe1cbd7829431..b60f5a67c1e26b0b6aa2c08eec75ea2616b90d6e 100644 (file)
@@ -4133,6 +4133,32 @@ select * from mki4(42);
 
 drop function mki8(bigint, bigint);
 drop function mki4(int);
+-- test const-folding of a whole-row Var into a per-field Var
+-- (need to inline a function to reach this case, else parser does it)
+create function f_field_select(t onek) returns int4 as
+$$ select t.unique2; $$ language sql immutable;
+explain (verbose, costs off)
+select (t2.*).unique1, f_field_select(t2) from tenk1 t1
+    left join onek t2 on t1.unique1 = t2.unique1
+    left join int8_tbl t3 on true;
+                             QUERY PLAN                             
+--------------------------------------------------------------------
+ Nested Loop Left Join
+   Output: t2.unique1, t2.unique2
+   ->  Hash Left Join
+         Output: t2.unique1, t2.unique2
+         Hash Cond: (t1.unique1 = t2.unique1)
+         ->  Index Only Scan using tenk1_unique1 on public.tenk1 t1
+               Output: t1.unique1
+         ->  Hash
+               Output: t2.unique1, t2.unique2
+               ->  Seq Scan on public.onek t2
+                     Output: t2.unique1, t2.unique2
+   ->  Materialize
+         ->  Seq Scan on public.int8_tbl t3
+(13 rows)
+
+drop function f_field_select(t onek);
 --
 -- test extraction of restriction OR clauses from join OR clause
 -- (we used to only do this for indexable clauses)
index a41787d1f1e0e7715b705b42bc926965b686c8fd..fbaee480d32a6565660e82d9242bb9ba7a712ed2 100644 (file)
@@ -1381,6 +1381,18 @@ select * from mki4(42);
 drop function mki8(bigint, bigint);
 drop function mki4(int);
 
+-- test const-folding of a whole-row Var into a per-field Var
+-- (need to inline a function to reach this case, else parser does it)
+create function f_field_select(t onek) returns int4 as
+$$ select t.unique2; $$ language sql immutable;
+
+explain (verbose, costs off)
+select (t2.*).unique1, f_field_select(t2) from tenk1 t1
+    left join onek t2 on t1.unique1 = t2.unique1
+    left join int8_tbl t3 on true;
+
+drop function f_field_select(t onek);
+
 --
 -- test extraction of restriction OR clauses from join OR clause
 -- (we used to only do this for indexable clauses)