| 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-29 05:41:24 |
| Message-ID: | CAJTYsWWH6Pm2xiq=OM3vnBEBz5=Jp93G5_VUSKKGdkXinbXzZw@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi,
On Tue, 28 Jul 2026 at 12:57, Ayush Tiwari <ayushtiwari(dot)slg01(at)gmail(dot)com>
wrote:
> 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);
>
> Apart from the above, your patch looked good to me.
>
I tried the patch on current master after Tom's SAOP fix. I may be
missing something, but I think there could be an issue when a NOT IN
left-hand expression contains an upper-level Var.
For example:
CREATE TEMP TABLE outer_t (a int);
CREATE TEMP TABLE local_t (a int);
CREATE TEMP TABLE inner_t (x int NOT NULL, y int NOT NULL);
INSERT INTO outer_t VALUES (NULL), (10);
INSERT INTO local_t VALUES (1);
INSERT INTO inner_t VALUES (1, 999);
SELECT o.a,
ARRAY(SELECT l.a
FROM local_t l
WHERE l.a IS NOT NULL
AND (l.a, o.a) NOT IN
(SELECT i.x, i.y FROM inner_t i)) AS vals
FROM outer_t o
ORDER BY o.a NULLS FIRST;
With the patch I get {1} for both outer rows, while without the
anti-join conversion the NULL row produces {}.
It looks like l.a and the upper-level o.a can have the same
varno/varattno, despite having different varlevelsup values. Could the
mbms_is_member() fallback therefore mistake the proof from
l.a IS NOT NULL as applying to o.a?
Would it make sense to restrict that fallback to level-zero Vars, for
example:
if (nonnullable_quals != NIL && IsA(expr, Var) &&
((Var *) expr)->varlevelsup == 0)
With that additional check, I get the expected result.
Regards,
Ayush
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Chao Li | 2026-07-29 06:00:19 | Re: Fix stale comment in parallel_vacuum_main(). |
| Previous Message | Amit Kapila | 2026-07-29 05:28:34 | Re: Fix "unexpected logical decoding status change" error; from concurrent logical decoding activation |