| From: | Ayush Tiwari <ayushtiwari(dot)slg01(at)gmail(dot)com> |
|---|---|
| To: | Richard Guo <guofenglinux(at)gmail(dot)com> |
| Cc: | Pg Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org> |
| Subject: | Re: Prove a NOT IN's left-hand expressions non-nullable from quals |
| Date: | 2026-07-28 07:27:08 |
| Message-ID: | CAJTYsWXk0ZfiXSydDDsJLCe1sFtBF9OvEf2t1qH15o8gnybabg@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi,
On Fri, 19 Jun 2026 at 06:43, Richard Guo <guofenglinux(at)gmail(dot)com> wrote:
> 383eb21eb teaches the planner to convert "x NOT IN (SELECT ...)" to an
> anti-join when both sides of the comparison are known to be non-null.
> On the outer side we currently get that only from NOT NULL constraints
> and the outer-join-aware-Var infrastructure.
>
> That misses a case: the left-hand column has no NOT NULL constraint,
> but a qual forces it non-null anyway, as in "x IS NOT NULL AND x NOT
> IN (...)" or "x > 0 AND x NOT IN (...)". We leave those as SubPlan
> filters today, even though x clearly can't be NULL where the NOT IN is
> evaluated.
>
> The attached patch proves the left-hand Var non-null from such a qual.
> pull_up_sublinks_jointree_recurse collects the quals at or below the
> NOT IN's jointree node (only those on rels not below the nullable side
> of an outer join, so they really do filter the rows) and hands them to
> convert_ANY_sublink_to_join, which checks them with
> find_nonnullable_vars.
>
> Quals above the NOT IN's node could help in some cases too, but that's
> a separate extension and I've left it as a follow-up. See the details
> in the draft commit message.
>
Thanks for the patch!
I did see one issue in this here's a statement for repro:
CREATE TEMP TABLE outer_t (x int);
CREATE TEMP TABLE inner_t (y int NOT NULL);
INSERT INTO outer_t VALUES (NULL), (1);
INSERT INTO inner_t VALUES (2);
SELECT x
FROM outer_t
WHERE NOT (x = ANY('{}'::int[]))
AND x NOT IN (SELECT y FROM inner_t);
SQL semantics return only 1. With the patch, the planner
produces a hash anti-join and also returns the NULL row: the
first qual admits it, and the anti-join preserves it, whereas the
original NOT IN evaluates to NULL and filters it out.
This helper weakness also affects the existing inner-query
proof path, but the patch creates a new regression surface.
I've posted a likely fix in [1].
Apart from the above, your patch looked good to me.
Regards,
Ayush
[1] PostgreSQL: [PATCH] SAOP nullability analysis below NOT/BooleanTest
<https://www.postgresql.org/message-id/flat/CAJTYsWV3vqRJmST-gv1NsXEef-zOnjVJpYS910aBaiuMij4nFg(at)mail(dot)gmail(dot)com>
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Tender Wang | 2026-07-28 07:41:01 | Re: Partition pruning can incorrectly exclude a RANGE default partition |
| Previous Message | Peter Smith | 2026-07-28 07:07:23 | PSQL schema "describe" \dn is not escaping quotes |