Re: BUG #19553: Wrong results from nested LEFT JOINs over an empty subquery (regression since v16)

From: "Matheus Alcantara" <matheusssilv97(at)gmail(dot)com>
To: <leis(at)in(dot)tum(dot)de>, <pgsql-bugs(at)lists(dot)postgresql(dot)org>
Subject: Re: BUG #19553: Wrong results from nested LEFT JOINs over an empty subquery (regression since v16)
Date: 2026-07-16 22:37:27
Message-ID: DK0CT1H1TW0G.3AJAOZTZJVTLL@gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

On Thu Jul 16, 2026 at 11:12 AM -03, PG Bug reporting form wrote:
> The following bug has been logged on the website:
>
> Bug reference: 19553
> Logged by: Viktor Leis
> Email address: leis(at)in(dot)tum(dot)de
> PostgreSQL version: 19beta2
> Operating system: Ubuntu 26.04
> Description:
>
> Hi,
>
> The following self-contained query returns wrong results on every release
> since v16:
>
> select * from (values (1),(2)) v(x)
> left join (select q from (select 7 as q from (select where false) ss1)
> ss2
> left join (select 8 as z) ss3 on true) ss4 on true;
>
> x | q
> ---+---
> 1 | 7
> 2 | 7
> (2 rows)
>
> The right-hand side of the top left join is provably empty (ss1 produces no
> rows, and the inner left join preserves that), so the correct result
> null-extends both rows:
>
> x | q
> ---+---
> 1 |
> 2 |
> (2 rows)
>
> EXPLAIN (VERBOSE) on affected versions shows that the entire RHS has been
> optimized away and the constant is emitted unconditionally:
>
> Values Scan on "*VALUES*"
> Output: "*VALUES*".column1, 7
>
> I bisected the regression to commit 3af87736bf5 ("Fix another cause of
> 'wrong varnullingrels' planner failures" by Tom Lane).
>

Hi, thank you for the report and for the script reproducer.

According to my findings, the issue seems to be in
remove_useless_results_recurse(), specifically in find_dependent_phvs().
When a LEFT JOIN's RHS reduces to an RTE_RESULT, that function is used
to check whether any PlaceHolderVar still depends on the RTE_RESULT
being considered for removal; if not, the join is discarded along with
the RTE_RESULT. The check was testing whether a PHV's phrels was exactly
equal to {varno}, but that's too narrow. In your example, the constant
"7" gets wrapped in a PlaceHolderVar whose phrels ends up as the union
of both RTE_RESULTs in the inner commuted left join (the ss1 "WHERE
false" result and the ss3 "SELECT 8" result), because the pulled-up
subquery's output needs to be nullable by the outer join. Since phrels
was {ss1, ss3} rather than exactly {ss1}, find_dependent_phvs() wrongly
concluded the outer join had no remaining dependents and dropped it,
taking the join's null-extension semantics with it. That's why the
constant got emitted unconditionally instead of being nulled out for the
"no match" case.

Attached patch seems to fix find_dependent_phvs() to test membership
rather than exact equality, since that's what actually indicates the
PHV's value depends on that relation. find_dependent_phvs_in_jointree(),
used for a different purpose so keeps the exact-match behavior it had,
as that's it seems to still correct for its use case. I added a field to
the shared context struct to keep both behaviors sharing one walker.

Verified against your original repro and a version using tables instead
of VALUES/constants (make it more easier for me to debug):

create table t (id int, name text);
insert into t values (1, 'Alice'), (2, 'Bob');

select c.id, c.name, promo.discount
from t c
left join (
select discount
from (select 0.20 as discount from (select where false) no_matching_promo) rates
left join (select true as eligible) elig on true
) promo on true;

--
Matheus Alcantara
EDB: https://www.enterprisedb.com

Attachment Content-Type Size
0001-Fix-wrong-results-from-remove_useless_result_rtes-wi.patch text/plain 6.8 KB

In response to

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message Peter Geoghegan 2026-07-16 22:57:37 Re: Bug in heap_get_root_tuples
Previous Message Masahiko Sawada 2026-07-16 19:23:08 Re: uuidv7 improperly accepts dates before 1970-01-01