Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Fix minmax-multi distance for extreme interval values
authorTomas Vondra <tomas.vondra@postgresql.org>
Fri, 27 Oct 2023 15:57:44 +0000 (17:57 +0200)
committerTomas Vondra <tomas.vondra@postgresql.org>
Fri, 27 Oct 2023 16:38:05 +0000 (18:38 +0200)
When calculating distance for interval values, the code mostly mimicked
interval_mi, i.e. it built a new interval value for the difference.
That however does not work for sufficiently distant interval values,
when the difference overflows the interval range.

Instead, we can calculate the distance directly, without constructing
the intermediate (and unnecessary) interval value.

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

Reported-by: Dean Rasheed
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 b135be44afb2e7ede9a265956704ca7972778064..9e29a08b121ee3ce41f63611261ed931865f454c 100644 (file)
@@ -2154,45 +2154,20 @@ brin_minmax_multi_distance_interval(PG_FUNCTION_ARGS)
 
    Interval   *ia = PG_GETARG_INTERVAL_P(0);
    Interval   *ib = PG_GETARG_INTERVAL_P(1);
-   Interval   *result;
 
    int64       dayfraction;
    int64       days;
 
-   result = (Interval *) palloc(sizeof(Interval));
-
-   result->month = ib->month - ia->month;
-   /* overflow check copied from int4mi */
-   if (!SAMESIGN(ib->month, ia->month) &&
-       !SAMESIGN(result->month, ib->month))
-       ereport(ERROR,
-               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                errmsg("interval out of range")));
-
-   result->day = ib->day - ia->day;
-   if (!SAMESIGN(ib->day, ia->day) &&
-       !SAMESIGN(result->day, ib->day))
-       ereport(ERROR,
-               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                errmsg("interval out of range")));
-
-   result->time = ib->time - ia->time;
-   if (!SAMESIGN(ib->time, ia->time) &&
-       !SAMESIGN(result->time, ib->time))
-       ereport(ERROR,
-               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                errmsg("interval out of range")));
-
    /*
     * Delta is (fractional) number of days between the intervals. Assume
     * months have 30 days for consistency with interval_cmp_internal. We
     * don't need to be exact, in the worst case we'll build a bit less
     * efficient ranges. But we should not contradict interval_cmp.
     */
-   dayfraction = result->time % USECS_PER_DAY;
-   days = result->time / USECS_PER_DAY;
-   days += result->month * INT64CONST(30);
-   days += result->day;
+   dayfraction = (ib->time % USECS_PER_DAY) - (ia->time % USECS_PER_DAY);
+   days = (ib->time / USECS_PER_DAY) - (ia->time / USECS_PER_DAY);
+   days += (int64) ib->day - (int64) ia->day;
+   days += ((int64) ib->month - (int64) ia->month) * INT64CONST(30);
 
    /* convert to double precision */
    delta = (double) days + dayfraction / (double) USECS_PER_DAY;
index acd6bf455ef2c7d68f5549a0d8e33b53a17e939c..e0b5890ff871a0d3b8843acd37b3583592dee93a 100644 (file)
@@ -558,3 +558,32 @@ SELECT * FROM brin_date_test WHERE a = '1900-01-01'::date;
 DROP TABLE brin_date_test;
 RESET enable_seqscan;
 RESET datestyle;
+-- test handling of overflow for interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series(-178000000, -177999980) s(i);
+INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series( 177999980,  178000000) s(i);
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+SET enable_seqscan = off;
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+   Recheck Cond: (a = '@ 30 years ago'::interval)
+   ->  Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+         Index Cond: (a = '@ 30 years ago'::interval)
+(4 rows)
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+                                 QUERY PLAN                                  
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+   Recheck Cond: (a = '@ 30 years'::interval)
+   ->  Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+         Index Cond: (a = '@ 30 years'::interval)
+(4 rows)
+
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+RESET datestyle;
index d635d9bf6cfe5ae1e7d9ea91ac98de8da0fcc91e..16670814d0f6b2da9967c214afc681af58cf10e4 100644 (file)
@@ -499,3 +499,24 @@ SELECT * FROM brin_date_test WHERE a = '1900-01-01'::date;
 DROP TABLE brin_date_test;
 RESET enable_seqscan;
 RESET datestyle;
+
+-- test handling of overflow for interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+
+INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series(-178000000, -177999980) s(i);
+
+INSERT INTO brin_interval_test SELECT (i || ' years')::interval FROM generate_series( 177999980,  178000000) s(i);
+
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan = off;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+RESET datestyle;