From b7a872195b4b79ccf407853792e57e9c56cf93ca Mon Sep 17 00:00:00 2001 From: Richard Guo Date: Thu, 17 Sep 2026 11:09:41 +0900 Subject: [PATCH v1] 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. --- src/backend/rewrite/rewriteManip.c | 11 ++++++----- src/test/regress/expected/join.out | 10 ++++++++++ src/test/regress/sql/join.sql | 4 ++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c index 3653f00d383..7011275f8c6 100644 --- a/src/backend/rewrite/rewriteManip.c +++ b/src/backend/rewrite/rewriteManip.c @@ -536,7 +536,9 @@ OffsetVarNodes(Node *node, int offset, int sublevels_up) * 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 @@ -569,11 +571,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 75544fe6aa3..b8c0b2920fb 100644 --- a/src/test/regress/expected/join.out +++ b/src/test/regress/expected/join.out @@ -7727,6 +7727,16 @@ 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 (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 a + SubPlan expr_1 + -> Result +(3 rows) + -- check the case when the placeholder relates to an outer join and its -- inner in the press field but actually uses only the outer side of the join explain (costs off) diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql index fb83a96e939..683a3550df1 100644 --- a/src/test/regress/sql/join.sql +++ b/src/test/regress/sql/join.sql @@ -2866,6 +2866,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 (costs off) +select j.b_id, (select j.b_id) from (a left join b on a.b_id = b.id) j; + -- check the case when the placeholder relates to an outer join and its -- inner in the press field but actually uses only the outer side of the join explain (costs off) -- 2.37.1 (Apple Git-137.1)