Incorrect NOT IN results with implicit casts returning NULL after anti-join optimization

Started by BHUVAN CHILUKUTI1 day ago1 messagesbugs
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliessuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t254363
psql -h localhost -U postgres

Built from patchset v1 (message #1), October 24, 2026 at 06:54 AM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t254363_1 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t254363_1 && git checkout t254363_1

Patchset v1 (message #1) is on t254363_1

Jump to latest
#1BHUVAN CHILUKUTI
c.bhuvan@iitg.ac.in

Hi,
I am reporting a query correctness issue in PostgreSQL involving the optimization of NOT IN subqueries into anti-joins when an implicit cast on the inner comparison operand can return SQL NULL for a non-NULL input.
Summary
The issue occurs when PostgreSQL converts a NOT IN (SELECT ...) predicate into an anti-join without preserving the NULL semantics of the inner comparison after an implicit cast.
The investigation attributes the introduction of this behavior to commit 383eb21ebffe9ddd97dca03e529fa363580e7ccf, dated 2026-03-12. I would appreciate confirmation from the PostgreSQL maintainers.
Steps to reproduce
On a fresh test database, using a sufficiently privileged role, run:

CREATE FUNCTION i2t(int) RETURNS text
IMMUTABLE STRICT LANGUAGE sql
AS $$ SELECT CASE WHEN $1 <> 0 THEN format('%s', $1) END $$;

CREATE CAST (int AS text)
WITH FUNCTION i2t(int) AS IMPLICIT;

CREATE TABLE o (t text NOT NULL);
INSERT INTO o VALUES ('1');

CREATE TABLE s (i int NOT NULL);
INSERT INTO s VALUES (0);

SELECT t, t NOT IN (SELECT i FROM s) AS not_in FROM o;

SELECT t FROM o WHERE t NOT IN (SELECT i FROM s);

The function returns SQL NULL for input 0. Therefore, the NOT IN predicate must not evaluate to TRUE for the outer row.
Expected behavior
The scalar expression should evaluate to NULL, and the query with the predicate in the WHERE clause should return zero rows.
Actual behavior
On the affected builds tested, the WHERE query incorrectly returns the outer row containing '1'. The investigation also found that control queries retaining the SubPlan return the expected result.
Tested versions
The issue was reproduced on the tested PostgreSQL 19 RC1 build, the tested REL_19_STABLE tip (015f4c9fc35f91a1ab46bafab05a3686cfe09d46), and the tested master tip (896246ea10cb9d20f69d0442223ce5fa9b365636). The tested PostgreSQL 18.6 build was unaffected.
These results describe the specific builds tested; they are not a claim about every build of these branches.
Proposed fix and testing
I have prepared a proposed patch that prevents the unsafe anti-join conversion when the inner comparison operand is not a direct PARAM_SUBLINK, allowing for a surrounding RelabelType. The patch includes regression tests.
On patched master, the recorded results were:

*
Core regression suite: 239/239 passed.
*
Isolation tests: 134/134 passed.
*
Contrib tests: 191 successful ok lines, with no not ok lines.

The patch has not been fully built and tested on REL_19_STABLE; there, only patch applicability was checked. Additional checks, including check-world/TAP-enabled testing, a non-assert production-style build, pgindent, and dedicated performance measurements, remain outstanding.
The fix may cause some queries involving non-binary casts to use a SubPlan instead of an anti-join. This is a potential performance trade-off that should be considered during review.
Attachments
I am including the proposed source fix and regression tests as a separate patch. I can provide the full investigation report, reproduction scripts, and captured logs if further evidence is useful.
I would appreciate your review of the reported behavior and the proposed fix. Please let me know if additional reproduction details or testing are required.
Regards,
Bhuvan Chilukuti

Attachments:

t254363_1
AUDIT-F1-fix-with-test.diffapplication/octet-stream; name=AUDIT-F1-fix-with-test.diffDownload+164-0
report_blockA.sqlapplication/octet-stream; name=report_blockA.sqlDownload
report_blockB.sqlapplication/octet-stream; name=report_blockB.sqlDownload