| From: | Tender Wang <tndrwang(at)gmail(dot)com> |
|---|---|
| To: | PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org> |
| Cc: | Alexander Korotkov <aekorotkov(at)gmail(dot)com> |
| Subject: | Wrong results from self-join elimination with different restriction operators |
| Date: | 2026-10-12 06:03:50 |
| Message-ID: | CAHewXNmwYrp0DTuisCyfXXjVa_irUdDFApUzLVDg3J4MAR6gEg@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi,
After finding the SJE bug involving inheritance scans and ONLY, I
asked an LLM to generate fuzz tests to look for further issues in
self-join elimination.
This uncovered another wrong-result case involving restriction clauses
with different equality operators.
How to reproduce:
...
postgres=# CREATE FUNCTION text_ci_eq(text, text) RETURNS boolean
LANGUAGE plpgsql IMMUTABLE STRICT PARALLEL SAFE
AS $$
BEGIN
RETURN lower($1) = lower($2);
END $$;
CREATE FUNCTION text_ci_cmp(text, text) RETURNS int
LANGUAGE sql IMMUTABLE STRICT PARALLEL SAFE
AS $$SELECT CASE WHEN lower($1) < lower($2) THEN -1
WHEN lower($1) > lower($2) THEN 1 ELSE 0 END$$;
CREATE OPERATOR === (LEFTARG=text, RIGHTARG=text, PROCEDURE=text_ci_eq,
COMMUTATOR='===', RESTRICT=eqsel, JOIN=eqjoinsel, MERGES);
CREATE OPERATOR FAMILY ci_text_fam USING btree;
CREATE OPERATOR CLASS ci_text_ops FOR TYPE text USING btree FAMILY
ci_text_fam AS
OPERATOR 1 <, OPERATOR 2 <=, OPERATOR 3 ===, OPERATOR 4 >=, OPERATOR 5 >,
FUNCTION 1 text_ci_cmp;
CREATE TABLE ci_t(id int, a text, b int);
CREATE UNIQUE INDEX ci_t_a ON ci_t(a); -- deterministic (standard)
unique index
INSERT INTO ci_t VALUES (1,'a',1),(2,'A',1),(3,'b',2);
ANALYZE ci_t;
SET enable_self_join_elimination = on;
SELECT p1.id, p1.a, p2.id, p2.a
FROM ci_t p1 JOIN ci_t p2 ON p1.b = p2.b
WHERE p1.a === 'a' AND p2.a = 'a'
ORDER BY 1, 3;
id | a | id | a
----+---+----+---
1 | a | 1 | a
(1 row)
SET enable_self_join_elimination = off;
SELECT p1.id, p1.a, p2.id, p2.a
FROM ci_t p1 JOIN ci_t p2 ON p1.b = p2.b
WHERE p1.a === 'a' AND p2.a = 'a'
ORDER BY 1, 3;
id | a | id | a
----+---+----+---
1 | a | 1 | a
2 | A | 1 | a
(2 rows)
...
SJE incorrectly considers the two restrictions equivalent and
eliminates the join.
The unique index and p2.a = 'a' establish uniqueness on the kept side.
match_unique_clauses() then checks for a corresponding restriction on
the removed side, but compares only the operands, ignoring the
operator and input collation.
It therefore treats p1.a === 'a' as matching p2.a = 'a', even though
they select different rows.
The attached patch requires both the operator and input collation to
match, in addition to the existing operand checks.
It includes a regression test for the different-operator case and
verifies that identical restrictions still allow self-join
elimination.
The full reproducer, including the custom operator definitions, is in
the regression test.
--
Thanks,
Tender Wang
| Attachment | Content-Type | Size |
|---|---|---|
| 0001-Fix-restriction-matching-in-self-join-elimination.patch | text/plain | 10.3 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Jakub Wartak | 2026-10-12 06:08:47 | Re: pg_*_advice: tsv load failure, etc. |
| Previous Message | shveta malik | 2026-10-12 06:01:30 | Re: Proposal: Conflict log history table for Logical Replication |