GiST multirange index scans can fail to return rows

Started by Peter Geoghegan22 days ago8 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

appliessuccessCI history

You can run a PostgreSQL built from this patch straight from Docker, with no checkout and no build:

docker run --rm -p 5432:5432 ghcr.io/hackorum-dev/postgres-patch:t253275
psql -h localhost -U postgres

Built from patchset v7 (message #7), August 23, 2026 at 04:10 AM.

Every patchset is also pushed to a branch of our PostgreSQL fork, so you can check out the same tree CI built. Without a PostgreSQL checkout:

git clone --branch t253275_7 https://github.com/hackorum-dev/postgres.git

In a checkout you already have, add the fork once:

git remote add hackorum https://github.com/hackorum-dev/postgres.git

then, for this patchset and every later one:

git fetch hackorum t253275_7 && git checkout t253275_7

Patchset v7 (message #7) is on t253275_7

Jump to latest

The attached regression test patch (written by Claude Code) shows that
a GiST index on a multirange column can fail to return rows during
scans that use the contained-by operator.

The multirangetypes test already runs every GiST query twice, once
with a sequential scan and once with the index, and the two must
agree. The added query asks for rows contained in
'{[1,11),[21,31),[41,51)}', which is a value the test table already
contains. The sequential scan returns 501 and the index scan returns
500. The row it loses has a multirange that exactly matches the one
from the query.

The multirange GiST support was added by db6335b5b. This is an issue
on 14 and up.

--
Peter Geoghegan

Attachments:

t253275_1
0001-Add-regression-test-for-multirange-GiST-containment-.patchapplication/octet-stream; name=0001-Add-regression-test-for-multirange-GiST-containment-.patchDownload+14-1
#2Andrey Borodin
amborodin@acm.org
In reply to: Peter Geoghegan (#1)
Re: GiST multirange index scans can fail to return rows

On 1 Aug 2026, at 20:01, Peter Geoghegan <pg@bowt.ie> wrote:

<0001-Add-regression-test-for-multirange-GiST-containment-.patch>

Hi Peter,

I agree with the test and also think this is a bug. The check in
multirange_gist_consistent() is too strict and rejects tuples that
should reach the recheck. I think we should use
range_gist_consistent_int_multirange() for this case. Using the
internal predicate for every strategy would also be correct, but would
weaken filtering and, IMO, cause unnecessary heap rechecks.

PFA v2 patch set. The first patch is your test, and the second is the
fix as I see it.

Thanks!

Best regards, Andrey Borodin.

Attachments:

v2-0001-Add-regression-test-for-multirange-GiST-contained.patchapplication/octet-stream; name=v2-0001-Add-regression-test-for-multirange-GiST-contained.patch; x-unix-mode=0644Download+14-1
v2-0001-Add-regression-test-for-multirange-GiST-contained.patchapplication/octet-stream; name=v2-0001-Add-regression-test-for-multirange-GiST-contained.patch; x-unix-mode=0644Download+14-1
In reply to: Andrey Borodin (#2)
Re: GiST multirange index scans can fail to return rows

Hi Andrey,

I think that you forgot to attach the patch with the actual fix?

--
Peter Geoghegan

On Sun, Aug 2, 2026 at 2:12 AM Andrey Borodin <x4mmm@yandex-team.ru> wrote:

On 1 Aug 2026, at 20:01, Peter Geoghegan <pg@bowt.ie> wrote:

<0001-Add-regression-test-for-multirange-GiST-containment-.patch>

Hi Peter,

I agree with the test and also think this is a bug. The check in
multirange_gist_consistent() is too strict and rejects tuples that
should reach the recheck. I think we should use
range_gist_consistent_int_multirange() for this case. Using the
internal predicate for every strategy would also be correct, but would
weaken filtering and, IMO, cause unnecessary heap rechecks.

PFA v2 patch set. The first patch is your test, and the second is the
fix as I see it.

Thanks!

Best regards, Andrey Borodin.

--
Peter Geoghegan

#4Andrey Borodin
amborodin@acm.org
In reply to: Peter Geoghegan (#3)
Re: GiST multirange index scans can fail to return rows

On 2 Aug 2026, at 12:03, Peter Geoghegan <pg@bowt.ie> wrote:

I think that you forgot to attach the patch with the actual fix?

Oops. Sorry.

Best regards, Andrey Borodin.

Attachments:

t253275_4
v2-0002-Fix-GiST-contained-by-scans-of-multiranges.patchapplication/octet-stream; name=v2-0002-Fix-GiST-contained-by-scans-of-multiranges.patch; x-unix-mode=0644Download+16-3
v2-0001-Add-regression-test-for-multirange-GiST-contained.patchapplication/octet-stream; name=v2-0001-Add-regression-test-for-multirange-GiST-contained.patch; x-unix-mode=0644Download+14-1
#5Alexander Korotkov
aekorotkov@gmail.com
In reply to: Andrey Borodin (#4)
Re: GiST multirange index scans can fail to return rows

Hi Peter,
Hi Andrey,

On Sun, Aug 2, 2026 at 9:55 AM Andrey Borodin <x4mmm@yandex-team.ru> wrote:

On 2 Aug 2026, at 12:03, Peter Geoghegan <pg@bowt.ie> wrote:

I think that you forgot to attach the patch with the actual fix?

Oops. Sorry.

Peter, thank you for catching this. Andrey, thank you for the patch.
Yes, multirange_gist_consistent() re-uses consistent functions from
range opclass. And range_gist_consistent_leaf_multirange() generate
false negatives when applied to union range of multirange. I've
rechecked, and it appears to be the only case among the predicates.

Regarding this contained-by case, I think we can build more precise
test function. If we have multiranges q and k, and union() produces
union range from multirange.

q @> r doesn't mean q @> union(r)
But if q @> r then union(q) @> union(r)

------
Regards,
Alexander Korotkov
Supabase

Attachments:

t253275_5
v3-0001-Fix-GiST-contained-by-scans-of-multiranges.patchapplication/octet-stream; name=v3-0001-Fix-GiST-contained-by-scans-of-multiranges.patchDownload+71-3
#6Kirill Reshke
reshkekirill@gmail.com
In reply to: Alexander Korotkov (#5)
Re: GiST multirange index scans can fail to return rows

On Sun, 2 Aug 2026 at 20:30, Alexander Korotkov <aekorotkov@gmail.com> wrote:

Hi Peter,
Hi Andrey,

On Sun, Aug 2, 2026 at 9:55 AM Andrey Borodin <x4mmm@yandex-team.ru> wrote:

On 2 Aug 2026, at 12:03, Peter Geoghegan <pg@bowt.ie> wrote:

I think that you forgot to attach the patch with the actual fix?

Oops. Sorry.

Peter, thank you for catching this. Andrey, thank you for the patch.
Yes, multirange_gist_consistent() re-uses consistent functions from
range opclass. And range_gist_consistent_leaf_multirange() generate
false negatives when applied to union range of multirange. I've
rechecked, and it appears to be the only case among the predicates.

Regarding this contained-by case, I think we can build more precise
test function. If we have multiranges q and k, and union() produces
union range from multirange.

q @> r doesn't mean q @> union(r)
But if q @> r then union(q) @> union(r)

Hi!
I reviewed v3.

multirange_contains_union_range function comment contains typo at 'give'

'Every multiranges contains an infinite number of empty multiranges,
even an empty one.' also contain typos

Return statement can be :
return range_cmp_bounds(rangetyp, &lower1, &lower2) <= 0 &&
range_cmp_bounds(rangetyp, &upper1, &upper2) >= 0;

But I don't insist. Better for my taste.

--
Best regards,
Kirill Reshke

#7Alexander Korotkov
aekorotkov@gmail.com
In reply to: Kirill Reshke (#6)
Re: GiST multirange index scans can fail to return rows

On Sun, Aug 2, 2026 at 8:04 PM Kirill Reshke <reshkekirill@gmail.com> wrote:

On Sun, 2 Aug 2026 at 20:30, Alexander Korotkov <aekorotkov@gmail.com> wrote:

On Sun, Aug 2, 2026 at 9:55 AM Andrey Borodin <x4mmm@yandex-team.ru> wrote:

On 2 Aug 2026, at 12:03, Peter Geoghegan <pg@bowt.ie> wrote:

I think that you forgot to attach the patch with the actual fix?

Oops. Sorry.

Peter, thank you for catching this. Andrey, thank you for the patch.
Yes, multirange_gist_consistent() re-uses consistent functions from
range opclass. And range_gist_consistent_leaf_multirange() generate
false negatives when applied to union range of multirange. I've
rechecked, and it appears to be the only case among the predicates.

Regarding this contained-by case, I think we can build more precise
test function. If we have multiranges q and k, and union() produces
union range from multirange.

q @> r doesn't mean q @> union(r)
But if q @> r then union(q) @> union(r)

Hi!
I reviewed v3.

multirange_contains_union_range function comment contains typo at 'give'

'Every multiranges contains an infinite number of empty multiranges,
even an empty one.' also contain typos

Thank you, fixed.

Return statement can be :
return range_cmp_bounds(rangetyp, &lower1, &lower2) <= 0 &&
range_cmp_bounds(rangetyp, &upper1, &upper2) >= 0;

But I don't insist. Better for my taste.

Corrected as you proposed.

------
Regards,
Alexander Korotkov
Supabase

Attachments:

t253275_7
v4-0001-Fix-GiST-contained-by-scans-of-multiranges.patchapplication/octet-stream; name=v4-0001-Fix-GiST-contained-by-scans-of-multiranges.patchDownload+68-3
#8Andrey Borodin
amborodin@acm.org
In reply to: Alexander Korotkov (#7)
Re: GiST multirange index scans can fail to return rows

On 2 Aug 2026, at 23:11, Alexander Korotkov <aekorotkov@gmail.com> wrote:

<v4-0001-Fix-GiST-contained-by-scans-of-multiranges.patch>

Hi Alexander,

I read v4 and think that the new predicate is correct.

This comment does not look perfect to me:

Every multirange contains an infinite number of empty multiranges,
even an empty one.

Perhaps just say "The empty multirange is contained by every multirange."

There's typo "multirage" in the commit message :)

Thank you!

Best regards, Andrey Borodin.