Fix calculation in brin_minmax_multi_distance_date
authorTomas Vondra <[email protected]>
Fri, 27 Oct 2023 15:57:11 +0000 (17:57 +0200)
committerTomas Vondra <[email protected]>
Fri, 27 Oct 2023 16:28:19 +0000 (18:28 +0200)
When calculating the distance between date values, make sure to subtract
them in the right order, i.e. (larger - smaller).

The distance is used to determine which values to merge, and is expected
to be a positive value. The code unfortunately did the subtraction in
the opposite order, i.e. (smaller - larger), thus producing negative
values and merging values the most distant values first.

The resulting index is correct (i.e. produces correct results), but may
be significantly less efficient. This affects all minmax-multi indexes
on date columns.

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

Reported-by: Ashutosh Bapat
Reviewed-by: Ashutosh Bapat, Dean Rasheed
Backpatch-through: 14
Discussion: https://postgr.es/m/eef0ea8c-4aaa-8d0d-027f-58b1f35dd170@enterprisedb.com

src/backend/access/brin/brin_minmax_multi.c
src/test/regress/expected/brin_multi.out
src/test/regress/sql/brin_multi.sql

index 1a7a80d20e63acce679e3e04dbcf740a30bfbcb4..3bac8c98a00f15a4e4b9e410c72e0243afc66929 100644 (file)
@@ -2075,13 +2075,18 @@ brin_minmax_multi_distance_uuid(PG_FUNCTION_ARGS)
 Datum
 brin_minmax_multi_distance_date(PG_FUNCTION_ARGS)
 {
+   float8      delta = 0;
    DateADT     dateVal1 = PG_GETARG_DATEADT(0);
    DateADT     dateVal2 = PG_GETARG_DATEADT(1);
 
    if (DATE_NOT_FINITE(dateVal1) || DATE_NOT_FINITE(dateVal2))
        PG_RETURN_FLOAT8(0);
 
-   PG_RETURN_FLOAT8(dateVal1 - dateVal2);
+   delta = (float8) dateVal2 - (float8) dateVal1;
+
+   Assert(delta >= 0);
+
+   PG_RETURN_FLOAT8(delta);
 }
 
 /*
index b6a2ef195d8b8cfb2d6d93e594a81188e9896022..ce10147afeeda0417200258be066cb2504a1208f 100644 (file)
@@ -479,5 +479,25 @@ SELECT '294276-12-01 00:00:01'::timestamptz + (i || ' seconds')::interval
   FROM generate_series(1,30) s(i);
 CREATE INDEX ON brin_timestamp_test USING brin (a timestamptz_minmax_multi_ops) WITH (pages_per_range=1);
 DROP TABLE brin_timestamp_test;
+-- test overflows during CREATE INDEX with extreme date values
+CREATE TABLE brin_date_test(a DATE);
+-- insert values close to date minimum
+INSERT INTO brin_date_test SELECT '4713-01-01 BC'::date + i FROM generate_series(1, 30) s(i);
+-- insert values close to date minimum
+INSERT INTO brin_date_test SELECT '5874897-12-01'::date + i FROM generate_series(1, 30) s(i);
+CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_per_range=1);
+SET enable_seqscan = off;
+-- make sure the ranges were built correctly and 2023-01-01 eliminates all
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date;
+                               QUERY PLAN                                
+-------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_date_test (actual rows=0 loops=1)
+   Recheck Cond: (a = '2023-01-01'::date)
+   ->  Bitmap Index Scan on brin_date_test_a_idx (actual rows=0 loops=1)
+         Index Cond: (a = '2023-01-01'::date)
+(4 rows)
+
+DROP TABLE brin_date_test;
 RESET enable_seqscan;
 RESET datestyle;
index 95710511ca93e593b17f5220f3345cb491f72cb1..b0dac67f5b045de4d36f143ef4191399a66b150c 100644 (file)
@@ -440,5 +440,23 @@ SELECT '294276-12-01 00:00:01'::timestamptz + (i || ' seconds')::interval
 CREATE INDEX ON brin_timestamp_test USING brin (a timestamptz_minmax_multi_ops) WITH (pages_per_range=1);
 DROP TABLE brin_timestamp_test;
 
+-- test overflows during CREATE INDEX with extreme date values
+CREATE TABLE brin_date_test(a DATE);
+
+-- insert values close to date minimum
+INSERT INTO brin_date_test SELECT '4713-01-01 BC'::date + i FROM generate_series(1, 30) s(i);
+
+-- insert values close to date minimum
+INSERT INTO brin_date_test SELECT '5874897-12-01'::date + i FROM generate_series(1, 30) s(i);
+
+CREATE INDEX ON brin_date_test USING brin (a date_minmax_multi_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan = off;
+
+-- make sure the ranges were built correctly and 2023-01-01 eliminates all
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_date_test WHERE a = '2023-01-01'::date;
+
+DROP TABLE brin_date_test;
 RESET enable_seqscan;
 RESET datestyle;