From 1839ddc72adf78925aa2ff2f873c14caf1bb0fe3 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <tomas@vondra.me>
Date: Fri, 30 Oct 2026 14:12:04 +0100
Subject: [PATCH vmaster 1/4] Handle numeric overflow in
 numeric_minmax_multi_ops

When calculating the distance between numeric values, the code could
overflow in two ways. A subtraction of two finite values may still
overflow, or the result may not fit into float8.

The distances don't need to be perfectly accurate, although it may make
the index less efficient. And for most minmax-multi opclasses it works
like that, but for numeric the functions may error-out. That prevents
summarization, either when creating an idex or later during vacuum.

Fix by using numeric_sub_safe() and numeric_float8_no_overflow(), and
returning infinity when either of those fails. The older releases need
to use numeric_sub_opt_error(), but the principle is the same.

While at it, free the detoasted copies of arguments, and the calculated
numeric distance. For small values it does not matter, but for extreme
numeric values it may make a difference.

Backpatch to 14, where minmax-multi indexes were introduced.

Author: Kirill Reshke <reshkekirill@gmail.com>
Backpatch-through: 14
Discussion: https://postgr.es/m/CALdSSPgBvXz-VSF0p-5k%3D_uFfN38cWrERmm_AC4K67N6MWQhmg%40mail.gmail.com
---
 src/backend/access/brin/brin_minmax_multi.c | 32 +++++++++++++++++----
 src/test/regress/expected/brin_multi.out    | 10 +++++++
 src/test/regress/sql/brin_multi.sql         | 14 +++++++++
 3 files changed, 50 insertions(+), 6 deletions(-)

diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c
index a0494e90242..8bb5bd1b1e4 100644
--- a/src/backend/access/brin/brin_minmax_multi.c
+++ b/src/backend/access/brin/brin_minmax_multi.c
@@ -69,6 +69,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_amop.h"
 #include "catalog/pg_type.h"
+#include "nodes/miscnodes.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/date.h"
@@ -77,6 +78,7 @@
 #include "utils/inet.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/numeric.h"
 #include "utils/pg_lsn.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
@@ -2018,19 +2020,37 @@ brin_minmax_multi_distance_tid(PG_FUNCTION_ARGS)
 Datum
 brin_minmax_multi_distance_numeric(PG_FUNCTION_ARGS)
 {
-	Datum		d;
-	Datum		a1 = PG_GETARG_DATUM(0);
-	Datum		a2 = PG_GETARG_DATUM(1);
+	Numeric		d;
+	Numeric		a1 = PG_GETARG_NUMERIC(0);
+	Numeric		a2 = PG_GETARG_NUMERIC(1);
+	float8		distance;
+	ErrorSaveContext escontext = {T_ErrorSaveContext};
 
 	/*
 	 * We know the values are range boundaries, but the range may be collapsed
 	 * (i.e. single points), with equal values.
 	 */
-	Assert(DatumGetBool(DirectFunctionCall2(numeric_le, a1, a2)));
+	Assert(DatumGetBool(DirectFunctionCall2(numeric_le, NumericGetDatum(a1),
+											NumericGetDatum(a2))));
+
+	/*
+	 * Calculate the float8 distance in an overflow-safe way. Return infinity
+	 * instead of error-ing out.
+	 */
+	d = numeric_sub_safe(a2, a1, (Node *) &escontext);
+	if (SOFT_ERROR_OCCURRED(&escontext))
+		distance = get_float8_infinity();
+	else
+	{
+		distance = DatumGetFloat8(DirectFunctionCall1(numeric_float8_no_overflow,
+													  NumericGetDatum(d)));
+		pfree(d);
+	}
 
-	d = DirectFunctionCall2(numeric_sub, a2, a1);	/* a2 - a1 */
+	PG_FREE_IF_COPY(a1, 0);
+	PG_FREE_IF_COPY(a2, 1);
 
-	PG_RETURN_DATUM(DirectFunctionCall1(numeric_float8, d));
+	PG_RETURN_FLOAT8(distance);
 }
 
 /*
diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
index cb5b5e53e86..300abc2e1ab 100644
--- a/src/test/regress/expected/brin_multi.out
+++ b/src/test/regress/expected/brin_multi.out
@@ -981,3 +981,13 @@ SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
 DROP TABLE brin_interval_test;
 RESET enable_seqscan;
 RESET datestyle;
+-- test handling of overflow in numeric values
+CREATE TABLE brin_overflow_test (n numeric);
+-- overflow in subtraction
+INSERT INTO brin_overflow_test VALUES (-repeat('9', 131072)::numeric), (repeat('9', 131072)::numeric);
+CREATE INDEX ON brin_overflow_test USING brin (n numeric_minmax_multi_ops);
+TRUNCATE brin_overflow_test;
+-- overflow in conversion to float8
+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;
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
index dda9fb73e0c..2cbdff00047 100644
--- a/src/test/regress/sql/brin_multi.sql
+++ b/src/test/regress/sql/brin_multi.sql
@@ -704,3 +704,17 @@ SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
 DROP TABLE brin_interval_test;
 RESET enable_seqscan;
 RESET datestyle;
+
+-- test handling of overflow in numeric values
+CREATE TABLE brin_overflow_test (n numeric);
+
+-- overflow in subtraction
+INSERT INTO brin_overflow_test VALUES (-repeat('9', 131072)::numeric), (repeat('9', 131072)::numeric);
+CREATE INDEX ON brin_overflow_test USING brin (n numeric_minmax_multi_ops);
+TRUNCATE brin_overflow_test;
+
+-- overflow in conversion to float8
+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;
-- 
2.55.0

