{
Interval *v = palloc(sizeof(Interval));
- v->time = PG_INT64_MIN;
- v->day = PG_INT32_MIN;
- v->month = PG_INT32_MIN;
+ INTERVAL_NOBEGIN(v);
+
return IntervalPGetDatum(v);
}
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for <type>timestamp</type> inputs; <literal>epoch</literal>, <literal>hour</literal>,
+ <literal>day</literal>, <literal>year</literal>, <literal>decade</literal>,
+ <literal>century</literal>, and <literal>millennium</literal> for
+ <type>interval</type> inputs).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
#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"
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;
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))
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))
/*
* 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,
/*
* 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);
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;
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;
/*
* 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,
/*
* 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)
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),
*
* 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,
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;
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;
}
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);
* 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);
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);
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
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);
*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);
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.
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:
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)
{
/* 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)
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)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
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)
{
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,
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))
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,
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))
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
{
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),
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
{
{
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);
}
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)
{
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);
}
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)
{
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
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);
}
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)
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);
}
(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);
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);
}
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);
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,
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,
/*
- * 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.
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;
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;
(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),
(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),
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);
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
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.
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);
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),
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),
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();
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);
}
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();
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);
}
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202311141
+#define CATALOG_VERSION_NO 202311142
#endif
{ aggfnoid => 'avg(float8)', aggtransfn => 'float8_accum',
aggfinalfn => 'float8_avg', aggcombinefn => 'float8_combine',
aggtranstype => '_float8', agginitval => '{0,0,0}' },
-{ aggfnoid => 'avg(interval)', aggtransfn => 'interval_accum',
- aggfinalfn => 'interval_avg', aggcombinefn => 'interval_combine',
- aggmtransfn => 'interval_accum', aggminvtransfn => 'interval_accum_inv',
- aggmfinalfn => 'interval_avg', aggtranstype => '_interval',
- aggmtranstype => '_interval', agginitval => '{0 second,0 second}',
- aggminitval => '{0 second,0 second}' },
+{ aggfnoid => 'avg(interval)', aggtransfn => 'interval_avg_accum',
+ aggfinalfn => 'interval_avg', aggcombinefn => 'interval_avg_combine',
+ aggserialfn => 'interval_avg_serialize',
+ aggdeserialfn => 'interval_avg_deserialize',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv',
+ aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
+ aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40' },
# sum
{ aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
{ aggfnoid => 'sum(money)', aggtransfn => 'cash_pl', aggcombinefn => 'cash_pl',
aggmtransfn => 'cash_pl', aggminvtransfn => 'cash_mi',
aggtranstype => 'money', aggmtranstype => 'money' },
-{ aggfnoid => 'sum(interval)', aggtransfn => 'interval_pl',
- aggcombinefn => 'interval_pl', aggmtransfn => 'interval_pl',
- aggminvtransfn => 'interval_mi', aggtranstype => 'interval',
- aggmtranstype => 'interval' },
+{ aggfnoid => 'sum(interval)', aggtransfn => 'interval_avg_accum',
+ aggfinalfn => 'interval_sum', aggcombinefn => 'interval_avg_combine',
+ aggserialfn => 'interval_avg_serialize',
+ aggdeserialfn => 'interval_avg_deserialize',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv',
+ aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
+ aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40'},
{ aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
aggserialfn => 'numeric_avg_serialize',
prosrc => 'numeric_poly_stddev_samp' },
{ oid => '1843', descr => 'aggregate transition function',
- proname => 'interval_accum', prorettype => '_interval',
- proargtypes => '_interval interval', prosrc => 'interval_accum' },
+ proname => 'interval_avg_accum', proisstrict => 'f',
+ prorettype => 'internal', proargtypes => 'internal interval',
+ prosrc => 'interval_avg_accum' },
{ oid => '3325', descr => 'aggregate combine function',
- proname => 'interval_combine', prorettype => '_interval',
- proargtypes => '_interval _interval', prosrc => 'interval_combine' },
+ proname => 'interval_avg_combine', proisstrict => 'f',
+ prorettype => 'internal', proargtypes => 'internal internal',
+ prosrc => 'interval_avg_combine' },
{ oid => '3549', descr => 'aggregate transition function',
- proname => 'interval_accum_inv', prorettype => '_interval',
- proargtypes => '_interval interval', prosrc => 'interval_accum_inv' },
+ proname => 'interval_avg_accum_inv', proisstrict => 'f',
+ prorettype => 'internal', proargtypes => 'internal interval',
+ prosrc => 'interval_avg_accum_inv' },
+{ oid => '8505', descr => 'aggregate serial function',
+ proname => 'interval_avg_serialize', prorettype => 'bytea',
+ proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
+{ oid => '8506', descr => 'aggregate deserial function',
+ proname => 'interval_avg_deserialize', prorettype => 'internal',
+ proargtypes => 'bytea internal', prosrc => 'interval_avg_deserialize' },
{ oid => '1844', descr => 'aggregate final function',
- proname => 'interval_avg', prorettype => 'interval',
- proargtypes => '_interval', prosrc => 'interval_avg' },
+ proname => 'interval_avg', proisstrict => 'f', prorettype => 'interval',
+ proargtypes => 'internal', prosrc => 'interval_avg' },
+{ oid => '8507', descr => 'aggregate final function',
+ proname => 'interval_sum', proisstrict => 'f', prorettype => 'interval',
+ proargtypes => 'internal', prosrc => 'interval_sum' },
{ oid => '1962', descr => 'aggregate transition function',
proname => 'int2_avg_accum', prorettype => '_int8',
proargtypes => '_int8 int2', prosrc => 'int2_avg_accum' },
#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
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+/*
+ * Infinite intervals are represented by setting all fields to the minimum or
+ * maximum integer values.
+ */
+#define INTERVAL_NOBEGIN(i) \
+ do { \
+ (i)->time = PG_INT64_MIN; \
+ (i)->day = PG_INT32_MIN; \
+ (i)->month = PG_INT32_MIN; \
+ } while (0)
+
+#define INTERVAL_IS_NOBEGIN(i) \
+ ((i)->month == PG_INT32_MIN && (i)->day == PG_INT32_MIN && (i)->time == PG_INT64_MIN)
+
+#define INTERVAL_NOEND(i) \
+ do { \
+ (i)->time = PG_INT64_MAX; \
+ (i)->day = PG_INT32_MAX; \
+ (i)->month = PG_INT32_MAX; \
+ } while (0)
+
+#define INTERVAL_IS_NOEND(i) \
+ ((i)->month == PG_INT32_MAX && (i)->day == PG_INT32_MAX && (i)->time == PG_INT64_MAX)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
Index Cond: (a = '@ 30 years'::interval)
(4 rows)
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+SET enable_seqscan = off;
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years ago'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years ago'::interval)
+(4 rows)
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years'::interval)
+(4 rows)
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | -infinity | -infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | -infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | -infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | -infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | -infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | -infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | -infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | -infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | -infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | -infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | -infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | -infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | -infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | -infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | -infinity
Thu Jan 01 00:00:00 1970 PST | @ 14 secs ago | Wed Dec 31 23:59:46 1969 PST
Thu Jan 01 00:00:00 1970 PST | @ 1 min | Thu Jan 01 00:01:00 1970 PST
Thu Jan 01 00:00:00 1970 PST | @ 5 hours | Thu Jan 01 05:00:00 1970 PST
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | infinity | -infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | -infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | -infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | -infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | -infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | -infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | -infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | -infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | -infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | -infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | -infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | -infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | -infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | -infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | -infinity
Thu Jan 01 00:00:00 1970 PST | @ 34 years | Wed Jan 01 00:00:00 1936 PST
Wed Feb 28 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
Thu Feb 29 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
34 years | 5 mons
34 years | 5 mons 12:00:00
34 years | 6 years
-(45 rows)
+ infinity | -infinity
+ infinity | -00:00:14
+ infinity | 00:01:00
+ infinity | 05:00:00
+ infinity | 1 day 02:03:04
+ infinity | 10 days
+ infinity | 3 mons
+ infinity | 5 mons
+ infinity | 5 mons 12:00:00
+ infinity | 6 years
+ infinity | 34 years
+(66 rows)
+
+-- test unary minus
+SELECT f1, -f1 FROM INTERVAL_TBL;
+ f1 | ?column?
+-----------------+-------------------
+ 00:01:00 | -00:01:00
+ 05:00:00 | -05:00:00
+ 10 days | -10 days
+ 34 years | -34 years
+ 3 mons | -3 mons
+ -00:00:14 | 00:00:14
+ 1 day 02:03:04 | -1 days -02:03:04
+ 6 years | -6 years
+ 5 mons | -5 mons
+ 5 mons 12:00:00 | -5 mons -12:00:00
+ infinity | -infinity
+ -infinity | infinity
+(12 rows)
+
+SELECT -('-2147483648 months'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-2147483647 months'::interval); -- ok
+ ?column?
+------------------------
+ 178956970 years 7 mons
+(1 row)
+
+SELECT -('-2147483648 days'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-2147483647 days'::interval); -- ok
+ ?column?
+-----------------
+ 2147483647 days
+(1 row)
+
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-9223372036854775807 us'::interval); -- ok
+ ?column?
+-------------------------
+ 2562047788:00:54.775807
+(1 row)
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+ERROR: interval out of range
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
(5 rows)
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column?
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- Floating point arithmetic rounding errors can lead to unexpected results,
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
(f1 + INTERVAL '1 month')::INTERVAL MONTH::INTERVAL YEAR AS "years"
FROM interval_tbl;
- f1 | minutes | years
------------------+-----------------+----------
+ f1 | minutes | years
+-----------------+-----------------+-----------
00:01:00 | 00:01:00 | 00:00:00
05:00:00 | 05:00:00 | 00:00:00
10 days | 10 days | 00:00:00
6 years | 6 years | 6 years
5 mons | 5 mons | 00:00:00
5 mons 12:00:00 | 5 mons 12:00:00 | 00:00:00
-(10 rows)
+ infinity | infinity | infinity
+ -infinity | -infinity | -infinity
+(12 rows)
-- test inputting and outputting SQL standard interval literals
SET IntervalStyle TO sql_standard;
ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
--------------------------------------------------------------------
- -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+ -178956970 years -7 mons -2147483648 days -2562047788:00:54.775808
(1 row)
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
---------------------------------------------------
- -178956970-8 -2147483648 -2562047788:00:54.775808
+ -178956970-7 -2147483648 -2562047788:00:54.775808
(1 row)
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
-----------------------------------------------------
- P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+ P-178956970Y-7M-2147483648DT-2562047788H-54.775808S
(1 row)
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
------------------------------------------------------------------------------
- @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+ @ 178956970 years 7 mons 2147483648 days 2562047788 hours 54.775808 secs ago
(1 row)
-- check that '30 days' equals '1 month' according to the hash function
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+--
+-- test infinite intervals
+--
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+ interval
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+ interval
+--------------------------------------------------------------------------
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs
+(1 row)
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ interval
+----------
+ infinity
+(1 row)
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ date | interval | plus | minus
+------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 | -infinity | -infinity | infinity
+ 1995-08-06 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+ interval1 | interval2 | plus | minus
+-----------+-----------+-----------------------+-----------------------
+ -infinity | -infinity | -infinity | interval out of range
+ -infinity | @ 10 days | -infinity | -infinity
+ -infinity | infinity | interval out of range | -infinity
+ @ 2 mons | -infinity | -infinity | infinity
+ @ 2 mons | @ 10 days | @ 2 mons 10 days | @ 2 mons -10 days
+ @ 2 mons | infinity | infinity | -infinity
+ infinity | -infinity | interval out of range | infinity
+ infinity | @ 10 days | infinity | infinity
+ infinity | infinity | infinity | interval out of range
+(9 rows)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamp | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamptz | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT time '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+ lhs | rhs | lt | le | eq | gt | ge | ne
+-----------+-------------------------+----+----+----+----+----+----
+ infinity | infinity | f | t | t | f | t | f
+ -infinity | infinity | t | t | f | f | f | t
+ infinity | -infinity | f | f | f | t | t | t
+ -infinity | -infinity | f | t | t | f | t | f
+ infinity | @ 1 year 2 days 3 hours | f | f | f | t | t | t
+ -infinity | @ 1 year 2 days 3 hours | t | t | f | f | f | t
+(6 rows)
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | um | mul | mul_neg | mul_inf | mul_inf_neg | div | div_neg
+-----------+-----------+-----------+-----------+-----------+-------------+-----------+-----------
+ infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity
+ -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity
+(2 rows)
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+ERROR: interval out of range
+SELECT interval 'infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+ERROR: interval out of range
+SELECT interval 'infinity' * 0;
+ERROR: interval out of range
+SELECT interval '-infinity' * 0;
+ERROR: interval out of range
+SELECT interval '0 days' * 'infinity'::float;
+ERROR: interval out of range
+SELECT interval '0 days' * '-infinity'::float;
+ERROR: interval out of range
+SELECT interval '5 days' * 'infinity'::float;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '5 days' * '-infinity'::float;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+ERROR: interval out of range
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | date_trunc
+-----------+------------
+ infinity | infinity
+ -infinity | -infinity
+(2 rows)
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | justify_days | justify_hours | justify_interval
+-----------+--------------+---------------+------------------
+ infinity | infinity | infinity | infinity
+ -infinity | -infinity | -infinity | -infinity
+(2 rows)
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT 'infinity'::interval::time;
+ERROR: cannot convert infinite interval to time
+SELECT '-infinity'::interval::time;
+ERROR: cannot convert infinite interval to time
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
ERROR: invalid input syntax for type interval: "42 days 2 seconds ago ago"
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '-infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp 'infinity', timestamp 'infinity');
+ERROR: interval out of range
+select age(timestamp 'infinity', timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp '-infinity');
+ERROR: interval out of range
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+ERROR: interval out of range
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
+ERROR: interval out of range
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'),
(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
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
'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
5 | 15:00:00 | 6 | 3
4 | 14:00:00 | 6 | 2
3 | 13:00:00 | 4 | 1
- 2 | 12:00:00 | 3 | 1
- 1 | 11:00:00 | 2 | 1
-(10 rows)
+ 2 | 12:00:00 | 3 | 0
+ 1 | 11:00:00 | 2 | 0
+ 0 | 10:00:00 | 1 | 0
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | 0 | 11
+ 1 | 11:00:00 | 0 | 11
+ 2 | 12:00:00 | 0 | 11
+ 3 | 13:00:00 | 0 | 11
+ 4 | 14:00:00 | 0 | 11
+ 5 | 15:00:00 | 0 | 11
+ 6 | 15:00:00 | 0 | 11
+ 7 | 17:00:00 | 0 | 11
+ 8 | 18:00:00 | 0 | 11
+ 9 | 19:00:00 | 0 | 11
+ 10 | 20:00:00 | 0 | 11
+ 11 | 21:00:00 | 0 | 11
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 1 | 11:00:00+01 | 1 | 3
+ 0 | 10:00:00+01 | 0 | 2
+ 1 | 11:00:00+01 | 0 | 3
2 | 12:00:00+01 | 1 | 4
3 | 13:00:00+01 | 2 | 6
4 | 14:00:00+01 | 3 | 6
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
'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
5 | 15:00:00+01 | 6 | 3
4 | 14:00:00+01 | 6 | 2
3 | 13:00:00+01 | 4 | 1
- 2 | 12:00:00+01 | 3 | 1
- 1 | 11:00:00+01 | 2 | 1
-(10 rows)
+ 2 | 12:00:00+01 | 3 | 0
+ 1 | 11:00:00+01 | 2 | 0
+ 0 | 10:00:00+01 | 1 | 0
+(12 rows)
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | 0 | 11
+ 1 | 11:00:00+01 | 0 | 11
+ 2 | 12:00:00+01 | 0 | 11
+ 3 | 13:00:00+01 | 0 | 11
+ 4 | 14:00:00+01 | 0 | 11
+ 5 | 15:00:00+01 | 0 | 11
+ 6 | 15:00:00+01 | 0 | 11
+ 7 | 17:00:00+01 | 0 | 11
+ 8 | 18:00:00+01 | 0 | 11
+ 9 | 19:00:00+01 | 0 | 11
+ 10 | 20:00:00+01 | 0 | 11
+ 11 | 21:00:00+01 | 0 | 11
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | @ 1 year | 1 | 2
2 | @ 2 years | 1 | 3
3 | @ 3 years | 2 | 4
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
'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
3 | @ 3 years | 4 | 2
2 | @ 2 years | 3 | 1
1 | @ 1 year | 2 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 0 | 11
+ 2 | @ 2 years | 0 | 11
+ 3 | @ 3 years | 0 | 11
+ 4 | @ 4 years | 0 | 11
+ 5 | @ 5 years | 0 | 11
+ 6 | @ 5 years | 0 | 11
+ 7 | @ 7 years | 0 | 11
+ 8 | @ 8 years | 0 | 11
+ 9 | @ 9 years | 0 | 11
+ 10 | @ 10 years | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | @ 1 year | 0 | 0
+ 2 | @ 2 years | 0 | 0
+ 3 | @ 3 years | 0 | 0
+ 4 | @ 4 years | 0 | 0
+ 5 | @ 5 years | 0 | 0
+ 6 | @ 5 years | 0 | 0
+ 7 | @ 7 years | 0 | 0
+ 8 | @ 8 years | 0 | 0
+ 9 | @ 9 years | 0 | 0
+ 10 | @ 10 years | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 11 | 11
+ 2 | @ 2 years | 11 | 11
+ 3 | @ 3 years | 11 | 11
+ 4 | @ 4 years | 11 | 11
+ 5 | @ 5 years | 11 | 11
+ 6 | @ 5 years | 11 | 11
+ 7 | @ 7 years | 11 | 11
+ 8 | @ 8 years | 11 | 11
+ 9 | @ 9 years | 11 | 11
+ 10 | @ 10 years | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 02:23:54 2000 PDT | 1 | 3
2 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
3 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
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
'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
3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 0
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 0
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 0
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 0
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 0
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 0
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 0
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 11 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 11 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 11 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 11 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 11 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 11 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 11 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 10:23:54 2000 | 1 | 3
2 | Fri Oct 19 10:23:54 2001 | 1 | 4
3 | Fri Oct 19 10:23:54 2001 | 1 | 4
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
'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
3 | Fri Oct 19 10:23:54 2001 | 4 | 1
2 | Fri Oct 19 10:23:54 2001 | 4 | 1
1 | Thu Oct 19 10:23:54 2000 | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 0
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 0
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 0
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 0
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 0
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 0
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 0
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 11 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 11 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 11 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 11 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 11 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 11 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 11 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
4 |
(4 rows)
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev_curr_avg | curr_next_sum | prev_curr_sum
+------------------------------------------------------------------------------+-------------------+-------------------+---------------+---------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago | -infinity | infinity | -infinity | infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs | infinity | -infinity | infinity | -infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
+(10 rows)
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
i | sum
EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan = off;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
WHERE r1.f1 > r2.f1
ORDER BY r1.f1, r2.f1;
+-- test unary minus
+
+SELECT f1, -f1 FROM INTERVAL_TBL;
+SELECT -('-2147483648 months'::interval); -- should fail
+SELECT -('-2147483647 months'::interval); -- ok
+SELECT -('-2147483648 days'::interval); -- should fail
+SELECT -('-2147483647 days'::interval); -- ok
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+SELECT -('-9223372036854775807 us'::interval); -- ok
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
-- check that '30 days' equals '1 month' according to the hash function
select '30 days'::interval = '1 month'::interval as t;
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+--
+-- test infinite intervals
+--
+
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+SELECT time '11:27:42' + interval '-infinity';
+SELECT time '11:27:42' - interval 'infinity';
+SELECT time '11:27:42' - interval '-infinity';
+SELECT timetz '11:27:42' + interval 'infinity';
+SELECT timetz '11:27:42' + interval '-infinity';
+SELECT timetz '11:27:42' - interval 'infinity';
+SELECT timetz '11:27:42' - interval '-infinity';
+
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+SELECT interval 'infinity' * 'nan';
+SELECT interval '-infinity' * 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+SELECT interval 'infinity' * 0;
+SELECT interval '-infinity' * 0;
+SELECT interval '0 days' * 'infinity'::float;
+SELECT interval '0 days' * '-infinity'::float;
+SELECT interval '5 days' * 'infinity'::float;
+SELECT interval '5 days' * '-infinity'::float;
+
+SELECT interval 'infinity' / 'infinity';
+SELECT interval 'infinity' / '-infinity';
+SELECT interval 'infinity' / 'nan';
+SELECT interval '-infinity' / 'infinity';
+SELECT interval '-infinity' / '-infinity';
+SELECT interval '-infinity' / 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+
+SELECT 'infinity'::interval::time;
+SELECT '-infinity'::interval::time;
+
+SELECT to_char('infinity'::interval, 'YYYY');
+SELECT to_char('-infinity'::interval, 'YYYY');
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
);
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'),
(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 desc range between
'70 min' preceding and '2 hours' following);
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup