From 784c97837355bedcf84f2aed9d2c608773a81d3a Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Thu, 28 Sep 2023 14:54:02 +0100 Subject: [PATCH v27 7/8] Code review of infinite interval support. --- doc/src/sgml/func.sgml | 5 +- src/backend/utils/adt/date.c | 27 +- src/backend/utils/adt/datetime.c | 4 +- src/backend/utils/adt/selfuncs.c | 16 +- src/backend/utils/adt/timestamp.c | 366 +++++++++---------- src/include/catalog/pg_aggregate.dat | 11 +- src/include/catalog/pg_proc.dat | 6 +- src/include/datatype/timestamp.h | 24 +- src/include/utils/timestamp.h | 3 - src/test/regress/expected/interval.out | 375 ++++++++------------ src/test/regress/expected/timestamp.out | 8 +- src/test/regress/expected/timestamptz.out | 8 +- src/test/regress/expected/window.out | 406 +++++++++++++++++++--- src/test/regress/sql/interval.sql | 131 +++---- src/test/regress/sql/window.sql | 93 ++++- 15 files changed, 912 insertions(+), 571 deletions(-) diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index c4f09df544..682e3713c6 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -10435,7 +10435,10 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40'); +/-Infinity for monotonically-increasing fields (epoch, julian, year, isoyear, decade, century, and millennium - for all types and hour and day just for interval). + for timestamp inputs; epoch, hour, + day, year, decade, + century, and millennium for + interval inputs). For other fields, NULL is returned. PostgreSQL versions before 9.6 returned zero for all cases of infinite input. diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index ad97f07601..cafe3abe21 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" @@ -2050,8 +2051,6 @@ time_mi_time(PG_FUNCTION_ARGS) result->day = 0; result->time = time1 - time2; - Assert(!INTERVAL_NOT_FINITE(result)); - PG_RETURN_INTERVAL_P(result); } @@ -2116,7 +2115,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, @@ -2126,13 +2126,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); @@ -2666,7 +2667,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, @@ -2676,13 +2678,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) diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index 73dadb747c..fca9a2a6e9 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -3613,9 +3613,9 @@ DecodeInterval(char **field, int *ftype, int nf, int range, return DTERR_BAD_FORMAT; /* - * infinity can not be followed by anything else. We + * Infinity cannot be followed by anything else. We * could allow "ago" to reverse the sign of infinity - * but that signed infinity is more intuitive. + * but using signed infinity is more intuitive. */ if (i != nf - 1) return DTERR_BAD_FORMAT; diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 6e87173846..4ea5415f20 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -4795,7 +4795,21 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure) case DATEOID: return date2timestamp_no_overflow(DatumGetDateADT(value)); case INTERVALOID: - return interval2timestamp_no_overflow(DatumGetIntervalP(value)); + { + Interval *interval = DatumGetIntervalP(value); + + /* + * 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); + } case TIMEOID: return DatumGetTimeADT(value); case TIMETZOID: diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 0b385fd72c..23d43094e8 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -18,7 +18,6 @@ #include #include #include -#include #include #include "access/xact.h" @@ -74,16 +73,16 @@ typedef struct } generate_series_timestamptz_fctx; /* - * The transition datatype for interval aggregates is declared as Internal. It's - * a pointer to a IntervalAggState allocated in the aggregate context. + * 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 processed intervals */ - Interval sumX; /* sum of processed intervals */ + 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 +Inf values */ - int64 nInfcount; /* count of -Inf values */ + int64 pInfcount; /* count of +infinity intervals */ + int64 nInfcount; /* count of -infinity intervals */ } IntervalAggState; #define IA_TOTAL_COUNT(ia) \ @@ -96,11 +95,12 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod, static TimestampTz timestamp2timestamptz(Timestamp timestamp); static Timestamp timestamptz2timestamp(TimestampTz timestamp); -static void interval_um_internal(Interval *interval, Interval *result); -static void finite_interval_pl(Interval *result, Interval *span1, - Interval *span2); -static void finite_interval_mi(Interval *result, Interval *span1, - Interval *span2); +static void EncodeSpecialInterval(const Interval *interval, char *str); +static void interval_um_internal(const Interval *interval, Interval *result); +static void finite_interval_pl(const Interval *span1, const Interval *span2, + Interval *result); +static void finite_interval_mi(const Interval *span1, const Interval *span2, + Interval *result); /* common code for timestamptypmodin and timestamptztypmodin */ static int32 @@ -1604,8 +1604,8 @@ EncodeSpecialTimestamp(Timestamp dt, char *str) elog(ERROR, "invalid argument for EncodeSpecialTimestamp"); } -void -EncodeSpecialInterval(Interval *interval, char *str) +static void +EncodeSpecialInterval(const Interval *interval, char *str) { if (INTERVAL_IS_NOBEGIN(interval)) strcpy(str, EARLY); @@ -2101,6 +2101,13 @@ itm2interval(struct pg_itm *itm, Interval *span) /* 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) @@ -2112,8 +2119,6 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span) span->month = (int32) total_months; span->day = itm_in->tm_mday; span->time = itm_in->tm_usec; - if (INTERVAL_NOT_FINITE(span)) - return -1; return 0; } @@ -2280,29 +2285,6 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup) } #endif -double -interval2timestamp_no_overflow(Interval *interval) -{ - double result; - - if (INTERVAL_IS_NOBEGIN(interval)) - result = -DBL_MAX; - else if (INTERVAL_IS_NOEND(interval)) - result = DBL_MAX; - else - { - /* - * 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. - */ - return interval->time + interval->day * (double) USECS_PER_DAY + - interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY); - } - - return result; -} - Datum timestamp_sortsupport(PG_FUNCTION_ARGS) { @@ -2526,6 +2508,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) { @@ -2801,14 +2792,14 @@ timestamp_mi(PG_FUNCTION_ARGS) /* * Subtracting two infinite timestamps with different signs results in an * infinite interval with the same sign as the left operand. Subtracting - * two infinte timestamps with the same sign results in an error. + * two infinite timestamps with the same sign results in an error. */ if (TIMESTAMP_IS_NOBEGIN(dt1)) { if (TIMESTAMP_IS_NOBEGIN(dt2)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + errmsg("interval out of range"))); else INTERVAL_NOBEGIN(result); } @@ -2817,7 +2808,7 @@ timestamp_mi(PG_FUNCTION_ARGS) if (TIMESTAMP_IS_NOEND(dt2)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + errmsg("interval out of range"))); else INTERVAL_NOEND(result); } @@ -3068,16 +3059,16 @@ timestamp_pl_interval(PG_FUNCTION_ARGS) Timestamp result; /* - * Adding two infinites with the same sign results in an infinite - * timestamp with the same sign. Adding two infintes with different signs - * results in an error. + * Adding two infinities with the same sign results in an infinite + * timestamp with the same sign. Adding two infinities with different + * signs results in an error. */ if (INTERVAL_IS_NOBEGIN(span)) { if (TIMESTAMP_IS_NOEND(timestamp)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("interval out of range"))); + errmsg("timestamp out of range"))); else TIMESTAMP_NOBEGIN(result); } @@ -3086,7 +3077,7 @@ timestamp_pl_interval(PG_FUNCTION_ARGS) if (TIMESTAMP_IS_NOBEGIN(timestamp)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("interval out of range"))); + errmsg("timestamp out of range"))); else TIMESTAMP_NOEND(result); } @@ -3197,16 +3188,16 @@ timestamptz_pl_interval_internal(TimestampTz timestamp, int tz; /* - * Adding two infinites with the same sign results in an infinite - * timestamp with the same sign. Adding two infintes with different signs - * results in an error. + * Adding two infinities with the same sign results in an infinite + * timestamp with the same sign. Adding two infinities with different + * signs results in an error. */ if (INTERVAL_IS_NOBEGIN(span)) { if (TIMESTAMP_IS_NOEND(timestamp)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("interval out of range"))); + errmsg("timestamp out of range"))); else TIMESTAMP_NOBEGIN(result); } @@ -3215,7 +3206,7 @@ timestamptz_pl_interval_internal(TimestampTz timestamp, if (TIMESTAMP_IS_NOBEGIN(timestamp)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("interval out of range"))); + errmsg("timestamp out of range"))); else TIMESTAMP_NOEND(result); } @@ -3362,16 +3353,12 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS) /* Negates the given interval */ static void -interval_um_internal(Interval *interval, Interval *result) +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 if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("interval out of range"))); else { result->time = -interval->time; @@ -3438,8 +3425,8 @@ interval_pl(PG_FUNCTION_ARGS) /* * Adding two infinite intervals with the same signs results in an - * infinite interval with the same sign. Adding two infinte intervals with - * different signs results in an error. + * infinite interval with the same sign. Adding two infinite intervals + * with different signs results in an error. */ if (INTERVAL_IS_NOBEGIN(span1)) { @@ -3462,7 +3449,7 @@ interval_pl(PG_FUNCTION_ARGS) else if (INTERVAL_NOT_FINITE(span2)) memcpy(result, span2, sizeof(Interval)); else - finite_interval_pl(result, span1, span2); + finite_interval_pl(span1, span2, result); PG_RETURN_INTERVAL_P(result); } @@ -3479,7 +3466,7 @@ interval_mi(PG_FUNCTION_ARGS) /* * Subtracting two infinite intervals with different signs results in an * infinite interval with the same sign as the left operand. Subtracting - * two infinte intervals with the same sign results in an error. + * two infinite intervals with the same sign results in an error. */ if (INTERVAL_IS_NOBEGIN(span1)) { @@ -3504,7 +3491,7 @@ interval_mi(PG_FUNCTION_ARGS) else if (INTERVAL_IS_NOEND(span2)) INTERVAL_NOBEGIN(result); else - finite_interval_mi(result, span1, span2); + finite_interval_mi(span1, span2, result); PG_RETURN_INTERVAL_P(result); } @@ -3552,13 +3539,11 @@ interval_mul(PG_FUNCTION_ARGS) } else if (isinf(factor)) { - Interval zero; int factor_sign, result_sign; factor_sign = factor >= 0.0 ? 1 : -1; - memset(&zero, 0, sizeof(zero)); - result_sign = interval_cmp_internal(span, &zero) * factor_sign; + result_sign = interval_sign(span) * factor_sign; if (result_sign == 0) ereport(ERROR, @@ -3740,11 +3725,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); @@ -3767,11 +3762,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, @@ -3798,11 +3803,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, @@ -3848,21 +3863,13 @@ makeIntervalAggState(FunctionCallInfo fcinfo) } /* - * Functions to add or subtract two finite intervals. + * Functions to add or subtract finite intervals. * - * We handle non-finite intervals in different ways when accumulating/discarding - * intervals and in actual mathematical operations respectively. But the - * addition or subtraction of finite intervals have same implementation - * respectively in both these cases. - * - * Addition/Subtraction of span1 and span2 is stored in the result. It is fine - * to pass either of span1 or span2 as a result pointer. In such a case result - * will overwrite the given value. - * - * If the result is not a valid interval, the function throws an error. + * These are used for normal arithmetic and aggregation of finite intervals + * only. Non-finite intervals require special handling. */ static void -finite_interval_pl(Interval *result, Interval *span1, Interval *span2) +finite_interval_pl(const Interval *span1, const Interval *span2, Interval *result) { Assert(!INTERVAL_NOT_FINITE(span1)); Assert(!INTERVAL_NOT_FINITE(span2)); @@ -3889,7 +3896,7 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2) } static void -finite_interval_mi(Interval *result, Interval *span1, Interval *span2) +finite_interval_mi(const Interval *span1, const Interval *span2, Interval *result) { Assert(!INTERVAL_NOT_FINITE(span1)); Assert(!INTERVAL_NOT_FINITE(span2)); @@ -3921,26 +3928,56 @@ finite_interval_mi(Interval *result, Interval *span1, Interval *span2) static void do_interval_accum(IntervalAggState *state, Interval *newval) { - /* Count -infinity inputs separately from all else */ + /* Infinite inputs are counted separately, and do not affect "N" */ if (INTERVAL_IS_NOBEGIN(newval)) { state->nInfcount++; return; } - /* Count infinity inputs separately from all else */ if (INTERVAL_IS_NOEND(newval)) { state->pInfcount++; return; } - finite_interval_pl(&state->sumX, &state->sumX, newval); + finite_interval_pl(&state->sumX, newval, &state->sumX); state->N++; } /* - * Transition function for interval aggregates. + * 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 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) @@ -3960,10 +3997,10 @@ interval_avg_accum(PG_FUNCTION_ARGS) } /* - * Combine function for interval aggregates. + * Combine function for sum() and avg() interval aggregates. * - * Combine the given internal aggregate states and place the combination in the - * first argument. + * Combine the given internal aggregate states and place the combination in + * the first argument. */ Datum interval_avg_combine(PG_FUNCTION_ARGS) @@ -3999,7 +4036,7 @@ interval_avg_combine(PG_FUNCTION_ARGS) /* Accumulate finite interval values, if any. */ if (state2->N > 0) - finite_interval_pl(&state1->sumX, &state1->sumX, &state2->sumX); + finite_interval_pl(&state1->sumX, &state2->sumX, &state1->sumX); PG_RETURN_POINTER(state1); } @@ -4019,20 +4056,26 @@ interval_avg_serialize(PG_FUNCTION_ARGS) if (!AggCheckCallContext(fcinfo, NULL)) elog(ERROR, "aggregate function called in non-aggregate context"); - /* TODO: Handle NULL inputs? */ state = (IntervalAggState *) PG_GETARG_POINTER(0); + pq_begintypsend(&buf); + /* N */ pq_sendint64(&buf, state->N); - /* Finite interval value */ + + /* sumX */ pq_sendint64(&buf, state->sumX.time); pq_sendint32(&buf, state->sumX.day); pq_sendint32(&buf, state->sumX.month); + /* pInfcount */ pq_sendint64(&buf, state->pInfcount); + /* nInfcount */ pq_sendint64(&buf, state->nInfcount); + result = pq_endtypsend(&buf); + PG_RETURN_BYTEA_P(result); } @@ -4064,10 +4107,10 @@ interval_avg_deserialize(PG_FUNCTION_ARGS) /* N */ result->N = pq_getmsgint64(&buf); - /* Interval struct elements, one by one. */ + /* sumX */ result->sumX.time = pq_getmsgint64(&buf); - result->sumX.day = pq_getmsgint(&buf, sizeof(result->sumX.day)); - result->sumX.month = pq_getmsgint(&buf, sizeof(result->sumX.month)); + result->sumX.day = pq_getmsgint(&buf, 4); + result->sumX.month = pq_getmsgint(&buf, 4); /* pInfcount */ result->pInfcount = pq_getmsgint64(&buf); @@ -4081,42 +4124,10 @@ interval_avg_deserialize(PG_FUNCTION_ARGS) } /* - * Remove the given interval value from the aggregated state. - */ -static void -do_interval_discard(IntervalAggState *state, Interval *newval) -{ - /* Count -infinity inputs separately from all else */ - if (INTERVAL_IS_NOBEGIN(newval)) - { - state->nInfcount--; - return; - } - - /* Count infinity inputs separately from all else */ - if (INTERVAL_IS_NOEND(newval)) - { - state->pInfcount--; - return; - } - - /* Handle to be discarded finite value. */ - state->N--; - if (state->N > 0) - finite_interval_mi(&state->sumX, &state->sumX, newval); - else - { - /* All values discarded, reset the state */ - Assert(state->N == 0); - memset(&state->sumX, 0, sizeof(state->sumX)); - } -} - -/* - * Generic inverse transition function for interval aggregates + * Inverse transition function for sum() and avg() interval aggregates. */ Datum -interval_accum_inv(PG_FUNCTION_ARGS) +interval_avg_accum_inv(PG_FUNCTION_ARGS) { IntervalAggState *state; @@ -4124,7 +4135,7 @@ interval_accum_inv(PG_FUNCTION_ARGS) /* Should not get here with no state */ if (state == NULL) - elog(ERROR, "interval_accum_inv called with NULL state"); + elog(ERROR, "interval_avg_accum_inv called with NULL state"); if (!PG_ARGISNULL(1)) do_interval_discard(state, PG_GETARG_INTERVAL_P(1)); @@ -4137,38 +4148,39 @@ Datum interval_avg(PG_FUNCTION_ARGS) { IntervalAggState *state; - double N_datum; - Interval *sumX; - sumX = (Interval *) palloc(sizeof(Interval)); 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(); - /* adding plus and minus infinities gives error */ - if (state->pInfcount > 0 && state->nInfcount > 0) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("interval out of range."))); - - if (state->pInfcount > 0) + /* + * 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_NOEND(sumX); - PG_RETURN_INTERVAL_P(sumX); - } + Interval *result; - if (state->nInfcount > 0) - { - INTERVAL_NOBEGIN(sumX); - PG_RETURN_INTERVAL_P(sumX); - } + 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); - N_datum = (double) state->N; - sumX = &state->sumX; + PG_RETURN_INTERVAL_P(result); + } - PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX), - Float8GetDatum(N_datum))); + return DirectFunctionCall2(interval_div, + IntervalPGetDatum(&state->sumX), + Float8GetDatum((double) state->N)); } /* sum(interval) aggregate final function */ @@ -4178,33 +4190,31 @@ interval_sum(PG_FUNCTION_ARGS) IntervalAggState *state; Interval *result; - result = (Interval *) palloc(sizeof(Interval)); - 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(); - /* adding plus and minus interval infinities is not possible */ + /* + * 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); - PG_RETURN_INTERVAL_P(result); - } - - if (state->nInfcount > 0) - { + else if (state->nInfcount > 0) INTERVAL_NOBEGIN(result); - PG_RETURN_INTERVAL_P(result); - } + else + memcpy(result, &state->sumX, sizeof(Interval)); - memcpy(result, &state->sumX, sizeof(Interval)); PG_RETURN_INTERVAL_P(result); } @@ -4234,14 +4244,14 @@ timestamp_age(PG_FUNCTION_ARGS) /* * Subtracting two infinite timestamps with different signs results in an * infinite interval with the same sign as the left operand. Subtracting - * two infinte timestamps with the same sign results in an error. + * two infinite timestamps with the same sign results in an error. */ if (TIMESTAMP_IS_NOBEGIN(dt1)) { if (TIMESTAMP_IS_NOBEGIN(dt2)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + errmsg("interval out of range"))); else INTERVAL_NOBEGIN(result); } @@ -4250,7 +4260,7 @@ timestamp_age(PG_FUNCTION_ARGS) if (TIMESTAMP_IS_NOEND(dt2)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + errmsg("interval out of range"))); else INTERVAL_NOEND(result); } @@ -4381,14 +4391,14 @@ timestamptz_age(PG_FUNCTION_ARGS) /* * Subtracting two infinite timestamps with different signs results in an * infinite interval with the same sign as the left operand. Subtracting - * two infinte timestamps with the same sign results in an error. + * two infinite timestamps with the same sign results in an error. */ if (TIMESTAMP_IS_NOBEGIN(dt1)) { if (TIMESTAMP_IS_NOBEGIN(dt2)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + errmsg("interval out of range"))); else INTERVAL_NOBEGIN(result); } @@ -4397,7 +4407,7 @@ timestamptz_age(PG_FUNCTION_ARGS) if (TIMESTAMP_IS_NOEND(dt2)) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + errmsg("interval out of range"))); else INTERVAL_NOEND(result); } @@ -5829,17 +5839,16 @@ extract_timestamptz(PG_FUNCTION_ARGS) /* * 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). + * 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, bool isTz) +NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative) { if ((type != UNITS) && (type != RESERV)) ereport(ERROR, @@ -5906,8 +5915,7 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric) if (INTERVAL_NOT_FINITE(interval)) { double r = NonFiniteIntervalPart(type, val, lowunits, - INTERVAL_IS_NOBEGIN(interval), - false); + INTERVAL_IS_NOBEGIN(interval)); if (r != 0.0) { @@ -6453,7 +6461,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(); @@ -6476,7 +6483,7 @@ 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, @@ -6539,7 +6546,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(); @@ -6563,7 +6569,7 @@ 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, diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat index 0e62c3f7a6..e1a17cddd8 100644 --- a/src/include/catalog/pg_aggregate.dat +++ b/src/include/catalog/pg_aggregate.dat @@ -47,11 +47,9 @@ aggfinalfn => 'interval_avg', aggcombinefn => 'interval_avg_combine', aggserialfn => 'interval_avg_serialize', aggdeserialfn => 'interval_avg_deserialize', - aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv', + aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv', aggmfinalfn => 'interval_avg', aggtranstype => 'internal', - aggmtranstype => 'internal', aggtransspace => '128', - aggmtransspace => '128' -}, + aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40' }, # sum { aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum', @@ -79,10 +77,9 @@ aggfinalfn => 'interval_sum', aggcombinefn => 'interval_avg_combine', aggserialfn => 'interval_avg_serialize', aggdeserialfn => 'interval_avg_deserialize', - aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv', + aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv', aggmfinalfn => 'interval_sum', aggtranstype => 'internal', - aggmtranstype => 'internal', aggtransspace => '128', - aggmtransspace => '128'}, + aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40'}, { aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum', aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine', aggserialfn => 'numeric_avg_serialize', diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 7d66de5043..455ed16c79 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -4925,9 +4925,9 @@ prorettype => 'internal', proargtypes => 'internal internal', prosrc => 'interval_avg_combine' }, { oid => '3549', descr => 'aggregate transition function', - proname => 'interval_accum_inv', proisstrict => 'f', + proname => 'interval_avg_accum_inv', proisstrict => 'f', prorettype => 'internal', proargtypes => 'internal interval', - prosrc => 'interval_accum_inv' }, + prosrc => 'interval_avg_accum_inv' }, { oid => '3813', descr => 'aggregate serial function', proname => 'interval_avg_serialize', prorettype => 'bytea', proargtypes => 'internal', prosrc => 'interval_avg_serialize' }, @@ -4935,7 +4935,7 @@ proname => 'interval_avg_deserialize', prorettype => 'internal', proargtypes => 'bytea internal', prosrc => 'interval_avg_deserialize' }, { oid => '1844', descr => 'aggregate final function', - proname => 'interval_avg', prorettype => 'interval', + proname => 'interval_avg', proisstrict => 'f', prorettype => 'interval', proargtypes => 'internal', prosrc => 'interval_avg' }, { oid => '8069', descr => 'aggregate final function', proname => 'interval_sum', proisstrict => 'f', prorettype => 'interval', diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h index f9bd30d225..b63acc0a2f 100644 --- a/src/include/datatype/timestamp.h +++ b/src/include/datatype/timestamp.h @@ -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,23 +168,29 @@ 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; \ + (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) +#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; \ + (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) +#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)) diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h index fe713fb6dd..c4dd96c8c9 100644 --- a/src/include/utils/timestamp.h +++ b/src/include/utils/timestamp.h @@ -118,7 +118,6 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm, fsec_t *fsec, const char **tzn, pg_tz *attimezone); extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec); -extern void EncodeSpecialInterval(Interval *interval, char *str); extern void interval2itm(Interval span, struct pg_itm *itm); extern int itm2interval(struct pg_itm *itm, Interval *span); extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span); @@ -128,8 +127,6 @@ extern void GetEpochTime(struct pg_tm *tm); extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2); -extern double interval2timestamp_no_overflow(Interval *interval); - /* timestamp comparison works for timestamptz also */ #define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2) diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index 8bd72f60b0..00cd3e84f1 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -284,11 +284,11 @@ SELECT r1.*, r2.* -- 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 - ('2147483647 days 2147483646 months'), - ('2147483647 days -2147483647 months'), + ('2147483647 days 2147483647 months'), + ('2147483647 days -2147483648 months'), ('1 year'), - ('-2147483648 days 2147483646 months'), - ('-2147483648 days -2147483647 months'); + ('-2147483648 days 2147483647 months'), + ('-2147483648 days -2147483648 months'); -- these should fail as out-of-range INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days'); ERROR: interval field value out of range: "2147483648 days" @@ -315,16 +315,16 @@ SELECT r1.*, r2.* ORDER BY r1.f1, r2.f1; f1 | f1 -------------------------------------------+------------------------------------------- - -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days - 1 year | -178956970 years -7 mons -2147483648 days - 1 year | -178956970 years -7 mons +2147483647 days - 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days - 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days - 178956970 years 6 mons -2147483648 days | 1 year - 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days - 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days - 178956970 years 6 mons 2147483647 days | 1 year - 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days + -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days + 1 year | -178956970 years -8 mons -2147483648 days + 1 year | -178956970 years -8 mons +2147483647 days + 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days + 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days + 178956970 years 7 mons -2147483648 days | 1 year + 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days + 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days + 178956970 years 7 mons 2147483647 days | 1 year + 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days (10 rows) CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1); @@ -339,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1; SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1; f1 ------------------------------------------- - -178956970 years -7 mons -2147483648 days - -178956970 years -7 mons +2147483647 days + -178956970 years -8 mons -2147483648 days + -178956970 years -8 mons +2147483647 days 1 year - 178956970 years 6 mons -2147483648 days - 178956970 years 6 mons 2147483647 days + 178956970 years 7 mons -2147483648 days + 178956970 years 7 mons 2147483647 days (5 rows) RESET enable_seqscan; @@ -485,9 +485,7 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as SELECT justify_hours(interval '2147483647 days 24 hrs'); ERROR: interval out of range -SELECT justify_days(interval '2147483646 months 30 days'); -ERROR: interval out of range -SELECT justify_days(interval '2147483646 months 60 days'); +SELECT justify_days(interval '2147483647 months 30 days'); ERROR: interval out of range -- test justify_interval() SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour"; @@ -508,29 +506,25 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs'); @ 5965232 years 4 mons 9 days ago (1 row) -SELECT justify_interval(interval '2147483646 months 30 days'); -ERROR: interval out of range -SELECT justify_interval(interval '2147483646 months 60 days'); -ERROR: interval out of range -SELECT justify_interval(interval '-2147483647 months -30 days'); +SELECT justify_interval(interval '2147483647 months 30 days'); ERROR: interval out of range -SELECT justify_interval(interval '-2147483647 months -60 days'); +SELECT justify_interval(interval '-2147483648 months -30 days'); ERROR: interval out of range -SELECT justify_interval(interval '2147483646 months 30 days -24 hrs'); +SELECT justify_interval(interval '2147483647 months 30 days -24 hrs'); justify_interval ---------------------------------- - @ 178956970 years 6 mons 29 days + @ 178956970 years 7 mons 29 days (1 row) -SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs'); +SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs'); justify_interval -------------------------------------- - @ 178956970 years 7 mons 29 days ago + @ 178956970 years 8 mons 29 days ago (1 row) -SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs'); +SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs'); ERROR: interval out of range -SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs'); +SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs'); ERROR: interval out of range -- test fractional second input, and detection of duplicate units SET DATESTYLE = 'ISO'; @@ -1882,233 +1876,144 @@ SELECT extract(epoch from interval '1000000000 days'); 86400000000000.000000 (1 row) --- infinite intervals -SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us'; -ERROR: interval out of range -LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337... - ^ -SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us'; -ERROR: interval out of range -LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203... - ^ -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) - -SELECT date '1995-08-06' + interval 'infinity'; - ?column? ----------- - infinity -(1 row) - -SELECT date '1995-08-06' + interval '-infinity'; - ?column? ------------ - -infinity -(1 row) - -SELECT date '1995-08-06' - interval 'infinity'; - ?column? ------------ - -infinity -(1 row) - -SELECT date '1995-08-06' - interval '-infinity'; - ?column? ----------- - infinity -(1 row) - -SELECT date 'infinity' + interval 'infinity'; - ?column? ----------- - infinity -(1 row) - -SELECT date 'infinity' + interval '-infinity'; -ERROR: interval out of range -SELECT date '-infinity' + interval 'infinity'; -ERROR: interval out of range -SELECT date '-infinity' + interval '-infinity'; - ?column? ------------ - -infinity +-- +-- 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 date 'infinity' - interval 'infinity'; -ERROR: interval out of range -SELECT date 'infinity' - interval '-infinity'; - ?column? ----------- - infinity +SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us'; + interval +-------------------------------------------------------------------------- + @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs (1 row) -SELECT date '-infinity' - interval 'infinity'; - ?column? +-- infinite intervals +SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us'; + interval ----------- -infinity (1 row) -SELECT date '-infinity' - interval '-infinity'; -ERROR: interval out of range -SELECT interval 'infinity' + interval 'infinity'; - ?column? +SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us'; + interval ---------- infinity (1 row) -SELECT interval 'infinity' + interval '-infinity'; -ERROR: interval out of range -SELECT interval '-infinity' + interval 'infinity'; -ERROR: interval out of range -SELECT interval '-infinity' + interval '-infinity'; - ?column? ------------ - -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) -SELECT interval 'infinity' + interval '10 days'; - ?column? ----------- - infinity -(1 row) +-- 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 interval '-infinity' + interval '10 days'; - ?column? ------------ - -infinity -(1 row) +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 'infinity' - interval 'infinity'; -ERROR: interval out of range -SELECT interval 'infinity' - interval '-infinity'; - ?column? ----------- - infinity -(1 row) - -SELECT interval '-infinity' - interval 'infinity'; - ?column? ------------ - -infinity -(1 row) - -SELECT interval '-infinity' - interval '-infinity'; -ERROR: interval out of range -SELECT interval 'infinity' - interval '10 days'; - ?column? ----------- - infinity -(1 row) - -SELECT interval '-infinity' - interval '10 days'; - ?column? ------------ - -infinity -(1 row) - 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 timestamp 'infinity' + interval 'infinity'; - ?column? ----------- - infinity -(1 row) - -SELECT timestamp 'infinity' + interval '-infinity'; -ERROR: interval out of range -SELECT timestamp '-infinity' + interval 'infinity'; -ERROR: interval out of range -SELECT timestamp '-infinity' + interval '-infinity'; - ?column? ------------ - -infinity -(1 row) - -SELECT timestamp 'infinity' - interval 'infinity'; -ERROR: interval out of range -SELECT timestamp 'infinity' - interval '-infinity'; - ?column? ----------- - infinity -(1 row) - -SELECT timestamp '-infinity' - interval 'infinity'; - ?column? ------------ - -infinity -(1 row) - -SELECT timestamp '-infinity' - interval '-infinity'; -ERROR: interval out of range -SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity'; - ?column? ----------- - infinity -(1 row) - -SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity'; - ?column? ------------ - -infinity -(1 row) - -SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity'; - ?column? ------------ - -infinity -(1 row) - -SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity'; - ?column? ----------- - infinity -(1 row) - -SELECT timestamptz 'infinity' + interval 'infinity'; - ?column? ----------- - infinity -(1 row) - -SELECT timestamptz 'infinity' + interval '-infinity'; -ERROR: interval out of range -SELECT timestamptz '-infinity' + interval 'infinity'; -ERROR: interval out of range -SELECT timestamptz '-infinity' + interval '-infinity'; - ?column? ------------ - -infinity -(1 row) - -SELECT timestamptz 'infinity' - interval 'infinity'; -ERROR: interval out of range -SELECT timestamptz 'infinity' - interval '-infinity'; - ?column? ----------- - infinity -(1 row) +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 timestamptz '-infinity' - interval 'infinity'; - ?column? ------------ - -infinity -(1 row) +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) -SELECT timestamptz '-infinity' - interval '-infinity'; -ERROR: interval out of range +-- 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'; diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out index 53542e076b..835f0e5762 100644 --- a/src/test/regress/expected/timestamp.out +++ b/src/test/regress/expected/timestamp.out @@ -2131,7 +2131,7 @@ select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12 ERROR: step size cannot be infinite -- test arithmetic with infinite timestamps select timestamp 'infinity' - timestamp 'infinity'; -ERROR: timestamp out of range +ERROR: interval out of range select timestamp 'infinity' - timestamp '-infinity'; ?column? ---------- @@ -2145,7 +2145,7 @@ select timestamp '-infinity' - timestamp 'infinity'; (1 row) select timestamp '-infinity' - timestamp '-infinity'; -ERROR: timestamp out of range +ERROR: interval out of range select timestamp 'infinity' - timestamp '1995-08-06 12:12:12'; ?column? ---------- @@ -2172,7 +2172,7 @@ select age(timestamp '-infinity'); (1 row) select age(timestamp 'infinity', timestamp 'infinity'); -ERROR: timestamp out of range +ERROR: interval out of range select age(timestamp 'infinity', timestamp '-infinity'); age ---------- @@ -2186,4 +2186,4 @@ select age(timestamp '-infinity', timestamp 'infinity'); (1 row) select age(timestamp '-infinity', timestamp '-infinity'); -ERROR: timestamp out of range +ERROR: interval out of range diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out index e3f0d918b9..a084357480 100644 --- a/src/test/regress/expected/timestamptz.out +++ b/src/test/regress/expected/timestamptz.out @@ -3216,7 +3216,7 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00'; -- test arithmetic with infinite timestamps SELECT timestamptz 'infinity' - timestamptz 'infinity'; -ERROR: timestamp out of range +ERROR: interval out of range SELECT timestamptz 'infinity' - timestamptz '-infinity'; ?column? ---------- @@ -3230,7 +3230,7 @@ SELECT timestamptz '-infinity' - timestamptz 'infinity'; (1 row) SELECT timestamptz '-infinity' - timestamptz '-infinity'; -ERROR: timestamp out of range +ERROR: interval out of range SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12'; ?column? ---------- @@ -3257,7 +3257,7 @@ SELECT age(timestamptz '-infinity'); (1 row) SELECT age(timestamptz 'infinity', timestamptz 'infinity'); -ERROR: timestamp out of range +ERROR: interval out of range SELECT age(timestamptz 'infinity', timestamptz '-infinity'); age ---------- @@ -3271,4 +3271,4 @@ SELECT age(timestamptz '-infinity', timestamptz 'infinity'); (1 row) SELECT age(timestamptz '-infinity', timestamptz '-infinity'); -ERROR: timestamp out of range +ERROR: interval out of range diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out index 63f36e069e..6b8c3c3413 100644 --- a/src/test/regress/expected/window.out +++ b/src/test/regress/expected/window.out @@ -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,9 +2419,70 @@ 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 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_timetz, first_value(id) over w, last_value(id) over w from datetimes @@ -2424,7 +2490,8 @@ 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 +2499,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 +2510,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,9 +2519,70 @@ 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 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_interval, first_value(id) over w, last_value(id) over w from datetimes @@ -2460,6 +2590,7 @@ 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 +2601,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 +2610,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,7 +2621,68 @@ 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 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_timestamptz, first_value(id) over w, last_value(id) over w from datetimes @@ -2496,6 +2690,7 @@ 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 +2701,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 +2710,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,7 +2721,68 @@ 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 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_timestamp, first_value(id) over w, last_value(id) over w from datetimes @@ -2532,6 +2790,7 @@ 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 +2801,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 +2810,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,7 +2821,68 @@ 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 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) -- 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 @@ -4375,36 +4697,34 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE 4 | (4 rows) --- window function over interval, infinity and extreme values test the --- behaviour of accumulation and elimination of these values as the window --- slides. +-- 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 prev1_curr_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 prev1_curr_sum + ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum FROM (VALUES (NULL::interval), ('infinity'::interval), - ('2147483647 days 2147483646 months'), -- extreme interval value - ('infinity'::timestamptz - now()), - ('-2147483648 days -2147483647 months'), -- extreme interval value + ('-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 | prev1_curr_avg | curr_next_sum | prev1_curr_sum -----------------------------------------------+-------------------+-------------------+---------------+---------------- - | infinity | | infinity | - infinity | infinity | infinity | infinity | infinity - @ 178956970 years 6 mons 2147483647 days | infinity | infinity | infinity | infinity - infinity | infinity | infinity | infinity | infinity - @ 178956970 years 7 mons 2147483648 days ago | 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 + 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. diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index 414c12be1c..1bffa50fe9 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -73,11 +73,11 @@ SELECT r1.*, r2.* -- 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 - ('2147483647 days 2147483646 months'), - ('2147483647 days -2147483647 months'), + ('2147483647 days 2147483647 months'), + ('2147483647 days -2147483648 months'), ('1 year'), - ('-2147483648 days 2147483646 months'), - ('-2147483648 days -2147483647 months'); + ('-2147483648 days 2147483647 months'), + ('-2147483648 days -2147483648 months'); -- these should fail as out-of-range INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days'); INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days'); @@ -164,8 +164,7 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds"; SELECT justify_hours(interval '2147483647 days 24 hrs'); -SELECT justify_days(interval '2147483646 months 30 days'); -SELECT justify_days(interval '2147483646 months 60 days'); +SELECT justify_days(interval '2147483647 months 30 days'); -- test justify_interval() @@ -173,14 +172,12 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour"; SELECT justify_interval(interval '2147483647 days 24 hrs'); SELECT justify_interval(interval '-2147483648 days -24 hrs'); -SELECT justify_interval(interval '2147483646 months 30 days'); -SELECT justify_interval(interval '2147483646 months 60 days'); -SELECT justify_interval(interval '-2147483647 months -30 days'); -SELECT justify_interval(interval '-2147483647 months -60 days'); -SELECT justify_interval(interval '2147483646 months 30 days -24 hrs'); -SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs'); -SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs'); -SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs'); +SELECT justify_interval(interval '2147483647 months 30 days'); +SELECT justify_interval(interval '-2147483648 months -30 days'); +SELECT justify_interval(interval '2147483647 months 30 days -24 hrs'); +SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs'); +SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs'); +SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs'); -- test fractional second input, and detection of duplicate units SET DATESTYLE = 'ISO'; @@ -608,6 +605,14 @@ 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'; @@ -617,54 +622,64 @@ INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL; -SELECT date '1995-08-06' + interval 'infinity'; -SELECT date '1995-08-06' + interval '-infinity'; -SELECT date '1995-08-06' - interval 'infinity'; -SELECT date '1995-08-06' - interval '-infinity'; -SELECT date 'infinity' + interval 'infinity'; -SELECT date 'infinity' + interval '-infinity'; -SELECT date '-infinity' + interval 'infinity'; -SELECT date '-infinity' + interval '-infinity'; -SELECT date 'infinity' - interval 'infinity'; -SELECT date 'infinity' - interval '-infinity'; -SELECT date '-infinity' - interval 'infinity'; -SELECT date '-infinity' - interval '-infinity'; -SELECT interval 'infinity' + interval 'infinity'; -SELECT interval 'infinity' + interval '-infinity'; -SELECT interval '-infinity' + interval 'infinity'; -SELECT interval '-infinity' + interval '-infinity'; -SELECT interval 'infinity' + interval '10 days'; -SELECT interval '-infinity' + interval '10 days'; +-- 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 'infinity' - interval 'infinity'; -SELECT interval 'infinity' - interval '-infinity'; -SELECT interval '-infinity' - interval 'infinity'; -SELECT interval '-infinity' - interval '-infinity'; -SELECT interval 'infinity' - interval '10 days'; -SELECT interval '-infinity' - interval '10 days'; 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 timestamp 'infinity' + interval 'infinity'; -SELECT timestamp 'infinity' + interval '-infinity'; -SELECT timestamp '-infinity' + interval 'infinity'; -SELECT timestamp '-infinity' + interval '-infinity'; -SELECT timestamp 'infinity' - interval 'infinity'; -SELECT timestamp 'infinity' - interval '-infinity'; -SELECT timestamp '-infinity' - interval 'infinity'; -SELECT timestamp '-infinity' - interval '-infinity'; -SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity'; -SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity'; -SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity'; -SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity'; -SELECT timestamptz 'infinity' + interval 'infinity'; -SELECT timestamptz 'infinity' + interval '-infinity'; -SELECT timestamptz '-infinity' + interval 'infinity'; -SELECT timestamptz '-infinity' + interval '-infinity'; -SELECT timestamptz 'infinity' - interval 'infinity'; -SELECT timestamptz 'infinity' - interval '-infinity'; -SELECT timestamptz '-infinity' - interval 'infinity'; -SELECT timestamptz '-infinity' - interval '-infinity'; + +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'; diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql index 6dd9885949..c2a4cb8d64 100644 --- a/src/test/regress/sql/window.sql +++ b/src/test/regress/sql/window.sql @@ -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,21 @@ 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 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_timetz, first_value(id) over w, last_value(id) over w from datetimes window w as (order by f_timetz range between @@ -704,6 +721,21 @@ 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 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_interval, first_value(id) over w, last_value(id) over w from datetimes window w as (order by f_interval range between @@ -714,6 +746,21 @@ 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 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_timestamptz, first_value(id) over w, last_value(id) over w from datetimes window w as (order by f_timestamptz range between @@ -724,6 +771,21 @@ 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 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_timestamp, first_value(id) over w, last_value(id) over w from datetimes window w as (order by f_timestamp range between @@ -734,6 +796,21 @@ 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 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); + -- 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,19 +1668,17 @@ 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); --- window function over interval, infinity and extreme values test the --- behaviour of accumulation and elimination of these values as the window --- slides. +-- 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 prev1_curr_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 prev1_curr_sum + ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum FROM (VALUES (NULL::interval), ('infinity'::interval), - ('2147483647 days 2147483646 months'), -- extreme interval value - ('infinity'::timestamptz - now()), - ('-2147483648 days -2147483647 months'), -- extreme interval value + ('-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), -- 2.35.3