Support +/- infinity in the interval data type.
authorDean Rasheed <[email protected]>
Tue, 14 Nov 2023 10:58:49 +0000 (10:58 +0000)
committerDean Rasheed <[email protected]>
Tue, 14 Nov 2023 10:58:49 +0000 (10:58 +0000)
This adds support for infinity to the interval data type, using the
same input/output representation as the other date/time data types
that support infinity. This allows various arithmetic operations on
infinite dates, timestamps and intervals.

The new values are represented by setting all fields of the interval
to INT32/64_MIN for -infinity, and INT32/64_MAX for +infinity. This
ensures that they compare as less/greater than all other interval
values, without the need for any special-case comparison code.

Note that, since those 2 values were formerly accepted as legal finite
intervals, pg_upgrade and dump/restore from an old database will turn
them from finite to infinite intervals. That seems OK, since those
exact values should be extremely rare in practice, and they are
outside the documented range supported by the interval type, which
gives us a certain amount of leeway.

Bump catalog version.

Joseph Koshakow, Jian He, and Ashutosh Bapat, reviewed by me.

Discussion: https://postgr.es/m/CAAvxfHea4%2BsPybKK7agDYOMo9N-Z3J6ZXf3BOM79pFsFNcRjwA%40mail.gmail.com

25 files changed:
contrib/btree_gin/btree_gin.c
doc/src/sgml/datatype.sgml
doc/src/sgml/func.sgml
src/backend/utils/adt/date.c
src/backend/utils/adt/datetime.c
src/backend/utils/adt/formatting.c
src/backend/utils/adt/selfuncs.c
src/backend/utils/adt/timestamp.c
src/include/catalog/catversion.h
src/include/catalog/pg_aggregate.dat
src/include/catalog/pg_proc.dat
src/include/datatype/timestamp.h
src/test/regress/expected/brin_multi.out
src/test/regress/expected/horology.out
src/test/regress/expected/interval.out
src/test/regress/expected/timestamp.out
src/test/regress/expected/timestamptz.out
src/test/regress/expected/window.out
src/test/regress/sql/brin_multi.sql
src/test/regress/sql/horology.sql
src/test/regress/sql/interval.sql
src/test/regress/sql/timestamp.sql
src/test/regress/sql/timestamptz.sql
src/test/regress/sql/window.sql
src/tools/pgindent/typedefs.list

index b09bb8df964c295e7e932a75289b9cf6bafc0fcd..5e27906f802b0da0812a6aa1afd6eacf0eaee9ed 100644 (file)
@@ -306,9 +306,8 @@ leftmostvalue_interval(void)
 {
        Interval   *v = palloc(sizeof(Interval));
 
-       v->time = PG_INT64_MIN;
-       v->day = PG_INT32_MIN;
-       v->month = PG_INT32_MIN;
+       INTERVAL_NOBEGIN(v);
+
        return IntervalPGetDatum(v);
 }
 
index 55340b00ade36d3e9fd33614a96175c3ea753482..e4a7b07033dd31bdab1faf11947c2c53e7976aac 100644 (file)
@@ -2328,12 +2328,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
          </row>
          <row>
           <entry><literal>infinity</literal></entry>
-          <entry><type>date</type>, <type>timestamp</type></entry>
+          <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
           <entry>later than all other time stamps</entry>
          </row>
          <row>
           <entry><literal>-infinity</literal></entry>
-          <entry><type>date</type>, <type>timestamp</type></entry>
+          <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
           <entry>earlier than all other time stamps</entry>
          </row>
          <row>
index d76df6239fe631e13edb8eb0f9403db5f348798f..93f068edcf99a96f663cac994ed33b42ff8d49bd 100644 (file)
@@ -9565,7 +9565,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
          <returnvalue>boolean</returnvalue>
         </para>
         <para>
-         Test for finite interval (currently always true)
+         Test for finite interval (not +/-infinity)
         </para>
         <para>
          <literal>isfinite(interval '4 hours')</literal>
@@ -10462,7 +10462,11 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
      When the input value is +/-Infinity, <function>extract</function> returns
      +/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
      <literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
-     <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+     <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+     for <type>timestamp</type> inputs; <literal>epoch</literal>, <literal>hour</literal>,
+     <literal>day</literal>, <literal>year</literal>, <literal>decade</literal>,
+     <literal>century</literal>, and <literal>millennium</literal> for
+     <type>interval</type> inputs).
      For other fields, NULL is returned.  <productname>PostgreSQL</productname>
      versions before 9.6 returned zero for all cases of infinite input.
     </para>
index 544e1d32bfc084c8abc6617843eb34ae5bea40ba..13745a0adc9575a4a6e717b3f7e57ab06caedb5d 100644 (file)
@@ -24,6 +24,7 @@
 #include "access/xact.h"
 #include "catalog/pg_type.h"
 #include "common/hashfn.h"
+#include "common/int.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "nodes/supportnodes.h"
@@ -2013,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
        Interval   *span = PG_GETARG_INTERVAL_P(0);
        TimeADT         result;
 
+       if (INTERVAL_NOT_FINITE(span))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("cannot convert infinite interval to time")));
+
        result = span->time % USECS_PER_DAY;
        if (result < 0)
                result += USECS_PER_DAY;
@@ -2049,6 +2055,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
        Interval   *span = PG_GETARG_INTERVAL_P(1);
        TimeADT         result;
 
+       if (INTERVAL_NOT_FINITE(span))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("cannot add infinite interval to time")));
+
        result = time + span->time;
        result -= result / USECS_PER_DAY * USECS_PER_DAY;
        if (result < INT64CONST(0))
@@ -2067,6 +2078,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
        Interval   *span = PG_GETARG_INTERVAL_P(1);
        TimeADT         result;
 
+       if (INTERVAL_NOT_FINITE(span))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("cannot subtract infinite interval from time")));
+
        result = time - span->time;
        result -= result / USECS_PER_DAY * USECS_PER_DAY;
        if (result < INT64CONST(0))
@@ -2090,7 +2106,8 @@ in_range_time_interval(PG_FUNCTION_ARGS)
 
        /*
         * Like time_pl_interval/time_mi_interval, we disregard the month and day
-        * fields of the offset.  So our test for negative should too.
+        * fields of the offset.  So our test for negative should too.  This also
+        * catches -infinity, so we only need worry about +infinity below.
         */
        if (offset->time < 0)
                ereport(ERROR,
@@ -2100,13 +2117,14 @@ in_range_time_interval(PG_FUNCTION_ARGS)
        /*
         * We can't use time_pl_interval/time_mi_interval here, because their
         * wraparound behavior would give wrong (or at least undesirable) answers.
-        * Fortunately the equivalent non-wrapping behavior is trivial, especially
-        * since we don't worry about integer overflow.
+        * Fortunately the equivalent non-wrapping behavior is trivial, except
+        * that adding an infinite (or very large) interval might cause integer
+        * overflow.  Subtraction cannot overflow here.
         */
        if (sub)
                sum = base - offset->time;
-       else
-               sum = base + offset->time;
+       else if (pg_add_s64_overflow(base, offset->time, &sum))
+               PG_RETURN_BOOL(less);
 
        if (less)
                PG_RETURN_BOOL(val <= sum);
@@ -2581,6 +2599,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
        Interval   *span = PG_GETARG_INTERVAL_P(1);
        TimeTzADT  *result;
 
+       if (INTERVAL_NOT_FINITE(span))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("cannot add infinite interval to time")));
+
        result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
 
        result->time = time->time + span->time;
@@ -2603,6 +2626,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
        Interval   *span = PG_GETARG_INTERVAL_P(1);
        TimeTzADT  *result;
 
+       if (INTERVAL_NOT_FINITE(span))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("cannot subtract infinite interval from time")));
+
        result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
 
        result->time = time->time - span->time;
@@ -2630,7 +2658,8 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
 
        /*
         * Like timetz_pl_interval/timetz_mi_interval, we disregard the month and
-        * day fields of the offset.  So our test for negative should too.
+        * day fields of the offset.  So our test for negative should too. This
+        * also catches -infinity, so we only need worry about +infinity below.
         */
        if (offset->time < 0)
                ereport(ERROR,
@@ -2640,13 +2669,14 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
        /*
         * We can't use timetz_pl_interval/timetz_mi_interval here, because their
         * wraparound behavior would give wrong (or at least undesirable) answers.
-        * Fortunately the equivalent non-wrapping behavior is trivial, especially
-        * since we don't worry about integer overflow.
+        * Fortunately the equivalent non-wrapping behavior is trivial, except
+        * that adding an infinite (or very large) interval might cause integer
+        * overflow.  Subtraction cannot overflow here.
         */
        if (sub)
                sum.time = base->time - offset->time;
-       else
-               sum.time = base->time + offset->time;
+       else if (pg_add_s64_overflow(base->time, offset->time, &sum.time))
+               PG_RETURN_BOOL(less);
        sum.zone = base->zone;
 
        if (less)
@@ -3096,6 +3126,13 @@ timetz_izone(PG_FUNCTION_ARGS)
        TimeTzADT  *result;
        int                     tz;
 
+       if (INTERVAL_NOT_FINITE(zone))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("interval time zone \"%s\" must be finite",
+                                               DatumGetCString(DirectFunctionCall1(interval_out,
+                                                                                                                       PointerGetDatum(zone))))));
+
        if (zone->month != 0 || zone->day != 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
index 267dfd37b2e8b9bc63797c69b9ca2e45e6bfde61..fca9a2a6e936a7e2d6c683159fc975a2d4c7592d 100644 (file)
@@ -3271,6 +3271,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
  *
  * Allow ISO-style time span, with implicit units on number of days
  *     preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
  */
 int
 DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3574,6 +3577,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                                if (parsing_unit_val)
                                        return DTERR_BAD_FORMAT;
                                type = DecodeUnits(i, field[i], &uval);
+                               if (type == UNKNOWN_FIELD)
+                                       type = DecodeSpecial(i, field[i], &uval);
                                if (type == IGNORE_DTF)
                                        continue;
 
@@ -3597,6 +3602,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
                                                type = uval;
                                                break;
 
+                                       case RESERV:
+                                               tmask = (DTK_DATE_M | DTK_TIME_M);
+
+                                               /*
+                                                * Only reserved words corresponding to infinite
+                                                * intervals are accepted.
+                                                */
+                                               if (uval != DTK_LATE && uval != DTK_EARLY)
+                                                       return DTERR_BAD_FORMAT;
+
+                                               /*
+                                                * Infinity cannot be followed by anything else. We
+                                                * could allow "ago" to reverse the sign of infinity
+                                                * but using signed infinity is more intuitive.
+                                                */
+                                               if (i != nf - 1)
+                                                       return DTERR_BAD_FORMAT;
+
+                                               *dtype = uval;
+                                               break;
+
                                        default:
                                                return DTERR_BAD_FORMAT;
                                }
index 8131091f794a8d22513a45a2eb58ad9095e8eae8..d176723d952c5b04408ed63bd8059afa27761221 100644 (file)
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
        struct pg_itm tt,
                           *itm = &tt;
 
-       if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+       if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
                PG_RETURN_NULL();
 
        ZERO_tmtc(&tmtc);
index c4fcd0076eac0507d5c5d16aa8823e6cc2aa6edb..4ea5415f204fd74c038f898b68b9281d1a20e1b1 100644 (file)
@@ -4802,6 +4802,10 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
                                 * Convert the month part of Interval to days using assumed
                                 * average month length of 365.25/12.0 days.  Not too
                                 * accurate, but plenty good enough for our purposes.
+                                *
+                                * This also works for infinite intervals, which just have all
+                                * fields set to INT_MIN/INT_MAX, and so will produce a result
+                                * smaller/larger than any finite interval.
                                 */
                                return interval->time + interval->day * (double) USECS_PER_DAY +
                                        interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
index 647b97aca6978c2b9f5a7a148c1ce495adb82cd3..45abb79e766c3047f8a9bf481508e4798449103e 100644 (file)
@@ -72,6 +72,21 @@ typedef struct
        pg_tz      *attimezone;
 } generate_series_timestamptz_fctx;
 
+/*
+ * The transition datatype for interval aggregates is declared as internal.
+ * It's a pointer to an IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+       int64           N;                              /* count of finite intervals processed */
+       Interval        sumX;                   /* sum of finite intervals processed */
+       /* These counts are *not* included in N!  Use IA_TOTAL_COUNT() as needed */
+       int64           pInfcount;              /* count of +infinity intervals */
+       int64           nInfcount;              /* count of -infinity intervals */
+} IntervalAggState;
+
+#define IA_TOTAL_COUNT(ia) \
+       ((ia)->N + (ia)->pInfcount + (ia)->nInfcount)
 
 static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
 static Timestamp dt2local(Timestamp dt, int timezone);
@@ -80,6 +95,8 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
 static TimestampTz timestamp2timestamptz(Timestamp timestamp);
 static Timestamp timestamptz2timestamp(TimestampTz timestamp);
 
+static void EncodeSpecialInterval(const Interval *interval, char *str);
+static void interval_um_internal(const Interval *interval, Interval *result);
 
 /* common code for timestamptypmodin and timestamptztypmodin */
 static int32
@@ -941,6 +958,14 @@ interval_in(PG_FUNCTION_ARGS)
                                                 errmsg("interval out of range")));
                        break;
 
+               case DTK_LATE:
+                       INTERVAL_NOEND(result);
+                       break;
+
+               case DTK_EARLY:
+                       INTERVAL_NOBEGIN(result);
+                       break;
+
                default:
                        elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
                                 dtype, str);
@@ -963,8 +988,13 @@ interval_out(PG_FUNCTION_ARGS)
                           *itm = &tt;
        char            buf[MAXDATELEN + 1];
 
-       interval2itm(*span, itm);
-       EncodeInterval(itm, IntervalStyle, buf);
+       if (INTERVAL_NOT_FINITE(span))
+               EncodeSpecialInterval(span, buf);
+       else
+       {
+               interval2itm(*span, itm);
+               EncodeInterval(itm, IntervalStyle, buf);
+       }
 
        result = pstrdup(buf);
        PG_RETURN_CSTRING(result);
@@ -1350,6 +1380,10 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod,
                INT64CONST(0)
        };
 
+       /* Typmod has no effect on infinite intervals */
+       if (INTERVAL_NOT_FINITE(interval))
+               return true;
+
        /*
         * Unspecified range and precision? Then not necessary to adjust. Setting
         * typmod to -1 is the convention for all data types.
@@ -1536,6 +1570,10 @@ make_interval(PG_FUNCTION_ARGS)
                pg_add_s64_overflow(result->time, (int64) secs, &result->time))
                goto out_of_range;
 
+       /* make sure that the result is finite */
+       if (INTERVAL_NOT_FINITE(result))
+               goto out_of_range;
+
        PG_RETURN_INTERVAL_P(result);
 
 out_of_range:
@@ -1560,6 +1598,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
                elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
 }
 
+static void
+EncodeSpecialInterval(const Interval *interval, char *str)
+{
+       if (INTERVAL_IS_NOBEGIN(interval))
+               strcpy(str, EARLY);
+       else if (INTERVAL_IS_NOEND(interval))
+               strcpy(str, LATE);
+       else                                            /* shouldn't happen */
+               elog(ERROR, "invalid argument for EncodeSpecialInterval");
+}
+
 Datum
 now(PG_FUNCTION_ARGS)
 {
@@ -2015,6 +2064,9 @@ interval2itm(Interval span, struct pg_itm *itm)
 /* itm2interval()
  * Convert a pg_itm structure to an Interval.
  * Returns 0 if OK, -1 on overflow.
+ *
+ * This is for use in computations expected to produce finite results.  Any
+ * inputs that lead to infinite results are treated as overflows.
  */
 int
 itm2interval(struct pg_itm *itm, Interval *span)
@@ -2038,12 +2090,21 @@ itm2interval(struct pg_itm *itm, Interval *span)
        if (pg_add_s64_overflow(span->time, itm->tm_usec,
                                                        &span->time))
                return -1;
+       if (INTERVAL_NOT_FINITE(span))
+               return -1;
        return 0;
 }
 
 /* itmin2interval()
  * Convert a pg_itm_in structure to an Interval.
  * Returns 0 if OK, -1 on overflow.
+ *
+ * Note: if the result is infinite, it is not treated as an overflow.  This
+ * avoids any dump/reload hazards from pre-17 databases that do not support
+ * infinite intervals, but do allow finite intervals with all fields set to
+ * INT_MIN/INT_MAX (outside the documented range).  Such intervals will be
+ * silently converted to +/-infinity.  This may not be ideal, but seems
+ * preferable to failure, and ought to be pretty unlikely in practice.
  */
 int
 itmin2interval(struct pg_itm_in *itm_in, Interval *span)
