1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Fix skipping of join quals for appendrel children

Commit 767c59895 made set_append_rel_size() skip join quals that came
from above an outer join that can null the parent rel.  Such quals are
of no use while generating paths for the child, and skipping them also
keeps them away from adjust_appendrel_attrs(), which cannot apply
nullingrels to a non-Var translation.

However, it identified such quals by looking at their clause_relids.
That misses a reference to the parent rel that is nulled by such an
outer join but appears only within a PlaceHolderVar, because
pull_varnos() reports only where the PHV is evaluated.  Such a qual
could then reach adjust_appendrel_attrs(), which failed with "failed
to apply nullingrels to a non-Var" if the referenced output of a UNION
ALL member was not a plain Var.

To fix, also skip quals whose clause_relids overlap the parent rel's
lateral_referencers.  A reference to the parent rel within a PHV that
is evaluated elsewhere is necessarily a lateral reference, so this
catches all such quals.  These quals are no use for the child either,
since they can never be enforced by a scan of the parent rel; this is
the same reasoning join_clause_is_movable_to() uses.

Back-patch to v16, where commit 767c59895 was introduced.

Author: Richard Guo <guofenglinux@gmail.com>
Reviewed-by: wenhui qiu <qiuwenhuifx@gmail.com>
Discussion: https://postgr.es/m/CAMbWs4_fzrrN9G6-wLwwrdM-n=fwj3Uhr1zkgj3o91EEC-CuEA@mail.gmail.com
Backpatch-through: 16
This commit is contained in:
Richard Guo
2026-10-17 10:28:53 +09:00
parent a69ac35598
commit 58d16be8ac
3 changed files with 102 additions and 1 deletions

View File

@@ -1126,13 +1126,22 @@ set_append_rel_size(PlannerInfo *root, RelOptInfo *rel,
* for the child. This saves some effort while processing the child
* rel, and it also avoids an implementation restriction in
* adjust_appendrel_attrs (it can't apply nullingrels to a non-Var).
*
* clause_relids does not reflect references to this rel that appear
* within PlaceHolderVars, but those are lateral references, so we
* also skip quals that use any rel laterally referencing this rel.
* This is safe since such quals are useless for the child: enforcing
* one at a scan of this rel would require the scan to be
* parameterized by a rel that laterally references this rel, which is
* impossible since that rel needs this rel's output first.
*/
childrinfos = NIL;
foreach(lc, rel->joininfo)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
if (!bms_overlap(rinfo->clause_relids, rel->nulling_relids))
if (!bms_overlap(rinfo->clause_relids, rel->nulling_relids) &&
!bms_overlap(rinfo->clause_relids, rel->lateral_referencers))
childrinfos = lappend(childrinfos,
adjust_appendrel_attrs(root,
(Node *) rinfo,

View File

@@ -4858,6 +4858,84 @@ where ss.x is null;
Output: 'bar'::text
(12 rows)
-- Test a join qual referencing a nulled appendrel output only within a PHV
explain (verbose, costs off)
select 1 from int4_tbl t1
left join (select 1 as x union all select 2) s on true
left join lateral (select s.x as y from int4_tbl t2) ss on true
left join int4_tbl t3 on ss.y is null;
QUERY PLAN
--------------------------------------------------
Nested Loop Left Join
Output: 1
Join Filter: (((1)) IS NULL)
-> Nested Loop Left Join
Output: ((1))
-> Nested Loop Left Join
Output: (1)
-> Seq Scan on public.int4_tbl t1
Output: t1.f1
-> Materialize
Output: (1)
-> Append
-> Result
Output: 1
-> Result
Output: 2
-> Memoize
Output: ((1))
Cache Key: (1)
Cache Mode: binary
-> Seq Scan on public.int4_tbl t2
Output: (1)
-> Materialize
-> Seq Scan on public.int4_tbl t3
(24 rows)
-- Same, but the join qual's required_relids don't include the nulling OJ
explain (verbose, costs off)
select 1 from ((int4_tbl t1 left join (select 1 as x union all select 2) s on t1.f1 < s.x)
join lateral (select coalesce(s.x, t2.f1) as y from int4_tbl t2) ss on true)
full join int4_tbl t3 on t3.f1 = t1.f1 and ss.y = t3.f1
right join int4_tbl t4 on t3.f1 = t4.f1;
QUERY PLAN
---------------------------------------------------------------------------
Hash Right Join
Output: 1
Hash Cond: (t3.f1 = t4.f1)
-> Hash Right Join
Output: t3.f1
Hash Cond: ((t1.f1 = t3.f1) AND ((COALESCE((1), t2.f1)) = t3.f1))
-> Nested Loop
Output: t1.f1, (COALESCE((1), t2.f1))
-> Nested Loop Left Join
Output: t1.f1, (1)
Join Filter: (t1.f1 < (1))
-> Seq Scan on public.int4_tbl t1
Output: t1.f1
-> Materialize
Output: (1)
-> Append
-> Result
Output: 1
-> Result
Output: 2
-> Memoize
Output: (COALESCE((1), t2.f1))
Cache Key: (1)
Cache Mode: binary
-> Seq Scan on public.int4_tbl t2
Output: COALESCE((1), t2.f1)
-> Hash
Output: t3.f1
-> Seq Scan on public.int4_tbl t3
Output: t3.f1
-> Hash
Output: t4.f1
-> Seq Scan on public.int4_tbl t4
Output: t4.f1
(34 rows)
-- Test computation of varnullingrels when translating appendrel Var
begin;
create temp table t_append (a int not null, b int);

View File

@@ -1648,6 +1648,20 @@ select * from int4_tbl left join (
) ss(x) on true
where ss.x is null;
-- Test a join qual referencing a nulled appendrel output only within a PHV
explain (verbose, costs off)
select 1 from int4_tbl t1
left join (select 1 as x union all select 2) s on true
left join lateral (select s.x as y from int4_tbl t2) ss on true
left join int4_tbl t3 on ss.y is null;
-- Same, but the join qual's required_relids don't include the nulling OJ
explain (verbose, costs off)
select 1 from ((int4_tbl t1 left join (select 1 as x union all select 2) s on t1.f1 < s.x)
join lateral (select coalesce(s.x, t2.f1) as y from int4_tbl t2) ss on true)
full join int4_tbl t3 on t3.f1 = t1.f1 and ss.y = t3.f1
right join int4_tbl t4 on t3.f1 = t4.f1;
-- Test computation of varnullingrels when translating appendrel Var
begin;