Re: BUG #19460: FULL JOIN rewriting issue on empty queries
Tom Lane <tgl@sss.pgh.pa.us>
From: Tom Lane <tgl@sss.pgh.pa.us>
To: Richard Guo <guofenglinux@gmail.com>
Cc: francois.jehl@pigment.com, pgsql-bugs@lists.postgresql.org,
Robert Haas <robertmhaas@gmail.com>
Date: 2026-04-20T19:01:20Z
Lists: pgsql-bugs
Richard Guo <guofenglinux@gmail.com> writes: > On Mon, Apr 20, 2026 at 11:32 AM Tom Lane <tgl@sss.pgh.pa.us> wrote: >> Hmm. The bug also goes away if "t" doesn't have a unique/pkey >> constraint, and I find that easy to understand: we can't apply outer >> join removal unless rel_supports_distinctness/rel_is_distinct_for >> succeed, so that this buggy code in remove_rel_from_restrictinfo >> is not reached. But that logic doesn't consider WHERE constraints >> AFAICS. So I think there is some other code path involved. > Hmm, relation_has_unique_index_for does consider the lower "WHERE t.id > = ..." clause, as that clause is a restriction clause for "t", and > relation_has_unique_index_for automatically adds any usable > restriction clauses for the rel. Ah, I finally got it through my head that there are two distinct proof paths by which we might reach the conclusion that the lower left join is removable. I had been thinking that we were proving that from the combination of the "sub.id = empty_source.id" clause with the unique index on t.id. But we're not, in the query as-submitted, because we pull up the NULL::UUID constant and const-fold that clause to NULL. Instead, it's the lowest "WHERE t.id = ..." that is combined with the unique index to make the proof. So without an equality test there, we don't think the inner side is unique and don't do join removal, thus dodging the bug. The WHERE FALSE bit masks this omission because it causes us to reduce the outer join to a dummy relation anyway, later on. But if you take that out, you can see that join removal is not being performed. I thought it was worth memorializing these two variants in separate test queries, so I did that. The variant without the lowest WHERE has a non-null constant in the LOJ's left-hand side, so it's able to make the removal proof from the "sub.id = empty_source.id" clause. Pushed at cfcd57111 et al. Thanks again for the report! regards, tom lane
Commits
-
Fix relid-set clobber during join removal.
- f0ac6d494b56 19 (unreleased) landed
- 798dabe83887 16.14 landed
- 53cb4ec1ded7 17.10 landed
-
Clean up all relid fields of RestrictInfos during join removal.
- d509be4ace90 16.14 landed
- cfcd5711160a 19 (unreleased) landed
- 766d40286600 17.10 landed
- 16fb94605c8f 18.4 landed