@@ -2088,7 +2149,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
 Datum
 interval_finite(PG_FUNCTION_ARGS)
 {
-       PG_RETURN_BOOL(true);
+       Interval   *interval = PG_GETARG_INTERVAL_P(0);
+
+       PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
 }
 
 
@@ -2442,6 +2505,15 @@ interval_cmp_internal(const Interval *interval1, const Interval *interval2)
        return int128_compare(span1, span2);
 }
 
+static int
+interval_sign(const Interval *interval)
+{
+       INT128          span = interval_cmp_value(interval);
+       INT128          zero = int64_to_int128(0);
+
+       return int128_compare(span, zero);
+}
+
 Datum
 interval_eq(PG_FUNCTION_ARGS)
 {
@@ -2714,10 +2786,39 @@ timestamp_mi(PG_FUNCTION_ARGS)
 
        result = (Interval *) palloc(sizeof(Interval));
 
+       /*
+        * Handle infinities.
+        *
+        * We treat anything that amounts to "infinity - infinity" as an error,
+        * since the interval type has nothing equivalent to NaN.
+        */
        if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
-               ereport(ERROR,
-                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                                errmsg("cannot subtract infinite timestamps")));
+       {
+               if (TIMESTAMP_IS_NOBEGIN(dt1))
+               {
+                       if (TIMESTAMP_IS_NOBEGIN(dt2))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                                errmsg("interval out of range")));
+                       else
+                               INTERVAL_NOBEGIN(result);
+               }
+               else if (TIMESTAMP_IS_NOEND(dt1))
+               {
+                       if (TIMESTAMP_IS_NOEND(dt2))
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                                errmsg("interval out of range")));
+                       else
+                               INTERVAL_NOEND(result);
+               }
+               else if (TIMESTAMP_IS_NOBEGIN(dt2))
+                       INTERVAL_NOEND(result);
+               else                                    /* TIMESTAMP_IS_NOEND(dt2) */
+                       INTERVAL_NOBEGIN(result);
+
+               PG_RETURN_INTERVAL_P(result);
+       }
 
        if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
                ereport(ERROR,
@@ -2783,6 +2884,10 @@ interval_justify_interval(PG_FUNCTION_ARGS)
        result->day = span->day;
        result->time = span->time;
 
+       /* do nothing for infinite intervals */
+       if (INTERVAL_NOT_FINITE(result))
+               PG_RETURN_INTERVAL_P(result);
+
        /* pre-justify days if it might prevent overflow */
        if ((result->day > 0 && result->time > 0) ||
                (result->day < 0 && result->time < 0))
@@ -2858,6 +2963,10 @@ interval_justify_hours(PG_FUNCTION_ARGS)
        result->day = span->day;
        result->time = span->time;
 
+       /* do nothing for infinite intervals */
+       if (INTERVAL_NOT_FINITE(result))
+               PG_RETURN_INTERVAL_P(result);
+
        TMODULO(result->time, wholeday, USECS_PER_DAY);
        if (pg_add_s32_overflow(result->day, wholeday, &result->day))
                ereport(ERROR,
@@ -2896,6 +3005,10 @@ interval_justify_days(PG_FUNCTION_ARGS)
        result->day = span->day;
        result->time = span->time;
 
+       /* do nothing for infinite intervals */
+       if (INTERVAL_NOT_FINITE(result))
+               PG_RETURN_INTERVAL_P(result);
+
        wholemonth = result->day / DAYS_PER_MONTH;
        result->day -= wholemonth * DAYS_PER_MONTH;
        if (pg_add_s32_overflow(result->month, wholemonth, &result->month))
@@ -2934,7 +3047,31 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
        Interval   *span = PG_GETARG_INTERVAL_P(1);
        Timestamp       result;
 
-       if (TIMESTAMP_NOT_FINITE(timestamp))
+       /*
+        * Handle infinities.
+        *
+        * We treat anything that amounts to "infinity - infinity" as an error,
+        * since the timestamp type has nothing equivalent to NaN.
+        */
+       if (INTERVAL_IS_NOBEGIN(span))
+       {
+               if (TIMESTAMP_IS_NOEND(timestamp))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("timestamp out of range")));
+               else
+                       TIMESTAMP_NOBEGIN(result);
+       }
+       else if (INTERVAL_IS_NOEND(span))
+       {
+               if (TIMESTAMP_IS_NOBEGIN(timestamp))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("timestamp out of range")));
+               else
+                       TIMESTAMP_NOEND(result);
+       }
+       else if (TIMESTAMP_NOT_FINITE(timestamp))
                result = timestamp;
        else
        {
@@ -3013,9 +3150,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
        Interval   *span = PG_GETARG_INTERVAL_P(1);
        Interval        tspan;
 
-       tspan.month = -span->month;
-       tspan.day = -span->day;
-       tspan.time = -span->time;
+       interval_um_internal(span, &tspan);
 
        return DirectFunctionCall2(timestamp_pl_interval,
                                                           TimestampGetDatum(timestamp),
@@ -3042,7 +3177,31 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
        TimestampTz result;
        int                     tz;
 
-       if (TIMESTAMP_NOT_FINITE(timestamp))
+       /*
+        * Handle infinities.
+        *
+        * We treat anything that amounts to "infinity - infinity" as an error,
+        * since the timestamptz type has nothing equivalent to NaN.
+        */
+       if (INTERVAL_IS_NOBEGIN(span))
+       {
+               if (TIMESTAMP_IS_NOEND(timestamp))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("timestamp out of range")));
+               else
+                       TIMESTAMP_NOBEGIN(result);
+       }
+       else if (INTERVAL_IS_NOEND(span))
+       {
+               if (TIMESTAMP_IS_NOBEGIN(timestamp))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("timestamp out of range")));
+               else
+                       TIMESTAMP_NOEND(result);
+       }
+       else if (TIMESTAMP_NOT_FINITE(timestamp))
                result = timestamp;
        else
        {
@@ -3132,9 +3291,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
 {
        Interval        tspan;
 
-       tspan.month = -span->month;
-       tspan.day = -span->day;
-       tspan.time = -span->time;
+       interval_um_internal(span, &tspan);
 
        return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
 }
@@ -3185,6 +3342,29 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
        PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
 }
 
+/* interval_um_internal()
+ * Negate an interval.
+ */
+static void
+interval_um_internal(const Interval *interval, Interval *result)
+{
+       if (INTERVAL_IS_NOBEGIN(interval))
+               INTERVAL_NOEND(result);
+       else if (INTERVAL_IS_NOEND(interval))
+               INTERVAL_NOBEGIN(result);
+       else
+       {
+               /* Negate each field, guarding against overflow */
+               if (pg_sub_s64_overflow(INT64CONST(0), interval->time, &result->time) ||
+                       pg_sub_s32_overflow(0, interval->day, &result->day) ||
+                       pg_sub_s32_overflow(0, interval->month, &result->month) ||
+                       INTERVAL_NOT_FINITE(result))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("interval out of range")));
+       }
+}
+
 Datum
 interval_um(PG_FUNCTION_ARGS)
 {
@@ -3192,23 +3372,7 @@ interval_um(PG_FUNCTION_ARGS)
        Interval   *result;
 
        result = (Interval *) palloc(sizeof(Interval));
-
-       result->time = -interval->time;
-       /* overflow check copied from int4um */
-       if (interval->time != 0 && SAMESIGN(result->time, interval->time))
-               ereport(ERROR,
-                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                                errmsg("interval out of range")));
-       result->day = -interval->day;
-       if (interval->day != 0 && SAMESIGN(result->day, interval->day))
-               ereport(ERROR,
-                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                                errmsg("interval out of range")));
-       result->month = -interval->month;
-       if (interval->month != 0 && SAMESIGN(result->month, interval->month))
-               ereport(ERROR,
-                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                                errmsg("interval out of range")));
+       interval_um_internal(interval, result);
 
        PG_RETURN_INTERVAL_P(result);
 }
@@ -3243,6 +3407,21 @@ interval_larger(PG_FUNCTION_ARGS)
        PG_RETURN_INTERVAL_P(result);
 }
 
+static void
+finite_interval_pl(const Interval *span1, const Interval *span2, Interval *result)
+{
+       Assert(!INTERVAL_NOT_FINITE(span1));
+       Assert(!INTERVAL_NOT_FINITE(span2));
+
+       if (pg_add_s32_overflow(span1->month, span2->month, &result->month) ||
+               pg_add_s32_overflow(span1->day, span2->day, &result->day) ||
+               pg_add_s64_overflow(span1->time, span2->time, &result->time) ||
+               INTERVAL_NOT_FINITE(result))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("interval out of range")));
+}
+
 Datum
 interval_pl(PG_FUNCTION_ARGS)
 {
@@ -3252,29 +3431,51 @@ interval_pl(PG_FUNCTION_ARGS)
 
        result = (Interval *) palloc(sizeof(Interval));
 
-       result->month = span1->month + span2->month;
-       /* overflow check copied from int4pl */
-       if (SAMESIGN(span1->month, span2->month) &&
-               !SAMESIGN(result->month, span1->month))
-               ereport(ERROR,
-                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                                errmsg("interval out of range")));
+       /*
+        * Handle infinities.
+        *
+        * We treat anything that amounts to "infinity - infinity" as an error,
+        * since the interval type has nothing equivalent to NaN.
+        */
+       if (INTERVAL_IS_NOBEGIN(span1))
+       {
+               if (INTERVAL_IS_NOEND(span2))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("interval out of range")));
+               else
+                       INTERVAL_NOBEGIN(result);
+       }
+       else if (INTERVAL_IS_NOEND(span1))
+       {
+               if (INTERVAL_IS_NOBEGIN(span2))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("interval out of range")));
+               else
+                       INTERVAL_NOEND(result);
+       }
+       else if (INTERVAL_NOT_FINITE(span2))
+               memcpy(result, span2, sizeof(Interval));
+       else
+               finite_interval_pl(span1, span2, result);
 
-       result->day = span1->day + span2->day;
-       if (SAMESIGN(span1->day, span2->day) &&
-               !SAMESIGN(result->day, span1->day))
-               ereport(ERROR,
-                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                                errmsg("interval out of range")));
+       PG_RETURN_INTERVAL_P(result);
+}
+
+static void
+finite_interval_mi(const Interval *span1, const Interval *span2, Interval *result)
+{
+       Assert(!INTERVAL_NOT_FINITE(span1));
+       Assert(!INTERVAL_NOT_FINITE(span2));
 
-       result->time = span1->time + span2->time;
-       if (SAMESIGN(span1->time, span2->time) &&
-               !SAMESIGN(result->time, span1->time))
+       if (pg_sub_s32_overflow(span1->month, span2->month, &result->month) ||
+               pg_sub_s32_overflow(span1->day, span2->day, &result->day) ||
+               pg_sub_s64_overflow(span1->time, span2->time, &result->time) ||
+               INTERVAL_NOT_FINITE(result))
                ereport(ERROR,
                                (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                                 errmsg("interval out of range")));
-
-       PG_RETURN_INTERVAL_P(result);
 }
 
 Datum
@@ -3286,27 +3487,36 @@ interval_mi(PG_FUNCTION_ARGS)
 
        result = (Interval *) palloc(sizeof(Interval));
 
-       result->month = span1->month - span2->month;
-       /* overflow check copied from int4mi */
-       if (!SAMESIGN(span1->month, span2->month) &&
-               !SAMESIGN(result->month, span1->month))
-               ereport(ERROR,
-                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                                errmsg("interval out of range")));
-
-       result->day = span1->day - span2->day;
-       if (!SAMESIGN(span1->day, span2->day) &&
-               !SAMESIGN(result->day, span1->day))
-               ereport(ERROR,
-                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                                errmsg("interval out of range")));
-
-       result->time = span1->time - span2->time;
-       if (!SAMESIGN(span1->time, span2->time) &&
-               !SAMESIGN(result->time, span1->time))
-               ereport(ERROR,
-                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
-                                errmsg("interval out of range")));
+       /*
+        * Handle infinities.
+        *
+        * We treat anything that amounts to "infinity - infinity" as an error,
+        * since the interval type has nothing equivalent to NaN.
+        */
+       if (INTERVAL_IS_NOBEGIN(span1))
+       {
+               if (INTERVAL_IS_NOBEGIN(span2))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("interval out of range")));
+               else
+                       INTERVAL_NOBEGIN(result);
+       }
+       else if (INTERVAL_IS_NOEND(span1))
+       {
+               if (INTERVAL_IS_NOEND(span2))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("interval out of range")));
+               else
+                       INTERVAL_NOEND(result);
+       }
+       else if (INTERVAL_IS_NOBEGIN(span2))
+               INTERVAL_NOEND(result);
+       else if (INTERVAL_IS_NOEND(span2))
+               INTERVAL_NOBEGIN(result);
+       else
+               finite_interval_mi(span1, span2, result);
 
        PG_RETURN_INTERVAL_P(result);
 }
@@ -3331,6 +3541,46 @@ interval_mul(PG_FUNCTION_ARGS)
 
        result = (Interval *) palloc(sizeof(Interval));
 
+       /*
+        * Handle NaN and infinities.
+        *
+        * We treat "0 * infinity" and "infinity * 0" as errors, since the
+        * interval type has nothing equivalent to NaN.
+        */
+       if (isnan(factor))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("interval out of range")));
+
+       if (INTERVAL_NOT_FINITE(span))
+       {
+               if (factor == 0.0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("interval out of range")));
+               else if (factor < 0.0)
+                       interval_um_internal(span, result);
+               else
+                       memcpy(result, span, sizeof(Interval));
+
+               PG_RETURN_INTERVAL_P(result);
+       }
+       if (isinf(factor))
+       {
+               int                     isign = interval_sign(span);
+
+               if (isign == 0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("interval out of range")));
+               else if (factor * isign < 0)
+                       INTERVAL_NOBEGIN(result);
+               else
+                       INTERVAL_NOEND(result);
+
+               PG_RETURN_INTERVAL_P(result);
+       }
+
        result_double = span->month * factor;
        if (isnan(result_double) ||
                result_double > INT_MAX || result_double < INT_MIN)
@@ -3391,6 +3641,11 @@ interval_mul(PG_FUNCTION_ARGS)
                                 errmsg("interval out of range")));
        result->time = (int64) result_double;
 
+       if (INTERVAL_NOT_FINITE(result))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("interval out of range")));
+
        PG_RETURN_INTERVAL_P(result);
 }
 
@@ -3422,6 +3677,33 @@ interval_div(PG_FUNCTION_ARGS)
                                (errcode(ERRCODE_DIVISION_BY_ZERO),
                                 errmsg("division by zero")));
 
+       /*
+        * Handle NaN and infinities.
+        *
+        * We treat "infinity / infinity" as an error, since the interval type has
+        * nothing equivalent to NaN.  Otherwise, dividing by infinity is handled
+        * by the regular division code, causing all fields to be set to zero.
+        */
+       if (isnan(factor))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("interval out of range")));
+
+       if (INTERVAL_NOT_FINITE(span))
+       {
+               if (isinf(factor))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("interval out of range")));
+
+               if (factor < 0.0)
+                       interval_um_internal(span, result);
+               else
+                       memcpy(result, span, sizeof(Interval));
+
+               PG_RETURN_INTERVAL_P(result);
+       }
+
        result->month = (int32) (span->month / factor);
        result->day = (int32) (span->day / factor);
 
@@ -3443,6 +3725,11 @@ interval_div(PG_FUNCTION_ARGS)
        result->day += (int32) month_remainder_days;
        result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
 
+       if (INTERVAL_NOT_FINITE(result))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("interval out of range")));
+
        PG_RETURN_INTERVAL_P(result);
 }
 
