Skip to content

Commit 5dd80cf

Browse files
Richard GuoRucha Kulkarni
authored andcommitted
Fix computation of varnullingrels when translating appendrel Var
When adjust_appendrel_attrs translates a Var referencing a parent relation into a Var referencing a child relation, it propagates varnullingrels from the parent Var to the translated Var. Previously, the code simply overwrote the translated Var's varnullingrels with those of the parent. This was incorrect because the translated Var might already possess nonempty varnullingrels. This happens, for example, when a LATERAL subquery within a UNION ALL references a Var from the nullable side of an outer join. In such cases, the translated Var correctly carries the outer join's relid in its varnullingrels. Overwriting these bits with the parent Var's set caused the planner to lose track of the fact that the Var could be nulled by that outer join. In the reported case, because the underlying column had a NOT NULL constraint, the planner incorrectly deduced that the Var could never be NULL and discarded essential IS NOT NULL filters. This led to incorrect query results where NULL rows were returned instead of being filtered out. To fix, use bms_add_members to merge the parent Var's varnullingrels into the translated Var's existing set, preserving both sources of nullability. Back-patch to v16. Although the reported case does not seem to cause problems in v16, leaving incorrect varnullingrels in the tree seems like a trap for the unwary. Bug: #19412 Reported-by: Sergey Shinderuk <s.shinderuk@postgrespro.ru> Author: Richard Guo <guofenglinux@gmail.com> Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/19412-1d0318089b86859e@postgresql.org Backpatch-through: 16 (cherry picked from commit bcaf1b5101c44a4549774ef047dafcbf46cfd904)
1 parent abe403b commit 5dd80cf

3 files changed

Lines changed: 84 additions & 3 deletions

File tree

src/backend/optimizer/util/appendinfo.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,9 @@ adjust_appendrel_attrs_mutator(Node *node,
233233
* You might think we need to adjust var->varnullingrels, but that
234234
* shouldn't need any changes. It will contain outer-join relids,
235235
* while the transformation we are making affects only baserels.
236-
* Below, we just propagate var->varnullingrels into the translated
237-
* Var.
236+
* Below, we just merge var->varnullingrels into the translated Var.
237+
* (We must merge not just copy: the child Var could have some
238+
* nullingrel bits set already, and we mustn't drop those.)
238239
*
239240
* If var->varnullingrels isn't empty, and the translation wouldn't be
240241
* a Var, we have to fail. One could imagine wrapping the translated
@@ -279,7 +280,12 @@ adjust_appendrel_attrs_mutator(Node *node,
279280
elog(ERROR, "attribute %d of relation \"%s\" does not exist",
280281
var->varattno, get_rel_name(appinfo->parent_reloid));
281282
if (IsA(newnode, Var))
282-
((Var *) newnode)->varnullingrels = var->varnullingrels;
283+
{
284+
Var *newvar = (Var *) newnode;
285+
286+
newvar->varnullingrels = bms_add_members(newvar->varnullingrels,
287+
var->varnullingrels);
288+
}
283289
else if (var->varnullingrels != NULL)
284290
elog(ERROR, "failed to apply nullingrels to a non-Var");
285291
return newnode;

src/test/regress/expected/join.out

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4020,6 +4020,57 @@ where ss.x is null;
40204020
Output: 'bar'::text
40214021
(12 rows)
40224022

4023+
-- Test computation of varnullingrels when translating appendrel Var
4024+
begin;
4025+
create temp table t_append (a int not null, b int);
4026+
insert into t_append values (1, 1);
4027+
insert into t_append values (2, 3);
4028+
explain (verbose, costs off)
4029+
select t1.a, s.a from t_append t1
4030+
left join t_append t2 on t1.a = t2.b
4031+
join lateral (
4032+
select t1.a as a union all select t2.a as a
4033+
) s on true
4034+
where s.a is not null;
4035+
QUERY PLAN
4036+
---------------------------------------------------
4037+
Nested Loop
4038+
Output: t1.a, (t1.a)
4039+
-> Merge Left Join
4040+
Output: t1.a, t2.a
4041+
Merge Cond: (t1.a = t2.b)
4042+
-> Sort
4043+
Output: t1.a
4044+
Sort Key: t1.a
4045+
-> Seq Scan on pg_temp.t_append t1
4046+
Output: t1.a
4047+
-> Sort
4048+
Output: t2.a, t2.b
4049+
Sort Key: t2.b
4050+
-> Seq Scan on pg_temp.t_append t2
4051+
Output: t2.a, t2.b
4052+
-> Append
4053+
-> Result
4054+
Output: t1.a
4055+
-> Result
4056+
Output: t2.a
4057+
One-Time Filter: (t2.a IS NOT NULL)
4058+
(21 rows)
4059+
4060+
select t1.a, s.a from t_append t1
4061+
left join t_append t2 on t1.a = t2.b
4062+
join lateral (
4063+
select t1.a as a union all select t2.a as a
4064+
) s on true
4065+
where s.a is not null;
4066+
a | a
4067+
---+---
4068+
1 | 1
4069+
1 | 1
4070+
2 | 2
4071+
(3 rows)
4072+
4073+
rollback;
40234074
--
40244075
-- test inlining of immutable functions
40254076
--

src/test/regress/sql/join.sql

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,30 @@ select * from int4_tbl left join (
13291329
) ss(x) on true
13301330
where ss.x is null;
13311331

1332+
-- Test computation of varnullingrels when translating appendrel Var
1333+
begin;
1334+
1335+
create temp table t_append (a int not null, b int);
1336+
insert into t_append values (1, 1);
1337+
insert into t_append values (2, 3);
1338+
1339+
explain (verbose, costs off)
1340+
select t1.a, s.a from t_append t1
1341+
left join t_append t2 on t1.a = t2.b
1342+
join lateral (
1343+
select t1.a as a union all select t2.a as a
1344+
) s on true
1345+
where s.a is not null;
1346+
1347+
select t1.a, s.a from t_append t1
1348+
left join t_append t2 on t1.a = t2.b
1349+
join lateral (
1350+
select t1.a as a union all select t2.a as a
1351+
) s on true
1352+
where s.a is not null;
1353+
1354+
rollback;
1355+
13321356
--
13331357
-- test inlining of immutable functions
13341358
--

0 commit comments

Comments
 (0)