From 519fc1bd9e9d7b408903e44f55f83f6db30742b7 Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Tue, 14 Nov 2023 10:58:49 +0000 Subject: Support +/- infinity in the interval data type. 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 --- src/backend/utils/adt/date.c | 57 +- src/backend/utils/adt/datetime.c | 26 + src/backend/utils/adt/formatting.c | 2 +- src/backend/utils/adt/selfuncs.c | 4 + src/backend/utils/adt/timestamp.c | 1040 +++++++++++++++++++++++++++++------- 5 files changed, 926 insertions(+), 203 deletions(-) (limited to 'src/backend/utils') diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 544e1d32bfc..13745a0adc9 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -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), diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 267dfd37b2e..fca9a2a6e93 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -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; } diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c index 8131091f794..d176723d952 100644 --- a/src/backend/utils/adt/formatting.c +++ b/src/backend/utils/adt/formatting.c @@ -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); diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index c4fcd0076ea..4ea5415f204 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -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); diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 647b97aca69..45abb79e766 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -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); } -- cgit v1.2.3