@@ -3466,11 +3753,21 @@ in_range_timestamptz_interval(PG_FUNCTION_ARGS)
        bool            less = PG_GETARG_BOOL(4);
        TimestampTz sum;
 
-       if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+       if (interval_sign(offset) < 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
                                 errmsg("invalid preceding or following size in window function")));
 
+       /*
+        * Deal with cases where both base and offset are infinite, and computing
+        * base +/- offset would cause an error.  As for float and numeric types,
+        * we assume that all values infinitely precede +infinity and infinitely
+        * follow -infinity.  See in_range_float8_float8() for reasoning.
+        */
+       if (INTERVAL_IS_NOEND(offset) &&
+               (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+               PG_RETURN_BOOL(true);
+
        /* We don't currently bother to avoid overflow hazards here */
        if (sub)
                sum = timestamptz_mi_interval_internal(base, offset, NULL);
@@ -3493,11 +3790,21 @@ in_range_timestamp_interval(PG_FUNCTION_ARGS)
        bool            less = PG_GETARG_BOOL(4);
        Timestamp       sum;
 
-       if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+       if (interval_sign(offset) < 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
                                 errmsg("invalid preceding or following size in window function")));
 
+       /*
+        * Deal with cases where both base and offset are infinite, and computing
+        * base +/- offset would cause an error.  As for float and numeric types,
+        * we assume that all values infinitely precede +infinity and infinitely
+        * follow -infinity.  See in_range_float8_float8() for reasoning.
+        */
+       if (INTERVAL_IS_NOEND(offset) &&
+               (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+               PG_RETURN_BOOL(true);
+
        /* We don't currently bother to avoid overflow hazards here */
        if (sub)
                sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval,
@@ -3524,11 +3831,21 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
        bool            less = PG_GETARG_BOOL(4);
        Interval   *sum;
 
-       if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+       if (interval_sign(offset) < 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
                                 errmsg("invalid preceding or following size in window function")));
 
+       /*
+        * Deal with cases where both base and offset are infinite, and computing
+        * base +/- offset would cause an error.  As for float and numeric types,
+        * we assume that all values infinitely precede +infinity and infinitely
+        * follow -infinity.  See in_range_float8_float8() for reasoning.
+        */
+       if (INTERVAL_IS_NOEND(offset) &&
+               (sub ? INTERVAL_IS_NOEND(base) : INTERVAL_IS_NOBEGIN(base)))
+               PG_RETURN_BOOL(true);
+
        /* We don't currently bother to avoid overflow hazards here */
        if (sub)
                sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
@@ -3547,161 +3864,327 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
 
 
 /*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function, that needs to compute
+ * sum and count, in the aggregate's memory context.
  *
- * The transition datatype for this aggregate is a 2-element array of
- * intervals, where the first is the running sum and the second contains
- * the number of values so far in its 'time' field.  This is a bit ugly
- * but it beats inventing a specialized datatype for the purpose.
+ * The function is used when the state data needs to be allocated in aggregate's
+ * context. When the state data needs to be allocated in the current memory
+ * context, we use palloc0 directly e.g. interval_avg_deserialize().
+ */
+static IntervalAggState *
+makeIntervalAggState(FunctionCallInfo fcinfo)
+{
+       IntervalAggState *state;
+       MemoryContext agg_context;
+       MemoryContext old_context;
+
+       if (!AggCheckCallContext(fcinfo, &agg_context))
+               elog(ERROR, "aggregate function called in non-aggregate context");
+
+       old_context = MemoryContextSwitchTo(agg_context);
+
+       state = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
+
+       MemoryContextSwitchTo(old_context);
+
+       return state;
+}
+
+/*
+ * Accumulate a new input value for interval aggregate functions.
+ */
+static void
+do_interval_accum(IntervalAggState *state, Interval *newval)
+{
+       /* Infinite inputs are counted separately, and do not affect "N" */
+       if (INTERVAL_IS_NOBEGIN(newval))
+       {
+               state->nInfcount++;
+               return;
+       }
+
+       if (INTERVAL_IS_NOEND(newval))
+       {
+               state->pInfcount++;
+               return;
+       }
+
+       finite_interval_pl(&state->sumX, newval, &state->sumX);
+       state->N++;
+}
+
+/*
+ * Remove the given interval value from the aggregated state.
+ */
+static void
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+       /* Infinite inputs are counted separately, and do not affect "N" */
+       if (INTERVAL_IS_NOBEGIN(newval))
+       {
+               state->nInfcount--;
+               return;
+       }
+
+       if (INTERVAL_IS_NOEND(newval))
+       {
+               state->pInfcount--;
+               return;
+       }
+
+       /* Handle the to-be-discarded finite value. */
+       state->N--;
+       if (state->N > 0)
+               finite_interval_mi(&state->sumX, newval, &state->sumX);
+       else
+       {
+               /* All values discarded, reset the state */
+               Assert(state->N == 0);
+               memset(&state->sumX, 0, sizeof(state->sumX));
+       }
+}
+
+/*
+ * Transition function for sum() and avg() interval aggregates.
  */
+Datum
+interval_avg_accum(PG_FUNCTION_ARGS)
+{
+       IntervalAggState *state;
+
+       state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
 
+       /* Create the state data on the first call */
+       if (state == NULL)
+               state = makeIntervalAggState(fcinfo);
+
+       if (!PG_ARGISNULL(1))
+               do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
+
+       PG_RETURN_POINTER(state);
+}
+
+/*
+ * Combine function for sum() and avg() interval aggregates.
+ *
+ * Combine the given internal aggregate states and place the combination in
+ * the first argument.
+ */
 Datum
-interval_accum(PG_FUNCTION_ARGS)
+interval_avg_combine(PG_FUNCTION_ARGS)
 {
-       ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
-       Interval   *newval = PG_GETARG_INTERVAL_P(1);
-       Datum      *transdatums;
-       int                     ndatums;
-       Interval        sumX,
-                               N;
-       Interval   *newsum;
-       ArrayType  *result;
+       IntervalAggState *state1;
+       IntervalAggState *state2;
+
+       state1 = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+       state2 = PG_ARGISNULL(1) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(1);
+
+       if (state2 == NULL)
+               PG_RETURN_POINTER(state1);
+
+       if (state1 == NULL)
+       {
+               /* manually copy all fields from state2 to state1 */
+               state1 = makeIntervalAggState(fcinfo);
 
-       deconstruct_array(transarray,
-                                         INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
-                                         &transdatums, NULL, &ndatums);
-       if (ndatums != 2)
-               elog(ERROR, "expected 2-element interval array");
+               state1->N = state2->N;
+               state1->pInfcount = state2->pInfcount;
+               state1->nInfcount = state2->nInfcount;
 
-       sumX = *(DatumGetIntervalP(transdatums[0]));
-       N = *(DatumGetIntervalP(transdatums[1]));
+               state1->sumX.day = state2->sumX.day;
+               state1->sumX.month = state2->sumX.month;
+               state1->sumX.time = state2->sumX.time;
 
-       newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
-                                                                                                  IntervalPGetDatum(&sumX),
-                                                                                                  IntervalPGetDatum(newval)));
-       N.time += 1;
+               PG_RETURN_POINTER(state1);
+       }
 
-       transdatums[0] = IntervalPGetDatum(newsum);
-       transdatums[1] = IntervalPGetDatum(&N);
+       state1->N += state2->N;
+       state1->pInfcount += state2->pInfcount;
+       state1->nInfcount += state2->nInfcount;
 
-       result = construct_array(transdatums, 2,
-                                                        INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+       /* Accumulate finite interval values, if any. */
+       if (state2->N > 0)
+               finite_interval_pl(&state1->sumX, &state2->sumX, &state1->sumX);
 
-       PG_RETURN_ARRAYTYPE_P(result);
+       PG_RETURN_POINTER(state1);
 }
 
+/*
+ * interval_avg_serialize
+ *             Serialize IntervalAggState for interval aggregates.
+ */
 Datum
-interval_combine(PG_FUNCTION_ARGS)
+interval_avg_serialize(PG_FUNCTION_ARGS)
 {
-       ArrayType  *transarray1 = PG_GETARG_ARRAYTYPE_P(0);
-       ArrayType  *transarray2 = PG_GETARG_ARRAYTYPE_P(1);
-       Datum      *transdatums1;
-       Datum      *transdatums2;
-       int                     ndatums1;
-       int                     ndatums2;
-       Interval        sum1,
-                               N1;
-       Interval        sum2,
-                               N2;
+       IntervalAggState *state;
+       StringInfoData buf;
+       bytea      *result;
 
-       Interval   *newsum;
-       ArrayType  *result;
+       /* Ensure we disallow calling when not in aggregate context */
+       if (!AggCheckCallContext(fcinfo, NULL))
+               elog(ERROR, "aggregate function called in non-aggregate context");
 
-       deconstruct_array(transarray1,
-                                         INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
-                                         &transdatums1, NULL, &ndatums1);
-       if (ndatums1 != 2)
-               elog(ERROR, "expected 2-element interval array");
+       state = (IntervalAggState *) PG_GETARG_POINTER(0);
 
-       sum1 = *(DatumGetIntervalP(transdatums1[0]));
-       N1 = *(DatumGetIntervalP(transdatums1[1]));
+       pq_begintypsend(&buf);
 
-       deconstruct_array(transarray2,
-                                         INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
-                                         &transdatums2, NULL, &ndatums2);
-       if (ndatums2 != 2)
-               elog(ERROR, "expected 2-element interval array");
+       /* N */
+       pq_sendint64(&buf, state->N);
 
-       sum2 = *(DatumGetIntervalP(transdatums2[0]));
-       N2 = *(DatumGetIntervalP(transdatums2[1]));
+       /* sumX */
+       pq_sendint64(&buf, state->sumX.time);
+       pq_sendint32(&buf, state->sumX.day);
+       pq_sendint32(&buf, state->sumX.month);
 
-       newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
-                                                                                                  IntervalPGetDatum(&sum1),
-                                                                                                  IntervalPGetDatum(&sum2)));
-       N1.time += N2.time;
+       /* pInfcount */
+       pq_sendint64(&buf, state->pInfcount);
 
-       transdatums1[0] = IntervalPGetDatum(newsum);
-       transdatums1[1] = IntervalPGetDatum(&N1);
+       /* nInfcount */
+       pq_sendint64(&buf, state->nInfcount);
 
-       result = construct_array(transdatums1, 2,
-                                                        INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+       result = pq_endtypsend(&buf);
 
-       PG_RETURN_ARRAYTYPE_P(result);
+       PG_RETURN_BYTEA_P(result);
 }
 
+/*
+ * interval_avg_deserialize
+ *             Deserialize bytea into IntervalAggState for interval aggregates.
+ */
 Datum
-interval_accum_inv(PG_FUNCTION_ARGS)
+interval_avg_deserialize(PG_FUNCTION_ARGS)
 {
-       ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
-       Interval   *newval = PG_GETARG_INTERVAL_P(1);
-       Datum      *transdatums;
-       int                     ndatums;
-       Interval        sumX,
-                               N;
-       Interval   *newsum;
-       ArrayType  *result;
+       bytea      *sstate;
+       IntervalAggState *result;
+       StringInfoData buf;
+
+       if (!AggCheckCallContext(fcinfo, NULL))
+               elog(ERROR, "aggregate function called in non-aggregate context");
+
+       sstate = PG_GETARG_BYTEA_PP(0);
+
+       /*
+        * Initialize a StringInfo so that we can "receive" it using the standard
+        * recv-function infrastructure.
+        */
+       initReadOnlyStringInfo(&buf, VARDATA_ANY(sstate),
+                                                  VARSIZE_ANY_EXHDR(sstate));
+
+       result = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
 
-       deconstruct_array(transarray,
-                                         INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
-                                         &transdatums, NULL, &ndatums);
-       if (ndatums != 2)
-               elog(ERROR, "expected 2-element interval array");
+       /* N */
+       result->N = pq_getmsgint64(&buf);
 
-       sumX = *(DatumGetIntervalP(transdatums[0]));
-       N = *(DatumGetIntervalP(transdatums[1]));
+       /* sumX */
+       result->sumX.time = pq_getmsgint64(&buf);
+       result->sumX.day = pq_getmsgint(&buf, 4);
+       result->sumX.month = pq_getmsgint(&buf, 4);
 
-       newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
-                                                                                                  IntervalPGetDatum(&sumX),
-                                                                                                  IntervalPGetDatum(newval)));
-       N.time -= 1;
+       /* pInfcount */
+       result->pInfcount = pq_getmsgint64(&buf);
 
-       transdatums[0] = IntervalPGetDatum(newsum);
-       transdatums[1] = IntervalPGetDatum(&N);
+       /* nInfcount */
+       result->nInfcount = pq_getmsgint64(&buf);
 
-       result = construct_array(transdatums, 2,
-                                                        INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+       pq_getmsgend(&buf);
 
-       PG_RETURN_ARRAYTYPE_P(result);
+       PG_RETURN_POINTER(result);
 }
 
+/*
+ * Inverse transition function for sum() and avg() interval aggregates.
+ */
 Datum
-interval_avg(PG_FUNCTION_ARGS)
+interval_avg_accum_inv(PG_FUNCTION_ARGS)
 {
-       ArrayType  *transarray = PG_GETARG_ARRAYTYPE_P(0);
-       Datum      *transdatums;
-       int                     ndatums;
-       Interval        sumX,
-                               N;
+       IntervalAggState *state;
+
+       state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
 
-       deconstruct_array(transarray,
-                                         INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
-                                         &transdatums, NULL, &ndatums);
-       if (ndatums != 2)
-               elog(ERROR, "expected 2-element interval array");
+       /* Should not get here with no state */
+       if (state == NULL)
+               elog(ERROR, "interval_avg_accum_inv called with NULL state");
 
-       sumX = *(DatumGetIntervalP(transdatums[0]));
-       N = *(DatumGetIntervalP(transdatums[1]));
+       if (!PG_ARGISNULL(1))
+               do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
 
-       /* SQL defines AVG of no values to be NULL */
-       if (N.time == 0)
+       PG_RETURN_POINTER(state);
+}
+
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+       IntervalAggState *state;
+
+       state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+       /* If there were no non-null inputs, return NULL */
+       if (state == NULL || IA_TOTAL_COUNT(state) == 0)
                PG_RETURN_NULL();
 
+       /*
+        * Aggregating infinities that all have the same sign produces infinity
+        * with that sign.  Aggregating infinities with different signs results in
+        * an error.
+        */
+       if (state->pInfcount > 0 || state->nInfcount > 0)
+       {
+               Interval   *result;
+
+               if (state->pInfcount > 0 && state->nInfcount > 0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("interval out of range.")));
+
+               result = (Interval *) palloc(sizeof(Interval));
+               if (state->pInfcount > 0)
+                       INTERVAL_NOEND(result);
+               else
+                       INTERVAL_NOBEGIN(result);
+
+               PG_RETURN_INTERVAL_P(result);
+       }
+
        return DirectFunctionCall2(interval_div,
-                                                          IntervalPGetDatum(&sumX),
-                                                          Float8GetDatum((double) N.time));
+                                                          IntervalPGetDatum(&state->sumX),
+                                                          Float8GetDatum((double) state->N));
 }
 
