On Sat, Dec 17, 2022 at 2:34 PM Joseph Koshakow <kosh...@gmail.com> wrote: > > Hi Ashutosh, > > I've added tests for all the operators and functions involving > intervals and what I think the expected behaviors to be. The > formatting might be slightly off and I've left the contents of the > error messages as TODOs. Hopefully it's a good reference for the > implementation. > > > Adding infinite interval to an infinite timestamp with opposite > > direction is not going to yield 0 but some infinity. Since we are adding > > interval to the timestamp the resultant timestamp is an infinity > > preserving the direction. > > I think I disagree with this. Tom Lane in one of the previous threads > said: > > tl;dr: we should model it after the behavior of IEEE float infinities, > > except we'll want to throw errors where those produce NaNs. > and I agree with this opinion. I believe that means that adding an > infinite interval to an infinite timestamp with opposite directions > should yield an error instead of some infinity. Since with floats this > would yield a NaN. > > > Dividing infinite interval by finite number keeps it infinite. > > TODO: Do we change the sign of infinity if factor is negative? > Again if we model this after the IEEE float behavior, then the answer > is yes, we do change the sign of infinity. > > - Joe Koshakow I ended up doing some more work in the attached patch. Here are some updates:
- I modified the arithmetic operators to more closely match IEEE floats. Error messages are still all TODO, and they may have the wrong error code. - I implemented some more operators and functions. - I moved the helper functions you created into macros in timestamp.h to more closely match the implementation of infinite timestamps and dates. Also so dates.c could access them. - There seems to be an existing overflow error with interval subtraction. Many of the arithmetic operators of the form `X - Interval` are converted to `X + (-Interval)`. This will overflow in the case that some interval field is INT32_MIN or INT64_MIN. Additionally, negating a positive infinity interval won't result in a negative infinity interval and vice versa. We'll have to come up with an efficient solution for this. - Joe Koshakow
From e6e764dd8f8423f2aec0fb3782f170c59557adf6 Mon Sep 17 00:00:00 2001 From: Joseph Koshakow <kosh...@gmail.com> Date: Sat, 17 Dec 2022 14:21:26 -0500 Subject: [PATCH] This is WIP. Following things are supported 1. Accepts '+/-infinity' as a valid string input for interval type. 2. Support interval_pl, interval_div 3. Tests in interval.sql for comparison operators working fine. TODOs 1. Various TODOs in code 2. interval_pl: how to handle infinite values with opposite signs 3. timestamp, timestamptz, date and time arithmetic 4. Fix horology test. Ashutosh Bapat --- src/backend/utils/adt/date.c | 20 + src/backend/utils/adt/datetime.c | 2 + src/backend/utils/adt/timestamp.c | 188 +++++++- src/include/datatype/timestamp.h | 22 + src/test/regress/expected/interval.out | 613 ++++++++++++++++++++++++- src/test/regress/sql/interval.sql | 121 +++++ 6 files changed, 949 insertions(+), 17 deletions(-) diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c index 1cf7c7652d..a2c9214bcf 100644 --- a/src/backend/utils/adt/date.c +++ b/src/backend/utils/adt/date.c @@ -2073,6 +2073,11 @@ time_pl_interval(PG_FUNCTION_ARGS) Interval *span = PG_GETARG_INTERVAL_P(1); TimeADT result; + if (INTERVAL_NOT_FINITE(span)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("TODO"))); + result = time + span->time; result -= result / USECS_PER_DAY * USECS_PER_DAY; if (result < INT64CONST(0)) @@ -2091,6 +2096,11 @@ time_mi_interval(PG_FUNCTION_ARGS) Interval *span = PG_GETARG_INTERVAL_P(1); TimeADT result; + if (INTERVAL_NOT_FINITE(span)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("TODO"))); + result = time - span->time; result -= result / USECS_PER_DAY * USECS_PER_DAY; if (result < INT64CONST(0)) @@ -2605,6 +2615,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS) Interval *span = PG_GETARG_INTERVAL_P(1); TimeTzADT *result; + if (INTERVAL_NOT_FINITE(span)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("TODO"))); + result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); result->time = time->time + span->time; @@ -2627,6 +2642,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS) Interval *span = PG_GETARG_INTERVAL_P(1); TimeTzADT *result; + if (INTERVAL_NOT_FINITE(span)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("TODO"))); + result = (TimeTzADT *) palloc(sizeof(TimeTzADT)); result->time = time->time - span->time; diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index b5b117a8ca..1e98c6dc78 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -3634,6 +3634,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range, case DTK_STRING: case DTK_SPECIAL: type = DecodeUnits(i, field[i], &uval); + if (type == UNKNOWN_FIELD) + type = DecodeSpecial(i, field[i], &uval); if (type == IGNORE_DTF) continue; diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c index 3f2508c0c4..e501e253a6 100644 --- a/src/backend/utils/adt/timestamp.c +++ b/src/backend/utils/adt/timestamp.c @@ -79,6 +79,8 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod, static TimestampTz timestamp2timestamptz(Timestamp timestamp); static Timestamp timestamptz2timestamp(TimestampTz timestamp); +static void EncodeSpecialInterval(Interval *interval, char *str); +static void neg_interval_infinite(Interval *interval); /* common code for timestamptypmodin and timestamptztypmodin */ static int32 @@ -943,6 +945,14 @@ interval_in(PG_FUNCTION_ARGS) errmsg("interval out of range"))); break; + case DTK_LATE: + INTERVAL_NOEND(result); + break; + + case DTK_EARLY: + INTERVAL_NOBEGIN(result); + break; + default: elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"", dtype, str); @@ -965,8 +975,13 @@ interval_out(PG_FUNCTION_ARGS) *itm = &tt; char buf[MAXDATELEN + 1]; - interval2itm(*span, itm); - EncodeInterval(itm, IntervalStyle, buf); + if (INTERVAL_NOT_FINITE(span)) + EncodeSpecialInterval(span, buf); + else + { + interval2itm(*span, itm); + EncodeInterval(itm, IntervalStyle, buf); + } result = pstrdup(buf); PG_RETURN_CSTRING(result); @@ -1352,6 +1367,13 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod, INT64CONST(0) }; + /* + * Infinite interval after being subjected to typmod conversion remains + * infinite. + */ + 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. @@ -1545,6 +1567,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str) elog(ERROR, "invalid argument for EncodeSpecialTimestamp"); } +static void +EncodeSpecialInterval(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) { @@ -2083,7 +2116,8 @@ timestamp_finite(PG_FUNCTION_ARGS) Datum interval_finite(PG_FUNCTION_ARGS) { - PG_RETURN_BOOL(true); + Interval *interval = PG_GETARG_INTERVAL_P(0); + PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval)); } @@ -2775,6 +2809,9 @@ interval_justify_interval(PG_FUNCTION_ARGS) result->day = span->day; result->time = span->time; + 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)) @@ -2850,6 +2887,9 @@ interval_justify_hours(PG_FUNCTION_ARGS) result->day = span->day; result->time = span->time; + 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, @@ -2888,6 +2928,9 @@ interval_justify_days(PG_FUNCTION_ARGS) result->day = span->day; result->time = span->time; + 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)) @@ -2926,10 +2969,29 @@ timestamp_pl_interval(PG_FUNCTION_ARGS) Interval *span = PG_GETARG_INTERVAL_P(1); Timestamp result; - if (TIMESTAMP_NOT_FINITE(timestamp)) + /* + * Adding two infinites with the same sign results in an infinite + * timestamp with the same sign. Adding two infintes with + * different signs results in an error. + */ + // TODO this logic can probably be combined and cleaned up. + if (INTERVAL_IS_NOBEGIN(span) && TIMESTAMP_IS_NOBEGIN(timestamp)) + TIMESTAMP_NOBEGIN(result); + else if (INTERVAL_IS_NOEND(span) && TIMESTAMP_IS_NOEND(timestamp)) + TIMESTAMP_NOEND(result); + else if (INTERVAL_NOT_FINITE(span) && TIMESTAMP_NOT_FINITE(timestamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("TODO"))); + else if (INTERVAL_IS_NOBEGIN(span)) + TIMESTAMP_NOBEGIN(result); + else if (INTERVAL_IS_NOEND(span)) + TIMESTAMP_NOEND(result); + else if (TIMESTAMP_NOT_FINITE(timestamp)) result = timestamp; else { + elog(INFO, "Went to main branch. time: %ld, days: %d, months: %d", span->time, span->day, span->month); if (span->month != 0) { struct pg_tm tt, @@ -3032,8 +3094,19 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS) TimestampTz result; int tz; - if (TIMESTAMP_NOT_FINITE(timestamp)) - result = timestamp; + /* + * Adding two infinites with the same sign results in an infinite + * timestamp with the same sign. Adding two infintes with + * different signs results in an error. + */ + if (INTERVAL_IS_NOBEGIN(span) && TIMESTAMP_IS_NOBEGIN(timestamp)) + TIMESTAMP_NOBEGIN(result); + else if (INTERVAL_IS_NOEND(span) && TIMESTAMP_IS_NOEND(timestamp)) + TIMESTAMP_NOEND(result); + else if (INTERVAL_NOT_FINITE(span) && TIMESTAMP_NOT_FINITE(timestamp)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("TODO"))); else { if (span->month != 0) @@ -3133,6 +3206,13 @@ interval_um(PG_FUNCTION_ARGS) result = (Interval *) palloc(sizeof(Interval)); + if (INTERVAL_NOT_FINITE(interval)) + { + memcpy(result, interval, sizeof(Interval)); + neg_interval_infinite(result); + PG_RETURN_INTERVAL_P(result); + } + result->time = -interval->time; /* overflow check copied from int4um */ if (interval->time != 0 && SAMESIGN(result->time, interval->time)) @@ -3192,6 +3272,24 @@ interval_pl(PG_FUNCTION_ARGS) result = (Interval *) palloc(sizeof(Interval)); + /* + * Adding two infinite intervals with the same signs results + * in an infinite interval with the same sign. Adding two + * infinte intervals with different signs results in an error. + */ + if ((INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOBEGIN(span2)) || + (INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOEND(span2))) + { + memcpy(result, span1, sizeof(Interval)); + PG_RETURN_INTERVAL_P(result); + } + else if (INTERVAL_NOT_FINITE(span1) || INTERVAL_NOT_FINITE(span2)) + { + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("TODO"))); + } + result->month = span1->month + span2->month; /* overflow check copied from int4pl */ if (SAMESIGN(span1->month, span2->month) && @@ -3226,6 +3324,25 @@ interval_mi(PG_FUNCTION_ARGS) result = (Interval *) palloc(sizeof(Interval)); + /* + * Subtracting two infinite intervals with different signs results + * in an infinite interval with the same sign as the left operand. + * Subtracting two infinte intervals with the same sign results in + * an error. + */ + if ((INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOEND(span2)) || + (INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOBEGIN(span2))) + { + memcpy(result, span1, sizeof(Interval)); + PG_RETURN_INTERVAL_P(result); + } + else if (INTERVAL_NOT_FINITE(span1) || INTERVAL_NOT_FINITE(span2)) + { + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("TODO"))); + } + result->month = span1->month - span2->month; /* overflow check copied from int4mi */ if (!SAMESIGN(span1->month, span2->month) && @@ -3271,6 +3388,18 @@ interval_mul(PG_FUNCTION_ARGS) result = (Interval *) palloc(sizeof(Interval)); + /* + * Multiplying infinite interval by finite number keeps it infinite but may change + * the sign. + */ + if (INTERVAL_NOT_FINITE(span)) + { + memcpy(result, span, sizeof(Interval)); + if (factor < 0.0) + neg_interval_infinite(result); + PG_RETURN_INTERVAL_P(result); + } + result_double = span->month * factor; if (isnan(result_double) || result_double > INT_MAX || result_double < INT_MIN) @@ -3362,6 +3491,18 @@ interval_div(PG_FUNCTION_ARGS) (errcode(ERRCODE_DIVISION_BY_ZERO), errmsg("division by zero"))); + /* + * Dividing infinite interval by finite number keeps it infinite but may change + * the sign. + */ + if (INTERVAL_NOT_FINITE(span)) + { + memcpy(result, span, sizeof(Interval)); + if (factor < 0.0) + neg_interval_infinite(result); + PG_RETURN_INTERVAL_P(result); + } + result->month = (int32) (span->month / factor); result->day = (int32) (span->day / factor); @@ -3916,6 +4057,11 @@ timestamp_bin(PG_FUNCTION_ARGS) (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("origin out of range"))); + if (INTERVAL_NOT_FINITE(stride)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("TODO"))); + if (stride->month != 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -4371,6 +4517,12 @@ interval_trunc(PG_FUNCTION_ARGS) result = (Interval *) palloc(sizeof(Interval)); + if (INTERVAL_NOT_FINITE(interval)) + { + memcpy(result, interval, sizeof(Interval)); + PG_RETURN_INTERVAL_P(result); + } + lowunits = downcase_truncate_identifier(VARDATA_ANY(units), VARSIZE_ANY_EXHDR(units), false); @@ -5253,6 +5405,11 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric) if (type == UNKNOWN_FIELD) type = DecodeSpecial(0, lowunits, &val); + if (INTERVAL_NOT_FINITE(interval)) + { + // TODO: copy logic from timestamp_part_common + } + if (type == UNITS) { interval2itm(*interval, tm); @@ -5955,3 +6112,22 @@ generate_series_timestamptz(PG_FUNCTION_ARGS) SRF_RETURN_DONE(funcctx); } } + +/* + * TODO: possibly we should move these to a place along with other interval_* + * functions. + */ + +/* Negates the given interval if it's infinite */ +static void +neg_interval_infinite(Interval *interval) +{ + if (INTERVAL_IS_NOBEGIN(interval)) + { + INTERVAL_NOEND(interval); + } + else if (INTERVAL_IS_NOEND(interval)) + { + INTERVAL_NOBEGIN(interval); + } +} diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h index d155f1b03b..4c281c9112 100644 --- a/src/include/datatype/timestamp.h +++ b/src/include/datatype/timestamp.h @@ -160,6 +160,28 @@ struct pg_itm_in #define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j)) +// TODO: Should we make custom NOBEGIN and NOEND constants for Interval? +#define INTERVAL_NOBEGIN(i) \ + do { \ + (i->time) = DT_NOBEGIN; \ + (i->day) = PG_INT32_MIN; \ + (i->month) = PG_INT32_MIN; \ + } while (0) + +#define INTERVAL_IS_NOBEGIN(i) \ + ((i->time) == DT_NOBEGIN && (i->day) == PG_INT32_MIN && (i->month) == PG_INT32_MIN) + +#define INTERVAL_NOEND(i) \ + do { \ + (i->time) = DT_NOEND; \ + (i->day) = PG_INT32_MAX; \ + (i->month) = PG_INT32_MAX; \ + } while (0) + +#define INTERVAL_IS_NOEND(i) \ + ((i->time) == DT_NOEND && (i->day) == PG_INT32_MAX && (i->month) == PG_INT32_MAX) + +#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i)) /* * Julian date support. diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out index 579e92e7b3..2a3e9eaf52 100644 --- a/src/test/regress/expected/interval.out +++ b/src/test/regress/expected/interval.out @@ -52,6 +52,19 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years..."; 9 years 1 mon -12 days +13:14:00 (1 row) +SELECT INTERVAL 'infinity' AS "eternity"; + eternity +---------- + infinity +(1 row) + +SELECT INTERVAL '-infinity' AS "beginning of time"; + beginning of time +------------------- + -infinity +(1 row) + +--TODO: Add tests for operators etc. by looking at the other tests below CREATE TABLE INTERVAL_TBL (f1 interval); INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute'); INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour'); @@ -63,6 +76,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds'); INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years'); INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months'); INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours'); +INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity'); +INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity'); -- badly formatted interval INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval'); ERROR: invalid input syntax for type interval: "badly formatted interval" @@ -72,6 +87,10 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 30 eons ago'); ERROR: invalid input syntax for type interval: "@ 30 eons ago" LINE 1: INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 30 eons ago'); ^ +INSERT INTO INTERVAL_TBL (f1) VALUES ('+infinity'); +ERROR: invalid input syntax for type interval: "+infinity" +LINE 1: INSERT INTO INTERVAL_TBL (f1) VALUES ('+infinity'); + ^ -- Test non-error-throwing API SELECT pg_input_is_valid('1.5 weeks', 'interval'); pg_input_is_valid @@ -117,7 +136,9 @@ SELECT * FROM INTERVAL_TBL; 6 years 5 mons 5 mons 12:00:00 -(10 rows) + infinity + -infinity +(12 rows) SELECT * FROM INTERVAL_TBL WHERE INTERVAL_TBL.f1 <> interval '@ 10 days'; @@ -132,7 +153,9 @@ SELECT * FROM INTERVAL_TBL 6 years 5 mons 5 mons 12:00:00 -(9 rows) + infinity + -infinity +(11 rows) SELECT * FROM INTERVAL_TBL WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours'; @@ -141,7 +164,8 @@ SELECT * FROM INTERVAL_TBL 00:01:00 05:00:00 -00:00:14 -(3 rows) + -infinity +(4 rows) SELECT * FROM INTERVAL_TBL WHERE INTERVAL_TBL.f1 < interval '@ 1 day'; @@ -150,7 +174,8 @@ SELECT * FROM INTERVAL_TBL 00:01:00 05:00:00 -00:00:14 -(3 rows) + -infinity +(4 rows) SELECT * FROM INTERVAL_TBL WHERE INTERVAL_TBL.f1 = interval '@ 34 years'; @@ -168,7 +193,8 @@ SELECT * FROM INTERVAL_TBL 6 years 5 mons 5 mons 12:00:00 -(5 rows) + infinity +(6 rows) SELECT * FROM INTERVAL_TBL WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago'; @@ -183,7 +209,8 @@ SELECT * FROM INTERVAL_TBL 6 years 5 mons 5 mons 12:00:00 -(9 rows) + infinity +(10 rows) SELECT r1.*, r2.* FROM INTERVAL_TBL r1, INTERVAL_TBL r2 @@ -191,27 +218,35 @@ SELECT r1.*, r2.* ORDER BY r1.f1, r2.f1; f1 | f1 -----------------+----------------- + -00:00:14 | -infinity + 00:01:00 | -infinity 00:01:00 | -00:00:14 + 05:00:00 | -infinity 05:00:00 | -00:00:14 05:00:00 | 00:01:00 + 1 day 02:03:04 | -infinity 1 day 02:03:04 | -00:00:14 1 day 02:03:04 | 00:01:00 1 day 02:03:04 | 05:00:00 + 10 days | -infinity 10 days | -00:00:14 10 days | 00:01:00 10 days | 05:00:00 10 days | 1 day 02:03:04 + 3 mons | -infinity 3 mons | -00:00:14 3 mons | 00:01:00 3 mons | 05:00:00 3 mons | 1 day 02:03:04 3 mons | 10 days + 5 mons | -infinity 5 mons | -00:00:14 5 mons | 00:01:00 5 mons | 05:00:00 5 mons | 1 day 02:03:04 5 mons | 10 days 5 mons | 3 mons + 5 mons 12:00:00 | -infinity 5 mons 12:00:00 | -00:00:14 5 mons 12:00:00 | 00:01:00 5 mons 12:00:00 | 05:00:00 @@ -219,6 +254,7 @@ SELECT r1.*, r2.* 5 mons 12:00:00 | 10 days 5 mons 12:00:00 | 3 mons 5 mons 12:00:00 | 5 mons + 6 years | -infinity 6 years | -00:00:14 6 years | 00:01:00 6 years | 05:00:00 @@ -227,6 +263,7 @@ SELECT r1.*, r2.* 6 years | 3 mons 6 years | 5 mons 6 years | 5 mons 12:00:00 + 34 years | -infinity 34 years | -00:00:14 34 years | 00:01:00 34 years | 05:00:00 @@ -236,7 +273,18 @@ SELECT r1.*, r2.* 34 years | 5 mons 34 years | 5 mons 12:00:00 34 years | 6 years -(45 rows) + infinity | -infinity + infinity | -00:00:14 + infinity | 00:01:00 + infinity | 05:00:00 + infinity | 1 day 02:03:04 + infinity | 10 days + infinity | 3 mons + infinity | 5 mons + infinity | 5 mons 12:00:00 + infinity | 6 years + infinity | 34 years +(66 rows) -- Test intervals that are large enough to overflow 64 bits in comparisons CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval); @@ -386,12 +434,20 @@ SELECT * FROM INTERVAL_TBL; @ 6 years @ 5 mons @ 5 mons 12 hours -(10 rows) + infinity + -infinity +(12 rows) -- test avg(interval), which is somewhat fragile since people have been -- known to change the allowed input syntax for type interval without -- updating pg_aggregate.agginitval select avg(f1) from interval_tbl; + avg +---------- + infinity +(1 row) + +select avg(f1) from interval_tbl where isfinite(f1); avg ------------------------------------------------- @ 4 years 1 mon 10 days 4 hours 18 mins 23 secs @@ -820,8 +876,8 @@ SELECT interval '1 2:03:04.5678' minute to second(2); SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes", (f1 + INTERVAL '1 month')::INTERVAL MONTH::INTERVAL YEAR AS "years" FROM interval_tbl; - f1 | minutes | years ------------------+-----------------+---------- + f1 | minutes | years +-----------------+-----------------+----------- 00:01:00 | 00:01:00 | 00:00:00 05:00:00 | 05:00:00 | 00:00:00 10 days | 10 days | 00:00:00 @@ -832,7 +888,9 @@ SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes", 6 years | 6 years | 6 years 5 mons | 5 mons | 00:00:00 5 mons 12:00:00 | 5 mons 12:00:00 | 00:00:00 -(10 rows) + infinity | infinity | infinity + -infinity | -infinity | -infinity +(12 rows) -- test inputting and outputting SQL standard interval literals SET IntervalStyle TO sql_standard; @@ -1776,3 +1834,536 @@ SELECT extract(epoch from interval '1000000000 days'); 86400000000000.000000 (1 row) +-- infinite intervals +SELECT isfinite(interval 'infinity'); + isfinite +---------- + f +(1 row) + +SELECT isfinite(interval '-infinity'); + isfinite +---------- + f +(1 row) + +SELECT date '1995-08-06' + interval 'infinity'; + ?column? +---------- + infinity +(1 row) + +SELECT date '1995-08-06' + interval '-infinity'; + ?column? +----------- + -infinity +(1 row) + +SELECT date '1995-08-06' - interval 'infinity'; + ?column? +---------- + infinity +(1 row) + +SELECT date '1995-08-06' - interval '-infinity'; + ?column? +----------- + -infinity +(1 row) + +SELECT date 'infinity' + interval 'infinity'; + ?column? +---------- + infinity +(1 row) + +SELECT date 'infinity' + interval '-infinity'; +ERROR: TODO +SELECT date '-infinity' + interval 'infinity'; +ERROR: TODO +SELECT date '-infinity' + interval '-infinity'; + ?column? +----------- + -infinity +(1 row) + +SELECT date 'infinity' - interval 'infinity'; +ERROR: TODO +SELECT date 'infinity' - interval '-infinity'; + ?column? +---------- + infinity +(1 row) + +SELECT date '-infinity' - interval 'infinity'; + ?column? +----------- + -infinity +(1 row) + +SELECT date '-infinity' - interval '-infinity'; +ERROR: TODO +SELECT interval 'infinity' + interval 'infinity'; + ?column? +---------- + infinity +(1 row) + +SELECT interval 'infinity' + interval '-infinity'; +ERROR: TODO +SELECT interval '-infinity' + interval 'infinity'; +ERROR: TODO +SELECT interval '-infinity' + interval '-infinity'; + ?column? +----------- + -infinity +(1 row) + +SELECT interval 'infinity' + interval '10 days'; + ?column? +---------- + infinity +(1 row) + +SELECT interval '-infinity' + interval '10 days'; + ?column? +----------- + -infinity +(1 row) + +SELECT interval 'infinity' - interval 'infinity'; +ERROR: TODO +SELECT interval 'infinity' - interval '-infinity'; + ?column? +---------- + infinity +(1 row) + +SELECT interval '-infinity' - interval 'infinity'; + ?column? +----------- + -infinity +(1 row) + +SELECT interval '-infinity' - interval '-infinity'; +ERROR: TODO +SELECT interval 'infinity' - interval '10 days'; + ?column? +---------- + infinity +(1 row) + +SELECT interval '-infinity' - interval '10 days'; + ?column? +----------- + -infinity +(1 row) + +SELECT timestamp '1995-08-06 12:30:15' + interval 'infinity'; + ?column? +---------- + infinity +(1 row) + +SELECT timestamp '1995-08-06 12:30:15' + interval '-infinity'; + ?column? +----------- + -infinity +(1 row) + +SELECT timestamp '1995-08-06 12:30:15' - interval 'infinity'; + ?column? +---------- + infinity +(1 row) + +SELECT timestamp '1995-08-06 12:30:15' - interval '-infinity'; + ?column? +----------- + -infinity +(1 row) + +SELECT timestamp 'infinity' + interval 'infinity' + ?column? +---------- + infinity +(1 row) + +SELECT timestamp 'infinity' + interval '-infinity' +ERROR: TODO +SELECT timestamp '-infinity' + interval 'infinity' +ERROR: TODO +SELECT timestamp '-infinity' + interval '-infinity' + ?column? +----------- + -infinity +(1 row) + +SELECT timestamp 'infinity' - interval 'infinity' +ERROR: TODO +SELECT timestamp 'infinity' - interval '-infinity' + ?column? +---------- + infinity +(1 row) + +SELECT timestamp '-infinity' - interval 'infinity' + ?column? +----------- + -infinity +(1 row) + +SELECT timestamp '-infinity' - interval '-infinity' +ERROR: TODO +SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity'; + ?column? +---------- + infinity +(1 row) + +SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity'; + ?column? +----------- + -infinity +(1 row) + +SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity'; + ?column? +---------- + infinity +(1 row) + +SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity'; + ?column? +----------- + -infinity +(1 row) + +SELECT timestamptz 'infinity' + interval 'infinity' + ?column? +---------- + infinity +(1 row) + +SELECT timestamptz 'infinity' + interval '-infinity' +ERROR: TODO +SELECT timestamptz '-infinity' + interval 'infinity' +ERROR: TODO +SELECT timestamptz '-infinity' + interval '-infinity' + ?column? +----------- + -infinity +(1 row) + +SELECT timestamptz 'infinity' - interval 'infinity' +ERROR: TODO +SELECT timestamptz 'infinity' - interval '-infinity' + ?column? +---------- + infinity +(1 row) + +SELECT timestamptz '-infinity' - interval 'infinity' + ?column? +----------- + -infinity +(1 row) + +SELECT timestamptz '-infinity' - interval '-infinity' +ERROR: TODO +SELECT time '11:27:42' + interval 'infinity'; +ERROR: TODO +SELECT time '11:27:42' + interval '-infinity'; +ERROR: TODO +SELECT time '11:27:42' - interval 'infinity'; +ERROR: TODO +SELECT time '11:27:42' - interval '-infinity'; +ERROR: TODO +SELECT timetz '11:27:42' + interval 'infinity'; +ERROR: TODO +SELECT timetz '11:27:42' + interval '-infinity'; +ERROR: TODO +SELECT timetz '11:27:42' - interval 'infinity'; +ERROR: TODO +SELECT timetz '11:27:42' - interval '-infinity'; +ERROR: TODO +SELECT interval 'infinity' < interval 'infinity'; + ?column? +---------- + f +(1 row) + +SELECT interval 'infinity' < interval '-infinity'; + ?column? +---------- + f +(1 row) + +SELECT interval '-infinity' < interval 'infinity'; + ?column? +---------- + t +(1 row) + +SELECT interval '-infinity' < interval '-infinity'; + ?column? +---------- + f +(1 row) + +SELECT interval 'infinity' <= interval 'infinity'; + ?column? +---------- + t +(1 row) + +SELECT interval 'infinity' <= interval '-infinity'; + ?column? +---------- + f +(1 row) + +SELECT interval '-infinity' <= interval 'infinity'; + ?column? +---------- + t +(1 row) + +SELECT interval '-infinity' <= interval '-infinity'; + ?column? +---------- + t +(1 row) + +SELECT interval 'infinity' > interval 'infinity'; + ?column? +---------- + f +(1 row) + +SELECT interval 'infinity' > interval '-infinity'; + ?column? +---------- + t +(1 row) + +SELECT interval '-infinity' > interval 'infinity'; + ?column? +---------- + f +(1 row) + +SELECT interval '-infinity' > interval '-infinity'; + ?column? +---------- + f +(1 row) + +SELECT interval 'infinity' >= interval 'infinity'; + ?column? +---------- + t +(1 row) + +SELECT interval 'infinity' >= interval '-infinity'; + ?column? +---------- + t +(1 row) + +SELECT interval '-infinity' >= interval 'infinity'; + ?column? +---------- + f +(1 row) + +SELECT interval '-infinity' >= interval '-infinity'; + ?column? +---------- + t +(1 row) + +SELECT interval 'infinity' = interval 'infinity'; + ?column? +---------- + t +(1 row) + +SELECT interval 'infinity' = interval '-infinity'; + ?column? +---------- + f +(1 row) + +SELECT interval '-infinity' = interval 'infinity'; + ?column? +---------- + f +(1 row) + +SELECT interval '-infinity' = interval '-infinity'; + ?column? +---------- + t +(1 row) + +SELECT interval 'infinity' <> interval 'infinity'; + ?column? +---------- + f +(1 row) + +SELECT interval 'infinity' <> interval '-infinity'; + ?column? +---------- + t +(1 row) + +SELECT interval '-infinity' <> interval 'infinity'; + ?column? +---------- + t +(1 row) + +SELECT interval '-infinity' <> interval '-infinity'; + ?column? +---------- + f +(1 row) + +SELECT -interval 'infinity'; + interval +----------- + -infinity +(1 row) + +SELECT -interval '-infinity'; + ?column? +---------- + infinity +(1 row) + +SELECT interval 'infinity' * 2; + ?column? +---------- + infinity +(1 row) + +SELECT interval 'infinity' * -2; + interval +----------- + -infinity +(1 row) + +SELECT interval '-infinity' * 2; + interval +----------- + -infinity +(1 row) + +SELECT interval '-infinity' * -2; + ?column? +---------- + infinity +(1 row) + +SELECT interval 'infinity' / 3; + ?column? +---------- + infinity +(1 row) + +SELECT interval 'infinity' / -3; + interval +----------- + -infinity +(1 row) + +SELECT interval '-infinity' / 3; + interval +----------- + -infinity +(1 row) + +SELECT interval '-infinity' / -3; + ?column? +---------- + infinity +(1 row) + + +SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00'); +ERROR: TODO +SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00'); +ERROR: TODO +SELECT date_part('month', interval 'infinity'); + date_part +----------- + +(1 row) + +SELECT date_part('month', interval '-infinity'); + date_part +----------- + +(1 row) + +SELECT date_trunc('hour', interval 'infinity'); + date_trunc +------------ + infinity +(1 row) + +SELECT date_trunc('hour', interval '-infinity'); + date_trunc +------------ + -infinity +(1 row) + +SELECT extract(month from interval 'infinity'); + extract +--------- + +(1 row) + +SELECT extract(month from interval '-infinity'); + extract +--------- + +(1 row) + +SELECT justify_days(interval 'infinity'); + justify_interval +------------------ + infinity +(1 row) + +SELECT justify_days(interval '-infinity'); + justify_interval +------------------ + -infinity +(1 row) + +SELECT justify_hours(interval 'infinity'); + justify_interval +------------------ + infinity +(1 row) + +SELECT justify_hours(interval '-infinity'); + justify_interval +------------------ + -infinity +(1 row) + +SELECT justify_interval(interval 'infinity'); + justify_interval +------------------ + infinity +(1 row) + +SELECT justify_interval(interval '-infinity'); + justify_interval +------------------ + -infinity +(1 row) diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql index 0517b5b82b..85b8007454 100644 --- a/src/test/regress/sql/interval.sql +++ b/src/test/regress/sql/interval.sql @@ -14,6 +14,10 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago..."; SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours"; SELECT INTERVAL '1.5 months' AS "One month 15 days"; SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years..."; +SELECT INTERVAL 'infinity' AS "eternity"; +SELECT INTERVAL '-infinity' AS "beginning of time"; + +--TODO: Add tests for operators etc. by looking at the other tests below CREATE TABLE INTERVAL_TBL (f1 interval); @@ -27,10 +31,13 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds'); INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years'); INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months'); INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours'); +INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity'); +INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity'); -- badly formatted interval INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval'); INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 30 eons ago'); +INSERT INTO INTERVAL_TBL (f1) VALUES ('+infinity'); -- Test non-error-throwing API SELECT pg_input_is_valid('1.5 weeks', 'interval'); @@ -141,6 +148,7 @@ SELECT * FROM INTERVAL_TBL; -- 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; @@ -578,3 +586,116 @@ SELECT f1, -- internal overflow test case SELECT extract(epoch from interval '1000000000 days'); + +-- infinite intervals +SELECT isfinite(interval 'infinity'); +SELECT isfinite(interval '-infinity'); + +SELECT date '1995-08-06' + interval 'infinity'; +SELECT date '1995-08-06' + interval '-infinity'; +SELECT date '1995-08-06' - interval 'infinity'; +SELECT date '1995-08-06' - interval '-infinity'; +SELECT date 'infinity' + interval 'infinity'; +SELECT date 'infinity' + interval '-infinity'; +SELECT date '-infinity' + interval 'infinity'; +SELECT date '-infinity' + interval '-infinity'; +SELECT date 'infinity' - interval 'infinity'; +SELECT date 'infinity' - interval '-infinity'; +SELECT date '-infinity' - interval 'infinity'; +SELECT date '-infinity' - interval '-infinity'; +SELECT interval 'infinity' + interval 'infinity'; +SELECT interval 'infinity' + interval '-infinity'; +SELECT interval '-infinity' + interval 'infinity'; +SELECT interval '-infinity' + interval '-infinity'; +SELECT interval 'infinity' + interval '10 days'; +SELECT interval '-infinity' + interval '10 days'; +SELECT interval 'infinity' - interval 'infinity'; +SELECT interval 'infinity' - interval '-infinity'; +SELECT interval '-infinity' - interval 'infinity'; +SELECT interval '-infinity' - interval '-infinity'; +SELECT interval 'infinity' - interval '10 days'; +SELECT interval '-infinity' - interval '10 days'; +SELECT timestamp '1995-08-06 12:30:15' + interval 'infinity'; +SELECT timestamp '1995-08-06 12:30:15' + interval '-infinity'; +SELECT timestamp '1995-08-06 12:30:15' - interval 'infinity'; +SELECT timestamp '1995-08-06 12:30:15' - interval '-infinity'; +SELECT timestamp 'infinity' + interval 'infinity' +SELECT timestamp 'infinity' + interval '-infinity' +SELECT timestamp '-infinity' + interval 'infinity' +SELECT timestamp '-infinity' + interval '-infinity' +SELECT timestamp 'infinity' - interval 'infinity' +SELECT timestamp 'infinity' - interval '-infinity' +SELECT timestamp '-infinity' - interval 'infinity' +SELECT timestamp '-infinity' - interval '-infinity' +SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity'; +SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity'; +SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity'; +SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity'; +SELECT timestamptz 'infinity' + interval 'infinity' +SELECT timestamptz 'infinity' + interval '-infinity' +SELECT timestamptz '-infinity' + interval 'infinity' +SELECT timestamptz '-infinity' + interval '-infinity' +SELECT timestamptz 'infinity' - interval 'infinity' +SELECT timestamptz 'infinity' - interval '-infinity' +SELECT timestamptz '-infinity' - interval 'infinity' +SELECT timestamptz '-infinity' - interval '-infinity' +SELECT 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 interval 'infinity' < interval 'infinity'; +SELECT interval 'infinity' < interval '-infinity'; +SELECT interval '-infinity' < interval 'infinity'; +SELECT interval '-infinity' < interval '-infinity'; +SELECT interval 'infinity' <= interval 'infinity'; +SELECT interval 'infinity' <= interval '-infinity'; +SELECT interval '-infinity' <= interval 'infinity'; +SELECT interval '-infinity' <= interval '-infinity'; +SELECT interval 'infinity' > interval 'infinity'; +SELECT interval 'infinity' > interval '-infinity'; +SELECT interval '-infinity' > interval 'infinity'; +SELECT interval '-infinity' > interval '-infinity'; +SELECT interval 'infinity' >= interval 'infinity'; +SELECT interval 'infinity' >= interval '-infinity'; +SELECT interval '-infinity' >= interval 'infinity'; +SELECT interval '-infinity' >= interval '-infinity'; +SELECT interval 'infinity' = interval 'infinity'; +SELECT interval 'infinity' = interval '-infinity'; +SELECT interval '-infinity' = interval 'infinity'; +SELECT interval '-infinity' = interval '-infinity'; +SELECT interval 'infinity' <> interval 'infinity'; +SELECT interval 'infinity' <> interval '-infinity'; +SELECT interval '-infinity' <> interval 'infinity'; +SELECT interval '-infinity' <> interval '-infinity'; + +SELECT -interval 'infinity'; +SELECT -interval '-infinity'; +SELECT interval 'infinity' * 2; +SELECT interval 'infinity' * -2; +SELECT interval '-infinity' * 2; +SELECT interval '-infinity' * -2; +SELECT interval 'infinity' / 3; +SELECT interval 'infinity' / -3; +SELECT interval '-infinity' / 3; +SELECT interval '-infinity' / -3; + +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 date_part('month', interval 'infinity'); +SELECT date_part('month', interval '-infinity'); +SELECT date_trunc('hour', interval 'infinity'); +SELECT date_trunc('hour', interval '-infinity'); +SELECT extract(month from interval 'infinity'); +SELECT extract(month from interval '-infinity'); + +SELECT justify_days(interval 'infinity'); +SELECT justify_days(interval '-infinity'); +SELECT justify_hours(interval 'infinity'); +SELECT justify_hours(interval '-infinity'); +SELECT justify_interval(interval 'infinity'); +SELECT justify_interval(interval '-infinity'); -- 2.34.1