Re: RANGE partition pruning can still exclude the default partition

From: Tender Wang <tndrwang(at)gmail(dot)com>
To: Ewan Young <kdbase(dot)hack(at)gmail(dot)com>
Cc: PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>, dgrowleyml(at)gmail(dot)com, Jacob Brazeal <jacob(dot)brazeal(at)gmail(dot)com>, amitlan(at)postgresql(dot)org
Subject: Re: RANGE partition pruning can still exclude the default partition
Date: 2026-08-10 05:34:33
Message-ID: CAHewXN=5THoLU_gCK61ua1aAW5F2WEGx39UdnWHx8JyFXDuo3g@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi Ewan,

Ewan Young <kdbase(dot)hack(at)gmail(dot)com> 于2026年8月6日周四 18:47写道:
>
> Hi,
>
> Issue still happens on master as of 9b917a93116, i.e. with 709dfd27f14
> already applied. It is a different instance of the same defect, in another
> branch of the same function, and it silently returns wrong results.
>
> CREATE TABLE t (a int, b int) PARTITION BY RANGE (a, b);
> CREATE TABLE t1 PARTITION OF t FOR VALUES FROM (13, 0) TO (19, MAXVALUE);
> CREATE TABLE td PARTITION OF t DEFAULT;
> INSERT INTO t VALUES (32, 5);
>
> SELECT count(*) FROM t WHERE a = 32; -- 1, correct
> SELECT count(*) FROM t WHERE a = 32 AND b >= -7; -- 0, should be 1

Nice catch.

>
> Adding a qual that is true for every matching row makes the row disappear;
> the plan is a One-Time Filter: false. It takes a multi-column range key, a
> bound unbounded in a trailing key but finite in the first key, and a query
> constraining all key columns - with nvalues < partnatts,
> get_matching_range_bounds() sets scan_default up front, which masks it.
> DELETE and UPDATE silently skip rows as well, run-time pruning is affected,
> and FROM (13, MINVALUE) is the mirror image. A layout that hits this in
> the wild is RANGE (tenant_id, ts) with FROM (N, MINVALUE) TO (N, MAXVALUE)
> per tenant plus a default partition.
>
> At the end of get_matching_range_bounds() two adjustments drop the extreme
> bound offset when no partition covers the key space beyond it:
>
> int lastkey = nvalues - 1;
>
> if (boundinfo->kind[maxoff - 1][lastkey] == PARTITION_RANGE_DATUM_MAXVALUE)
> maxoff--;
>
> Only a bound unbounded in its *first* key has no key space beyond it.
> TO (19, MAXVALUE) merely means "unbounded within a = 19"; above it is real
> key space owned by the default partition. Dropping the offset also drops
> the last signal that the default has to be scanned: since 489247b0e615
> get_matching_partitions() derives that from a returned offset whose
> partindices[] entry is -1, and scan_default is not set on this path.

Yes, I came to the same conclusion.

> 0001 tests the first key of the bound instead of the last key of the lookup
> value; the code change is two lines. I did not delete the blocks the way
> 709dfd27f14 did, because when the first key really is unbounded there is
> nothing beyond it and removing them would scan the default partition for no
> reason. Tests cover a trailing MAXVALUE, the MINVALUE mirror, a genuinely
> unbounded first key, and the no-default case; the last two are unchanged by
> the patch, on purpose.

The fix WFM. I tweaked the comments a little, as in the attached v2-0001 patch.
Others look good to me.

--
Thanks,
Tender Wang

Attachment Content-Type Size
v2-0001-Don-t-prune-the-default-partition-for-bounds-unbo.patch application/octet-stream 11.7 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Previous Message Andrey Borodin 2026-08-10 05:31:50 Re: [PATCH] Fix vacuum_delay_point happening inside lock