+/* sum(interval) aggregate final function */
+Datum
+interval_sum(PG_FUNCTION_ARGS)
+{
+       IntervalAggState *state;
+       Interval   *result;
+
+       state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+       /* If there were no non-null inputs, return NULL */
+       if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+               PG_RETURN_NULL();
+
+       /*
+        * Aggregating infinities that all have the same sign produces infinity
+        * with that sign.  Aggregating infinities with different signs results in
+        * an error.
+        */
+       if (state->pInfcount > 0 && state->nInfcount > 0)
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("interval out of range.")));
+
+       result = (Interval *) palloc(sizeof(Interval));
+
+       if (state->pInfcount > 0)
+               INTERVAL_NOEND(result);
+       else if (state->nInfcount > 0)
+               INTERVAL_NOBEGIN(result);
+       else
+               memcpy(result, &state->sumX, sizeof(Interval));
+
+       PG_RETURN_INTERVAL_P(result);
+}
 
 /* timestamp_age()
  * Calculate time difference while retaining year/month fields.
@@ -3726,8 +4209,36 @@ timestamp_age(PG_FUNCTION_ARGS)
 
        result = (Interval *) palloc(sizeof(Interval));
 
-       if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
-               timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+       /*
+        * Handle infinities.
+        *
+        * We treat anything that amounts to "infinity - infinity" as an error,
+        * since the interval type has nothing equivalent to NaN.
+        */
+       if (TIMESTAMP_IS_NOBEGIN(dt1))
+       {
+               if (TIMESTAMP_IS_NOBEGIN(dt2))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("interval out of range")));
+               else
+                       INTERVAL_NOBEGIN(result);
+       }
+       else if (TIMESTAMP_IS_NOEND(dt1))
+       {
+               if (TIMESTAMP_IS_NOEND(dt2))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("interval out of range")));
+               else
+                       INTERVAL_NOEND(result);
+       }
+       else if (TIMESTAMP_IS_NOBEGIN(dt2))
+               INTERVAL_NOEND(result);
+       else if (TIMESTAMP_IS_NOEND(dt2))
+               INTERVAL_NOBEGIN(result);
+       else if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
+                        timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
        {
                /* form the symbolic difference */
                tm->tm_usec = fsec1 - fsec2;
@@ -3846,8 +4357,36 @@ timestamptz_age(PG_FUNCTION_ARGS)
 
        result = (Interval *) palloc(sizeof(Interval));
 
-       if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
-               timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+       /*
+        * Handle infinities.
+        *
+        * We treat anything that amounts to "infinity - infinity" as an error,
+        * since the interval type has nothing equivalent to NaN.
+        */
+       if (TIMESTAMP_IS_NOBEGIN(dt1))
+       {
+               if (TIMESTAMP_IS_NOBEGIN(dt2))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("interval out of range")));
+               else
+                       INTERVAL_NOBEGIN(result);
+       }
+       else if (TIMESTAMP_IS_NOEND(dt1))
+       {
+               if (TIMESTAMP_IS_NOEND(dt2))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                        errmsg("interval out of range")));
+               else
+                       INTERVAL_NOEND(result);
+       }
+       else if (TIMESTAMP_IS_NOBEGIN(dt2))
+               INTERVAL_NOEND(result);
+       else if (TIMESTAMP_IS_NOEND(dt2))
+               INTERVAL_NOBEGIN(result);
+       else if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
+                        timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
        {
                /* form the symbolic difference */
                tm->tm_usec = fsec1 - fsec2;
@@ -3972,6 +4511,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
                                (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                                 errmsg("origin out of range")));
 
+       if (INTERVAL_NOT_FINITE(stride))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("timestamps cannot be binned into infinite intervals")));
+
        if (stride->month != 0)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4155,6 +4699,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
                                (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
                                 errmsg("origin out of range")));
 
+       if (INTERVAL_NOT_FINITE(stride))
+               ereport(ERROR,
+                               (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+                                errmsg("timestamps cannot be binned into infinite intervals")));
+
        if (stride->month != 0)
                ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4393,6 +4942,12 @@ interval_trunc(PG_FUNCTION_ARGS)
 
        result = (Interval *) palloc(sizeof(Interval));
 
+       if (INTERVAL_NOT_FINITE(interval))
+       {
+               memcpy(result, interval, sizeof(Interval));
+               PG_RETURN_INTERVAL_P(result);
+       }
+
        lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
                                                                                        VARSIZE_ANY_EXHDR(units),
                                                                                        false);
@@ -4737,7 +5292,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
                                                                                                 TIMESTAMP_IS_NOBEGIN(timestamp),
                                                                                                 false);
 
-               if (r)
+               if (r != 0.0)
                {
                        if (retnumeric)
                        {
@@ -5011,7 +5566,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
                                                                                                 TIMESTAMP_IS_NOBEGIN(timestamp),
                                                                                                 true);
 
-               if (r)
+               if (r != 0.0)
                {
                        if (retnumeric)
                        {
@@ -5251,6 +5806,58 @@ extract_timestamptz(PG_FUNCTION_ARGS)
        return timestamptz_part_common(fcinfo, true);
 }
 
+/*
+ * NonFiniteIntervalPart
+ *
+ *     Used by interval_part when extracting from infinite interval.  Returns
+ *     +/-Infinity if that is the appropriate result, otherwise returns zero
+ *     (which should be taken as meaning to return NULL).
+ *
+ *     Errors thrown here for invalid units should exactly match those that
+ *     would be thrown in the calling functions, else there will be unexpected
+ *     discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative)
+{
+       if ((type != UNITS) && (type != RESERV))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("unit \"%s\" not recognized for type %s",
+                                               lowunits, format_type_be(INTERVALOID))));
+
+       switch (unit)
+       {
+                       /* Oscillating units */
+               case DTK_MICROSEC:
+               case DTK_MILLISEC:
+               case DTK_SECOND:
+               case DTK_MINUTE:
+               case DTK_MONTH:
+               case DTK_QUARTER:
+                       return 0.0;
+
+                       /* Monotonically-increasing units */
+               case DTK_HOUR:
+               case DTK_DAY:
+               case DTK_YEAR:
+               case DTK_DECADE:
+               case DTK_CENTURY:
+               case DTK_MILLENNIUM:
+               case DTK_EPOCH:
+                       if (isNegative)
+                               return -get_float8_infinity();
+                       else
+                               return get_float8_infinity();
+
+               default:
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("unit \"%s\" not supported for type %s",
+                                                       lowunits, format_type_be(INTERVALOID))));
+                       return 0.0;                     /* keep compiler quiet */
+       }
+}
 
 /* interval_part() and extract_interval()
  * Extract specified field from interval.
@@ -5275,6 +5882,33 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
        if (type == UNKNOWN_FIELD)
                type = DecodeSpecial(0, lowunits, &val);
 
+       if (INTERVAL_NOT_FINITE(interval))
+       {
+               double          r = NonFiniteIntervalPart(type, val, lowunits,
+                                                                                         INTERVAL_IS_NOBEGIN(interval));
+
+               if (r != 0.0)
+               {
+                       if (retnumeric)
+                       {
+                               if (r < 0)
+                                       return DirectFunctionCall3(numeric_in,
+                                                                                          CStringGetDatum("-Infinity"),
+                                                                                          ObjectIdGetDatum(InvalidOid),
+                                                                                          Int32GetDatum(-1));
+                               else if (r > 0)
+                                       return DirectFunctionCall3(numeric_in,
+                                                                                          CStringGetDatum("Infinity"),
+                                                                                          ObjectIdGetDatum(InvalidOid),
+                                                                                          Int32GetDatum(-1));
+                       }
+                       else
+                               PG_RETURN_FLOAT8(r);
+               }
+               else
+                       PG_RETURN_NULL();
+       }
+
        if (type == UNITS)
        {
                interval2itm(*interval, tm);
@@ -5517,6 +6151,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
        if (TIMESTAMP_NOT_FINITE(timestamp))
                PG_RETURN_TIMESTAMPTZ(timestamp);
 
+       if (INTERVAL_NOT_FINITE(zone))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("interval time zone \"%s\" must be finite",
+                                               DatumGetCString(DirectFunctionCall1(interval_out,
+                                                                                                                       PointerGetDatum(zone))))));
+
        if (zone->month != 0 || zone->day != 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5747,6 +6388,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
        if (TIMESTAMP_NOT_FINITE(timestamp))
                PG_RETURN_TIMESTAMP(timestamp);
 
+       if (INTERVAL_NOT_FINITE(zone))
+               ereport(ERROR,
+                               (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                errmsg("interval time zone \"%s\" must be finite",
+                                               DatumGetCString(DirectFunctionCall1(interval_out,
+                                                                                                                       PointerGetDatum(zone))))));
+
        if (zone->month != 0 || zone->day != 0)
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5783,7 +6431,6 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
                Timestamp       finish = PG_GETARG_TIMESTAMP(1);
                Interval   *step = PG_GETARG_INTERVAL_P(2);
                MemoryContext oldcontext;
-               const Interval interval_zero = {0};
 
                /* create a function context for cross-call persistence */
                funcctx = SRF_FIRSTCALL_INIT();
@@ -5806,13 +6453,18 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
                fctx->step = *step;
 
                /* Determine sign of the interval */
-               fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+               fctx->step_sign = interval_sign(&fctx->step);
 
                if (fctx->step_sign == 0)
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                         errmsg("step size cannot equal zero")));
 
+               if (INTERVAL_NOT_FINITE((&fctx->step)))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("step size cannot be infinite")));
+
                funcctx->user_fctx = fctx;
                MemoryContextSwitchTo(oldcontext);
        }
@@ -5864,7 +6516,6 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
                Interval   *step = PG_GETARG_INTERVAL_P(2);
                text       *zone = (PG_NARGS() == 4) ? PG_GETARG_TEXT_PP(3) : NULL;
                MemoryContext oldcontext;
-               const Interval interval_zero = {0};
 
                /* create a function context for cross-call persistence */
                funcctx = SRF_FIRSTCALL_INIT();
@@ -5888,13 +6539,18 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
                fctx->attimezone = zone ? lookup_timezone(zone) : session_timezone;
 
                /* Determine sign of the interval */
-               fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+               fctx->step_sign = interval_sign(&fctx->step);
 
                if (fctx->step_sign == 0)
                        ereport(ERROR,
                                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                                         errmsg("step size cannot equal zero")));
 
+               if (INTERVAL_NOT_FINITE((&fctx->step)))
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                                        errmsg("step size cannot be infinite")));
+
                funcctx->user_fctx = fctx;
                MemoryContextSwitchTo(oldcontext);
        }
index f2af8eb0dc2345b9fecfb6966840c68f0c678be8..cbc719be430a14ee7ea93d5f3d95bdd5b761f6dd 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                                                     yyyymmddN */
-#define CATALOG_VERSION_NO     202311141
+#define CATALOG_VERSION_NO     202311142
 
 #endif
index 1bc1d97d741c01837d9336816e969b8ea997bc9d..e1a17cddd8cc94c4e72b90e34fba5983a078d0a6 100644 (file)
 { aggfnoid => 'avg(float8)', aggtransfn => 'float8_accum',
   aggfinalfn => 'float8_avg', aggcombinefn => 'float8_combine',
   aggtranstype => '_float8', agginitval => '{0,0,0}' },
-{ aggfnoid => 'avg(interval)', aggtransfn => 'interval_accum',
-  aggfinalfn => 'interval_avg', aggcombinefn => 'interval_combine',
-  aggmtransfn => 'interval_accum', aggminvtransfn => 'interval_accum_inv',
-  aggmfinalfn => 'interval_avg', aggtranstype => '_interval',
-  aggmtranstype => '_interval', agginitval => '{0 second,0 second}',
-  aggminitval => '{0 second,0 second}' },
+{ aggfnoid => 'avg(interval)', aggtransfn => 'interval_avg_accum',
+  aggfinalfn => 'interval_avg', aggcombinefn => 'interval_avg_combine',
+  aggserialfn => 'interval_avg_serialize',
+  aggdeserialfn => 'interval_avg_deserialize',
+  aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv',
+  aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
+  aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40' },
 
 # sum
 { aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
 { aggfnoid => 'sum(money)', aggtransfn => 'cash_pl', aggcombinefn => 'cash_pl',
   aggmtransfn => 'cash_pl', aggminvtransfn => 'cash_mi',
   aggtranstype => 'money', aggmtranstype => 'money' },
-{ aggfnoid => 'sum(interval)', aggtransfn => 'interval_pl',
-  aggcombinefn => 'interval_pl', aggmtransfn => 'interval_pl',
-  aggminvtransfn => 'interval_mi', aggtranstype => 'interval',
-  aggmtranstype => 'interval' },
+{ aggfnoid => 'sum(interval)', aggtransfn => 'interval_avg_accum',
+  aggfinalfn => 'interval_sum', aggcombinefn => 'interval_avg_combine',
+  aggserialfn => 'interval_avg_serialize',
+  aggdeserialfn => 'interval_avg_deserialize',
+  aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv',
+  aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
+  aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40'},
 { aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
   aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
   aggserialfn => 'numeric_avg_serialize',
index ee351bb76214a2aa57108a46e31e19ea2fb8eb57..fb58dee3bcdf6460a71e40a0b3423c1ec39e55c9 100644 (file)
   prosrc => 'numeric_poly_stddev_samp' },
 
 { oid => '1843', descr => 'aggregate transition function',
-  proname => 'interval_accum', prorettype => '_interval',
-  proargtypes => '_interval interval', prosrc => 'interval_accum' },
+  proname => 'interval_avg_accum', proisstrict => 'f',
+  prorettype => 'internal', proargtypes => 'internal interval',
+  prosrc => 'interval_avg_accum' },
 { oid => '3325', descr => 'aggregate combine function',
-  proname => 'interval_combine', prorettype => '_interval',
-  proargtypes => '_interval _interval', prosrc => 'interval_combine' },
+  proname => 'interval_avg_combine', proisstrict => 'f',
+  prorettype => 'internal', proargtypes => 'internal internal',
+  prosrc => 'interval_avg_combine' },
 { oid => '3549', descr => 'aggregate transition function',
-  proname => 'interval_accum_inv', prorettype => '_interval',
-  proargtypes => '_interval interval', prosrc => 'interval_accum_inv' },
+  proname => 'interval_avg_accum_inv', proisstrict => 'f',
+  prorettype => 'internal', proargtypes => 'internal interval',
+  prosrc => 'interval_avg_accum_inv' },
+{ oid => '8505', descr => 'aggregate serial function',
+  proname => 'interval_avg_serialize', prorettype => 'bytea',
+  proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
+{ oid => '8506', descr => 'aggregate deserial function',
+  proname => 'interval_avg_deserialize', prorettype => 'internal',
+  proargtypes => 'bytea internal', prosrc => 'interval_avg_deserialize' },
 { oid => '1844', descr => 'aggregate final function',
-  proname => 'interval_avg', prorettype => 'interval',
-  proargtypes => '_interval', prosrc => 'interval_avg' },
+  proname => 'interval_avg', proisstrict => 'f', prorettype => 'interval',
+  proargtypes => 'internal', prosrc => 'interval_avg' },
+{ oid => '8507', descr => 'aggregate final function',
+  proname => 'interval_sum', proisstrict => 'f', prorettype => 'interval',
+  proargtypes => 'internal', prosrc => 'interval_sum' },
 { oid => '1962', descr => 'aggregate transition function',
   proname => 'int2_avg_accum', prorettype => '_int8',
   proargtypes => '_int8 int2', prosrc => 'int2_avg_accum' },
index 1a6390585c84e12e14fd2d8cdd655760d0241e1c..b63acc0a2fedebc47f1d49839e447631f5f12406 100644 (file)
@@ -151,7 +151,7 @@ struct pg_itm_in
 #define TIMESTAMP_INFINITY     PG_INT64_MAX
 
 /*
- * Historically these alias for infinity have been used.
+ * Historically these aliases for infinity have been used.
  */
 #define DT_NOBEGIN             TIMESTAMP_MINUS_INFINITY
 #define DT_NOEND               TIMESTAMP_INFINITY
@@ -168,6 +168,31 @@ struct pg_itm_in
 
 #define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
 
