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

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
This commit is contained in:
Richard Guo
2026-09-19 22:49:07 +09:00
parent 8ea97ee348
commit 9df23331f0
3 changed files with 22 additions and 5 deletions

View File

@@ -549,7 +549,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
@@ -582,11 +584,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;
}

View File

@@ -6266,6 +6266,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

View File

@@ -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);