Fix calculation in brin_minmax_multi_distance_date
authorTomas Vondra
Fri, 27 Oct 2023 15:57:11 +0000 (17:57 +0200)
committerTomas Vondra
Fri, 27 Oct 2023 16:37:59 +0000 (18:37 +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 760f90d71c7a45ef7dcd8fdd045e0e716f5dacb7..08cbb4fd79e602cb54b04db1c98d9822b91f014e 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 804467b494311b7fbd112b0788755a8db09d2433..d6fa4b5f98e4117e6596239a638a00649b09948c 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 d87839506b61e38ab591a4f1aca0299e2e0f8a2d..9613b452af195b89730d263f79833a8502870a10 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;