+/*
+ * Infinite intervals are represented by setting all fields to the minimum or
+ * maximum integer values.
+ */
+#define INTERVAL_NOBEGIN(i)    \
+       do {    \
+               (i)->time = PG_INT64_MIN;       \
+               (i)->day = PG_INT32_MIN;        \
+               (i)->month = PG_INT32_MIN;      \
+       } while (0)
+
+#define INTERVAL_IS_NOBEGIN(i) \
+       ((i)->month == PG_INT32_MIN && (i)->day == PG_INT32_MIN && (i)->time == PG_INT64_MIN)
+
+#define INTERVAL_NOEND(i)      \
+       do {    \
+               (i)->time = PG_INT64_MAX;       \
+               (i)->day = PG_INT32_MAX;        \
+               (i)->month = PG_INT32_MAX;      \
+       } while (0)
+
+#define INTERVAL_IS_NOEND(i)   \
+       ((i)->month == PG_INT32_MAX && (i)->day == PG_INT32_MAX && (i)->time == PG_INT64_MAX)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
 
 /*
  * Julian date support.
index 80801cd4ee71284cc434965ca41fd6e337afb460..8f4c95b9e64f4058c63ca61cf2da0d56334bab79 100644 (file)
@@ -941,6 +941,34 @@ SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
          Index Cond: (a = '@ 30 years'::interval)
 (4 rows)
 
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) 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 8f52661096f839cf24719984dfababf58be35681..cfb4b205e4d4fc8f0648177b194dcf41602cc2ef 100644 (file)
@@ -1171,6 +1171,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
 
 SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
   FROM TIME_TBL t, INTERVAL_TBL i
+  WHERE isfinite(i.f1)
   ORDER BY 1,2;
       t      |               i               |     add     |  subtract   
 -------------+-------------------------------+-------------+-------------
@@ -1278,6 +1279,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
 
 SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
   FROM TIMETZ_TBL t, INTERVAL_TBL i
+  WHERE isfinite(i.f1)
   ORDER BY 1,2;
        t        |               i               |      add       |    subtract    
 ----------------+-------------------------------+----------------+----------------
@@ -1556,6 +1558,22 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
   ORDER BY plus, "timestamp", "interval";
           timestamp           |           interval            |             plus             
 ------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | -infinity                     | -infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity                     | -infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity                     | -infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity                     | -infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity                     | -infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity                     | -infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity                     | -infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity                     | -infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity                     | -infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity                     | -infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity                     | -infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity                     | -infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity                     | -infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity                     | -infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity                     | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity                     | -infinity
  Thu Jan 01 00:00:00 1970 PST | @ 14 secs ago                 | Wed Dec 31 23:59:46 1969 PST
  Thu Jan 01 00:00:00 1970 PST | @ 1 min                       | Thu Jan 01 00:01:00 1970 PST
  Thu Jan 01 00:00:00 1970 PST | @ 5 hours                     | Thu Jan 01 05:00:00 1970 PST
@@ -1716,14 +1734,45 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
  Sun Dec 31 17:32:01 2000 PST | @ 34 years                    | Sun Dec 31 17:32:01 2034 PST
  Mon Jan 01 17:32:01 2001 PST | @ 34 years                    | Mon Jan 01 17:32:01 2035 PST
  Sat Sep 22 18:19:20 2001 PDT | @ 34 years                    | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity                      | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity                      | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity                      | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity                      | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity                      | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity                      | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity                      | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity                      | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity                      | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity                      | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity                      | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity                      | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity                      | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity                      | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity                      | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity                      | infinity
+(192 rows)
 
 SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
   FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
-  WHERE isfinite(d.f1)
   ORDER BY minus, "timestamp", "interval";
           timestamp           |           interval            |            minus             
 ------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | infinity                      | -infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity                      | -infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity                      | -infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity                      | -infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity                      | -infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity                      | -infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity                      | -infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity                      | -infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity                      | -infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity                      | -infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity                      | -infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity                      | -infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity                      | -infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity                      | -infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity                      | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity                      | -infinity
  Thu Jan 01 00:00:00 1970 PST | @ 34 years                    | Wed Jan 01 00:00:00 1936 PST
  Wed Feb 28 17:32:01 1996 PST | @ 34 years                    | Wed Feb 28 17:32:01 1962 PST
  Thu Feb 29 17:32:01 1996 PST | @ 34 years                    | Wed Feb 28 17:32:01 1962 PST
@@ -1884,7 +1933,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
  Sat Sep 22 18:19:20 2001 PDT | @ 5 hours                     | Sat Sep 22 13:19:20 2001 PDT
  Sat Sep 22 18:19:20 2001 PDT | @ 1 min                       | Sat Sep 22 18:18:20 2001 PDT
  Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago                 | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity                     | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity                     | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity                     | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity                     | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity                     | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity                     | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity                     | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity                     | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity                     | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity                     | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity                     | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity                     | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity                     | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity                     | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity                     | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity                     | infinity
+(192 rows)
 
 SELECT d.f1 AS "timestamp",
    timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
index 75d19d659491296cdb5f77e5a922e9dc5cbf1291..a481781475bbded38db6db365ea7e632e8385199 100644 (file)
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
  9 years 1 mon -12 days +13:14:00
 (1 row)
 
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity 
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time 
+-------------------
+ -infinity
+(1 row)
+
 CREATE TABLE INTERVAL_TBL (f1 interval);
 INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
 INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
 INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
 INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
 INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
 -- badly formatted interval
 INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
 ERROR:  invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
  6 years
  5 mons
  5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
 
 SELECT * FROM INTERVAL_TBL
    WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
  6 years
  5 mons
  5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
 
 SELECT * FROM INTERVAL_TBL
    WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
  00:01:00
  05:00:00
  -00:00:14
-(3 rows)
+ -infinity
+(4 rows)
 
 SELECT * FROM INTERVAL_TBL
    WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
  00:01:00
  05:00:00
  -00:00:14
-(3 rows)
+ -infinity
+(4 rows)
 
 SELECT * FROM INTERVAL_TBL
    WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
  6 years
  5 mons
  5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
 
 SELECT * FROM INTERVAL_TBL
    WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
  6 years
  5 mons
  5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
 
 SELECT r1.*, r2.*
    FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
    ORDER BY r1.f1, r2.f1;
        f1        |       f1        
 -----------------+-----------------
+ -00:00:14       | -infinity
+ 00:01:00        | -infinity
  00:01:00        | -00:00:14
+ 05:00:00        | -infinity
  05:00:00        | -00:00:14
  05:00:00        | 00:01:00
+ 1 day 02:03:04  | -infinity
  1 day 02:03:04  | -00:00:14
  1 day 02:03:04  | 00:01:00
  1 day 02:03:04  | 05:00:00
+ 10 days         | -infinity
  10 days         | -00:00:14
  10 days         | 00:01:00
  10 days         | 05:00:00
  10 days         | 1 day 02:03:04
+ 3 mons          | -infinity
  3 mons          | -00:00:14
  3 mons          | 00:01:00
  3 mons          | 05:00:00
  3 mons          | 1 day 02:03:04
  3 mons          | 10 days
+ 5 mons          | -infinity
  5 mons          | -00:00:14
  5 mons          | 00:01:00
  5 mons          | 05:00:00
  5 mons          | 1 day 02:03:04
  5 mons          | 10 days
  5 mons          | 3 mons
+ 5 mons 12:00:00 | -infinity
  5 mons 12:00:00 | -00:00:14
  5 mons 12:00:00 | 00:01:00
  5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
  5 mons 12:00:00 | 10 days
  5 mons 12:00:00 | 3 mons
  5 mons 12:00:00 | 5 mons
+ 6 years         | -infinity
  6 years         | -00:00:14
  6 years         | 00:01:00
  6 years         | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
  6 years         | 3 mons
  6 years         | 5 mons
  6 years         | 5 mons 12:00:00
+ 34 years        | -infinity
  34 years        | -00:00:14
  34 years        | 00:01:00
  34 years        | 05:00:00
@@ -236,8 +268,63 @@ SELECT r1.*, r2.*
  34 years        | 5 mons
  34 years        | 5 mons 12:00:00
  34 years        | 6 years
-(45 rows)
+ infinity        | -infinity
+ infinity        | -00:00:14
+ infinity        | 00:01:00
+ infinity        | 05:00:00
+ infinity        | 1 day 02:03:04
+ infinity        | 10 days
+ infinity        | 3 mons
+ infinity        | 5 mons
+ infinity        | 5 mons 12:00:00
+ infinity        | 6 years
+ infinity        | 34 years
+(66 rows)
+
+-- test unary minus
+SELECT f1, -f1 FROM INTERVAL_TBL;
+       f1        |     ?column?      
+-----------------+-------------------
+ 00:01:00        | -00:01:00
+ 05:00:00        | -05:00:00
+ 10 days         | -10 days
+ 34 years        | -34 years
+ 3 mons          | -3 mons
+ -00:00:14       | 00:00:14
+ 1 day 02:03:04  | -1 days -02:03:04
+ 6 years         | -6 years
+ 5 mons          | -5 mons
+ 5 mons 12:00:00 | -5 mons -12:00:00
+ infinity        | -infinity
+ -infinity       | infinity
+(12 rows)
+
+SELECT -('-2147483648 months'::interval); -- should fail
+ERROR:  interval out of range
+SELECT -('-2147483647 months'::interval); -- ok
+        ?column?        
+------------------------
+ 178956970 years 7 mons
+(1 row)
+
+SELECT -('-2147483648 days'::interval); -- should fail
+ERROR:  interval out of range
+SELECT -('-2147483647 days'::interval); -- ok
+    ?column?     
+-----------------
+ 2147483647 days
+(1 row)
+
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+ERROR:  interval out of range
+SELECT -('-9223372036854775807 us'::interval); -- ok
+        ?column?         
+-------------------------
+ 2562047788:00:54.775807
+(1 row)
 
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+ERROR:  interval out of range
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
@@ -304,6 +391,17 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
 (5 rows)
 
 RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column? 
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
 DROP TABLE INTERVAL_TBL_OF;
 -- Test multiplication and division with intervals.
 -- Floating point arithmetic rounding errors can lead to unexpected results,
@@ -386,12 +484,14 @@ SELECT * FROM INTERVAL_TBL;
  @ 6 years
  @ 5 mons
  @ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
 
 -- test avg(interval), which is somewhat fragile since people have been
 -- known to change the allowed input syntax for type interval without
 -- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
                        avg                       
 -------------------------------------------------
  @ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +920,8 @@ SELECT interval '1 2:03:04.5678' minute to second(2);
 SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
   (f1 + INTERVAL '1 month')::INTERVAL MONTH::INTERVAL YEAR AS "years"
   FROM interval_tbl;
-       f1        |     minutes     |  years   
------------------+-----------------+----------
+       f1        |     minutes     |   years   
+-----------------+-----------------+-----------
  00:01:00        | 00:01:00        | 00:00:00
  05:00:00        | 05:00:00        | 00:00:00
  10 days         | 10 days         | 00:00:00
@@ -832,7 +932,9 @@ SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
  6 years         | 6 years         | 6 years
  5 mons          | 5 mons          | 00:00:00
  5 mons 12:00:00 | 5 mons 12:00:00 | 00:00:00
-(10 rows)
+ infinity        | infinity        | infinity
+ -infinity       | -infinity       | -infinity
+(12 rows)
 
 -- test inputting and outputting SQL standard interval literals
 SET IntervalStyle TO sql_standard;
@@ -1616,31 +1718,31 @@ select make_interval(mins := -1, secs := -9223372036800.0);
 ERROR:  interval out of range
 -- test that INT_MIN number is formatted properly
 SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
                               interval                              
 --------------------------------------------------------------------
- -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+ -178956970 years -7 mons -2147483648 days -2562047788:00:54.775808
 (1 row)
 
 SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
                      interval                      
 ---------------------------------------------------
- -178956970-8 -2147483648 -2562047788:00:54.775808
+ -178956970-7 -2147483648 -2562047788:00:54.775808
 (1 row)
 
 SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
                       interval                       
 -----------------------------------------------------
- P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+ P-178956970Y-7M-2147483648DT-2562047788H-54.775808S
 (1 row)
 
 SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
                                    interval                                   
 ------------------------------------------------------------------------------
- @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+ @ 178956970 years 7 mons 2147483648 days 2562047788 hours 54.775808 secs ago
 (1 row)
 
 -- check that '30 days' equals '1 month' according to the hash function
@@ -1719,19 +1821,21 @@ SELECT f1,
     EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
     EXTRACT(EPOCH FROM f1) AS EPOCH
     FROM INTERVAL_TBL;
-              f1               | microsecond | millisecond |   second   | minute | hour | day | month | quarter | year | decade | century | millennium |       epoch       
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min                       |           0 |       0.000 |   0.000000 |      1 |    0 |   0 |     0 |       1 |    0 |      0 |       0 |          0 |         60.000000
- @ 5 hours                     |           0 |       0.000 |   0.000000 |      0 |    5 |   0 |     0 |       1 |    0 |      0 |       0 |          0 |      18000.000000
- @ 10 days                     |           0 |       0.000 |   0.000000 |      0 |    0 |  10 |     0 |       1 |    0 |      0 |       0 |          0 |     864000.000000
- @ 34 years                    |           0 |       0.000 |   0.000000 |      0 |    0 |   0 |     0 |       1 |   34 |      3 |       0 |          0 | 1072958400.000000
- @ 3 mons                      |           0 |       0.000 |   0.000000 |      0 |    0 |   0 |     3 |       2 |    0 |      0 |       0 |          0 |    7776000.000000
- @ 14 secs ago                 |   -14000000 |  -14000.000 | -14.000000 |      0 |    0 |   0 |     0 |       1 |    0 |      0 |       0 |          0 |        -14.000000
- @ 1 day 2 hours 3 mins 4 secs |     4000000 |    4000.000 |   4.000000 |      3 |    2 |   1 |     0 |       1 |    0 |      0 |       0 |          0 |      93784.000000
- @ 6 years                     |           0 |       0.000 |   0.000000 |      0 |    0 |   0 |     0 |       1 |    6 |      0 |       0 |          0 |  189345600.000000
- @ 5 mons                      |           0 |       0.000 |   0.000000 |      0 |    0 |   0 |     5 |       2 |    0 |      0 |       0 |          0 |   12960000.000000
- @ 5 mons 12 hours             |           0 |       0.000 |   0.000000 |      0 |   12 |   0 |     5 |       2 |    0 |      0 |       0 |          0 |   13003200.000000
-(10 rows)
+              f1               | microsecond | millisecond |   second   | minute |   hour    |    day    | month | quarter |   year    |  decade   |  century  | millennium |       epoch       
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min                       |           0 |       0.000 |   0.000000 |      1 |         0 |         0 |     0 |       1 |         0 |         0 |         0 |          0 |         60.000000
+ @ 5 hours                     |           0 |       0.000 |   0.000000 |      0 |         5 |         0 |     0 |       1 |         0 |         0 |         0 |          0 |      18000.000000
+ @ 10 days                     |           0 |       0.000 |   0.000000 |      0 |         0 |        10 |     0 |       1 |         0 |         0 |         0 |          0 |     864000.000000
+ @ 34 years                    |           0 |       0.000 |   0.000000 |      0 |         0 |         0 |     0 |       1 |        34 |         3 |         0 |          0 | 1072958400.000000
+ @ 3 mons                      |           0 |       0.000 |   0.000000 |      0 |         0 |         0 |     3 |       2 |         0 |         0 |         0 |          0 |    7776000.000000
+ @ 14 secs ago                 |   -14000000 |  -14000.000 | -14.000000 |      0 |         0 |         0 |     0 |       1 |         0 |         0 |         0 |          0 |        -14.000000
+ @ 1 day 2 hours 3 mins 4 secs |     4000000 |    4000.000 |   4.000000 |      3 |         2 |         1 |     0 |       1 |         0 |         0 |         0 |          0 |      93784.000000
+ @ 6 years                     |           0 |       0.000 |   0.000000 |      0 |         0 |         0 |     0 |       1 |         6 |         0 |         0 |          0 |  189345600.000000
+ @ 5 mons                      |           0 |       0.000 |   0.000000 |      0 |         0 |         0 |     5 |       2 |         0 |         0 |         0 |          0 |   12960000.000000
+ @ 5 mons 12 hours             |           0 |       0.000 |   0.000000 |      0 |        12 |         0 |     5 |       2 |         0 |         0 |         0 |          0 |   13003200.000000
+ infinity                      |             |             |            |        |  Infinity |  Infinity |       |         |  Infinity |  Infinity |  Infinity |   Infinity |          Infinity
+ -infinity                     |             |             |            |        | -Infinity | -Infinity |       |         | -Infinity | -Infinity | -Infinity |  -Infinity |         -Infinity
+(12 rows)
 
 SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days');  -- error
 ERROR:  unit "fortnight" not recognized for type interval
@@ -1805,7 +1909,9 @@ SELECT f1,
  @ 6 years                     |           0 |           0 |      0 |  189345600
  @ 5 mons                      |           0 |           0 |      0 |   12960000
  @ 5 mons 12 hours             |           0 |           0 |      0 |   13003200
-(10 rows)
+ infinity                      |             |             |        |   Infinity
+ -infinity                     |             |             |        |  -Infinity
+(12 rows)
 
 -- internal overflow test case
 SELECT extract(epoch from interval '1000000000 days');
@@ -1814,6 +1920,292 @@ SELECT extract(epoch from interval '1000000000 days');
  86400000000000.000000
 (1 row)
 
+--
+-- test infinite intervals
+--
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+                                   interval                                   
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+                                 interval                                 
+--------------------------------------------------------------------------
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs
+(1 row)
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval  
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ interval 
+----------
+ infinity
+(1 row)
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+            i            | isfinite 
+-------------------------+----------
+ infinity                | f
+ -infinity               | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+  result text;
+BEGIN
+  EXECUTE 'select '||expr INTO result;
+  RETURN result;
+EXCEPTION WHEN OTHERS THEN
+  RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+SELECT d AS date, i AS interval,
+       eval(format('date %L + interval %L', d, i)) AS plus,
+       eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+             (date '1995-08-06'),
+             (date 'infinity')) AS t1(d),
+     (VALUES (interval '-infinity'),
+             (interval 'infinity')) AS t2(i);
+    date    | interval  |          plus          |         minus          
+------------+-----------+------------------------+------------------------
+ -infinity  | -infinity | -infinity              | timestamp out of range
+ -infinity  | infinity  | timestamp out of range | -infinity
+ 1995-08-06 | -infinity | -infinity              | infinity
+ 1995-08-06 | infinity  | infinity               | -infinity
+ infinity   | -infinity | timestamp out of range | infinity
+ infinity   | infinity  | infinity               | timestamp out of range
+(6 rows)
+
+SELECT i1 AS interval1, i2 AS interval2,
+       eval(format('interval %L + interval %L', i1, i2)) AS plus,
+       eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+             (interval '2 months'),
+             (interval 'infinity')) AS t1(i1),
+     (VALUES (interval '-infinity'),
+             (interval '10 days'),
+             (interval 'infinity')) AS t2(i2);
+ interval1 | interval2 |         plus          |         minus         
+-----------+-----------+-----------------------+-----------------------
+ -infinity | -infinity | -infinity             | interval out of range
+ -infinity | @ 10 days | -infinity             | -infinity
+ -infinity | infinity  | interval out of range | -infinity
+ @ 2 mons  | -infinity | -infinity             | infinity
+ @ 2 mons  | @ 10 days | @ 2 mons 10 days      | @ 2 mons -10 days
+ @ 2 mons  | infinity  | infinity              | -infinity
+ infinity  | -infinity | interval out of range | infinity
+ infinity  | @ 10 days | infinity              | infinity
+ infinity  | infinity  | infinity              | interval out of range
+(9 rows)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR:  interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR:  interval out of range
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR:  interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR:  interval out of range
+SELECT t AS timestamp, i AS interval,
+       eval(format('timestamp %L + interval %L', t, i)) AS plus,
+       eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+             (timestamp '1995-08-06 12:30:15'),
+             (timestamp 'infinity')) AS t1(t),
+     (VALUES (interval '-infinity'),
+             (interval 'infinity')) AS t2(i);
+      timestamp      | interval  |          plus          |         minus          
+---------------------+-----------+------------------------+------------------------
+ -infinity           | -infinity | -infinity              | timestamp out of range
+ -infinity           | infinity  | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity              | infinity
+ 1995-08-06 12:30:15 | infinity  | infinity               | -infinity
+ infinity            | -infinity | timestamp out of range | infinity
+ infinity            | infinity  | infinity               | timestamp out of range
+(6 rows)
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+       eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+       eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+             (timestamptz '1995-08-06 12:30:15 GMT'),
+             (timestamptz 'infinity')) AS t1(t),
+     (VALUES (interval '-infinity'),
+             (interval 'infinity')) AS t2(i);
+     timestamptz     | interval  |          plus          |         minus          
+---------------------+-----------+------------------------+------------------------
+ -infinity           | -infinity | -infinity              | timestamp out of range
+ -infinity           | infinity  | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity              | infinity
+ 1995-08-06 12:30:15 | infinity  | infinity               | -infinity
+ infinity            | -infinity | timestamp out of range | infinity
+ infinity            | infinity  | infinity               | timestamp out of range
+(6 rows)
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+ERROR:  cannot add infinite interval to time
+SELECT time '11:27:42' + interval '-infinity';
+ERROR:  cannot add infinite interval to time
+SELECT time '11:27:42' - interval 'infinity';
+ERROR:  cannot subtract infinite interval from time
+SELECT time '11:27:42' - interval '-infinity';
+ERROR:  cannot subtract infinite interval from time
+SELECT timetz '11:27:42' + interval 'infinity';
+ERROR:  cannot add infinite interval to time
+SELECT timetz '11:27:42' + interval '-infinity';
+ERROR:  cannot add infinite interval to time
+SELECT timetz '11:27:42' - interval 'infinity';
+ERROR:  cannot subtract infinite interval from time
+SELECT timetz '11:27:42' - interval '-infinity';
+ERROR:  cannot subtract infinite interval from time
+SELECT lhst.i lhs,
+    rhst.i rhs,
+    lhst.i < rhst.i AS lt,
+    lhst.i <= rhst.i AS le,
+    lhst.i = rhst.i AS eq,
+    lhst.i > rhst.i AS gt,
+    lhst.i >= rhst.i AS ge,
+    lhst.i <> rhst.i AS ne
+    FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+    WHERE NOT isfinite(lhst.i);
+    lhs    |           rhs           | lt | le | eq | gt | ge | ne 
+-----------+-------------------------+----+----+----+----+----+----
+ infinity  | infinity                | f  | t  | t  | f  | t  | f
+ -infinity | infinity                | t  | t  | f  | f  | f  | t
+ infinity  | -infinity               | f  | f  | f  | t  | t  | t
+ -infinity | -infinity               | f  | t  | t  | f  | t  | f
+ infinity  | @ 1 year 2 days 3 hours | f  | f  | f  | t  | t  | t
+ -infinity | @ 1 year 2 days 3 hours | t  | t  | f  | f  | f  | t
+(6 rows)
+
+SELECT i AS interval,
+    -i AS um,
+    i * 2.0 AS mul,
+    i * -2.0 AS mul_neg,
+    i * 'infinity' AS mul_inf,
+    i * '-infinity' AS mul_inf_neg,
+    i / 3.0 AS div,
+    i / -3.0 AS div_neg
+    FROM INFINITE_INTERVAL_TBL
+    WHERE NOT isfinite(i);
+ interval  |    um     |    mul    |  mul_neg  |  mul_inf  | mul_inf_neg |    div    |  div_neg  
+-----------+-----------+-----------+-----------+-----------+-------------+-----------+-----------
+ infinity  | -infinity | infinity  | -infinity | infinity  | -infinity   | infinity  | -infinity
+ -infinity | infinity  | -infinity | infinity  | -infinity | infinity    | -infinity | infinity
+(2 rows)
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+ERROR:  interval out of range
+SELECT interval 'infinity' * 'nan';
+ERROR:  interval out of range
+SELECT interval '-infinity' * 'nan';
+ERROR:  interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+ERROR:  interval out of range
+SELECT interval 'infinity' * 0;
+ERROR:  interval out of range
+SELECT interval '-infinity' * 0;
+ERROR:  interval out of range
+SELECT interval '0 days' * 'infinity'::float;
+ERROR:  interval out of range
+SELECT interval '0 days' * '-infinity'::float;
+ERROR:  interval out of range
+SELECT interval '5 days' * 'infinity'::float;
+ ?column? 
+----------
+ infinity
+(1 row)
+
+SELECT interval '5 days' * '-infinity'::float;
+ ?column?  
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' / 'infinity';
+ERROR:  interval out of range
+SELECT interval 'infinity' / '-infinity';
+ERROR:  interval out of range
+SELECT interval 'infinity' / 'nan';
+ERROR:  interval out of range
+SELECT interval '-infinity' / 'infinity';
+ERROR:  interval out of range
+SELECT interval '-infinity' / '-infinity';
+ERROR:  interval out of range
+SELECT interval '-infinity' / 'nan';
+ERROR:  interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+ERROR:  interval out of range
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR:  timestamps cannot be binned into infinite intervals
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR:  timestamps cannot be binned into infinite intervals
+SELECT i AS interval, date_trunc('hour', i)
+    FROM INFINITE_INTERVAL_TBL
+    WHERE NOT isfinite(i);
+ interval  | date_trunc 
+-----------+------------
+ infinity  | infinity
+ -infinity | -infinity
+(2 rows)
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+    FROM INFINITE_INTERVAL_TBL
+    WHERE NOT isfinite(i);
+ interval  | justify_days | justify_hours | justify_interval 
+-----------+--------------+---------------+------------------
+ infinity  | infinity     | infinity      | infinity
+ -infinity | -infinity    | -infinity     | -infinity
+(2 rows)
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR:  interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR:  interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR:  interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR:  interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+ERROR:  interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+ERROR:  interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+ERROR:  interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+ERROR:  interval time zone "-infinity" must be finite
+SELECT 'infinity'::interval::time;
+ERROR:  cannot convert infinite interval to time
+SELECT '-infinity'::interval::time;
+ERROR:  cannot convert infinite interval to time
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char 
+---------
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char 
+---------
+(1 row)
+
 -- "ago" can only appear once at the end of an interval.
 SELECT INTERVAL '42 days 2 seconds ago ago';
 ERROR:  invalid input syntax for type interval: "42 days 2 seconds ago ago"
@@ -1832,3 +2224,42 @@ SELECT INTERVAL '1 year months days 5 hours';
 ERROR:  invalid input syntax for type interval: "1 year months days 5 hours"
 LINE 1: SELECT INTERVAL '1 year months days 5 hours';
                         ^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR:  invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+                        ^
+SELECT INTERVAL 'today';
+ERROR:  invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+                        ^
+SELECT INTERVAL 'tomorrow';
+ERROR:  invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+                        ^
+SELECT INTERVAL 'allballs';
+ERROR:  invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+                        ^
+SELECT INTERVAL 'epoch';
+ERROR:  invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+                        ^
+SELECT INTERVAL 'yesterday';
+ERROR:  invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+                        ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR:  invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+                        ^
+SELECT INTERVAL 'infinity ago';
+ERROR:  invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+                        ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR:  invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+                        ^
index c64bcb7c127fc3b0e3d23f9283345ed5126db578..835f0e5762fa05ec676c2d1d211a6edc35d1c2e9 100644 (file)
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
                               '2020-01-02 03:00'::timestamp,
                               '0 hour'::interval);
 ERROR:  step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR:  step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR:  step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR:  interval out of range
+select timestamp 'infinity' - timestamp '-infinity';
+ ?column? 
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp 'infinity';
+ ?column?  
+-----------
+ -infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '-infinity';
+ERROR:  interval out of range
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+ ?column? 
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?  
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+    age    
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity');
+   age    
+----------
+ infinity
+(1 row)
+
+select age(timestamp 'infinity', timestamp 'infinity');
+ERROR:  interval out of range
+select age(timestamp 'infinity', timestamp '-infinity');
+   age    
+----------
+ infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp 'infinity');
+    age    
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp '-infinity');
+ERROR:  interval out of range
index 2ca2101dd42f12425e2ef844a87ae8d8e6219c70..a084357480f4031d97de3af8cf7cecd7adf1cb6c 100644 (file)
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
                               '2020-01-02 03:00'::timestamptz,
                               '0 hour'::interval);
 ERROR:  step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR:  step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR:  step size cannot be infinite
 -- Interval crossing time shift for Europe/Warsaw timezone (with DST)
 SET TimeZone to 'UTC';
 SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3210,3 +3214,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
  Tue Jan 17 16:00:00 2017 PST
 (1 row)
 
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR:  interval out of range
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+ ?column? 
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+ ?column?  
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+ERROR:  interval out of range
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column? 
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?  
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+    age    
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity');
+   age    
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+ERROR:  interval out of range
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+   age    
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+    age    
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
+ERROR:  interval out of range
index 69a38df10b6ff4746b4f9bfe30b17481b1de177e..2201740c1858863254a02bc6f3ba94499adf1766 100644 (file)
@@ -2372,6 +2372,7 @@ create temp table datetimes(
     f_timestamp timestamp
 );
 insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
 (1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
 (2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
 (3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -2381,14 +2382,16 @@ insert into datetimes values
 (7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
 (8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
 (9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
 select id, f_time, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_time range between
              '70 min'::interval preceding and '2 hours'::interval following);
  id |  f_time  | first_value | last_value 
 ----+----------+-------------+------------
-  1 | 11:00:00 |           1 |          3
+  0 | 10:00:00 |           0 |          2
+  1 | 11:00:00 |           0 |          3
   2 | 12:00:00 |           1 |          4
   3 | 13:00:00 |           2 |          6
   4 | 14:00:00 |           3 |          6
@@ -2396,9 +2399,10 @@ window w as (order by f_time range between
   6 | 15:00:00 |           4 |          7
   7 | 17:00:00 |           7 |          9
   8 | 18:00:00 |           7 |         10
-  9 | 19:00:00 |           8 |         10
- 10 | 20:00:00 |           9 |         10
-(10 rows)
+  9 | 19:00:00 |           8 |         11
+ 10 | 20:00:00 |           9 |         11
+ 11 | 21:00:00 |          10 |         11
+(12 rows)
 
 select id, f_time, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2406,7 +2410,8 @@ window w as (order by f_time desc range between
              '70 min' preceding and '2 hours' following);
  id |  f_time  | first_value | last_value 
 ----+----------+-------------+------------
- 10 | 20:00:00 |          10 |          8
+ 11 | 21:00:00 |          11 |          9
+ 10 | 20:00:00 |          11 |          8
   9 | 19:00:00 |          10 |          7
   8 | 18:00:00 |           9 |          7
   7 | 17:00:00 |           8 |          5
@@ -2414,17 +2419,90 @@ window w as (order by f_time desc range between
   5 | 15:00:00 |           6 |          3
   4 | 14:00:00 |           6 |          2
   3 | 13:00:00 |           4 |          1
-  2 | 12:00:00 |           3 |          1
-  1 | 11:00:00 |           2 |          1
-(10 rows)
+  2 | 12:00:00 |           3 |          0
+  1 | 11:00:00 |           2 |          0
+  0 | 10:00:00 |           1 |          0
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+             '-70 min' preceding and '2 hours' following);  -- error, negative offset disallowed
+ERROR:  invalid preceding or following size in window function
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+ id |  f_time  | first_value | last_value 
+----+----------+-------------+------------
+  0 | 10:00:00 |           0 |         11
+  1 | 11:00:00 |           0 |         11
+  2 | 12:00:00 |           0 |         11
+  3 | 13:00:00 |           0 |         11
+  4 | 14:00:00 |           0 |         11
+  5 | 15:00:00 |           0 |         11
+  6 | 15:00:00 |           0 |         11
+  7 | 17:00:00 |           0 |         11
+  8 | 18:00:00 |           0 |         11
+  9 | 19:00:00 |           0 |         11
+ 10 | 20:00:00 |           0 |         11
+ 11 | 21:00:00 |           0 |         11
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+ id |  f_time  | first_value | last_value 
+----+----------+-------------+------------
+  0 | 10:00:00 |             |           
+  1 | 11:00:00 |             |           
+  2 | 12:00:00 |             |           
+  3 | 13:00:00 |             |           
+  4 | 14:00:00 |             |           
+  5 | 15:00:00 |             |           
+  6 | 15:00:00 |             |           
+  7 | 17:00:00 |             |           
+  8 | 18:00:00 |             |           
+  9 | 19:00:00 |             |           
+ 10 | 20:00:00 |             |           
+ 11 | 21:00:00 |             |           
+(12 rows)
 
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             'infinity'::interval following and 'infinity'::interval following);
+ id |  f_time  | first_value | last_value 
+----+----------+-------------+------------
+  0 | 10:00:00 |             |           
+  1 | 11:00:00 |             |           
+  2 | 12:00:00 |             |           
+  3 | 13:00:00 |             |           
+  4 | 14:00:00 |             |           
+  5 | 15:00:00 |             |           
+  6 | 15:00:00 |             |           
+  7 | 17:00:00 |             |           
+  8 | 18:00:00 |             |           
+  9 | 19:00:00 |             |           
+ 10 | 20:00:00 |             |           
+ 11 | 21:00:00 |             |           
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             '-infinity'::interval following and
+             'infinity'::interval following);  -- error, negative offset disallowed
+ERROR:  invalid preceding or following size in window function
 select id, f_timetz, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_timetz range between
              '70 min'::interval preceding and '2 hours'::interval following);
  id |  f_timetz   | first_value | last_value 
 ----+-------------+-------------+------------
-  1 | 11:00:00+01 |           1 |          3
+  0 | 10:00:00+01 |           0 |          2
+  1 | 11:00:00+01 |           0 |          3
   2 | 12:00:00+01 |           1 |          4
   3 | 13:00:00+01 |           2 |          6
   4 | 14:00:00+01 |           3 |          6
@@ -2432,9 +2510,10 @@ window w as (order by f_timetz range between
   6 | 15:00:00+01 |           4 |          7
   7 | 17:00:00+01 |           7 |          9
   8 | 18:00:00+01 |           7 |         10
-  9 | 19:00:00+01 |           8 |         10
- 10 | 20:00:00+01 |           9 |         10
-(10 rows)
+  9 | 19:00:00+01 |           8 |         11
+ 10 | 20:00:00+01 |           9 |         11
+ 11 | 21:00:00+01 |          10 |         11
+(12 rows)
 
 select id, f_timetz, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2442,7 +2521,8 @@ window w as (order by f_timetz desc range between
              '70 min' preceding and '2 hours' following);
  id |  f_timetz   | first_value | last_value 
 ----+-------------+-------------+------------
- 10 | 20:00:00+01 |          10 |          8
+ 11 | 21:00:00+01 |          11 |          9
+ 10 | 20:00:00+01 |          11 |          8
   9 | 19:00:00+01 |          10 |          7
   8 | 18:00:00+01 |           9 |          7
   7 | 17:00:00+01 |           8 |          5
@@ -2450,16 +2530,89 @@ window w as (order by f_timetz desc range between
   5 | 15:00:00+01 |           6 |          3
   4 | 14:00:00+01 |           6 |          2
   3 | 13:00:00+01 |           4 |          1
-  2 | 12:00:00+01 |           3 |          1
-  1 | 11:00:00+01 |           2 |          1
-(10 rows)
+  2 | 12:00:00+01 |           3 |          0
+  1 | 11:00:00+01 |           2 |          0
+  0 | 10:00:00+01 |           1 |          0
+(12 rows)
 
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+             '70 min' preceding and '-2 hours' following);  -- error, negative offset disallowed
+ERROR:  invalid preceding or following size in window function
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+ id |  f_timetz   | first_value | last_value 
+----+-------------+-------------+------------
+  0 | 10:00:00+01 |           0 |         11
+  1 | 11:00:00+01 |           0 |         11
+  2 | 12:00:00+01 |           0 |         11
+  3 | 13:00:00+01 |           0 |         11
+  4 | 14:00:00+01 |           0 |         11
+  5 | 15:00:00+01 |           0 |         11
+  6 | 15:00:00+01 |           0 |         11
+  7 | 17:00:00+01 |           0 |         11
+  8 | 18:00:00+01 |           0 |         11
+  9 | 19:00:00+01 |           0 |         11
+ 10 | 20:00:00+01 |           0 |         11
+ 11 | 21:00:00+01 |           0 |         11
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+ id |  f_timetz   | first_value | last_value 
+----+-------------+-------------+------------
+  0 | 10:00:00+01 |             |           
+  1 | 11:00:00+01 |             |           
+  2 | 12:00:00+01 |             |           
+  3 | 13:00:00+01 |             |           
+  4 | 14:00:00+01 |             |           
+  5 | 15:00:00+01 |             |           
+  6 | 15:00:00+01 |             |           
+  7 | 17:00:00+01 |             |           
+  8 | 18:00:00+01 |             |           
+  9 | 19:00:00+01 |             |           
+ 10 | 20:00:00+01 |             |           
+ 11 | 21:00:00+01 |             |           
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval following and 'infinity'::interval following);
+ id |  f_timetz   | first_value | last_value 
+----+-------------+-------------+------------
+  0 | 10:00:00+01 |             |           
+  1 | 11:00:00+01 |             |           
+  2 | 12:00:00+01 |             |           
+  3 | 13:00:00+01 |             |           
+  4 | 14:00:00+01 |             |           
+  5 | 15:00:00+01 |             |           
+  6 | 15:00:00+01 |             |           
+  7 | 17:00:00+01 |             |           
+  8 | 18:00:00+01 |             |           
+  9 | 19:00:00+01 |             |           
+ 10 | 20:00:00+01 |             |           
+ 11 | 21:00:00+01 |             |           
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval following and
+             '-infinity'::interval following);  -- error, negative offset disallowed
+ERROR:  invalid preceding or following size in window function
 select id, f_interval, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_interval range between
              '1 year'::interval preceding and '1 year'::interval following);
  id | f_interval | first_value | last_value 
 ----+------------+-------------+------------
+  0 | -infinity  |           0 |          0
   1 | @ 1 year   |           1 |          2
   2 | @ 2 years  |           1 |          3
   3 | @ 3 years  |           2 |          4
@@ -2470,7 +2623,8 @@ window w as (order by f_interval range between
   8 | @ 8 years  |           7 |          9
   9 | @ 9 years  |           8 |         10
  10 | @ 10 years |           9 |         10
-(10 rows)
+ 11 | infinity   |          11 |         11
+(12 rows)
 
 select id, f_interval, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2478,6 +2632,7 @@ window w as (order by f_interval desc range between
              '1 year' preceding and '1 year' following);
  id | f_interval | first_value | last_value 
 ----+------------+-------------+------------
+ 11 | infinity   |          11 |         11
  10 | @ 10 years |          10 |          9
   9 | @ 9 years  |          10 |          8
   8 | @ 8 years  |           9 |          7
@@ -2488,14 +2643,87 @@ window w as (order by f_interval desc range between
   3 | @ 3 years  |           4 |          2
   2 | @ 2 years  |           3 |          1
   1 | @ 1 year   |           2 |          1
-(10 rows)
+  0 | -infinity  |           0 |          0
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+             '-1 year' preceding and '1 year' following);  -- error, negative offset disallowed
+ERROR:  invalid preceding or following size in window function
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_interval | first_value | last_value 
+----+------------+-------------+------------
+  0 | -infinity  |           0 |         11
+  1 | @ 1 year   |           0 |         11
+  2 | @ 2 years  |           0 |         11
+  3 | @ 3 years  |           0 |         11
+  4 | @ 4 years  |           0 |         11
+  5 | @ 5 years  |           0 |         11
+  6 | @ 5 years  |           0 |         11
+  7 | @ 7 years  |           0 |         11
+  8 | @ 8 years  |           0 |         11
+  9 | @ 9 years  |           0 |         11
+ 10 | @ 10 years |           0 |         11
+ 11 | infinity   |           0 |         11
+(12 rows)
 
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_interval | first_value | last_value 
+----+------------+-------------+------------
+  0 | -infinity  |           0 |          0
+  1 | @ 1 year   |           0 |          0
+  2 | @ 2 years  |           0 |          0
+  3 | @ 3 years  |           0 |          0
+  4 | @ 4 years  |           0 |          0
+  5 | @ 5 years  |           0 |          0
+  6 | @ 5 years  |           0 |          0
+  7 | @ 7 years  |           0 |          0
+  8 | @ 8 years  |           0 |          0
+  9 | @ 9 years  |           0 |          0
+ 10 | @ 10 years |           0 |          0
+ 11 | infinity   |           0 |         11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             'infinity'::interval following and 'infinity'::interval following);
+ id | f_interval | first_value | last_value 
+----+------------+-------------+------------
+  0 | -infinity  |           0 |         11
+  1 | @ 1 year   |          11 |         11
+  2 | @ 2 years  |          11 |         11
+  3 | @ 3 years  |          11 |         11
+  4 | @ 4 years  |          11 |         11
+  5 | @ 5 years  |          11 |         11
+  6 | @ 5 years  |          11 |         11
+  7 | @ 7 years  |          11 |         11
+  8 | @ 8 years  |          11 |         11
+  9 | @ 9 years  |          11 |         11
+ 10 | @ 10 years |          11 |         11
+ 11 | infinity   |          11 |         11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             '-infinity'::interval following and
+             'infinity'::interval following);  -- error, negative offset disallowed
+ERROR:  invalid preceding or following size in window function
 select id, f_timestamptz, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_timestamptz range between
              '1 year'::interval preceding and '1 year'::interval following);
  id |        f_timestamptz         | first_value | last_value 
 ----+------------------------------+-------------+------------
+  0 | -infinity                    |           0 |          0
   1 | Thu Oct 19 02:23:54 2000 PDT |           1 |          3
   2 | Fri Oct 19 02:23:54 2001 PDT |           1 |          4
   3 | Fri Oct 19 02:23:54 2001 PDT |           1 |          4
@@ -2506,7 +2734,8 @@ window w as (order by f_timestamptz range between
   8 | Thu Oct 19 02:23:54 2006 PDT |           7 |          9
   9 | Fri Oct 19 02:23:54 2007 PDT |           8 |         10
  10 | Sun Oct 19 02:23:54 2008 PDT |           9 |         10
-(10 rows)
+ 11 | infinity                     |          11 |         11
+(12 rows)
 
 select id, f_timestamptz, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2514,6 +2743,7 @@ window w as (order by f_timestamptz desc range between
              '1 year' preceding and '1 year' following);
  id |        f_timestamptz         | first_value | last_value 
 ----+------------------------------+-------------+------------
+ 11 | infinity                     |          11 |         11
  10 | Sun Oct 19 02:23:54 2008 PDT |          10 |          9
   9 | Fri Oct 19 02:23:54 2007 PDT |          10 |          8
   8 | Thu Oct 19 02:23:54 2006 PDT |           9 |          7
@@ -2524,14 +2754,87 @@ window w as (order by f_timestamptz desc range between
   3 | Fri Oct 19 02:23:54 2001 PDT |           4 |          1
   2 | Fri Oct 19 02:23:54 2001 PDT |           4 |          1
   1 | Thu Oct 19 02:23:54 2000 PDT |           3 |          1
-(10 rows)
+  0 | -infinity                    |           0 |          0
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+             '1 year' preceding and '-1 year' following);  -- error, negative offset disallowed
+ERROR:  invalid preceding or following size in window function
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+ id |        f_timestamptz         | first_value | last_value 
+----+------------------------------+-------------+------------
+  0 | -infinity                    |           0 |         11
+  1 | Thu Oct 19 02:23:54 2000 PDT |           0 |         11
+  2 | Fri Oct 19 02:23:54 2001 PDT |           0 |         11
+  3 | Fri Oct 19 02:23:54 2001 PDT |           0 |         11
+  4 | Sat Oct 19 02:23:54 2002 PDT |           0 |         11
+  5 | Sun Oct 19 02:23:54 2003 PDT |           0 |         11
+  6 | Tue Oct 19 02:23:54 2004 PDT |           0 |         11
+  7 | Wed Oct 19 02:23:54 2005 PDT |           0 |         11
+  8 | Thu Oct 19 02:23:54 2006 PDT |           0 |         11
+  9 | Fri Oct 19 02:23:54 2007 PDT |           0 |         11
+ 10 | Sun Oct 19 02:23:54 2008 PDT |           0 |         11
+ 11 | infinity                     |           0 |         11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+ id |        f_timestamptz         | first_value | last_value 
+----+------------------------------+-------------+------------
+  0 | -infinity                    |           0 |          0
+  1 | Thu Oct 19 02:23:54 2000 PDT |           0 |          0
+  2 | Fri Oct 19 02:23:54 2001 PDT |           0 |          0
+  3 | Fri Oct 19 02:23:54 2001 PDT |           0 |          0
+  4 | Sat Oct 19 02:23:54 2002 PDT |           0 |          0
+  5 | Sun Oct 19 02:23:54 2003 PDT |           0 |          0
+  6 | Tue Oct 19 02:23:54 2004 PDT |           0 |          0
+  7 | Wed Oct 19 02:23:54 2005 PDT |           0 |          0
+  8 | Thu Oct 19 02:23:54 2006 PDT |           0 |          0
+  9 | Fri Oct 19 02:23:54 2007 PDT |           0 |          0
+ 10 | Sun Oct 19 02:23:54 2008 PDT |           0 |          0
+ 11 | infinity                     |           0 |         11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             'infinity'::interval following and 'infinity'::interval following);
+ id |        f_timestamptz         | first_value | last_value 
+----+------------------------------+-------------+------------
+  0 | -infinity                    |           0 |         11
+  1 | Thu Oct 19 02:23:54 2000 PDT |          11 |         11
+  2 | Fri Oct 19 02:23:54 2001 PDT |          11 |         11
+  3 | Fri Oct 19 02:23:54 2001 PDT |          11 |         11
+  4 | Sat Oct 19 02:23:54 2002 PDT |          11 |         11
+  5 | Sun Oct 19 02:23:54 2003 PDT |          11 |         11
+  6 | Tue Oct 19 02:23:54 2004 PDT |          11 |         11
+  7 | Wed Oct 19 02:23:54 2005 PDT |          11 |         11
+  8 | Thu Oct 19 02:23:54 2006 PDT |          11 |         11
+  9 | Fri Oct 19 02:23:54 2007 PDT |          11 |         11
+ 10 | Sun Oct 19 02:23:54 2008 PDT |          11 |         11
+ 11 | infinity                     |          11 |         11
+(12 rows)
 
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             '-infinity'::interval following and
+             'infinity'::interval following);  -- error, negative offset disallowed
+ERROR:  invalid preceding or following size in window function
 select id, f_timestamp, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_timestamp range between
              '1 year'::interval preceding and '1 year'::interval following);
  id |       f_timestamp        | first_value | last_value 
 ----+--------------------------+-------------+------------
+  0 | -infinity                |           0 |          0
   1 | Thu Oct 19 10:23:54 2000 |           1 |          3
   2 | Fri Oct 19 10:23:54 2001 |           1 |          4
   3 | Fri Oct 19 10:23:54 2001 |           1 |          4
@@ -2542,7 +2845,8 @@ window w as (order by f_timestamp range between
   8 | Thu Oct 19 10:23:54 2006 |           7 |          9
   9 | Fri Oct 19 10:23:54 2007 |           8 |         10
  10 | Sun Oct 19 10:23:54 2008 |           9 |         10
-(10 rows)
+ 11 | infinity                 |          11 |         11
+(12 rows)
 
 select id, f_timestamp, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -2550,6 +2854,7 @@ window w as (order by f_timestamp desc range between
              '1 year' preceding and '1 year' following);
  id |       f_timestamp        | first_value | last_value 
 ----+--------------------------+-------------+------------
+ 11 | infinity                 |          11 |         11
  10 | Sun Oct 19 10:23:54 2008 |          10 |          9
   9 | Fri Oct 19 10:23:54 2007 |          10 |          8
   8 | Thu Oct 19 10:23:54 2006 |           9 |          7
@@ -2560,8 +2865,80 @@ window w as (order by f_timestamp desc range between
   3 | Fri Oct 19 10:23:54 2001 |           4 |          1
   2 | Fri Oct 19 10:23:54 2001 |           4 |          1
   1 | Thu Oct 19 10:23:54 2000 |           3 |          1
-(10 rows)
+  0 | -infinity                |           0 |          0
+(12 rows)
 
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+             '-1 year' preceding and '1 year' following);  -- error, negative offset disallowed
+ERROR:  invalid preceding or following size in window function
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+ id |       f_timestamp        | first_value | last_value 
+----+--------------------------+-------------+------------
+  0 | -infinity                |           0 |         11
+  1 | Thu Oct 19 10:23:54 2000 |           0 |         11
+  2 | Fri Oct 19 10:23:54 2001 |           0 |         11
+  3 | Fri Oct 19 10:23:54 2001 |           0 |         11
+  4 | Sat Oct 19 10:23:54 2002 |           0 |         11
+  5 | Sun Oct 19 10:23:54 2003 |           0 |         11
+  6 | Tue Oct 19 10:23:54 2004 |           0 |         11
+  7 | Wed Oct 19 10:23:54 2005 |           0 |         11
+  8 | Thu Oct 19 10:23:54 2006 |           0 |         11
+  9 | Fri Oct 19 10:23:54 2007 |           0 |         11
+ 10 | Sun Oct 19 10:23:54 2008 |           0 |         11
+ 11 | infinity                 |           0 |         11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+ id |       f_timestamp        | first_value | last_value 
+----+--------------------------+-------------+------------
+  0 | -infinity                |           0 |          0
+  1 | Thu Oct 19 10:23:54 2000 |           0 |          0
+  2 | Fri Oct 19 10:23:54 2001 |           0 |          0
+  3 | Fri Oct 19 10:23:54 2001 |           0 |          0
+  4 | Sat Oct 19 10:23:54 2002 |           0 |          0
+  5 | Sun Oct 19 10:23:54 2003 |           0 |          0
+  6 | Tue Oct 19 10:23:54 2004 |           0 |          0
+  7 | Wed Oct 19 10:23:54 2005 |           0 |          0
+  8 | Thu Oct 19 10:23:54 2006 |           0 |          0
+  9 | Fri Oct 19 10:23:54 2007 |           0 |          0
+ 10 | Sun Oct 19 10:23:54 2008 |           0 |          0
+ 11 | infinity                 |           0 |         11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             'infinity'::interval following and 'infinity'::interval following);
+ id |       f_timestamp        | first_value | last_value 
+----+--------------------------+-------------+------------
+  0 | -infinity                |           0 |         11
+  1 | Thu Oct 19 10:23:54 2000 |          11 |         11
+  2 | Fri Oct 19 10:23:54 2001 |          11 |         11
+  3 | Fri Oct 19 10:23:54 2001 |          11 |         11
+  4 | Sat Oct 19 10:23:54 2002 |          11 |         11
+  5 | Sun Oct 19 10:23:54 2003 |          11 |         11
+  6 | Tue Oct 19 10:23:54 2004 |          11 |         11
+  7 | Wed Oct 19 10:23:54 2005 |          11 |         11
+  8 | Thu Oct 19 10:23:54 2006 |          11 |         11
+  9 | Fri Oct 19 10:23:54 2007 |          11 |         11
+ 10 | Sun Oct 19 10:23:54 2008 |          11 |         11
+ 11 | infinity                 |          11 |         11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             '-infinity'::interval following and
+             'infinity'::interval following);  -- error, negative offset disallowed
+ERROR:  invalid preceding or following size in window function
 -- RANGE offset PRECEDING/FOLLOWING error cases
 select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
        exclude ties), salary, enroll_date from empsalary;
@@ -4375,6 +4752,52 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
  4 | 
 (4 rows)
 
+-- moving aggregates over infinite intervals
+SELECT  x
+        ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+        ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+        ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+        ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+               ('infinity'::interval),
+               ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+               ('-infinity'::interval),
+               ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+               ('infinity'::interval),
+               ('6 days'::interval),
+               ('7 days'::interval),
+               (NULL::interval),
+               ('-infinity'::interval)) v(x);
+                                      x                                       |   curr_next_avg   |   prev_curr_avg   | curr_next_sum | prev_curr_sum 
+------------------------------------------------------------------------------+-------------------+-------------------+---------------+---------------
+                                                                              | infinity          |                   | infinity      | 
+ infinity                                                                     | infinity          | infinity          | infinity      | infinity
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago | -infinity         | infinity          | -infinity     | infinity
+ -infinity                                                                    | -infinity         | -infinity         | -infinity     | -infinity
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs     | infinity          | -infinity         | infinity      | -infinity
+ infinity                                                                     | infinity          | infinity          | infinity      | infinity
+ @ 6 days                                                                     | @ 6 days 12 hours | infinity          | @ 13 days     | infinity
+ @ 7 days                                                                     | @ 7 days          | @ 6 days 12 hours | @ 7 days      | @ 13 days
+                                                                              | -infinity         | @ 7 days          | -infinity     | @ 7 days
+ -infinity                                                                    | -infinity         | -infinity         | -infinity     | -infinity
+(10 rows)
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+               ('3 days'::interval),
+               ('infinity'::timestamptz - now()),
+               ('6 days'::interval),
+               ('-infinity'::interval)) v(x);
+ERROR:  interval out of range.
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+               ('3 days'::interval),
+               ('infinity'::timestamptz - now()),
+               ('6 days'::interval),
+               ('-infinity'::interval)) v(x);
+ERROR:  interval out of range.
 SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
  i | sum 
index b666dbad670b6582d627f8fdfc91bda5ca92980c..116106d30ffdb2b68049e93dee7e51cb9c8a14c9 100644 (file)
@@ -682,6 +682,25 @@ 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;
+
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) 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;
index 39a35a6b7ce1bdd801ff7ea2f70dc2f15d288756..252bce4b1ce186863de22fb7d6c0c7d5b2e24e8e 100644 (file)
@@ -213,10 +213,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
 
 SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
   FROM TIME_TBL t, INTERVAL_TBL i
+  WHERE isfinite(i.f1)
   ORDER BY 1,2;
 
 SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
   FROM TIMETZ_TBL t, INTERVAL_TBL i
+  WHERE isfinite(i.f1)
   ORDER BY 1,2;
 
 -- SQL9x OVERLAPS operator
@@ -293,7 +295,6 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
 
 SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
   FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
-  WHERE isfinite(d.f1)
   ORDER BY minus, "timestamp", "interval";
 
 SELECT d.f1 AS "timestamp",
index a0a373f08bd7f96839df58b739ca4775ea2b1846..caad291890c15abed6f22c753e801c86fdef02fc 100644 (file)
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
 SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
 SELECT INTERVAL '1.5 months' AS "One month 15 days";
 SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
 
 CREATE TABLE INTERVAL_TBL (f1 interval);
 
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
 INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
 INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
 INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
 
 -- badly formatted interval
 INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -66,6 +70,17 @@ SELECT r1.*, r2.*
    WHERE r1.f1 > r2.f1
    ORDER BY r1.f1, r2.f1;
 
+-- test unary minus
+
+SELECT f1, -f1 FROM INTERVAL_TBL;
+SELECT -('-2147483648 months'::interval); -- should fail
+SELECT -('-2147483647 months'::interval); -- ok
+SELECT -('-2147483648 days'::interval); -- should fail
+SELECT -('-2147483647 days'::interval); -- ok
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+SELECT -('-9223372036854775807 us'::interval); -- ok
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+
 -- Test intervals that are large enough to overflow 64 bits in comparisons
 CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
 INSERT INTO INTERVAL_TBL_OF (f1) VALUES
@@ -95,6 +110,9 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
 SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
 RESET enable_seqscan;
 
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
 DROP TABLE INTERVAL_TBL_OF;
 
 -- Test multiplication and division with intervals.
@@ -140,7 +158,7 @@ SELECT * FROM INTERVAL_TBL;
 -- known to change the allowed input syntax for type interval without
 -- updating pg_aggregate.agginitval
 
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
 
 -- test long interval input
 select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -528,13 +546,13 @@ select make_interval(mins := -1, secs := -9223372036800.0);
 
 -- test that INT_MIN number is formatted properly
 SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
 SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
 SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
 SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
 
 -- check that '30 days' equals '1 month' according to the hash function
 select '30 days'::interval = '1 month'::interval as t;
@@ -598,6 +616,157 @@ SELECT f1,
 -- internal overflow test case
 SELECT extract(epoch from interval '1000000000 days');
 
+--
+-- test infinite intervals
+--
+
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+  result text;
+BEGIN
+  EXECUTE 'select '||expr INTO result;
+  RETURN result;
+EXCEPTION WHEN OTHERS THEN
+  RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+
+SELECT d AS date, i AS interval,
+       eval(format('date %L + interval %L', d, i)) AS plus,
+       eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+             (date '1995-08-06'),
+             (date 'infinity')) AS t1(d),
+     (VALUES (interval '-infinity'),
+             (interval 'infinity')) AS t2(i);
+
+SELECT i1 AS interval1, i2 AS interval2,
+       eval(format('interval %L + interval %L', i1, i2)) AS plus,
+       eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+             (interval '2 months'),
+             (interval 'infinity')) AS t1(i1),
+     (VALUES (interval '-infinity'),
+             (interval '10 days'),
+             (interval 'infinity')) AS t2(i2);
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+
+SELECT t AS timestamp, i AS interval,
+       eval(format('timestamp %L + interval %L', t, i)) AS plus,
+       eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+             (timestamp '1995-08-06 12:30:15'),
+             (timestamp 'infinity')) AS t1(t),
+     (VALUES (interval '-infinity'),
+             (interval 'infinity')) AS t2(i);
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+       eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+       eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+             (timestamptz '1995-08-06 12:30:15 GMT'),
+             (timestamptz 'infinity')) AS t1(t),
+     (VALUES (interval '-infinity'),
+             (interval 'infinity')) AS t2(i);
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+SELECT time '11:27:42' + interval '-infinity';
+SELECT time '11:27:42' - interval 'infinity';
+SELECT time '11:27:42' - interval '-infinity';
+SELECT timetz '11:27:42' + interval 'infinity';
+SELECT timetz '11:27:42' + interval '-infinity';
+SELECT timetz '11:27:42' - interval 'infinity';
+SELECT timetz '11:27:42' - interval '-infinity';
+
+SELECT lhst.i lhs,
+    rhst.i rhs,
+    lhst.i < rhst.i AS lt,
+    lhst.i <= rhst.i AS le,
+    lhst.i = rhst.i AS eq,
+    lhst.i > rhst.i AS gt,
+    lhst.i >= rhst.i AS ge,
+    lhst.i <> rhst.i AS ne
+    FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+    WHERE NOT isfinite(lhst.i);
+
+SELECT i AS interval,
+    -i AS um,
+    i * 2.0 AS mul,
+    i * -2.0 AS mul_neg,
+    i * 'infinity' AS mul_inf,
+    i * '-infinity' AS mul_inf_neg,
+    i / 3.0 AS div,
+    i / -3.0 AS div_neg
+    FROM INFINITE_INTERVAL_TBL
+    WHERE NOT isfinite(i);
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+SELECT interval 'infinity' * 'nan';
+SELECT interval '-infinity' * 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+SELECT interval 'infinity' * 0;
+SELECT interval '-infinity' * 0;
+SELECT interval '0 days' * 'infinity'::float;
+SELECT interval '0 days' * '-infinity'::float;
+SELECT interval '5 days' * 'infinity'::float;
+SELECT interval '5 days' * '-infinity'::float;
+
+SELECT interval 'infinity' / 'infinity';
+SELECT interval 'infinity' / '-infinity';
+SELECT interval 'infinity' / 'nan';
+SELECT interval '-infinity' / 'infinity';
+SELECT interval '-infinity' / '-infinity';
+SELECT interval '-infinity' / 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+
+SELECT i AS interval, date_trunc('hour', i)
+    FROM INFINITE_INTERVAL_TBL
+    WHERE NOT isfinite(i);
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+    FROM INFINITE_INTERVAL_TBL
+    WHERE NOT isfinite(i);
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+
+SELECT 'infinity'::interval::time;
+SELECT '-infinity'::interval::time;
+
+SELECT to_char('infinity'::interval, 'YYYY');
+SELECT to_char('-infinity'::interval, 'YYYY');
+
 -- "ago" can only appear once at the end of an interval.
 SELECT INTERVAL '42 days 2 seconds ago ago';
 SELECT INTERVAL '2 minutes ago 5 days';
@@ -605,3 +774,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
 -- consecutive and dangling units are not allowed.
 SELECT INTERVAL 'hour 5 months';
 SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
index b9bcce9cfe35de0383dd5f2c74dc366d14fbe8d8..ea12ffd18d458a85c06a07319de9dde2134f4bce 100644 (file)
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
 select * from generate_series('2020-01-01 00:00'::timestamp,
                               '2020-01-02 03:00'::timestamp,
                               '0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
index cdc57bc16063083ce36ae27924234cd4de35af54..a2dcd5f5d8e2f711d20524fb30eebbe80ebe2146 100644 (file)
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
 select * from generate_series('2020-01-01 00:00'::timestamptz,
                               '2020-01-02 03:00'::timestamptz,
                               '0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
 
 -- Interval crossing time shift for Europe/Warsaw timezone (with DST)
 SET TimeZone to 'UTC';
@@ -642,3 +644,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
 explain (costs off)
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
 select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
index 9113a92ae0b1e1ac66e6b795f3324827dc6371bc..437e948d6c266dff77d8f1632b8ea03b8324df90 100644 (file)
@@ -673,6 +673,7 @@ create temp table datetimes(
 );
 
 insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
 (1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
 (2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
 (3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -682,7 +683,8 @@ insert into datetimes values
 (7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
 (8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
 (9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
 
 select id, f_time, first_value(id) over w, last_value(id) over w
 from datetimes
@@ -694,6 +696,32 @@ from datetimes
 window w as (order by f_time desc range between
              '70 min' preceding and '2 hours' following);
 
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+             '-70 min' preceding and '2 hours' following);  -- error, negative offset disallowed
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+             '-infinity'::interval following and
+             'infinity'::interval following);  -- error, negative offset disallowed
+
 select id, f_timetz, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_timetz range between
@@ -704,6 +732,32 @@ from datetimes
 window w as (order by f_timetz desc range between
              '70 min' preceding and '2 hours' following);
 
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+             '70 min' preceding and '-2 hours' following);  -- error, negative offset disallowed
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+             'infinity'::interval following and
+             '-infinity'::interval following);  -- error, negative offset disallowed
+
 select id, f_interval, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_interval range between
@@ -714,6 +768,32 @@ from datetimes
 window w as (order by f_interval desc range between
              '1 year' preceding and '1 year' following);
 
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+             '-1 year' preceding and '1 year' following);  -- error, negative offset disallowed
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+             '-infinity'::interval following and
+             'infinity'::interval following);  -- error, negative offset disallowed
+
 select id, f_timestamptz, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_timestamptz range between
@@ -724,6 +804,32 @@ from datetimes
 window w as (order by f_timestamptz desc range between
              '1 year' preceding and '1 year' following);
 
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+             '1 year' preceding and '-1 year' following);  -- error, negative offset disallowed
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+             '-infinity'::interval following and
+             'infinity'::interval following);  -- error, negative offset disallowed
+
 select id, f_timestamp, first_value(id) over w, last_value(id) over w
 from datetimes
 window w as (order by f_timestamp range between
@@ -734,6 +840,32 @@ from datetimes
 window w as (order by f_timestamp desc range between
              '1 year' preceding and '1 year' following);
 
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+             '-1 year' preceding and '1 year' following);  -- error, negative offset disallowed
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+             '-infinity'::interval following and
+             'infinity'::interval following);  -- error, negative offset disallowed
+
 -- RANGE offset PRECEDING/FOLLOWING error cases
 select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
        exclude ties), salary, enroll_date from empsalary;
@@ -1591,6 +1723,39 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
 SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
   FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
 
+-- moving aggregates over infinite intervals
+SELECT  x
+        ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+        ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+        ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+        ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+               ('infinity'::interval),
+               ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+               ('-infinity'::interval),
+               ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+               ('infinity'::interval),
+               ('6 days'::interval),
+               ('7 days'::interval),
+               (NULL::interval),
+               ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+               ('3 days'::interval),
+               ('infinity'::timestamptz - now()),
+               ('6 days'::interval),
+               ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+               ('3 days'::interval),
+               ('infinity'::timestamptz - now()),
+               ('6 days'::interval),
+               ('-infinity'::interval)) v(x);
+
 SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
   FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
 
index 92c0003ab19e15924af19e05069fb40b00d7ef7c..dba3498a13ec0666da99d7f60b681ce7b2eeb5e0 100644 (file)
@@ -1205,6 +1205,7 @@ IntegerSet
 InternalDefaultACL
 InternalGrant
 Interval
+IntervalAggState
 IntoClause
 InvalMessageArray
 InvalidationMsgsGroup