postgres.git / summary / log / commit / refs

commit    bd003dca09221998f801da15400e340800cbb0b6
Author:   Richard Guo <rguo@postgresql.org>
Date:     Sat Sep 19 13:49:07 2026 +0000

    Fix join removal when Vars reference the removed join via its alias
    
    When a join has an alias, the parser marks every Var referenced
    through that alias with the join's RT index as its syntactic referent
    (varnosyn), even though varno points at the underlying base relation.
    Since commit 2ebf25e7d, join removal strips the removed relids from
    the query tree using ChangeVarNodes() with INVALID_VAR, which asserts
    that the relid doesn't appear in any field identifying a single
    relation.  A varnosyn that names the removed join violates that, so a
    query like
    
        SELECT j.x FROM (t1 s(x) LEFT JOIN t2 ON s.x = t2.a) j;
    
    with t2.a unique fails an assertion.  In non-assert builds, varnosyn
    is silently set to INVALID_VAR.
    
    Such syntactic references are still valid after the removal: the join
    is only taken out of the jointree, and its RTE remains in the
    rangetable through to the finished plan.  So, when deleting a relid,
    leave varnosyn unchanged, as join removal did before 2ebf25e7d.
    
    Back-patch to v16, as with commit 2ebf25e7d.
    
    Author: Richard Guo <guofenglinux@gmail.com>
    Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
    Discussion: https://postgr.es/m/CAMbWs484SQSdzWTgZYKvwtuC6J4m7acw0LfBjp3aWPvav7gC_w@mail.gmail.com
    Backpatch-through: 16


src/backend/rewrite/rewriteManip.c | 11 ++++++----- src/test/regress/expected/join.out | 12 ++++++++++++ src/test/regress/sql/join.sql | 4 ++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index 62785f98243..10f5622c050 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -553,7 +553,9 @@ offset_relid_set(Relids relids, int offset) * Also, new_index can be INVALID_VAR to indicate that we are deleting the * given relid from the tree. In this case we expect to find rt_index only * in Relids fields (varnullingrels, phnullingrels, phrels), never in any - * field that identifies a single relation. + * field that identifies a single relation. The exception is varnosyn, + * which may name an aliased join being removed; the join's RTE remains in + * the rangetable, so we leave such syntactic references unchanged. * * NOTE: although this has the form of a walker, we cheat and modify the * nodes in-place. The given expression tree should have been copied @@ -586,11 +588,10 @@ ChangeVarNodes_walker(Node *node, ChangeVarNodes_context *context) var->varnullingrels = adjust_relid_set(var->varnullingrels, context->rt_index, context->new_index); - if (var->varnosyn == context->rt_index) - { - Assert(context->new_index != INVALID_VAR); + /* when deleting, leave syntactic refs to a removed join alone */ + if (var->varnosyn == context->rt_index && + context->new_index != INVALID_VAR) var->varnosyn = context->new_index; - } } return false; } diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out index a5e0efa3313..63d0e99b512 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -6265,6 +6265,18 @@ select c.id, ss.a from c -> Seq Scan on c (7 rows) +-- check join removal when Vars reference the removed join via its alias +explain (verbose, costs off) +select j.b_id, (select j.b_id) from (a left join b on a.b_id = b.id) j; + QUERY PLAN +------------------------------- + Seq Scan on pg_temp.a + Output: a.b_id, (SubPlan 1) + SubPlan 1 + -> Result + Output: a.b_id +(5 rows) + CREATE TEMP TABLE parted_b (id int PRIMARY KEY) partition by range(id); CREATE TEMP TABLE parted_b1 partition of parted_b for values from (0) to (10); -- test join removals on a partitioned table diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index 0a1476a3348..5848979630d 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -2287,6 +2287,10 @@ select c.id, ss.a from c left join (select d.a from onerow, d left join b on d.a = b.id) ss on c.id = ss.a; +-- check join removal when Vars reference the removed join via its alias +explain (verbose, costs off) +select j.b_id, (select j.b_id) from (a left join b on a.b_id = b.id) j; + CREATE TEMP TABLE parted_b (id int PRIMARY KEY) partition by range(id); CREATE TEMP TABLE parted_b1 partition of parted_b for values from (0) to (10); [parent: 51f9e30f38c5]