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:15:37 +0000 (18:15 +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 8c72a0a03661214cffbdc810ad8217cc01f11848..7e63dda14f426eba9407854d6c4c7c8b2bfced0d 100644 (file)
@@ -2081,13 +2081,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 c18d9764c1c7211821807cd4c9c7e338d0616ae7..465f93f55f14e3799b55b98b665f37b090c716cb 100644 (file)
@@ -836,5 +836,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 847bb5aa016ada1bf6139e056be8627f00bde08b..88ccf0bc6545e178538d5ed06871eb676ef2767b 100644 (file)
@@ -605,5 +605,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;