Re: remove_useless_joins vs. bug #19560

From: Haibo Yan <tristan(dot)yim(at)gmail(dot)com>
To: Richard Guo <guofenglinux(at)gmail(dot)com>
Cc: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>, 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-09-18 19:22:37
Message-ID: CABXr29FT0oF24bspc+LGP_8vEVJCFCTrt1fLN5kLm8XHK+301g@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Thu, Sep 17, 2026 at 12:15 AM Richard Guo <guofenglinux(at)gmail(dot)com> wrote:
>
> On Thu, Sep 17, 2026 at 11:52 AM Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us> wrote:
> > The main consumer of varnosyn is ruleutils.c, so I don't like
> > the fact that this test case isn't showing what ruleutils will
> > do with the Var. If we make it be "explain verbose", do we
> > get a display of the problematic Var?
>
> The Var is displayed, but not via varnosyn. ruleutils.c uses varnosyn
> only when dpns->plan is NULL, and EXPLAIN always sets the plan before
> deparsing an expression, so it prints the semantic referent (a.b_id).
>
> But I think it'd still be better to use "explain verbose", so at least
> what ruleutils prints is recorded. I'll make that change before push.
>
> - Richard
>
>
Hi Richard,

I found what looks like a regression from e28cf96e935
("Fix stale copies of PHVs in subqueries"). In particular, I think there is
still a way for an upper-level PHV containing an unprocessed SubLink to get
into a subquery after preprocess_subquery_phvs() has already run.

Here is a reproducer on current master:
create table t (a int, b int);
create table u (c int, d int);

select 1
from (t left join (select (select 1) as x) s on true) j
where (select 1 where j is null) is null;

On current master I get:
ERROR: unrecognized node type: 22

There are similar failures with RIGHT/FULL JOIN. For example, a nested join
shape can instead reach:
ERROR: cannot handle unplanned sub-select

I bisected this to e28cf96e935. The cases I tested work on e28cf96e935^ and
fail on e28cf96e935/current master. I also reproduced the regression on
REL_16_STABLE and REL_18_STABLE; source inspection shows the same relevant
code in the other branches to which this was back-patched.

The problem seems to be an ordering hole in the new PHV preprocessing scheme.

preprocess_subquery_phvs() runs early in subquery_planner(), on the assumption
that copies of upper-level PHVs in lower-level queries will be preprocessed at
their owning level before the lower query sees them. Later, however,
flatten_join_alias_vars() can expand a whole-row join alias inside a lower
Query and introduce a fresh upper-level PHV there.

One way this happens is when a joinaliasvars entry is already a PHV because a
pulled-up subquery was on the nullable side of an outer join. Another is when
add_nullingrels_if_needed() has to wrap the RowExpr produced by whole-row
join-alias expansion in a new PHV.

At that point preprocess_subquery_phvs() has already run, so the newly
introduced PHV can still contain a raw SubLink.

Later process_sublinks_mutator() deliberately leaves an upper-level PHV alone,
consistent with the new assumption that its contents were already processed at
its owning level. build_subplan() makes the same assumption after
e28cf96e935.
The result is that the raw SubLink survives inside the PHV.

With some instrumentation, for a failing case I see the SubPlan argument reach
build_subplan() as a PHV whose phexpr is still T_SubLink, whereas in a
corresponding working case the expression has already become a Param.

I also tried the obvious experiment of restoring the old
IsA(arg, PlaceHolderVar)

case in build_subplan() so that SS_process_sublinks() is run there. That fixes
these reproducers, but it is not a valid fix: it trips
Assert(!IsA(node, SubLink))

in process_sublinks_mutator() on the join.sql tests added by e28cf96e935,
resulting in many regression failures. So I don't think simply
restoring the old
build_subplan behavior is the answer.

This seems to violate the invariant established by e28cf96e935: an
upper-level PHV
encountered by a lower query may be left untouched only if all such copies have
already been preprocessed at their owning level. flatten_join_alias_vars() can
create or push down another such copy after that preprocessing pass.

I think the fix therefore needs to be on the producing/preprocessing side rather
than in build_subplan() -- either ensuring these alias-generated PHVs
are processed
at their owning level before they can be copied down, or arranging another
appropriately scoped PHV preprocessing step after join-alias flattening.

I have not tried to turn either of those into a patch yet, since I
wanted to check
whether you agree with this diagnosis first.

Regards,
Haibo

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Álvaro Rodríguez 2026-09-18 19:59:15 Re: Unexpected reindex when altering column types for partitioned tables
Previous Message Andrey Rachitskiy 2026-09-18 19:01:12 Happy Birthday, Tom Lane!