[PATCH] SAOP nullability analysis below NOT/BooleanTest

From: Ayush Tiwari <ayushtiwari(dot)slg01(at)gmail(dot)com>
To: pgsql-hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Cc: Richard Guo <guofenglinux(at)gmail(dot)com>
Subject: [PATCH] SAOP nullability analysis below NOT/BooleanTest
Date: 2026-07-27 13:09:20
Message-ID: CAJTYsWV3vqRJmST-gv1NsXEef-zOnjVJpYS910aBaiuMij4nFg@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

While looking at the recent work around converting NOT IN sublinks to
anti-joins, I came across what looks like a possible issue in the
ScalarArrayOpExpr handling in find_nonnullable_rels() and
find_nonnullable_vars().

Both walkers call is_strict_saop(expr, true), even when top_level is
false because the walk has descended below NOT, a BooleanTest, or a
strict function. My understanding is that falseOK = true is suitable
at the top level of a qual, where either FALSE or NULL rejects the row.
Below one of those nodes, however, don't we need the SAOP itself to
return NULL for NULL input?

An empty ANY array seems to expose the distinction:

CREATE TEMP TABLE saop_outer (x int NOT NULL);
CREATE TEMP TABLE saop_inner (y int);
INSERT INTO saop_outer VALUES (1);
INSERT INTO saop_inner VALUES (NULL);

SELECT * FROM saop_outer
WHERE x NOT IN (
SELECT y FROM saop_inner
WHERE NOT (y = ANY ('{}'::int[]))
);

I would expect no rows, since the subquery returns NULL and
1 NOT IN (NULL) is unknown. On current master I get x = 1, with the
query converted to a hash anti-join.

A related outer-join case is:

SELECT saop_outer.*
FROM saop_outer LEFT JOIN saop_inner ON x = y
WHERE (y = ANY ('{}'::int[])) IS FALSE;

I would expect x = 1 from the null-extended row. Current master returns
no rows after reducing the LEFT JOIN to an inner join.

Would it be appropriate to pass top_level as falseOK in both walkers?
That is the small change in the attached patch:

is_strict_saop(expr, top_level)

As far as I can tell, this preserves the existing top-level inference.
Below top level, it only gives up the proof when the array cannot be
shown to be nonempty. I noticed that 72153c0582 added the falseOK
handling to avoid a planning regression for foo <> ALL(array), so I
may be missing a case where using top_level here loses a valid and
important optimization.

Thoughts?

Regards,
Ayush

Attachment Content-Type Size
0001-Fix-SAOP-nullability-analysis-below-top-level.patch application/octet-stream 3.9 KB

Browse pgsql-hackers by date

  From Date Subject
Next Message Aleksander Alekseev 2026-07-27 13:19:24 Re: [PATCH] Cover get_json_table_plan() with tests
Previous Message Jonathan Gonzalez V. 2026-07-27 13:06:34 Re: [PATCH] Cover get_json_table_plan() with tests