Re: remove_useless_joins vs. bug #19560

From: Richard Guo <guofenglinux(at)gmail(dot)com>
To: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
Cc: Thom Brown <thom(at)linux(dot)com>, Jacob Brazeal <jacob(dot)brazeal(at)gmail(dot)com>, pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: remove_useless_joins vs. bug #19560
Date: 2026-08-26 05:26:42
Message-ID: CAMbWs4-C4fwZTtY911MfAkW51G2d9TpqoRqmxCiJbjeO0GG1ug@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Mon, Aug 17, 2026 at 8:58 AM Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us> wrote:
> One idea for fixing this is to null out the subquery field of a
> subquery RTE as soon as we've pulled it up, but there is probably
> code that will crash on a null subquery pointer.

It seems to me that the subquery in question isn't a pulled-up one.
pull_up_simple_subquery() already sets rte->subquery to NULL at line
prepjointree.c:1725.

What is in question is the subquery of a rel removed by join removal
itself. Its RTE stays in the rangetable with the query tree intact,
and that tree can contain lateral references to rels that are still
around. Those references are what prevented the referenced rel from
being removed; once the referencing subquery is gone, the next pass
removes the referenced rel, and remove_rels_from_query_tree() then
walks into the dead subquery and finds the stale Var. To be concrete,
consider:

select d.* from d
left join a on d.a = a.id
left join lateral (select count(*) from c where c.id = a.b_id) s
on true;

I think we can just null out the subquery field of a subquery RTE that
gets removed. We've been doing that for pulled-up subqueries for many
years, so it should be safe. I mean something like below.

@@ -155,6 +154,10 @@ remove_useless_outer_joins(PlannerInfo *root)
removed_relids = bms_add_member(removed_relids, innerrelid);
removed_relids = bms_add_member(removed_relids, sjinfo->ojrelid);

+ /* As in pull_up_simple_subquery, discard a no-longer-needed subquery */
+ if (root->simple_rte_array[innerrelid]->rtekind == RTE_SUBQUERY)
+ root->simple_rte_array[innerrelid]->subquery = NULL;
+

While at it, I still don't think we need ChangeVarNodes() here, since
only PHV phrels can still mention a removed relid. I think we can
leverage substitute_phv_relids() to do the job, and thus we don't need
duplicate tree-walking code. The Assert(!IsA(node, AppendRelInfo))
you mentioned is not a problem. We can handle that by a three-line
loop:

+ foreach_node(AppendRelInfo, appinfo, root->append_rel_list)
+ {
+ Assert(appinfo->parent_relid != relid);
+ Assert(appinfo->child_relid != relid);
+ substitute_phv_relids((Node *) appinfo->translated_vars,
+ relid, NULL);
+ }

I agree that there may not be any speed gain, since the walk is the
same. I think the point is that join removal no longer writes -1 into
varno fields.

Attached is a draft patch showing what I mean.

- Richard

Attachment Content-Type Size
nocfbot.Some-updates-to-v5.patch application/octet-stream 8.4 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Chao Li 2026-08-26 05:30:07 Re: tablecmds: fix bug where index rebuild loses replica identity on partitions
Previous Message Laurenz Albe 2026-08-26 05:20:18 Re: Adding a stored generated column without long-lived locks