| From: | Venu K <venu2805k(at)gmail(dot)com> |
|---|---|
| To: | venu2805k(at)gmail(dot)com, pgsql-bugs(at)lists(dot)postgresql(dot)org |
| Subject: | Re: BUG #19793: Range containment with empty/unbounded constant range returns true/false for NULL element |
| Date: | 2026-10-23 18:57:47 |
| Message-ID: | CAEYijEiDSREL+7jYyApypivAr=KQ=jhi8Bo8WU5sMnHY-4OJ8g@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-bugs |
Attached is a patch for master that only reduces the expression to a
constant when expr_is_nonnullable() proves the element non-null, plus
regression tests. make check passes.
The second patch is for REL_18_STABLE and REL_17_STABLE, where
expr_is_nonnullable() is static and Var-only; it only treats a
non-null Const element as known non-null. make check passes on both.
One side effect: "current_date <@ daterange 'empty'" and
"current_date <@ daterange(NULL, NULL)" are no longer folded to
false/true, because expr_is_nonnullable() doesn't know CURRENT_DATE is
never NULL. Teaching it about the date/time SQLValueFunctions would
keep those folded, if that seems worthwhile.
On Sat, Oct 24, 2026 at 12:24 AM PG Bug reporting form <
noreply(at)postgresql(dot)org> wrote:
> The following bug has been logged on the website:
>
> Bug reference: 19793
> Logged by: Venu Kummari
> Email address: venu2805k(at)gmail(dot)com
> PostgreSQL version: 18.6
> Operating system: Ubuntu 24.04, x86_64
> Description:
>
> Since PostgreSQL 17, "elem <@ range" and "range @> elem" return true or
> false instead of NULL when the element is NULL and the range is a
> constant that is empty or unbounded on both sides. This gives wrong
> query results, including in WHERE clauses:
>
> CREATE TEMP TABLE bookings(id int, booked_on date);
> INSERT INTO bookings VALUES (1, '2026-01-05'), (2, NULL);
> SELECT id FROM bookings WHERE booked_on <@ daterange(NULL, NULL);
>
> returns ids 1 and 2; only 1 is expected (PostgreSQL 16 returns only 1).
> The same happens with PREPARE ... WHERE booked_on <@ daterange($1, $2)
> executed with NULL, NULL.
>
> More directly:
>
> SELECT c, '(,)'::int4range @> c, NOT ('empty'::int4range @> c)
> FROM (VALUES (NULL::int), (1)) v(c);
>
> returns t and t for the NULL row; both should be NULL, since
> range_contains_elem and elem_contained_by_range are strict. With a
> sub-select around the range, which blocks the planner simplification,
> the result is NULL. EXPLAIN VERBOSE shows the expression replaced by
> the constant true.
>
> Reproduced on 17.11, 18.6 and master (126976a). Not reproducible on 16.
>
> Cause: find_simplified_clause() in src/backend/utils/adt/rangetypes.c,
> added in commit 075df6b20, returns makeBoolConst(false) for an empty
> constant range and makeBoolConst(true) for an unbounded one without
> considering that the element may be NULL.
>
> I have a patch for master and a back-branch version for 17/18, with
> regression tests; I will send them as a reply to this report.
>
> Found with a metamorphic (equivalent-query) fuzzer.
>
>
>
| Attachment | Content-Type | Size |
|---|---|---|
| v1-0001-range-containment-null-REL_17_18.patch | application/octet-stream | 5.9 KB |
| v1-0001-range-containment-null-master.patch | application/octet-stream | 6.2 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Venu K | 2026-10-23 19:14:21 | Re: BUG #19768: JSON_TABLE: row/text comparisons in context item or PASSING give wrong results or spurious collation |
| Previous Message | Tom Lane | 2026-10-23 16:09:58 | Re: BUG #19784: "no relation entry for relid 1" when costing = ANY (<array expr over max(col)>) in a MinMaxAggPath |