From 9d713d2e99017f8e3c17d3f41eaf08c4daa9aeec Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Fri, 30 Oct 2026 16:27:05 +0100
Subject: [PATCH vmaster 3/4] Fix BRIN minmax-multi distance calculation for
 OID

The oid_minmax_multi_ops opclass simply calculated the distance using
brin_minmax_multi_distance_int4. That however interprets the values as
int32, so OIDs above PG_INT32_MAX were treated as negative values, and
ranges crossing the boundary ended with a negative distance.

With assert-enabled guild, this tripped the (a1 <= a2) check. Without
asserts, the index was less efficient.

Fix by introducing brin_minmax_multi_distance_oid, correctly treating
the OID values as unsigned.

This required a catalog change (and catversion bump), so it can't be
backpatched. It'd be possible to adjust brin_minmax_multi_distance_int4
to handle the (a1 > a2) case differently. But that'd lose the assert,
and it does not seem worth it (indexes on OIDs don't seem very common).

Author: Tomas Vondra <tomas@vondra.me>
Discussion: https://postgr.es/m/CALdSSPgBvXz-VSF0p-5k%3D_uFfN38cWrERmm_AC4K67N6MWQhmg%40mail.gmail.com
---
 src/backend/access/brin/brin_minmax_multi.c | 21 +++++++++++++++++++++
 src/include/catalog/pg_amproc.dat           |  2 +-
 src/include/catalog/pg_proc.dat             |  4 ++++
 src/test/regress/expected/brin_multi.out    |  5 +++++
 src/test/regress/sql/brin_multi.sql         |  8 ++++++++
 5 files changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c
index 08e68cb48f4..89b78d726e9 100644
--- a/src/backend/access/brin/brin_minmax_multi.c
+++ b/src/backend/access/brin/brin_minmax_multi.c
@@ -1982,6 +1982,27 @@ brin_minmax_multi_distance_int8(PG_FUNCTION_ARGS)
 	PG_RETURN_FLOAT8((double) a2 - (double) a1);
 }
 
+/*
+ * Compute the distance between two oid values (plain subtraction).
+ *
+ * OIDs are unsigned, so we can't use the int4 function - values above
+ * PG_INT32_MAX would be interpreted as negative, breaking the ordering.
+ */
+Datum
+brin_minmax_multi_distance_oid(PG_FUNCTION_ARGS)
+{
+	Oid			a1 = PG_GETARG_OID(0);
+	Oid			a2 = PG_GETARG_OID(1);
+
+	/*
+	 * We know the values are range boundaries, but the range may be collapsed
+	 * (i.e. single points), with equal values.
+	 */
+	Assert(a1 <= a2);
+
+	PG_RETURN_FLOAT8((double) a2 - (double) a1);
+}
+
 /*
  * Compute the distance between two tid values (by mapping them to float8 and
  * then subtracting them).
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 27fb8cd2d35..4b83b6eccd8 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -1129,7 +1129,7 @@
   amproc => 'brin_minmax_multi_options' },
 { amprocfamily => 'brin/oid_minmax_multi_ops', amproclefttype => 'oid',
   amprocrighttype => 'oid', amprocnum => '11',
-  amproc => 'brin_minmax_multi_distance_int4' },
+  amproc => 'brin_minmax_multi_distance_oid' },
 
 # bloom oid
 { amprocfamily => 'brin/oid_bloom_ops', amproclefttype => 'oid',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 1f21a8eb6d4..e9c0e3937cb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9157,6 +9157,10 @@
   proname => 'brin_minmax_multi_distance_numeric', prorettype => 'float8',
   proargtypes => 'internal internal',
   prosrc => 'brin_minmax_multi_distance_numeric' },
+{ oid => '9126', descr => 'BRIN multi minmax oid distance',
+  proname => 'brin_minmax_multi_distance_oid', prorettype => 'float8',
+  proargtypes => 'internal internal',
+  prosrc => 'brin_minmax_multi_distance_oid' },
 { oid => '4627', descr => 'BRIN multi minmax tid distance',
   proname => 'brin_minmax_multi_distance_tid', prorettype => 'float8',
   proargtypes => 'internal internal',
diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
index 300abc2e1ab..ff9f0c39774 100644
--- a/src/test/regress/expected/brin_multi.out
+++ b/src/test/regress/expected/brin_multi.out
@@ -991,3 +991,8 @@ TRUNCATE brin_overflow_test;
 INSERT INTO brin_overflow_test VALUES (1), (1e500);
 CREATE INDEX ON brin_overflow_test USING brin (n numeric_minmax_multi_ops);
 DROP TABLE brin_overflow_test;
+-- OID values should not be treated as negative int4 values
+CREATE TABLE brin_oid_overflow_test (o oid);
+INSERT INTO brin_oid_overflow_test VALUES (1), (3000000000);
+CREATE INDEX ON brin_oid_overflow_test USING brin (o oid_minmax_multi_ops);
+DROP TABLE brin_oid_overflow_test;
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
index 2cbdff00047..445ba1d718a 100644
--- a/src/test/regress/sql/brin_multi.sql
+++ b/src/test/regress/sql/brin_multi.sql
@@ -718,3 +718,11 @@ INSERT INTO brin_overflow_test VALUES (1), (1e500);
 CREATE INDEX ON brin_overflow_test USING brin (n numeric_minmax_multi_ops);
 
 DROP TABLE brin_overflow_test;
+
+-- OID values should not be treated as negative int4 values
+CREATE TABLE brin_oid_overflow_test (o oid);
+
+INSERT INTO brin_oid_overflow_test VALUES (1), (3000000000);
+CREATE INDEX ON brin_oid_overflow_test USING brin (o oid_minmax_multi_ops);
+
+DROP TABLE brin_oid_overflow_test;
-- 
2.55.0

