Two bugs in reconsider_full_join_clause(), handling coalesce() = const clauses

From: Tomas Vondra <tomas(at)vondra(dot)me>
To: PostgreSQL Hackers <pgsql-hackers(at)postgresql(dot)org>
Subject: Two bugs in reconsider_full_join_clause(), handling coalesce() = const clauses
Date: 2026-11-01 19:46:24
Message-ID: 92808d26-1b4a-48fe-82f0-597da3ff471f@vondra.me
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

While working on some improvements to join order search, I cobbled
together a randomized join generator to shake out bugs in my patches.
But it pretty quickly tripped over two long-standing bugs in master.
This is the first one, with a proposed fix.

AFAIK the fix is correct (and not very complex). But it's an area of
code I don't know particularly well (or have dealt before). So it really
needs some review. CC Tom, who wrote some of this code.

The gist of the bug is relatively simple - consider this query

SELECT * FROM (t4 AS r2 JOIN t10 AS r3 ON (r2.id = r3.a1))
FULL JOIN t8 AS r4 ON (r4.t4_id = r2.id)
WHERE coalesce(r4.t4_id, r2.id) = 21501;

On a debug build trips this assert in process_implied_equality():

Assert(bms_is_subset(relids, qualscope));

AFAIK the reason is relatively simple - reconsider_full_join_clause is
not careful enough about which input the expressions come from. It seems
to assume that for clause (leftvar = rightvar) the leftvar comes from
left input, and rightvar comes from the right one. Which will be true
for many cases, but does not seem to be guaranteed. I may be missing
something important, of course. Maybe it's an assumption only clauses
with COALESCE break?

Anyway, reconsider_full_join_clause does it's business, creating the
clauses in both inputs, adjusting the EC etc. In assert build this
crashes in process_implied_equality(). In regular builds the query may
produce incorrect results - I think that can happen only with
variable-free clauses, but I'm not sure. (I'm attaching a reproducer for
this.)

But I think there's a second issue, when handling clauses that don't
relate an expression of one input to an expression of the other.
Consider this query (in repro-wrong-result-2.sql):

SELECT *
FROM (t4 r2 CROSS JOIN t10 r3)
FULL JOIN t8 r4 ON r2.id = r3.a1 AND r2.id = r4.t4_id
WHERE coalesce(r2.id, r3.a1) = 1;

AFAIK this pushes both the r2.id=1 and r3.a1=1 to the left side of the
join, which does not seem correct?

The releases before 16 (which invented the JoinDomain stuff) seem to
have only the second issue. And it does not crash on assert.

The commit messages are somewhat longer. It took me a while to
comprehend what's happening (and that it's not due to my patch), so
maybe it's too detailed.

regards

--
Tomas Vondra

Attachment Content-Type Size
v14-0001-Fix-constant-propagation-through-FULL-JOIN-claus.patch text/x-patch 9.3 KB
vmaster-0001-Fix-constant-propagation-through-FULL-JOIN-c.patch text/x-patch 13.5 KB
repro.sql application/sql 247 bytes
repro-14.sql application/sql 875 bytes
repro-wrong-result-1.sql application/sql 632 bytes
repro-wrong-result-2.sql application/sql 567 bytes

Browse pgsql-hackers by date

  From Date Subject
Next Message Bryan Green 2026-11-01 20:03:51 Re: [PATCH] Fix socket handle inheritance on Windows
Previous Message Andrey Borodin 2026-11-01 18:21:22 Re: 007_pre_auth.pl fails if injection_points_wakeup hits unintended process