Hi Radim,

Thanks for testing this -- that's exactly the case this thread is
about, and it's good to have independent confirmation from your
harness.

Just to untangle which fix is which: 6e5d5680b555 (David Rowley,
"Fix incorrect multi-column RANGE partition pruning") is a separate
bug in the loop bounds of get_matching_range_bounds().  It doesn't
touch the MINVALUE/MAXVALUE trailing-key blocks at the end of that
function, which is where your case comes from -- so your query is
still wrong on master even with it applied, as you found.  The
mc2ap/mc2bp cases are Rowley's, and they're orthogonal to this one.

The fix for your exact query is the patch already on this thread (the
two-line change to test the *first* key of the bound instead of the
last key of the lookup value, plus Tender's comment tweaks in v2-0001).
I re-verified on current master (a9eebaf3735, i.e. with 6e5d5680b555
already in): your query returns 0 unpatched and 1 with the patch, and
Rowley's mc2ap cases still prune correctly.  The patch's code hunk
applies cleanly over 6e5d5680b555; only the regression-test hunks
conflict because both add to partition_prune.sql, so I'll post a
rebased v3 that resolves that.

I'd gladly take you up on the offer to run a broader set of partitioned
queries against the rebased patch.

On Tue, Sep 1, 2026 at 8:10 PM Radim Marek <[email protected]> wrote:
>
> Hey,
>
> I'm not sure the patch is a full fix, even with 6e5d5680b555 applied.
>
>     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
>     SELECT count(*) FROM t WHERE a = 32 AND b >= -7;  -- 0, expected 1
>
>     SET enable_partition_pruning = off;
>     SELECT count(*) FROM t WHERE a = 32 AND b >= -7;  -- 1
>
> The two cases added by the commit (mc2ap, mc2bp) behave as described, so this 
> looks like a case it doesn't cover rather than a problem with it.
>
> I only tested master, so I do not know how far back this might go. Found 
> while testing my planner-regression harness against recent commits.
>
> Happy to test a patch against a broader set of partitioned queries if that 
> would help.
>
> Regards,
>
> Radim
>
>
>
> On Mon, 10 Aug 2026 at 07:34, Tender Wang <[email protected]> wrote:
>>
>> Hi Ewan,
>>
>> Ewan Young <[email protected]> 于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



-- 
Regards,
Ewan Young


Reply via email to