Infinite Interval
Hi all,
There have been multiple threads in the past discussing infinite
intervals:
/messages/by-id/4EB095C8.1050703@agliodbs.com
/messages/by-id/200101241913.f0OJDUu45423@hub.org
/messages/by-id/CANP8+jKTxQh4Mj+U3mWO3JHYb11SeQX9FW8SENrGbTdVxu6NNA@mail.gmail.com
As well as an entry in the TODO list:
https://wiki.postgresql.org/wiki/Todo#Dates_and_Times
However, it doesn't seem like this was ever implemented. Is there still
any interest in this feature? If so, I'd like to try and implement it.
The proposed design from the most recent thread was to reserve
INT32_MAX months for infinity and INT32_MIN months for negative
infinity. As pointed out in the thread, these are currently valid
non-infinite intervals, but they are out of the documented range.
Thanks,
Joe Koshakow
Hi Joseph,
I stumbled upon this requirement a few times. So I started working on
this support in my spare time as a hobby project to understand
horology code in PostgreSQL. This was sitting in my repositories for
more than an year. Now that I have someone else showing an interest,
it's time for it to face the world. Rebased it, fixed conflicts.
PFA patch implementing infinite interval. It's still WIP, there are
TODOs in the code and also the commit message lists things that are
known to be incomplete. You might want to assess expected output
carefully
On Sun, Dec 11, 2022 at 12:51 AM Joseph Koshakow <koshy44@gmail.com> wrote:>
The proposed design from the most recent thread was to reserve
INT32_MAX months for infinity and INT32_MIN months for negative
infinity. As pointed out in the thread, these are currently valid
non-infinite intervals, but they are out of the documented range.
The patch uses both months and days together to avoid this problem.
Please feel free to complete the patch, work on review comments etc. I
will help as and when I find time.
--
Best Wishes,
Ashutosh Bapat
Attachments:
0001-Support-infinite-interval.patchtext/x-patch; charset=US-ASCII; name=0001-Support-infinite-interval.patchDownload
From 09a8fef224a3682059fe1adf42957f693a41d242 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@2ndquadrant.com>
Date: Thu, 30 Apr 2020 10:06:49 +0530
Subject: [PATCH] Support infinite interval
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/datetime.c | 2 +
src/backend/utils/adt/timestamp.c | 166 ++++++++++++++++++++++++-
src/test/regress/expected/interval.out | 80 ++++++++++--
src/test/regress/sql/interval.sql | 8 ++
4 files changed, 242 insertions(+), 14 deletions(-)
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..0c7286b06e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -79,6 +79,12 @@ 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 interval_noend(Interval *interval);
+static bool interval_is_noend(Interval *interval);
+static void interval_nobegin(Interval *interval);
+static bool interval_is_nobegin(Interval *interval);
+static bool interval_not_finite(Interval *interval);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -943,6 +949,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 +979,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 +1371,13 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod,
INT64CONST(0)
};
+ /*
+ * Infinite interval after being subjected to typmod conversion remains
+ * infinite.
+ */
+ if (interval_not_finite(interval))
+ return;
+
/*
* Unspecified range and precision? Then not necessary to adjust. Setting
* typmod to -1 is the convention for all data types.
@@ -1545,6 +1571,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)
{
@@ -2080,10 +2117,12 @@ timestamp_finite(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(!TIMESTAMP_NOT_FINITE(timestamp));
}
+/* TODO: modify this to check finite-ness */
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+ PG_RETURN_BOOL(!interval_not_finite(interval));
}
@@ -2926,8 +2965,27 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Timestamp result;
+ /*
+ * Adding finite interval to an infinite time is going to be infinite in
+ * the same direction. Adding infinte interval to infinite timestamp in the
+ * same direction results in an infinite timestamp in the same direction.
+ * 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.
+ */
if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
+ else if (interval_not_finite(span))
+ {
+ if (interval_is_nobegin(span))
+ TIMESTAMP_NOBEGIN(result);
+ else
+ {
+ Assert(interval_is_noend(span));
+ TIMESTAMP_NOEND(result);
+ }
+ }
else
{
if (span->month != 0)
@@ -3032,8 +3090,27 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
TimestampTz result;
int tz;
+ /*
+ * Adding finite interval to an infinite time is going to be infinite in
+ * the same direction. Adding infinte interval to infinite timestamp in the
+ * same direction results in an infinite timestamp in the same direction.
+ * 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.
+ */
if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
+ else if (interval_not_finite(span))
+ {
+ if (interval_is_nobegin(span))
+ TIMESTAMP_NOBEGIN(result);
+ else
+ {
+ Assert(interval_is_noend(span));
+ TIMESTAMP_NOEND(result);
+ }
+ }
else
{
if (span->month != 0)
@@ -3192,6 +3269,21 @@ interval_pl(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ /*
+ * TODO: What if both span1 and span2 are infinite and in oppposite
+ * direction?
+ */
+ if (interval_not_finite(span1))
+ {
+ memcpy(result, span1, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (interval_not_finite(span2))
+ {
+ memcpy(result, span2, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = span1->month + span2->month;
/* overflow check copied from int4pl */
if (SAMESIGN(span1->month, span2->month) &&
@@ -3362,6 +3454,14 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ /* Dividing infinite interval by finite number keeps it infinite. */
+ /* TODO: Do we change the sign of infinity if factor is negative? */
+ if (interval_not_finite(span))
+ {
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = (int32) (span->month / factor);
result->day = (int32) (span->day / factor);
@@ -5955,3 +6055,63 @@ generate_series_timestamptz(PG_FUNCTION_ARGS)
SRF_RETURN_DONE(funcctx);
}
}
+
+/*
+ * TODO: possibly we should move these to a place along with other interval_*
+ * functions.
+ */
+
+/* Set the given interval to indicate infinite interval in the future. */
+static void
+interval_noend(Interval *interval)
+{
+ interval->time = DT_NOEND;
+ interval->day = PG_INT32_MAX;
+ interval->month = PG_INT32_MAX;
+}
+
+/* Does the given interval indicate infinite interval in the future? */
+static bool
+interval_is_noend(Interval *interval)
+{
+ /*
+ * TODO: Possibly it makes sense to just check one of the fields to reduce
+ * the number of instructions here. But it's safer to check all the three
+ * fields.
+ */
+ return interval->time == DT_NOEND &&
+ interval->day == PG_INT32_MAX &&
+ interval->month == PG_INT32_MAX;
+}
+
+/* Set the given interval to indicate infinite interval in the past. */
+static void
+interval_nobegin(Interval *interval)
+{
+ interval->time = DT_NOBEGIN;
+ interval->day = PG_INT32_MIN;
+ interval->month = PG_INT32_MIN;
+}
+
+/* Does the given interval indicate infinite interval in the past? */
+static bool
+interval_is_nobegin(Interval *interval)
+{
+ /*
+ * TODO: Possibly it makes sense to just check one of the fields to reduce
+ * the number of instructions here. But it's safer to check all the three
+ * fields.
+ */
+ return interval->time == DT_NOBEGIN &&
+ interval->day == PG_INT32_MIN &&
+ interval->month == PG_INT32_MIN;
+}
+
+/* Is the given interval infinite? */
+/* TODO: probably a material for a macro. */
+static bool
+interval_not_finite(Interval *interval)
+{
+ return interval_is_nobegin(interval) || interval_is_noend(interval);
+}
+
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 579e92e7b3..8242d28945 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;
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 0517b5b82b..92f60ad005 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;
--
2.25.1
On Mon, Dec 12, 2022 at 8:05 AM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
Hi Joseph,
I stumbled upon this requirement a few times. So I started working on
this support in my spare time as a hobby project to understand
horology code in PostgreSQL. This was sitting in my repositories for
more than an year. Now that I have someone else showing an interest,
it's time for it to face the world. Rebased it, fixed conflicts.PFA patch implementing infinite interval. It's still WIP, there are
TODOs in the code and also the commit message lists things that are
known to be incomplete. You might want to assess expected output
carefully
That's great! I was also planning to just work on it as a hobby
project, so I'll try and review and add updates as I find free
time as well.
The proposed design from the most recent thread was to reserve
INT32_MAX months for infinity and INT32_MIN months for negative
infinity. As pointed out in the thread, these are currently valid
non-infinite intervals, but they are out of the documented range.The patch uses both months and days together to avoid this problem.
Can you expand on this part? I believe the full range of representable
intervals are considered valid as of v15.
- Joe Koshakow
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
Attachments:
v2-0001-Support-infinite-interval.patchtext/x-patch; charset=US-ASCII; name=v2-0001-Support-infinite-interval.patchDownload
From 4c1be4e2aa7abd56967fdce14b100715f3a63fee Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@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/datetime.c | 2 +
src/backend/utils/adt/timestamp.c | 166 +++++++-
src/test/regress/expected/interval.out | 565 ++++++++++++++++++++++++-
src/test/regress/sql/interval.sql | 105 +++++
4 files changed, 824 insertions(+), 14 deletions(-)
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..0c7286b06e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -79,6 +79,12 @@ 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 interval_noend(Interval *interval);
+static bool interval_is_noend(Interval *interval);
+static void interval_nobegin(Interval *interval);
+static bool interval_is_nobegin(Interval *interval);
+static bool interval_not_finite(Interval *interval);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -943,6 +949,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 +979,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 +1371,13 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod,
INT64CONST(0)
};
+ /*
+ * Infinite interval after being subjected to typmod conversion remains
+ * infinite.
+ */
+ if (interval_not_finite(interval))
+ return;
+
/*
* Unspecified range and precision? Then not necessary to adjust. Setting
* typmod to -1 is the convention for all data types.
@@ -1545,6 +1571,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)
{
@@ -2080,10 +2117,12 @@ timestamp_finite(PG_FUNCTION_ARGS)
PG_RETURN_BOOL(!TIMESTAMP_NOT_FINITE(timestamp));
}
+/* TODO: modify this to check finite-ness */
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+ PG_RETURN_BOOL(!interval_not_finite(interval));
}
@@ -2926,8 +2965,27 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Timestamp result;
+ /*
+ * Adding finite interval to an infinite time is going to be infinite in
+ * the same direction. Adding infinte interval to infinite timestamp in the
+ * same direction results in an infinite timestamp in the same direction.
+ * 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.
+ */
if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
+ else if (interval_not_finite(span))
+ {
+ if (interval_is_nobegin(span))
+ TIMESTAMP_NOBEGIN(result);
+ else
+ {
+ Assert(interval_is_noend(span));
+ TIMESTAMP_NOEND(result);
+ }
+ }
else
{
if (span->month != 0)
@@ -3032,8 +3090,27 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
TimestampTz result;
int tz;
+ /*
+ * Adding finite interval to an infinite time is going to be infinite in
+ * the same direction. Adding infinte interval to infinite timestamp in the
+ * same direction results in an infinite timestamp in the same direction.
+ * 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.
+ */
if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
+ else if (interval_not_finite(span))
+ {
+ if (interval_is_nobegin(span))
+ TIMESTAMP_NOBEGIN(result);
+ else
+ {
+ Assert(interval_is_noend(span));
+ TIMESTAMP_NOEND(result);
+ }
+ }
else
{
if (span->month != 0)
@@ -3192,6 +3269,21 @@ interval_pl(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ /*
+ * TODO: What if both span1 and span2 are infinite and in oppposite
+ * direction?
+ */
+ if (interval_not_finite(span1))
+ {
+ memcpy(result, span1, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (interval_not_finite(span2))
+ {
+ memcpy(result, span2, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = span1->month + span2->month;
/* overflow check copied from int4pl */
if (SAMESIGN(span1->month, span2->month) &&
@@ -3362,6 +3454,14 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ /* Dividing infinite interval by finite number keeps it infinite. */
+ /* TODO: Do we change the sign of infinity if factor is negative? */
+ if (interval_not_finite(span))
+ {
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = (int32) (span->month / factor);
result->day = (int32) (span->day / factor);
@@ -5955,3 +6055,63 @@ generate_series_timestamptz(PG_FUNCTION_ARGS)
SRF_RETURN_DONE(funcctx);
}
}
+
+/*
+ * TODO: possibly we should move these to a place along with other interval_*
+ * functions.
+ */
+
+/* Set the given interval to indicate infinite interval in the future. */
+static void
+interval_noend(Interval *interval)
+{
+ interval->time = DT_NOEND;
+ interval->day = PG_INT32_MAX;
+ interval->month = PG_INT32_MAX;
+}
+
+/* Does the given interval indicate infinite interval in the future? */
+static bool
+interval_is_noend(Interval *interval)
+{
+ /*
+ * TODO: Possibly it makes sense to just check one of the fields to reduce
+ * the number of instructions here. But it's safer to check all the three
+ * fields.
+ */
+ return interval->time == DT_NOEND &&
+ interval->day == PG_INT32_MAX &&
+ interval->month == PG_INT32_MAX;
+}
+
+/* Set the given interval to indicate infinite interval in the past. */
+static void
+interval_nobegin(Interval *interval)
+{
+ interval->time = DT_NOBEGIN;
+ interval->day = PG_INT32_MIN;
+ interval->month = PG_INT32_MIN;
+}
+
+/* Does the given interval indicate infinite interval in the past? */
+static bool
+interval_is_nobegin(Interval *interval)
+{
+ /*
+ * TODO: Possibly it makes sense to just check one of the fields to reduce
+ * the number of instructions here. But it's safer to check all the three
+ * fields.
+ */
+ return interval->time == DT_NOBEGIN &&
+ interval->day == PG_INT32_MIN &&
+ interval->month == PG_INT32_MIN;
+}
+
+/* Is the given interval infinite? */
+/* TODO: probably a material for a macro. */
+static bool
+interval_not_finite(Interval *interval)
+{
+ return interval_is_nobegin(interval) || interval_is_noend(interval);
+}
+
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 579e92e7b3..68d48a0b3e 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,488 @@ 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 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 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');
+ interval
+----------
+ infinity
+(1 row)
+
+SELECT date_trunc('hour', interval '-infinity');
+ interval
+-----------
+ -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');
+ interval
+----------
+ infinity
+(1 row)
+
+SELECT justify_days(interval '-infinity');
+ interval
+-----------
+ -infinity
+(1 row)
+
+SELECT justify_hours(interval 'infinity');
+ interval
+----------
+ infinity
+(1 row)
+
+SELECT justify_hours(interval '-infinity');
+ interval
+-----------
+ -infinity
+(1 row)
+
+SELECT justify_interval(interval 'infinity');
+ interval
+----------
+ infinity
+(1 row)
+
+SELECT justify_interval(interval '-infinity');
+ interval
+-----------
+ -infinity
+(1 row)
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 0517b5b82b..03d50140a4 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,100 @@ 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 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 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
On Sat, Dec 17, 2022 at 2:34 PM Joseph Koshakow <koshy44@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
Attachments:
v3-0001-Support-infinite-interval.patchtext/x-patch; charset=US-ASCII; name=v3-0001-Support-infinite-interval.patchDownload
From e6e764dd8f8423f2aec0fb3782f170c59557adf6 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@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
Hi Ashutosh,
I ended up doing some more work on this today. All of the major
features should be implemented now. Below are what I think are the
outstanding TODOs:
- Clean up error messages and error codes
- Figure out how to correctly implement interval_part for infinite
intervals. For now I pretty much copied the implementation of
timestamp_part, but I'm not convinced that's correct.
- Fix horology tests.
- Test consolidation. After looking through the interval tests, I
realized that I may have duplicated some test cases. It would probably
be best to remove those duplicate tests.
- General cleanup, remove TODOs.
Attached is my most recent patch.
- Joe Koshakow
Attachments:
v4-0001-Support-infinite-interval.patchtext/x-patch; charset=US-ASCII; name=v4-0001-Support-infinite-interval.patchDownload
From 380cde4061afd6eed4cde938a4c668a2c96bb58f Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@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 | 330 ++++++++-
src/include/datatype/timestamp.h | 22 +
src/test/regress/expected/interval.out | 953 ++++++++++++++++++++++++-
src/test/regress/sql/interval.sql | 182 ++++-
6 files changed, 1442 insertions(+), 67 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..d108057ce5 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 negate_interval(Interval *interval, Interval *result);
/* 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)
{
@@ -2033,6 +2066,8 @@ 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;
}
@@ -2050,6 +2085,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2083,7 +2120,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 +2813,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 +2891,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 +2932,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,7 +2973,25 @@ 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
{
@@ -3005,9 +3070,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ negate_interval(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3032,7 +3095,25 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
TimestampTz result;
int tz;
- 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
{
@@ -3115,9 +3196,7 @@ timestamptz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ negate_interval(span, &tspan);
return DirectFunctionCall2(timestamptz_pl_interval,
TimestampGetDatum(timestamp),
@@ -3132,23 +3211,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ negate_interval(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3192,6 +3255,38 @@ 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_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOEND(span2))
+ {
+ INTERVAL_NOEND(result);
+ 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")));
+ }
+ else if (INTERVAL_NOT_FINITE(span1))
+ {
+ memcpy(result, span1, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_NOT_FINITE(span2))
+ {
+ memcpy(result, span2, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = span1->month + span2->month;
/* overflow check copied from int4pl */
if (SAMESIGN(span1->month, span2->month) &&
@@ -3226,6 +3321,39 @@ 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_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOBEGIN(span2))
+ {
+ INTERVAL_NOEND(result);
+ 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")));
+ }
+ else if (INTERVAL_NOT_FINITE(span1))
+ {
+ memcpy(result, span1, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_NOT_FINITE(span2))
+ {
+ memcpy(result, span2, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = span1->month - span2->month;
/* overflow check copied from int4mi */
if (!SAMESIGN(span1->month, span2->month) &&
@@ -3271,6 +3399,19 @@ 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))
+ {
+ if (factor < 0.0)
+ negate_interval(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3362,6 +3503,19 @@ 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))
+ {
+ if (factor < 0.0)
+ negate_interval(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);
@@ -3916,6 +4070,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 +4530,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);
@@ -5230,6 +5395,62 @@ extract_timestamptz(PG_FUNCTION_ARGS)
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * 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.
+ */
+// TODO I don't actaully know if this is correct.
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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))));
+
+ // TODO: Maybe everything should be returning inf/-inf?
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ 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.
*/
@@ -5253,6 +5474,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteTimestampTzPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r)
+ {
+ 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);
@@ -5955,3 +6204,28 @@ 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 */
+static void
+negate_interval(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+ }
+}
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..7567d51661 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,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +870,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 +882,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;
@@ -1578,31 +1630,31 @@ LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
-- 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
@@ -1681,19 +1733,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | | | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | | | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1767,7 +1821,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1776,3 +1832,832 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+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';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT -interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * 2;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * -2;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' * 2;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' * -2;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' / 3;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' / -3;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' / 3;
+ ?column?
+-----------
+ -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_trunc('hour', interval 'infinity');
+ date_trunc
+------------
+ infinity
+(1 row)
+
+SELECT date_trunc('hour', interval '-infinity');
+ date_trunc
+------------
+ -infinity
+(1 row)
+
+SELECT date_part('us', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('us', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('ms', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('ms', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('second', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('second', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('minute', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('minute', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('hour', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('hour', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('day', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('day', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('month', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('month', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('quarter', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('quarter', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('year', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('year', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('decade', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('decade', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('century', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('century', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('millennium', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('millennium', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('epoch', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('epoch', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(us from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(us from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(ms from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(ms from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(second from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(second from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(minute from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(minute from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(hour from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(hour from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(day from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(day from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(month from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(month from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(quarter from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(quarter from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(year from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(year from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(decade from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(decade from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(century from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(century from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(millennium from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(millennium from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(epoch from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(epoch from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT justify_days(interval 'infinity');
+ justify_days
+--------------
+ infinity
+(1 row)
+
+SELECT justify_days(interval '-infinity');
+ justify_days
+--------------
+ -infinity
+(1 row)
+
+SELECT justify_hours(interval 'infinity');
+ justify_hours
+---------------
+ infinity
+(1 row)
+
+SELECT justify_hours(interval '-infinity');
+ justify_hours
+---------------
+ -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..e58135f3cb 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');
@@ -140,7 +147,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -509,13 +516,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
-- 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;
@@ -578,3 +585,168 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+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_trunc('hour', interval 'infinity');
+SELECT date_trunc('hour', interval '-infinity');
+
+SELECT date_part('us', interval 'infinity');
+SELECT date_part('us', interval '-infinity');
+SELECT date_part('ms', interval 'infinity');
+SELECT date_part('ms', interval '-infinity');
+SELECT date_part('second', interval 'infinity');
+SELECT date_part('second', interval '-infinity');
+SELECT date_part('minute', interval 'infinity');
+SELECT date_part('minute', interval '-infinity');
+SELECT date_part('hour', interval 'infinity');
+SELECT date_part('hour', interval '-infinity');
+SELECT date_part('day', interval 'infinity');
+SELECT date_part('day', interval '-infinity');
+SELECT date_part('month', interval 'infinity');
+SELECT date_part('month', interval '-infinity');
+SELECT date_part('quarter', interval 'infinity');
+SELECT date_part('quarter', interval '-infinity');
+SELECT date_part('year', interval 'infinity');
+SELECT date_part('year', interval '-infinity');
+SELECT date_part('decade', interval 'infinity');
+SELECT date_part('decade', interval '-infinity');
+SELECT date_part('century', interval 'infinity');
+SELECT date_part('century', interval '-infinity');
+SELECT date_part('millennium', interval 'infinity');
+SELECT date_part('millennium', interval '-infinity');
+SELECT date_part('epoch', interval 'infinity');
+SELECT date_part('epoch', interval '-infinity');
+SELECT extract(us from interval 'infinity');
+SELECT extract(us from interval '-infinity');
+SELECT extract(ms from interval 'infinity');
+SELECT extract(ms from interval '-infinity');
+SELECT extract(second from interval 'infinity');
+SELECT extract(second from interval '-infinity');
+SELECT extract(minute from interval 'infinity');
+SELECT extract(minute from interval '-infinity');
+SELECT extract(hour from interval 'infinity');
+SELECT extract(hour from interval '-infinity');
+SELECT extract(day from interval 'infinity');
+SELECT extract(day from interval '-infinity');
+SELECT extract(month from interval 'infinity');
+SELECT extract(month from interval '-infinity');
+SELECT extract(quarter from interval 'infinity');
+SELECT extract(quarter from interval '-infinity');
+SELECT extract(year from interval 'infinity');
+SELECT extract(year from interval '-infinity');
+SELECT extract(decade from interval 'infinity');
+SELECT extract(decade from interval '-infinity');
+SELECT extract(century from interval 'infinity');
+SELECT extract(century from interval '-infinity');
+SELECT extract(millennium from interval 'infinity');
+SELECT extract(millennium from interval '-infinity');
+SELECT extract(epoch from interval 'infinity');
+SELECT extract(epoch 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
I have another update, I cleaned up some of the error messages, fixed
the horology tests, and ran pgindent.
- Joe
Attachments:
v5-0001-Support-infinite-interval.patchtext/x-patch; charset=US-ASCII; name=v5-0001-Support-infinite-interval.patchDownload
From 518c59be586abf5779c5727c2117b6a46b466503 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@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 | 14 +-
src/backend/utils/adt/timestamp.c | 332 ++++++++-
src/include/datatype/timestamp.h | 22 +
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 953 ++++++++++++++++++++++++-
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 182 ++++-
8 files changed, 1460 insertions(+), 75 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 1cf7c7652d..c6259cd9c1 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("cannot add infinite interval to time")));
+
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("cannot subtract infinite interval from time")));
+
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("cannot add infinite interval to time")));
+
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("cannot subtract infinite interval from time")));
+
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..b60d91dfb8 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -70,7 +70,7 @@ static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
const char *abbr, pg_tz *tzp,
int *offset, int *isdst);
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra);
+ DateTimeErrorExtra * extra);
const int day_tab[2][13] =
@@ -977,7 +977,7 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
int
DecodeDateTime(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -1927,7 +1927,7 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
int
DecodeTimeOnly(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -3232,7 +3232,7 @@ DecodeTimezone(const char *str, int *tzp)
int
DecodeTimezoneAbbrev(int field, const char *lowtoken,
int *ftype, int *offset, pg_tz **tz,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
const datetkn *tp;
@@ -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;
@@ -4039,7 +4041,7 @@ DecodeUnits(int field, const char *lowtoken, int *val)
* separate SQLSTATE codes, so ...
*/
void
-DateTimeParseError(int dterr, DateTimeErrorExtra *extra,
+DateTimeParseError(int dterr, DateTimeErrorExtra * extra,
const char *str, const char *datatype,
Node *escontext)
{
@@ -4918,7 +4920,7 @@ InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
*/
static pg_tz *
FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
DynamicZoneAbbrev *dtza;
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3f2508c0c4..bbdf0cfdea 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 negate_interval(Interval *interval, Interval *result);
/* 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)
{
@@ -2033,6 +2066,8 @@ 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;
}
@@ -2050,6 +2085,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2083,7 +2120,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2775,6 +2814,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 +2892,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 +2933,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,7 +2974,25 @@ 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
{
@@ -3005,9 +3071,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ negate_interval(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3032,7 +3096,25 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
TimestampTz result;
int tz;
- 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
{
@@ -3115,9 +3197,7 @@ timestamptz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ negate_interval(span, &tspan);
return DirectFunctionCall2(timestamptz_pl_interval,
TimestampGetDatum(timestamp),
@@ -3132,23 +3212,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ negate_interval(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3192,6 +3256,39 @@ 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.
+ */
+ /* TODO can be combined and simplified */
+ if (INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOBEGIN(span2))
+ {
+ INTERVAL_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOEND(span2))
+ {
+ INTERVAL_NOEND(result);
+ 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")));
+ }
+ else if (INTERVAL_NOT_FINITE(span1))
+ {
+ memcpy(result, span1, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_NOT_FINITE(span2))
+ {
+ memcpy(result, span2, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = span1->month + span2->month;
/* overflow check copied from int4pl */
if (SAMESIGN(span1->month, span2->month) &&
@@ -3226,6 +3323,39 @@ 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.
+ */
+ /* TODO can be simplified and cleaned up */
+ if (INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOEND(span2))
+ {
+ INTERVAL_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOBEGIN(span2))
+ {
+ INTERVAL_NOEND(result);
+ 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")));
+ }
+ else if (INTERVAL_NOT_FINITE(span1))
+ {
+ memcpy(result, span1, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_NOT_FINITE(span2))
+ {
+ memcpy(result, span2, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = span1->month - span2->month;
/* overflow check copied from int4mi */
if (!SAMESIGN(span1->month, span2->month) &&
@@ -3271,6 +3401,19 @@ 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))
+ {
+ if (factor < 0.0)
+ negate_interval(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3362,6 +3505,19 @@ 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))
+ {
+ if (factor < 0.0)
+ negate_interval(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);
@@ -3916,6 +4072,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4371,6 +4532,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);
@@ -5230,6 +5397,62 @@ extract_timestamptz(PG_FUNCTION_ARGS)
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * 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.
+ */
+/* TODO I don't actaully know if this is correct. */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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))));
+
+ /* TODO: Maybe everything should be returning inf/-inf? */
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ 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.
*/
@@ -5253,6 +5476,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteTimestampTzPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r)
+ {
+ 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);
@@ -5955,3 +6206,28 @@ 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 */
+static void
+negate_interval(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+ }
+}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index d155f1b03b..ed7b2cc403 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/horology.out b/src/test/regress/expected/horology.out
index de73683690..10b86963ee 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -939,6 +939,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1050,6 +1051,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1157,6 +1159,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1432,6 +1435,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1599,7 +1603,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 579e92e7b3..a31f17f3fb 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,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +870,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 +882,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;
@@ -1578,31 +1630,31 @@ LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
-- 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
@@ -1681,19 +1733,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | | | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | | | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1767,7 +1821,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1776,3 +1832,832 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+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: 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 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';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT -interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * 2;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * -2;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' * 2;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' * -2;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' / 3;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' / -3;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' / 3;
+ ?column?
+-----------
+ -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: 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 date_trunc('hour', interval 'infinity');
+ date_trunc
+------------
+ infinity
+(1 row)
+
+SELECT date_trunc('hour', interval '-infinity');
+ date_trunc
+------------
+ -infinity
+(1 row)
+
+SELECT date_part('us', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('us', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('ms', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('ms', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('second', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('second', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('minute', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('minute', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('hour', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('hour', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('day', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('day', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('month', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('month', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('quarter', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('quarter', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('year', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('year', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('decade', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('decade', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('century', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('century', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('millennium', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('millennium', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('epoch', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('epoch', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(us from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(us from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(ms from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(ms from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(second from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(second from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(minute from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(minute from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(hour from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(hour from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(day from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(day from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(month from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(month from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(quarter from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(quarter from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(year from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(year from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(decade from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(decade from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(century from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(century from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(millennium from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(millennium from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(epoch from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(epoch from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT justify_days(interval 'infinity');
+ justify_days
+--------------
+ infinity
+(1 row)
+
+SELECT justify_days(interval '-infinity');
+ justify_days
+--------------
+ -infinity
+(1 row)
+
+SELECT justify_hours(interval 'infinity');
+ justify_hours
+---------------
+ infinity
+(1 row)
+
+SELECT justify_hours(interval '-infinity');
+ justify_hours
+---------------
+ -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/horology.sql b/src/test/regress/sql/horology.sql
index 2724a2bbc7..2494d852a6 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -173,14 +173,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -253,11 +256,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 0517b5b82b..e58135f3cb 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');
@@ -140,7 +147,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -509,13 +516,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
-- 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;
@@ -578,3 +585,168 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+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_trunc('hour', interval 'infinity');
+SELECT date_trunc('hour', interval '-infinity');
+
+SELECT date_part('us', interval 'infinity');
+SELECT date_part('us', interval '-infinity');
+SELECT date_part('ms', interval 'infinity');
+SELECT date_part('ms', interval '-infinity');
+SELECT date_part('second', interval 'infinity');
+SELECT date_part('second', interval '-infinity');
+SELECT date_part('minute', interval 'infinity');
+SELECT date_part('minute', interval '-infinity');
+SELECT date_part('hour', interval 'infinity');
+SELECT date_part('hour', interval '-infinity');
+SELECT date_part('day', interval 'infinity');
+SELECT date_part('day', interval '-infinity');
+SELECT date_part('month', interval 'infinity');
+SELECT date_part('month', interval '-infinity');
+SELECT date_part('quarter', interval 'infinity');
+SELECT date_part('quarter', interval '-infinity');
+SELECT date_part('year', interval 'infinity');
+SELECT date_part('year', interval '-infinity');
+SELECT date_part('decade', interval 'infinity');
+SELECT date_part('decade', interval '-infinity');
+SELECT date_part('century', interval 'infinity');
+SELECT date_part('century', interval '-infinity');
+SELECT date_part('millennium', interval 'infinity');
+SELECT date_part('millennium', interval '-infinity');
+SELECT date_part('epoch', interval 'infinity');
+SELECT date_part('epoch', interval '-infinity');
+SELECT extract(us from interval 'infinity');
+SELECT extract(us from interval '-infinity');
+SELECT extract(ms from interval 'infinity');
+SELECT extract(ms from interval '-infinity');
+SELECT extract(second from interval 'infinity');
+SELECT extract(second from interval '-infinity');
+SELECT extract(minute from interval 'infinity');
+SELECT extract(minute from interval '-infinity');
+SELECT extract(hour from interval 'infinity');
+SELECT extract(hour from interval '-infinity');
+SELECT extract(day from interval 'infinity');
+SELECT extract(day from interval '-infinity');
+SELECT extract(month from interval 'infinity');
+SELECT extract(month from interval '-infinity');
+SELECT extract(quarter from interval 'infinity');
+SELECT extract(quarter from interval '-infinity');
+SELECT extract(year from interval 'infinity');
+SELECT extract(year from interval '-infinity');
+SELECT extract(decade from interval 'infinity');
+SELECT extract(decade from interval '-infinity');
+SELECT extract(century from interval 'infinity');
+SELECT extract(century from interval '-infinity');
+SELECT extract(millennium from interval 'infinity');
+SELECT extract(millennium from interval '-infinity');
+SELECT extract(epoch from interval 'infinity');
+SELECT extract(epoch 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
On Fri, Dec 30, 2022 at 10:47 PM Joseph Koshakow <koshy44@gmail.com> wrote:
I have another update, I cleaned up some of the error messages, fixed
the horology tests, and ran pgindent.- Joe
Hi, there.
Since in float8 you can use '+inf', '+infinity', So should we also make
interval '+infinity' valid?
Also in timestamp. '+infinity'::timestamp is invalid, should we make it
valid.
In float8, select float8 'inf' / float8 'inf' return NaN. Now in your patch
select interval 'infinity' / float8 'infinity'; returns infinity.
I am not sure it's right. I found this related post (
https://math.stackexchange.com/questions/181304/what-is-infinity-divided-by-infinity
).
I recommend David Deutsch's <<The Beginning of Infinity>>
Jian
On 12/31/22 06:09, jian he wrote:
Since in float8 you can use '+inf', '+infinity', So should we also make
interval '+infinity' valid?
Yes.
Also in timestamp. '+infinity'::timestamp is invalid, should we make it
valid.
Yes, we should. I wrote a trivial patch for this a while ago but it
appears I never posted it. I will post that in a new thread so as not
to confuse the bots.
--
Vik Fearing
On Sat, Dec 31, 2022 at 12:09 AM jian he <jian.universality@gmail.com> wrote:
In float8, select float8 'inf' / float8 'inf' return NaN. Now in your patch select interval 'infinity' / float8 'infinity'; returns infinity.
I am not sure it's right. I found this related post (https://math.stackexchange.com/questions/181304/what-is-infinity-divided-by-infinity).
Good point, I agree this should return an error. We also need to
properly handle multiplication and division of infinite intervals by
float8 'nan'. My patch is returning an infinite interval, but it should
be returning an error. I'll upload a new patch shortly.
- Joe
On Mon, Jan 2, 2023 at 1:21 PM Joseph Koshakow <koshy44@gmail.com> wrote:
On Sat, Dec 31, 2022 at 12:09 AM jian he <jian.universality@gmail.com> wrote:
In float8, select float8 'inf' / float8 'inf' return NaN. Now in your patch select interval 'infinity' / float8 'infinity'; returns infinity.
I am not sure it's right. I found this related post (https://math.stackexchange.com/questions/181304/what-is-infinity-divided-by-infinity).Good point, I agree this should return an error. We also need to
properly handle multiplication and division of infinite intervals by
float8 'nan'. My patch is returning an infinite interval, but it should
be returning an error. I'll upload a new patch shortly.- Joe
Attached is the patch to handle these scenarios. Apparently dividing by
NaN is currently broken:
postgres=# SELECT INTERVAL '1 day' / float8 'nan';
?column?
---------------------------------------------------
-178956970 years -8 mons -2562047788:00:54.775808
(1 row)
This patch will fix the issue, but we may want a separate patch that
handles this specific, existing issue. Any thoughts?
- Joe
Attachments:
v6-0001-Support-infinite-interval.patchtext/x-patch; charset=US-ASCII; name=v6-0001-Support-infinite-interval.patchDownload
From 2110bbe8be4b1c5c66eb48c35b958d84352a6287 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@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 | 14 +-
src/backend/utils/adt/timestamp.c | 347 ++++++++-
src/include/datatype/timestamp.h | 22 +
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 993 ++++++++++++++++++++++++-
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 194 ++++-
8 files changed, 1527 insertions(+), 75 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 1cf7c7652d..c6259cd9c1 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("cannot add infinite interval to time")));
+
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("cannot subtract infinite interval from time")));
+
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("cannot add infinite interval to time")));
+
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("cannot subtract infinite interval from time")));
+
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..b60d91dfb8 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -70,7 +70,7 @@ static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
const char *abbr, pg_tz *tzp,
int *offset, int *isdst);
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra);
+ DateTimeErrorExtra * extra);
const int day_tab[2][13] =
@@ -977,7 +977,7 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
int
DecodeDateTime(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -1927,7 +1927,7 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
int
DecodeTimeOnly(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -3232,7 +3232,7 @@ DecodeTimezone(const char *str, int *tzp)
int
DecodeTimezoneAbbrev(int field, const char *lowtoken,
int *ftype, int *offset, pg_tz **tz,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
const datetkn *tp;
@@ -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;
@@ -4039,7 +4041,7 @@ DecodeUnits(int field, const char *lowtoken, int *val)
* separate SQLSTATE codes, so ...
*/
void
-DateTimeParseError(int dterr, DateTimeErrorExtra *extra,
+DateTimeParseError(int dterr, DateTimeErrorExtra * extra,
const char *str, const char *datatype,
Node *escontext)
{
@@ -4918,7 +4920,7 @@ InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
*/
static pg_tz *
FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
DynamicZoneAbbrev *dtza;
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3f2508c0c4..e672ee3728 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 negate_interval(Interval *interval, Interval *result);
/* 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)
{
@@ -2033,6 +2066,8 @@ 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;
}
@@ -2050,6 +2085,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2083,7 +2120,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2775,6 +2814,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 +2892,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 +2933,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,7 +2974,25 @@ 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
{
@@ -3005,9 +3071,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ negate_interval(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3032,7 +3096,25 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
TimestampTz result;
int tz;
- 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
{
@@ -3115,9 +3197,7 @@ timestamptz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ negate_interval(span, &tspan);
return DirectFunctionCall2(timestamptz_pl_interval,
TimestampGetDatum(timestamp),
@@ -3132,23 +3212,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ negate_interval(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3192,6 +3256,39 @@ 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.
+ */
+ /* TODO can be combined and simplified */
+ if (INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOBEGIN(span2))
+ {
+ INTERVAL_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOEND(span2))
+ {
+ INTERVAL_NOEND(result);
+ 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")));
+ }
+ else if (INTERVAL_NOT_FINITE(span1))
+ {
+ memcpy(result, span1, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_NOT_FINITE(span2))
+ {
+ memcpy(result, span2, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = span1->month + span2->month;
/* overflow check copied from int4pl */
if (SAMESIGN(span1->month, span2->month) &&
@@ -3226,6 +3323,39 @@ 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.
+ */
+ /* TODO can be simplified and cleaned up */
+ if (INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOEND(span2))
+ {
+ INTERVAL_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOBEGIN(span2))
+ {
+ INTERVAL_NOEND(result);
+ 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")));
+ }
+ else if (INTERVAL_NOT_FINITE(span1))
+ {
+ memcpy(result, span1, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_NOT_FINITE(span2))
+ {
+ memcpy(result, span2, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = span1->month - span2->month;
/* overflow check copied from int4mi */
if (!SAMESIGN(span1->month, span2->month) &&
@@ -3271,6 +3401,24 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor < 0.0)
+ negate_interval(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3362,6 +3510,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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)
+ negate_interval(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);
@@ -3916,6 +4087,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4371,6 +4547,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);
@@ -5230,6 +5412,62 @@ extract_timestamptz(PG_FUNCTION_ARGS)
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * 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.
+ */
+/* TODO I don't actaully know if this is correct. */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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))));
+
+ /* TODO: Maybe everything should be returning inf/-inf? */
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ 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.
*/
@@ -5253,6 +5491,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteTimestampTzPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r)
+ {
+ 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);
@@ -5955,3 +6221,28 @@ 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 */
+static void
+negate_interval(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+ }
+}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index d155f1b03b..ed7b2cc403 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/horology.out b/src/test/regress/expected/horology.out
index de73683690..10b86963ee 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -939,6 +939,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1050,6 +1051,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1157,6 +1159,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1432,6 +1435,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1599,7 +1603,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 579e92e7b3..bd503d7895 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,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +870,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 +882,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;
@@ -1578,31 +1630,31 @@ LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
-- 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
@@ -1681,19 +1733,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | | | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | | | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1767,7 +1821,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1776,3 +1832,872 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+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: 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 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';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT -interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * 2;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * -2;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' * 2;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' * -2;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' * 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' * '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval 'infinity' / 3;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' / -3;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' / 3;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' / -3;
+ ?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 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 date_trunc('hour', interval 'infinity');
+ date_trunc
+------------
+ infinity
+(1 row)
+
+SELECT date_trunc('hour', interval '-infinity');
+ date_trunc
+------------
+ -infinity
+(1 row)
+
+SELECT date_part('us', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('us', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('ms', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('ms', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('second', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('second', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('minute', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('minute', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('hour', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('hour', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('day', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('day', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('month', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('month', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('quarter', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('quarter', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('year', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('year', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('decade', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('decade', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('century', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('century', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('millennium', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('millennium', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('epoch', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('epoch', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(us from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(us from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(ms from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(ms from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(second from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(second from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(minute from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(minute from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(hour from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(hour from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(day from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(day from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(month from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(month from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(quarter from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(quarter from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(year from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(year from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(decade from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(decade from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(century from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(century from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(millennium from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(millennium from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(epoch from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(epoch from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT justify_days(interval 'infinity');
+ justify_days
+--------------
+ infinity
+(1 row)
+
+SELECT justify_days(interval '-infinity');
+ justify_days
+--------------
+ -infinity
+(1 row)
+
+SELECT justify_hours(interval 'infinity');
+ justify_hours
+---------------
+ infinity
+(1 row)
+
+SELECT justify_hours(interval '-infinity');
+ justify_hours
+---------------
+ -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/horology.sql b/src/test/regress/sql/horology.sql
index 2724a2bbc7..2494d852a6 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -173,14 +173,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -253,11 +256,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 0517b5b82b..901da3cc1e 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');
@@ -140,7 +147,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -509,13 +516,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
-- 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;
@@ -578,3 +585,180 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+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' * 'infinity';
+SELECT interval 'infinity' * '-infinity';
+SELECT interval 'infinity' * 'nan';
+SELECT interval '-infinity' * 'infinity';
+SELECT interval '-infinity' * '-infinity';
+SELECT interval '-infinity' * 'nan';
+SELECT interval 'infinity' / 3;
+SELECT interval 'infinity' / -3;
+SELECT interval '-infinity' / 3;
+SELECT interval '-infinity' / -3;
+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 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_trunc('hour', interval 'infinity');
+SELECT date_trunc('hour', interval '-infinity');
+
+SELECT date_part('us', interval 'infinity');
+SELECT date_part('us', interval '-infinity');
+SELECT date_part('ms', interval 'infinity');
+SELECT date_part('ms', interval '-infinity');
+SELECT date_part('second', interval 'infinity');
+SELECT date_part('second', interval '-infinity');
+SELECT date_part('minute', interval 'infinity');
+SELECT date_part('minute', interval '-infinity');
+SELECT date_part('hour', interval 'infinity');
+SELECT date_part('hour', interval '-infinity');
+SELECT date_part('day', interval 'infinity');
+SELECT date_part('day', interval '-infinity');
+SELECT date_part('month', interval 'infinity');
+SELECT date_part('month', interval '-infinity');
+SELECT date_part('quarter', interval 'infinity');
+SELECT date_part('quarter', interval '-infinity');
+SELECT date_part('year', interval 'infinity');
+SELECT date_part('year', interval '-infinity');
+SELECT date_part('decade', interval 'infinity');
+SELECT date_part('decade', interval '-infinity');
+SELECT date_part('century', interval 'infinity');
+SELECT date_part('century', interval '-infinity');
+SELECT date_part('millennium', interval 'infinity');
+SELECT date_part('millennium', interval '-infinity');
+SELECT date_part('epoch', interval 'infinity');
+SELECT date_part('epoch', interval '-infinity');
+SELECT extract(us from interval 'infinity');
+SELECT extract(us from interval '-infinity');
+SELECT extract(ms from interval 'infinity');
+SELECT extract(ms from interval '-infinity');
+SELECT extract(second from interval 'infinity');
+SELECT extract(second from interval '-infinity');
+SELECT extract(minute from interval 'infinity');
+SELECT extract(minute from interval '-infinity');
+SELECT extract(hour from interval 'infinity');
+SELECT extract(hour from interval '-infinity');
+SELECT extract(day from interval 'infinity');
+SELECT extract(day from interval '-infinity');
+SELECT extract(month from interval 'infinity');
+SELECT extract(month from interval '-infinity');
+SELECT extract(quarter from interval 'infinity');
+SELECT extract(quarter from interval '-infinity');
+SELECT extract(year from interval 'infinity');
+SELECT extract(year from interval '-infinity');
+SELECT extract(decade from interval 'infinity');
+SELECT extract(decade from interval '-infinity');
+SELECT extract(century from interval 'infinity');
+SELECT extract(century from interval '-infinity');
+SELECT extract(millennium from interval 'infinity');
+SELECT extract(millennium from interval '-infinity');
+SELECT extract(epoch from interval 'infinity');
+SELECT extract(epoch 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
I have another patch, this one adds validations to operations that
return intervals and updated error messages. I tried to give all of the
error messages meaningful text, but I'm starting to think that almost all
of them should just say "interval out of range". The current approach
may reveal some implementation details and lead to confusion. For
example, some subtractions are converted to additions which would lead
to an error message about addition.
SELECT date 'infinity' - interval 'infinity';
ERROR: cannot add infinite values with opposite signs
I've also updated the commit message to include the remaining TODOs,
which I've copied below
1. Various TODOs in code.
2. Correctly implement interval_part for infinite intervals.
3. Test consolidation.
4. Should we just use the months field to test for infinity?
Attachments:
v7-0001-Support-infinite-interval.patchtext/x-patch; charset=US-ASCII; name=v7-0001-Support-infinite-interval.patchDownload
From 65aceb25bc090375b60d140b1630cabcc90f1c9c Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 17 Dec 2022 14:21:26 -0500
Subject: [PATCH] This is WIP.
TODOs
1. Various TODOs in code.
2. Correctly implement interval_part for infinite intervals.
3. Test consolidation.
4. Should we just use the months field to test for infinity?
Ashutosh Bapat and Joe Koshakow
---
src/backend/utils/adt/date.c | 20 +
src/backend/utils/adt/datetime.c | 14 +-
src/backend/utils/adt/timestamp.c | 372 ++++++++-
src/include/datatype/timestamp.h | 22 +
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 1006 +++++++++++++++++++++++-
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 200 ++++-
8 files changed, 1571 insertions(+), 75 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 1cf7c7652d..c6259cd9c1 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("cannot add infinite interval to time")));
+
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("cannot subtract infinite interval from time")));
+
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("cannot add infinite interval to time")));
+
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("cannot subtract infinite interval from time")));
+
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..b60d91dfb8 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -70,7 +70,7 @@ static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
const char *abbr, pg_tz *tzp,
int *offset, int *isdst);
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra);
+ DateTimeErrorExtra * extra);
const int day_tab[2][13] =
@@ -977,7 +977,7 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
int
DecodeDateTime(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -1927,7 +1927,7 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
int
DecodeTimeOnly(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -3232,7 +3232,7 @@ DecodeTimezone(const char *str, int *tzp)
int
DecodeTimezoneAbbrev(int field, const char *lowtoken,
int *ftype, int *offset, pg_tz **tz,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
const datetkn *tp;
@@ -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;
@@ -4039,7 +4041,7 @@ DecodeUnits(int field, const char *lowtoken, int *val)
* separate SQLSTATE codes, so ...
*/
void
-DateTimeParseError(int dterr, DateTimeErrorExtra *extra,
+DateTimeParseError(int dterr, DateTimeErrorExtra * extra,
const char *str, const char *datatype,
Node *escontext)
{
@@ -4918,7 +4920,7 @@ InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
*/
static pg_tz *
FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
DynamicZoneAbbrev *dtza;
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3f2508c0c4..013dc40de2 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 negate_interval(Interval *interval, Interval *result);
/* 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)
{
@@ -2033,6 +2066,8 @@ 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;
}
@@ -2050,6 +2085,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2083,7 +2120,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2775,6 +2814,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 +2892,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 +2933,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,7 +2974,25 @@ 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("cannot add infinite values with opposite signs")));
+ 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
{
@@ -3005,9 +3071,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ negate_interval(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3032,7 +3096,25 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
TimestampTz result;
int tz;
- 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("cannot add infinite values with opposite signs")));
+ 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
{
@@ -3115,9 +3197,7 @@ timestamptz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ negate_interval(span, &tspan);
return DirectFunctionCall2(timestamptz_pl_interval,
TimestampGetDatum(timestamp),
@@ -3132,23 +3212,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ negate_interval(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3192,6 +3256,39 @@ 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.
+ */
+ /* TODO can be combined and simplified */
+ if (INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOBEGIN(span2))
+ {
+ INTERVAL_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOEND(span2))
+ {
+ INTERVAL_NOEND(result);
+ 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("cannot add infinite values with opposite signs")));
+ }
+ else if (INTERVAL_NOT_FINITE(span1))
+ {
+ memcpy(result, span1, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_NOT_FINITE(span2))
+ {
+ memcpy(result, span2, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = span1->month + span2->month;
/* overflow check copied from int4pl */
if (SAMESIGN(span1->month, span2->month) &&
@@ -3214,6 +3311,11 @@ interval_pl(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3226,6 +3328,39 @@ 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.
+ */
+ /* TODO can be simplified and cleaned up */
+ if (INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOEND(span2))
+ {
+ INTERVAL_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOBEGIN(span2))
+ {
+ INTERVAL_NOEND(result);
+ 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("cannot subtract infinite values with the same signs")));
+ }
+ else if (INTERVAL_NOT_FINITE(span1))
+ {
+ memcpy(result, span1, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+ else if (INTERVAL_NOT_FINITE(span2))
+ {
+ memcpy(result, span2, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = span1->month - span2->month;
/* overflow check copied from int4mi */
if (!SAMESIGN(span1->month, span2->month) &&
@@ -3248,6 +3383,11 @@ interval_mi(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3271,6 +3411,24 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor < 0.0)
+ negate_interval(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3331,6 +3489,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3362,6 +3525,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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)
+ negate_interval(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);
@@ -3383,6 +3569,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3916,6 +4107,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4371,6 +4567,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);
@@ -5230,6 +5432,62 @@ extract_timestamptz(PG_FUNCTION_ARGS)
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * 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.
+ */
+/* TODO I don't actaully know if this is correct. */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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))));
+
+ /* TODO: Maybe everything should be returning inf/-inf? */
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ 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.
*/
@@ -5253,6 +5511,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteTimestampTzPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r)
+ {
+ 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);
@@ -5955,3 +6241,33 @@ 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 */
+static void
+negate_interval(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index d155f1b03b..ed7b2cc403 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/horology.out b/src/test/regress/expected/horology.out
index de73683690..10b86963ee 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -939,6 +939,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1050,6 +1051,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1157,6 +1159,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1432,6 +1435,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1599,7 +1603,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 579e92e7b3..c76df77093 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -72,6 +86,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 +135,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 +152,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 +163,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 +173,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 +192,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 +208,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 +217,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 +253,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 +262,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 +272,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 +433,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +869,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 +881,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;
@@ -1578,31 +1629,31 @@ LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
-- 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
@@ -1681,19 +1732,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | | | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | | | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1767,7 +1820,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1776,3 +1831,886 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+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: cannot add infinite values with opposite signs
+SELECT date '-infinity' + interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: cannot subtract infinite values with the same signs
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: cannot subtract infinite values with the same signs
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp '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: cannot add infinite values with opposite signs
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: cannot add infinite values with opposite signs
+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: cannot add infinite values with opposite signs
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: cannot add infinite values with opposite signs
+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 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';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT -interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+ERROR: interval out of range
+SELECT interval 'infinity' * 2;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * -2;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' * 2;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' * -2;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' * 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' * '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+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' / 3;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' / -3;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' / 3;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' / -3;
+ ?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 date_trunc('hour', interval 'infinity');
+ date_trunc
+------------
+ infinity
+(1 row)
+
+SELECT date_trunc('hour', interval '-infinity');
+ date_trunc
+------------
+ -infinity
+(1 row)
+
+SELECT date_part('us', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('us', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('ms', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('ms', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('second', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('second', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('minute', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('minute', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('hour', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('hour', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('day', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('day', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('month', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('month', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('quarter', interval 'infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('quarter', interval '-infinity');
+ date_part
+-----------
+
+(1 row)
+
+SELECT date_part('year', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('year', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('decade', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('decade', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('century', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('century', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('millennium', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('millennium', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT date_part('epoch', interval 'infinity');
+ date_part
+-----------
+ Infinity
+(1 row)
+
+SELECT date_part('epoch', interval '-infinity');
+ date_part
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(us from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(us from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(ms from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(ms from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(second from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(second from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(minute from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(minute from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(hour from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(hour from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(day from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(day from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(month from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(month from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(quarter from interval 'infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(quarter from interval '-infinity');
+ extract
+---------
+
+(1 row)
+
+SELECT extract(year from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(year from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(decade from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(decade from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(century from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(century from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(millennium from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(millennium from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT extract(epoch from interval 'infinity');
+ extract
+----------
+ Infinity
+(1 row)
+
+SELECT extract(epoch from interval '-infinity');
+ extract
+-----------
+ -Infinity
+(1 row)
+
+SELECT justify_days(interval 'infinity');
+ justify_days
+--------------
+ infinity
+(1 row)
+
+SELECT justify_days(interval '-infinity');
+ justify_days
+--------------
+ -infinity
+(1 row)
+
+SELECT justify_hours(interval 'infinity');
+ justify_hours
+---------------
+ infinity
+(1 row)
+
+SELECT justify_hours(interval '-infinity');
+ justify_hours
+---------------
+ -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/horology.sql b/src/test/regress/sql/horology.sql
index 2724a2bbc7..2494d852a6 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -173,14 +173,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -253,11 +256,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 0517b5b82b..82f3180221 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,10 +29,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');
@@ -140,7 +145,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -509,13 +514,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
-- 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;
@@ -578,3 +583,188 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp '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 '-2147483647 months -2147483647 days -9223372036854775807 us';
+SELECT interval 'infinity' * 2;
+SELECT interval 'infinity' * -2;
+SELECT interval '-infinity' * 2;
+SELECT interval '-infinity' * -2;
+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' * 2;
+
+SELECT interval 'infinity' / 3;
+SELECT interval 'infinity' / -3;
+SELECT interval '-infinity' / 3;
+SELECT interval '-infinity' / -3;
+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 date_trunc('hour', interval 'infinity');
+SELECT date_trunc('hour', interval '-infinity');
+
+SELECT date_part('us', interval 'infinity');
+SELECT date_part('us', interval '-infinity');
+SELECT date_part('ms', interval 'infinity');
+SELECT date_part('ms', interval '-infinity');
+SELECT date_part('second', interval 'infinity');
+SELECT date_part('second', interval '-infinity');
+SELECT date_part('minute', interval 'infinity');
+SELECT date_part('minute', interval '-infinity');
+SELECT date_part('hour', interval 'infinity');
+SELECT date_part('hour', interval '-infinity');
+SELECT date_part('day', interval 'infinity');
+SELECT date_part('day', interval '-infinity');
+SELECT date_part('month', interval 'infinity');
+SELECT date_part('month', interval '-infinity');
+SELECT date_part('quarter', interval 'infinity');
+SELECT date_part('quarter', interval '-infinity');
+SELECT date_part('year', interval 'infinity');
+SELECT date_part('year', interval '-infinity');
+SELECT date_part('decade', interval 'infinity');
+SELECT date_part('decade', interval '-infinity');
+SELECT date_part('century', interval 'infinity');
+SELECT date_part('century', interval '-infinity');
+SELECT date_part('millennium', interval 'infinity');
+SELECT date_part('millennium', interval '-infinity');
+SELECT date_part('epoch', interval 'infinity');
+SELECT date_part('epoch', interval '-infinity');
+SELECT extract(us from interval 'infinity');
+SELECT extract(us from interval '-infinity');
+SELECT extract(ms from interval 'infinity');
+SELECT extract(ms from interval '-infinity');
+SELECT extract(second from interval 'infinity');
+SELECT extract(second from interval '-infinity');
+SELECT extract(minute from interval 'infinity');
+SELECT extract(minute from interval '-infinity');
+SELECT extract(hour from interval 'infinity');
+SELECT extract(hour from interval '-infinity');
+SELECT extract(day from interval 'infinity');
+SELECT extract(day from interval '-infinity');
+SELECT extract(month from interval 'infinity');
+SELECT extract(month from interval '-infinity');
+SELECT extract(quarter from interval 'infinity');
+SELECT extract(quarter from interval '-infinity');
+SELECT extract(year from interval 'infinity');
+SELECT extract(year from interval '-infinity');
+SELECT extract(decade from interval 'infinity');
+SELECT extract(decade from interval '-infinity');
+SELECT extract(century from interval 'infinity');
+SELECT extract(century from interval '-infinity');
+SELECT extract(millennium from interval 'infinity');
+SELECT extract(millennium from interval '-infinity');
+SELECT extract(epoch from interval 'infinity');
+SELECT extract(epoch 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
On Tue, Jan 3, 2023 at 6:14 AM Joseph Koshakow <koshy44@gmail.com> wrote:
I have another patch, this one adds validations to operations that
return intervals and updated error messages. I tried to give all of the
error messages meaningful text, but I'm starting to think that almost all
of them should just say "interval out of range". The current approach
may reveal some implementation details and lead to confusion. For
example, some subtractions are converted to additions which would lead
to an error message about addition.SELECT date 'infinity' - interval 'infinity';
ERROR: cannot add infinite values with opposite signsI've also updated the commit message to include the remaining TODOs,
which I've copied below1. Various TODOs in code.
2. Correctly implement interval_part for infinite intervals.
3. Test consolidation.
4. Should we just use the months field to test for infinity?
3. Test consolidation.
I used the DO command, reduced a lot of test sql code.
I don't know how to generate an interval.out file.
I hope the format is ok. I use https://sqlformat.darold.net/ format the sql
code.
Then I saw on the internet that one line should be no more than 80 chars.
so I slightly changed the format.
--
I recommend David Deutsch's <<The Beginning of Infinity>>
Jian
Attachments:
v8_0001_Support_infinite_interval.patchtext/x-patch; charset=US-ASCII; name=v8_0001_Support_infinite_interval.patchDownload
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 82f3180221..1fd99c53d4 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -652,30 +652,31 @@ 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';
+DO $$
+DECLARE
+ intv interval;
+ intv1 interval;
+ intvs interval[] := '{+infinity,-infinity}';
+ OPERATOR text[] := '{<,<=,=, >,>=,<>}';
+ opr text;
+ result boolean;
+BEGIN
+ FOREACH intv IN ARRAY intvs LOOP
+ FOREACH opr IN ARRAY OPERATOR LOOP
+ FOREACH intv1 IN ARRAY intvs LOOP
+ EXECUTE 'select interval ' || quote_literal(intv) || ' '
+ || opr || ' interval ' || quote_literal(intv1) INTO result;
+ RAISE NOTICE '%'
+ ,(format('%10s %2s %10s %2s'
+ ,intv
+ ,opr
+ ,intv1
+ ,result));
+ END LOOP;
+ END LOOP;
+ END LOOP;
+END
+$$;
SELECT -interval 'infinity';
SELECT -interval '-infinity';
@@ -709,58 +710,48 @@ SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02
SELECT date_trunc('hour', interval 'infinity');
SELECT date_trunc('hour', interval '-infinity');
-SELECT date_part('us', interval 'infinity');
-SELECT date_part('us', interval '-infinity');
-SELECT date_part('ms', interval 'infinity');
-SELECT date_part('ms', interval '-infinity');
-SELECT date_part('second', interval 'infinity');
-SELECT date_part('second', interval '-infinity');
-SELECT date_part('minute', interval 'infinity');
-SELECT date_part('minute', interval '-infinity');
-SELECT date_part('hour', interval 'infinity');
-SELECT date_part('hour', interval '-infinity');
-SELECT date_part('day', interval 'infinity');
-SELECT date_part('day', interval '-infinity');
-SELECT date_part('month', interval 'infinity');
-SELECT date_part('month', interval '-infinity');
-SELECT date_part('quarter', interval 'infinity');
-SELECT date_part('quarter', interval '-infinity');
-SELECT date_part('year', interval 'infinity');
-SELECT date_part('year', interval '-infinity');
-SELECT date_part('decade', interval 'infinity');
-SELECT date_part('decade', interval '-infinity');
-SELECT date_part('century', interval 'infinity');
-SELECT date_part('century', interval '-infinity');
-SELECT date_part('millennium', interval 'infinity');
-SELECT date_part('millennium', interval '-infinity');
-SELECT date_part('epoch', interval 'infinity');
-SELECT date_part('epoch', interval '-infinity');
-SELECT extract(us from interval 'infinity');
-SELECT extract(us from interval '-infinity');
-SELECT extract(ms from interval 'infinity');
-SELECT extract(ms from interval '-infinity');
-SELECT extract(second from interval 'infinity');
-SELECT extract(second from interval '-infinity');
-SELECT extract(minute from interval 'infinity');
-SELECT extract(minute from interval '-infinity');
-SELECT extract(hour from interval 'infinity');
-SELECT extract(hour from interval '-infinity');
-SELECT extract(day from interval 'infinity');
-SELECT extract(day from interval '-infinity');
-SELECT extract(month from interval 'infinity');
-SELECT extract(month from interval '-infinity');
-SELECT extract(quarter from interval 'infinity');
-SELECT extract(quarter from interval '-infinity');
-SELECT extract(year from interval 'infinity');
-SELECT extract(year from interval '-infinity');
-SELECT extract(decade from interval 'infinity');
-SELECT extract(decade from interval '-infinity');
-SELECT extract(century from interval 'infinity');
-SELECT extract(century from interval '-infinity');
-SELECT extract(millennium from interval 'infinity');
-SELECT extract(millennium from interval '-infinity');
-SELECT extract(epoch from interval 'infinity');
-SELECT extract(epoch from interval '-infinity');
+DO $$
+DECLARE
+ intv interval;
+ intvs interval[] := '{+infinity,-infinity}';
+ units text[] := '{us,ms,second,minute,hour,day,month
+ ,quarter,year,decade,century,millennium,epoch}';
+ unit text;
+ result double precision;
+BEGIN
+ FOREACH intv IN ARRAY intvs LOOP
+ FOREACH unit IN ARRAY units LOOP
+ EXECUTE 'select date_part( ' || quote_literal(unit) || ' ,interval '
+ || quote_literal(intv) || ' )' INTO result;
+ RAISE NOTICE '% %'
+ ,(format('date_part(%10s, interval %10s) yiled', unit, intv))
+ ,result;
+ END LOOP;
+ END LOOP;
+END
+$$;
+
+
+DO $$
+DECLARE
+ intv interval;
+ intvs interval[] := '{+infinity,-infinity}';
+ units text[] := '{us,ms,second,minute,hour,day,month
+ ,quarter,year,decade,century,millennium,epoch}';
+ unit text;
+ result numeric;
+BEGIN
+ FOREACH intv IN ARRAY intvs LOOP
+ FOREACH unit IN ARRAY units LOOP
+ EXECUTE 'select extract( ' || quote_literal(unit)
+ || ' from interval ' || quote_literal(intv) || ' )' INTO result;
+ RAISE NOTICE '% %'
+ ,(format('extract(%10s from interval %9s) yiled', unit, intv))
+ ,result;
+ END LOOP;
+ END LOOP;
+END
+$$;
SELECT justify_days(interval 'infinity');
SELECT justify_days(interval '-infinity');
On Wed, Jan 4, 2023 at 10:13 PM jian he <jian.universality@gmail.com> wrote:
On Tue, Jan 3, 2023 at 6:14 AM Joseph Koshakow <koshy44@gmail.com> wrote:
I have another patch, this one adds validations to operations that
return intervals and updated error messages. I tried to give all of the
error messages meaningful text, but I'm starting to think that almost all
of them should just say "interval out of range". The current approach
may reveal some implementation details and lead to confusion. For
example, some subtractions are converted to additions which would lead
to an error message about addition.SELECT date 'infinity' - interval 'infinity';
ERROR: cannot add infinite values with opposite signsI've also updated the commit message to include the remaining TODOs,
which I've copied below1. Various TODOs in code.
2. Correctly implement interval_part for infinite intervals.
3. Test consolidation.
4. Should we just use the months field to test for infinity?3. Test consolidation.
I used the DO command, reduced a lot of test sql code.
I don't know how to generate an interval.out file.
I hope the format is ok. I use https://sqlformat.darold.net/ format the
sql code.
Then I saw on the internet that one line should be no more than 80 chars.
so I slightly changed the format.--
I recommend David Deutsch's <<The Beginning of Infinity>>Jian
1. Various TODOs in code.
logic combine and clean up for functions in backend/utils/adt/timestamp.c
(timestamp_pl_interval,timestamptz_pl_interval, interval_pl, interval_mi).
3. Test consolidation in /regress/sql/interval.sql
For 1. I don't know how to format the code. I have a problem installing
pg_indent. If the format is wrong, please reformat.
3. As the previous email thread.
Attachments:
v8_0002_Support_infinite_interval.patchtext/x-patch; charset=US-ASCII; name=v8_0002_Support_infinite_interval.patchDownload
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 9484b29ec4..350363b9ad 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2973,21 +2973,21 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
* 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("cannot add infinite values with opposite signs")));
- 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;
+ if ( (INTERVAL_IS_NOBEGIN(span) && TIMESTAMP_IS_NOBEGIN(timestamp))
+ || (INTERVAL_IS_NOBEGIN(span) && !TIMESTAMP_NOT_FINITE(timestamp))
+ || (!INTERVAL_NOT_FINITE(span) && TIMESTAMP_IS_NOBEGIN(timestamp)))
+ TIMESTAMP_NOBEGIN(result);
+
+ else if ((INTERVAL_IS_NOEND(span) && TIMESTAMP_IS_NOEND(timestamp))
+ || (INTERVAL_IS_NOEND(span) && !TIMESTAMP_NOT_FINITE(timestamp))
+ ||(!INTERVAL_NOT_FINITE(span) && TIMESTAMP_IS_NOEND(timestamp)))
+ TIMESTAMP_NOEND(result);
+
+ else if ((!INTERVAL_NOT_FINITE(span) && TIMESTAMP_NOT_FINITE(timestamp))
+ || (INTERVAL_NOT_FINITE(span) && !TIMESTAMP_NOT_FINITE(timestamp)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite values with opposite signs")));
else
{
if (span->month != 0)
@@ -3095,21 +3095,21 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
* 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("cannot add infinite values with opposite signs")));
- 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;
+ if ((INTERVAL_IS_NOBEGIN(span) && TIMESTAMP_IS_NOBEGIN(timestamp))
+ || (INTERVAL_IS_NOBEGIN(span) && !TIMESTAMP_NOT_FINITE(timestamp))
+ || (!INTERVAL_NOT_FINITE(span) && TIMESTAMP_IS_NOBEGIN(timestamp)))
+ TIMESTAMP_NOBEGIN(result);
+
+ else if ((INTERVAL_IS_NOEND(span) && TIMESTAMP_IS_NOEND(timestamp))
+ || (INTERVAL_IS_NOEND(span) && !TIMESTAMP_NOT_FINITE(timestamp))
+ || (!INTERVAL_NOT_FINITE(span) && TIMESTAMP_IS_NOEND(timestamp)))
+ TIMESTAMP_NOEND(result);
+
+ else if ((!INTERVAL_NOT_FINITE(span) && TIMESTAMP_NOT_FINITE(timestamp))
+ || (INTERVAL_NOT_FINITE(span) && !TIMESTAMP_NOT_FINITE(timestamp)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite values with opposite signs")));
else
{
if (span->month != 0)
@@ -3255,61 +3255,52 @@ interval_pl(PG_FUNCTION_ARGS)
* infinite interval with the same sign. Adding two infinte intervals with
* different signs results in an error.
*/
- /* TODO can be combined and simplified */
- if (INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOBEGIN(span2))
- {
- INTERVAL_NOBEGIN(result);
- PG_RETURN_INTERVAL_P(result);
- }
- else if (INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOEND(span2))
- {
- INTERVAL_NOEND(result);
- PG_RETURN_INTERVAL_P(result);
- }
+ if ((INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOBEGIN(span2))
+ ||(INTERVAL_IS_NOBEGIN(span1) && !INTERVAL_NOT_FINITE(span2))
+ ||(!INTERVAL_NOT_FINITE(span1) && INTERVAL_IS_NOBEGIN(span2)))
+ INTERVAL_NOBEGIN(result);
+
+ else if
+ ((INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOEND(span2))
+ ||(INTERVAL_IS_NOEND(span1) && !INTERVAL_NOT_FINITE(span2))
+ ||(!INTERVAL_NOT_FINITE(span1) && INTERVAL_IS_NOEND(span2)))
+ INTERVAL_NOEND(result);
+
else if (INTERVAL_NOT_FINITE(span1) && INTERVAL_NOT_FINITE(span2))
{
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot add infinite values with opposite signs")));
}
- else if (INTERVAL_NOT_FINITE(span1))
- {
- memcpy(result, span1, sizeof(Interval));
- PG_RETURN_INTERVAL_P(result);
- }
- else if (INTERVAL_NOT_FINITE(span2))
+ else
{
- memcpy(result, span2, sizeof(Interval));
- PG_RETURN_INTERVAL_P(result);
- }
-
- 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")));
-
- 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->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")));
- 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")));
+ 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")));
- if (INTERVAL_NOT_FINITE(result))
- 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3327,61 +3318,51 @@ interval_mi(PG_FUNCTION_ARGS)
* infinite interval with the same sign as the left operand. Subtracting
* two infinte intervals with the same sign results in an error.
*/
- /* TODO can be simplified and cleaned up */
- if (INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOEND(span2))
- {
- INTERVAL_NOBEGIN(result);
- PG_RETURN_INTERVAL_P(result);
- }
- else if (INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOBEGIN(span2))
- {
- INTERVAL_NOEND(result);
- PG_RETURN_INTERVAL_P(result);
- }
+ if ((INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOEND(span2))
+ ||(INTERVAL_IS_NOBEGIN(span1) && !INTERVAL_NOT_FINITE(span2))
+ ||(!INTERVAL_NOT_FINITE(span1) && INTERVAL_IS_NOEND(span2)))
+ INTERVAL_NOBEGIN(result);
+
+ else if ((INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOBEGIN(span2))
+ ||(INTERVAL_IS_NOEND(span1) && !INTERVAL_NOT_FINITE(span2))
+ ||(!INTERVAL_NOT_FINITE(span1) && INTERVAL_IS_NOBEGIN(span2)))
+ INTERVAL_NOEND(result);
+
else if (INTERVAL_NOT_FINITE(span1) && INTERVAL_NOT_FINITE(span2))
{
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("cannot subtract infinite values with the same signs")));
- }
- else if (INTERVAL_NOT_FINITE(span1))
- {
- memcpy(result, span1, sizeof(Interval));
- PG_RETURN_INTERVAL_P(result);
- }
- else if (INTERVAL_NOT_FINITE(span2))
+ }
+ else
{
- memcpy(result, span2, sizeof(Interval));
- PG_RETURN_INTERVAL_P(result);
- }
-
- 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->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->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")));
+ 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")));
- if (INTERVAL_NOT_FINITE(result))
- 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 82f3180221..1fd99c53d4 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -652,30 +652,31 @@ 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';
+DO $$
+DECLARE
+ intv interval;
+ intv1 interval;
+ intvs interval[] := '{+infinity,-infinity}';
+ OPERATOR text[] := '{<,<=,=, >,>=,<>}';
+ opr text;
+ result boolean;
+BEGIN
+ FOREACH intv IN ARRAY intvs LOOP
+ FOREACH opr IN ARRAY OPERATOR LOOP
+ FOREACH intv1 IN ARRAY intvs LOOP
+ EXECUTE 'select interval ' || quote_literal(intv) || ' '
+ || opr || ' interval ' || quote_literal(intv1) INTO result;
+ RAISE NOTICE '%'
+ ,(format('%10s %2s %10s %2s'
+ ,intv
+ ,opr
+ ,intv1
+ ,result));
+ END LOOP;
+ END LOOP;
+ END LOOP;
+END
+$$;
SELECT -interval 'infinity';
SELECT -interval '-infinity';
@@ -709,58 +710,48 @@ SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02
SELECT date_trunc('hour', interval 'infinity');
SELECT date_trunc('hour', interval '-infinity');
-SELECT date_part('us', interval 'infinity');
-SELECT date_part('us', interval '-infinity');
-SELECT date_part('ms', interval 'infinity');
-SELECT date_part('ms', interval '-infinity');
-SELECT date_part('second', interval 'infinity');
-SELECT date_part('second', interval '-infinity');
-SELECT date_part('minute', interval 'infinity');
-SELECT date_part('minute', interval '-infinity');
-SELECT date_part('hour', interval 'infinity');
-SELECT date_part('hour', interval '-infinity');
-SELECT date_part('day', interval 'infinity');
-SELECT date_part('day', interval '-infinity');
-SELECT date_part('month', interval 'infinity');
-SELECT date_part('month', interval '-infinity');
-SELECT date_part('quarter', interval 'infinity');
-SELECT date_part('quarter', interval '-infinity');
-SELECT date_part('year', interval 'infinity');
-SELECT date_part('year', interval '-infinity');
-SELECT date_part('decade', interval 'infinity');
-SELECT date_part('decade', interval '-infinity');
-SELECT date_part('century', interval 'infinity');
-SELECT date_part('century', interval '-infinity');
-SELECT date_part('millennium', interval 'infinity');
-SELECT date_part('millennium', interval '-infinity');
-SELECT date_part('epoch', interval 'infinity');
-SELECT date_part('epoch', interval '-infinity');
-SELECT extract(us from interval 'infinity');
-SELECT extract(us from interval '-infinity');
-SELECT extract(ms from interval 'infinity');
-SELECT extract(ms from interval '-infinity');
-SELECT extract(second from interval 'infinity');
-SELECT extract(second from interval '-infinity');
-SELECT extract(minute from interval 'infinity');
-SELECT extract(minute from interval '-infinity');
-SELECT extract(hour from interval 'infinity');
-SELECT extract(hour from interval '-infinity');
-SELECT extract(day from interval 'infinity');
-SELECT extract(day from interval '-infinity');
-SELECT extract(month from interval 'infinity');
-SELECT extract(month from interval '-infinity');
-SELECT extract(quarter from interval 'infinity');
-SELECT extract(quarter from interval '-infinity');
-SELECT extract(year from interval 'infinity');
-SELECT extract(year from interval '-infinity');
-SELECT extract(decade from interval 'infinity');
-SELECT extract(decade from interval '-infinity');
-SELECT extract(century from interval 'infinity');
-SELECT extract(century from interval '-infinity');
-SELECT extract(millennium from interval 'infinity');
-SELECT extract(millennium from interval '-infinity');
-SELECT extract(epoch from interval 'infinity');
-SELECT extract(epoch from interval '-infinity');
+DO $$
+DECLARE
+ intv interval;
+ intvs interval[] := '{+infinity,-infinity}';
+ units text[] := '{us,ms,second,minute,hour,day,month
+ ,quarter,year,decade,century,millennium,epoch}';
+ unit text;
+ result double precision;
+BEGIN
+ FOREACH intv IN ARRAY intvs LOOP
+ FOREACH unit IN ARRAY units LOOP
+ EXECUTE 'select date_part( ' || quote_literal(unit) || ' ,interval '
+ || quote_literal(intv) || ' )' INTO result;
+ RAISE NOTICE '% %'
+ ,(format('date_part(%10s, interval %10s) yiled', unit, intv))
+ ,result;
+ END LOOP;
+ END LOOP;
+END
+$$;
+
+
+DO $$
+DECLARE
+ intv interval;
+ intvs interval[] := '{+infinity,-infinity}';
+ units text[] := '{us,ms,second,minute,hour,day,month
+ ,quarter,year,decade,century,millennium,epoch}';
+ unit text;
+ result numeric;
+BEGIN
+ FOREACH intv IN ARRAY intvs LOOP
+ FOREACH unit IN ARRAY units LOOP
+ EXECUTE 'select extract( ' || quote_literal(unit)
+ || ' from interval ' || quote_literal(intv) || ' )' INTO result;
+ RAISE NOTICE '% %'
+ ,(format('extract(%10s from interval %9s) yiled', unit, intv))
+ ,result;
+ END LOOP;
+ END LOOP;
+END
+$$;
SELECT justify_days(interval 'infinity');
SELECT justify_days(interval '-infinity');
On Thu, Jan 5, 2023 at 5:20 AM jian he <jian.universality@gmail.com> wrote:
On Wed, Jan 4, 2023 at 10:13 PM jian he <jian.universality@gmail.com> wrote:
I don't know how to generate an interval.out file.
Personally I just write the .out files manually. I think it especially
helps as a way to double-check that the results are what you expected.
After running make check a regressions.diff file will be generated with
all the differences between your .out file and the results of the test.
logic combine and clean up for functions in backend/utils/adt/timestamp.c (timestamp_pl_interval,timestamptz_pl_interval, interval_pl, interval_mi).
One thing I was hoping to achieve was to avoid redundant checks if
possible. For example, in the following code:
+ if ((INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOEND(span2)) + ||(INTERVAL_IS_NOBEGIN(span1) && !INTERVAL_NOT_FINITE(span2)) + ||(!INTERVAL_NOT_FINITE(span1) && INTERVAL_IS_NOEND(span2))) + INTERVAL_NOBEGIN(result);
If `(INTERVAL_IS_NOBEGIN(span1) && INTERVAL_IS_NOEND(span2))` is false,
then we end up checking `INTERVAL_IS_NOBEGIN(span1)` twice
For 1. I don't know how to format the code. I have a problem installing pg_indent. If the format is wrong, please reformat.
I'll run pg_indent and send an updated patch if anything changes.
Thanks for your help on this patch!
- Joe Koshakow
Jian,
I incorporated your changes and updated interval.out and ran
pgindent. Looks like some of the error messages have changed and we
have some issues with parsing "+infinity" after rebasing.
- Joe
Attachments:
v8-0001-Support-infinite-interval.patchtext/x-patch; charset=US-ASCII; name=v8-0001-Support-infinite-interval.patchDownload
From 4bf672f9079322cffde635dff2078582fca55f09 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 17 Dec 2022 14:21:26 -0500
Subject: [PATCH] This is WIP.
TODOs
1. Various TODOs in code.
2. Correctly implement interval_part for infinite intervals.
3. Fix Tests.
4. Should we just use the months field to test for infinity?
5. Update docs
Ashutosh Bapat and Joe Koshakow
---
src/backend/utils/adt/date.c | 20 +
src/backend/utils/adt/datetime.c | 14 +-
src/backend/utils/adt/timestamp.c | 425 ++++++++++++---
src/include/datatype/timestamp.h | 22 +
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 691 +++++++++++++++++++++++--
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 191 ++++++-
8 files changed, 1264 insertions(+), 111 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 99171d9c92..8334b9053f 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2067,6 +2067,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2085,6 +2090,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2599,6 +2609,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2621,6 +2636,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index d166613895..4192e7a74b 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -70,7 +70,7 @@ static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
const char *abbr, pg_tz *tzp,
int *offset, int *isdst);
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra);
+ DateTimeErrorExtra * extra);
const int day_tab[2][13] =
@@ -978,7 +978,7 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
int
DecodeDateTime(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -1928,7 +1928,7 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
int
DecodeTimeOnly(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -3233,7 +3233,7 @@ DecodeTimezone(const char *str, int *tzp)
int
DecodeTimezoneAbbrev(int field, const char *lowtoken,
int *ftype, int *offset, pg_tz **tz,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
const datetkn *tp;
@@ -3635,6 +3635,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;
@@ -4040,7 +4042,7 @@ DecodeUnits(int field, const char *lowtoken, int *val)
* separate SQLSTATE codes, so ...
*/
void
-DateTimeParseError(int dterr, DateTimeErrorExtra *extra,
+DateTimeParseError(int dterr, DateTimeErrorExtra * extra,
const char *str, const char *datatype,
Node *escontext)
{
@@ -4919,7 +4921,7 @@ InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
*/
static pg_tz *
FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
DynamicZoneAbbrev *dtza;
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 928c330897..87277f2f3b 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 negate_interval(Interval *interval, Interval *result);
/* 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)
{
@@ -2027,6 +2060,8 @@ 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;
}
@@ -2044,6 +2079,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2077,7 +2114,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2769,6 +2808,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))
@@ -2844,6 +2886,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,
@@ -2882,6 +2927,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))
@@ -2920,8 +2968,26 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Timestamp result;
- 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))
+ || (INTERVAL_IS_NOBEGIN(span) && !TIMESTAMP_NOT_FINITE(timestamp))
+ || (!INTERVAL_NOT_FINITE(span) && TIMESTAMP_IS_NOBEGIN(timestamp)))
+ TIMESTAMP_NOBEGIN(result);
+
+ else if ((INTERVAL_IS_NOEND(span) && TIMESTAMP_IS_NOEND(timestamp))
+ || (INTERVAL_IS_NOEND(span) && !TIMESTAMP_NOT_FINITE(timestamp))
+ || (!INTERVAL_NOT_FINITE(span) && TIMESTAMP_IS_NOEND(timestamp)))
+ TIMESTAMP_NOEND(result);
+
+ else if ((!INTERVAL_NOT_FINITE(span) && TIMESTAMP_NOT_FINITE(timestamp))
+ || (INTERVAL_NOT_FINITE(span) && !TIMESTAMP_NOT_FINITE(timestamp)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite values with opposite signs")));
else
{
if (span->month != 0)
@@ -2999,9 +3065,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ negate_interval(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3026,8 +3090,26 @@ 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))
+ || (INTERVAL_IS_NOBEGIN(span) && !TIMESTAMP_NOT_FINITE(timestamp))
+ || (!INTERVAL_NOT_FINITE(span) && TIMESTAMP_IS_NOBEGIN(timestamp)))
+ TIMESTAMP_NOBEGIN(result);
+
+ else if ((INTERVAL_IS_NOEND(span) && TIMESTAMP_IS_NOEND(timestamp))
+ || (INTERVAL_IS_NOEND(span) && !TIMESTAMP_NOT_FINITE(timestamp))
+ || (!INTERVAL_NOT_FINITE(span) && TIMESTAMP_IS_NOEND(timestamp)))
+ TIMESTAMP_NOEND(result);
+
+ else if ((!INTERVAL_NOT_FINITE(span) && TIMESTAMP_NOT_FINITE(timestamp))
+ || (INTERVAL_NOT_FINITE(span) && !TIMESTAMP_NOT_FINITE(timestamp)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite values with opposite signs")));
else
{
if (span->month != 0)
@@ -3109,9 +3191,7 @@ timestamptz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ negate_interval(span, &tspan);
return DirectFunctionCall2(timestamptz_pl_interval,
TimestampGetDatum(timestamp),
@@ -3126,23 +3206,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ negate_interval(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3186,28 +3250,57 @@ 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))
+ /*
+ * 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_NOBEGIN(span1) && !INTERVAL_NOT_FINITE(span2))
+ || (!INTERVAL_NOT_FINITE(span1) && INTERVAL_IS_NOBEGIN(span2)))
+ INTERVAL_NOBEGIN(result);
+
+ else if
+ ((INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOEND(span2))
+ || (INTERVAL_IS_NOEND(span1) && !INTERVAL_NOT_FINITE(span2))
+ || (!INTERVAL_NOT_FINITE(span1) && INTERVAL_IS_NOEND(span2)))
+ INTERVAL_NOEND(result);
+
+ else if (INTERVAL_NOT_FINITE(span1) && INTERVAL_NOT_FINITE(span2))
+ {
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("cannot add infinite values with opposite signs")));
+ }
+ else
+ {
+ 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")));
- 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->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")));
+ 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3220,28 +3313,56 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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_NOBEGIN(span1) && !INTERVAL_NOT_FINITE(span2))
+ || (!INTERVAL_NOT_FINITE(span1) && INTERVAL_IS_NOEND(span2)))
+ INTERVAL_NOBEGIN(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")));
+ else if ((INTERVAL_IS_NOEND(span1) && INTERVAL_IS_NOBEGIN(span2))
+ || (INTERVAL_IS_NOEND(span1) && !INTERVAL_NOT_FINITE(span2))
+ || (!INTERVAL_NOT_FINITE(span1) && INTERVAL_IS_NOBEGIN(span2)))
+ INTERVAL_NOEND(result);
- result->time = span1->time - span2->time;
- if (!SAMESIGN(span1->time, span2->time) &&
- !SAMESIGN(result->time, span1->time))
+ else if (INTERVAL_NOT_FINITE(span1) && INTERVAL_NOT_FINITE(span2))
+ {
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("cannot subtract infinite values with the same signs")));
+ }
+ else
+ {
+ 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3265,6 +3386,24 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor < 0.0)
+ negate_interval(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3325,6 +3464,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3356,6 +3500,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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)
+ negate_interval(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);
@@ -3377,6 +3544,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3910,6 +4082,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4365,6 +4542,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);
@@ -5224,6 +5407,62 @@ extract_timestamptz(PG_FUNCTION_ARGS)
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * 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.
+ */
+/* TODO I don't actaully know if this is correct. */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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))));
+
+ /* TODO: Maybe everything should be returning inf/-inf? */
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ 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.
*/
@@ -5247,6 +5486,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteTimestampTzPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r)
+ {
+ 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);
@@ -5949,3 +6216,33 @@ 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 */
+static void
+negate_interval(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 21a37e21e9..ba9f39a445 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/horology.out b/src/test/regress/expected/horology.out
index de73683690..10b86963ee 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -939,6 +939,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1050,6 +1051,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1157,6 +1159,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1432,6 +1435,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1599,7 +1603,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 579e92e7b3..84b4dd920a 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -72,6 +86,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 +135,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 +152,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 +163,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 +173,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 +192,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 +208,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 +217,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 +253,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 +262,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 +272,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 +433,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +869,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 +881,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;
@@ -1578,31 +1629,31 @@ LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
-- 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
@@ -1681,19 +1732,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | | | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | | | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1767,7 +1820,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1776,3 +1831,571 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+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: cannot add infinite values with opposite signs
+SELECT date '-infinity' + interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: cannot subtract infinite values with the same signs
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: cannot subtract infinite values with the same signs
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp '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: cannot add infinite values with opposite signs
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: cannot add infinite values with opposite signs
+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: cannot add infinite values with opposite signs
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: cannot add infinite values with opposite signs
+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
+DO $$
+DECLARE
+ intv interval;
+ intv1 interval;
+ intvs interval[] := '{+infinity,-infinity}';
+ OPERATOR text[] := '{<,<=,=, >,>=,<>}';
+ opr text;
+ result boolean;
+BEGIN
+ FOREACH intv IN ARRAY intvs LOOP
+ FOREACH opr IN ARRAY OPERATOR LOOP
+ FOREACH intv1 IN ARRAY intvs LOOP
+ EXECUTE 'select interval ' || quote_literal(intv) || ' '
+ || opr || ' interval ' || quote_literal(intv1) INTO result;
+ RAISE NOTICE '%'
+ ,(format('%10s %2s %10s %2s'
+ ,intv
+ ,opr
+ ,intv1
+ ,result));
+ END LOOP;
+ END LOOP;
+ END LOOP;
+END
+$$;
+NOTICE: infinity < infinity f
+NOTICE: infinity < -infinity f
+NOTICE: infinity <= infinity t
+NOTICE: infinity <= -infinity f
+NOTICE: infinity = infinity t
+NOTICE: infinity = -infinity f
+NOTICE: infinity > infinity f
+NOTICE: infinity > -infinity t
+NOTICE: infinity >= infinity t
+NOTICE: infinity >= -infinity t
+NOTICE: infinity <> infinity f
+NOTICE: infinity <> -infinity t
+NOTICE: -infinity < infinity t
+NOTICE: -infinity < -infinity f
+NOTICE: -infinity <= infinity t
+NOTICE: -infinity <= -infinity t
+NOTICE: -infinity = infinity f
+NOTICE: -infinity = -infinity t
+NOTICE: -infinity > infinity f
+NOTICE: -infinity > -infinity f
+NOTICE: -infinity >= infinity f
+NOTICE: -infinity >= -infinity t
+NOTICE: -infinity <> infinity t
+NOTICE: -infinity <> -infinity f
+SELECT -interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT -interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+ERROR: interval out of range
+SELECT interval 'infinity' * 2;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * -2;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' * 2;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' * -2;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' * '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' * 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' * '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+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' / 3;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' / -3;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' / 3;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' / -3;
+ ?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 date_trunc('hour', interval 'infinity');
+ date_trunc
+------------
+ infinity
+(1 row)
+
+SELECT date_trunc('hour', interval '-infinity');
+ date_trunc
+------------
+ -infinity
+(1 row)
+
+DO $$
+DECLARE
+ intv interval;
+ intvs interval[] := '{+infinity,-infinity}';
+ units text[] := '{us,ms,second,minute,hour,day,month
+ ,quarter,year,decade,century,millennium,epoch}';
+ unit text;
+ result double precision;
+BEGIN
+ FOREACH intv IN ARRAY intvs LOOP
+ FOREACH unit IN ARRAY units LOOP
+ EXECUTE 'select date_part( ' || quote_literal(unit) || ' ,interval '
+ || quote_literal(intv) || ' )' INTO result;
+ RAISE NOTICE '% %'
+ ,(format('date_part(%10s, interval %10s) yield', unit, intv))
+ ,result;
+ END LOOP;
+ END LOOP;
+END
+$$;
+NOTICE: date_part( us, interval infinity) yield <NULL>
+NOTICE: date_part( ms, interval infinity) yield <NULL>
+NOTICE: date_part( second, interval infinity) yield <NULL>
+NOTICE: date_part( minute, interval infinity) yield <NULL>
+NOTICE: date_part( hour, interval infinity) yield <NULL>
+NOTICE: date_part( day, interval infinity) yield <NULL>
+NOTICE: date_part( month, interval infinity) yield <NULL>
+NOTICE: date_part( quarter, interval infinity) yield <NULL>
+NOTICE: date_part( year, interval infinity) yield Infinity
+NOTICE: date_part( decade, interval infinity) yield Infinity
+NOTICE: date_part( century, interval infinity) yield Infinity
+NOTICE: date_part(millennium, interval infinity) yield Infinity
+NOTICE: date_part( epoch, interval infinity) yield Infinity
+NOTICE: date_part( us, interval -infinity) yield <NULL>
+NOTICE: date_part( ms, interval -infinity) yield <NULL>
+NOTICE: date_part( second, interval -infinity) yield <NULL>
+NOTICE: date_part( minute, interval -infinity) yield <NULL>
+NOTICE: date_part( hour, interval -infinity) yield <NULL>
+NOTICE: date_part( day, interval -infinity) yield <NULL>
+NOTICE: date_part( month, interval -infinity) yield <NULL>
+NOTICE: date_part( quarter, interval -infinity) yield <NULL>
+NOTICE: date_part( year, interval -infinity) yield -Infinity
+NOTICE: date_part( decade, interval -infinity) yield -Infinity
+NOTICE: date_part( century, interval -infinity) yield -Infinity
+NOTICE: date_part(millennium, interval -infinity) yield -Infinity
+NOTICE: date_part( epoch, interval -infinity) yield -Infinity
+DO $$
+DECLARE
+ intv interval;
+ intvs interval[] := '{+infinity,-infinity}';
+ units text[] := '{us,ms,second,minute,hour,day,month
+ ,quarter,year,decade,century,millennium,epoch}';
+ unit text;
+ result numeric;
+BEGIN
+ FOREACH intv IN ARRAY intvs LOOP
+ FOREACH unit IN ARRAY units LOOP
+ EXECUTE 'select extract( ' || quote_literal(unit)
+ || ' from interval ' || quote_literal(intv) || ' )' INTO result;
+ RAISE NOTICE '% %'
+ ,(format('extract(%10s from interval %9s) yield', unit, intv))
+ ,result;
+ END LOOP;
+ END LOOP;
+END
+$$;
+NOTICE: extract( us from interval infinity) yield <NULL>
+NOTICE: extract( ms from interval infinity) yield <NULL>
+NOTICE: extract( second from interval infinity) yield <NULL>
+NOTICE: extract( minute from interval infinity) yield <NULL>
+NOTICE: extract( hour from interval infinity) yield <NULL>
+NOTICE: extract( day from interval infinity) yield <NULL>
+NOTICE: extract( month from interval infinity) yield <NULL>
+NOTICE: extract( quarter from interval infinity) yield <NULL>
+NOTICE: extract( year from interval infinity) yield Infinity
+NOTICE: extract( decade from interval infinity) yield Infinity
+NOTICE: extract( century from interval infinity) yield Infinity
+NOTICE: extract(millennium from interval infinity) yield Infinity
+NOTICE: extract( epoch from interval infinity) yield Infinity
+NOTICE: extract( us from interval -infinity) yield <NULL>
+NOTICE: extract( ms from interval -infinity) yield <NULL>
+NOTICE: extract( second from interval -infinity) yield <NULL>
+NOTICE: extract( minute from interval -infinity) yield <NULL>
+NOTICE: extract( hour from interval -infinity) yield <NULL>
+NOTICE: extract( day from interval -infinity) yield <NULL>
+NOTICE: extract( month from interval -infinity) yield <NULL>
+NOTICE: extract( quarter from interval -infinity) yield <NULL>
+NOTICE: extract( year from interval -infinity) yield -Infinity
+NOTICE: extract( decade from interval -infinity) yield -Infinity
+NOTICE: extract( century from interval -infinity) yield -Infinity
+NOTICE: extract(millennium from interval -infinity) yield -Infinity
+NOTICE: extract( epoch from interval -infinity) yield -Infinity
+SELECT justify_days(interval 'infinity');
+ justify_days
+--------------
+ infinity
+(1 row)
+
+SELECT justify_days(interval '-infinity');
+ justify_days
+--------------
+ -infinity
+(1 row)
+
+SELECT justify_hours(interval 'infinity');
+ justify_hours
+---------------
+ infinity
+(1 row)
+
+SELECT justify_hours(interval '-infinity');
+ justify_hours
+---------------
+ -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/horology.sql b/src/test/regress/sql/horology.sql
index 2724a2bbc7..2494d852a6 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -173,14 +173,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -253,11 +256,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 0517b5b82b..51195d71d1 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,10 +29,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');
@@ -140,7 +145,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -509,13 +514,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
-- 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;
@@ -578,3 +583,179 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp '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';
+
+DO $$
+DECLARE
+ intv interval;
+ intv1 interval;
+ intvs interval[] := '{+infinity,-infinity}';
+ OPERATOR text[] := '{<,<=,=, >,>=,<>}';
+ opr text;
+ result boolean;
+BEGIN
+ FOREACH intv IN ARRAY intvs LOOP
+ FOREACH opr IN ARRAY OPERATOR LOOP
+ FOREACH intv1 IN ARRAY intvs LOOP
+ EXECUTE 'select interval ' || quote_literal(intv) || ' '
+ || opr || ' interval ' || quote_literal(intv1) INTO result;
+ RAISE NOTICE '%'
+ ,(format('%10s %2s %10s %2s'
+ ,intv
+ ,opr
+ ,intv1
+ ,result));
+ END LOOP;
+ END LOOP;
+ END LOOP;
+END
+$$;
+
+SELECT -interval 'infinity';
+SELECT -interval '-infinity';
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+SELECT interval 'infinity' * 2;
+SELECT interval 'infinity' * -2;
+SELECT interval '-infinity' * 2;
+SELECT interval '-infinity' * -2;
+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' * 2;
+
+SELECT interval 'infinity' / 3;
+SELECT interval 'infinity' / -3;
+SELECT interval '-infinity' / 3;
+SELECT interval '-infinity' / -3;
+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 date_trunc('hour', interval 'infinity');
+SELECT date_trunc('hour', interval '-infinity');
+
+DO $$
+DECLARE
+ intv interval;
+ intvs interval[] := '{+infinity,-infinity}';
+ units text[] := '{us,ms,second,minute,hour,day,month
+ ,quarter,year,decade,century,millennium,epoch}';
+ unit text;
+ result double precision;
+BEGIN
+ FOREACH intv IN ARRAY intvs LOOP
+ FOREACH unit IN ARRAY units LOOP
+ EXECUTE 'select date_part( ' || quote_literal(unit) || ' ,interval '
+ || quote_literal(intv) || ' )' INTO result;
+ RAISE NOTICE '% %'
+ ,(format('date_part(%10s, interval %10s) yield', unit, intv))
+ ,result;
+ END LOOP;
+ END LOOP;
+END
+$$;
+
+
+DO $$
+DECLARE
+ intv interval;
+ intvs interval[] := '{+infinity,-infinity}';
+ units text[] := '{us,ms,second,minute,hour,day,month
+ ,quarter,year,decade,century,millennium,epoch}';
+ unit text;
+ result numeric;
+BEGIN
+ FOREACH intv IN ARRAY intvs LOOP
+ FOREACH unit IN ARRAY units LOOP
+ EXECUTE 'select extract( ' || quote_literal(unit)
+ || ' from interval ' || quote_literal(intv) || ' )' INTO result;
+ RAISE NOTICE '% %'
+ ,(format('extract(%10s from interval %9s) yield', unit, intv))
+ ,result;
+ END LOOP;
+ END LOOP;
+END
+$$;
+
+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
On Fri, Jan 6, 2023 at 6:54 AM Joseph Koshakow <koshy44@gmail.com> wrote:
Jian,
I incorporated your changes and updated interval.out and ran
pgindent. Looks like some of the error messages have changed and we
have some issues with parsing "+infinity" after rebasing.- Joe
Looks like some of the error messages have changed and we
have some issues with parsing "+infinity" after rebasing.
There is a commit 2ceea5adb02603ef52579b568ca2c5aebed87358
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=2ceea5adb02603ef52579b568ca2c5aebed87358
if you pull this commit then you can do select interval '+infinity', even
though I don't know why.
On Thu, Jan 5, 2023 at 11:30 PM jian he <jian.universality@gmail.com> wrote:
On Fri, Jan 6, 2023 at 6:54 AM Joseph Koshakow <koshy44@gmail.com> wrote:
Looks like some of the error messages have changed and we
have some issues with parsing "+infinity" after rebasing.There is a commit 2ceea5adb02603ef52579b568ca2c5aebed87358
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=2ceea5adb02603ef52579b568ca2c5aebed87358
if you pull this commit then you can do select interval '+infinity', even though I don't know why.
It turns out that I was just misreading the error. The test was
expecting us to fail on "+infinity" but we succeeded. I just removed
that test case.
pgindent. Looks like some of the error messages have changed
The conditions for checking valid addition/subtraction between infinite
values were missing some cases which explains the change in error
messages. I've updated the logic and removed duplicate checks.
I removed the extract/date_part tests since they were duplicated in a
test above. I also converted the DO command tests to using SQL with
joins so it more closely matches the existing tests.
I've updated the extract/date_part logic for infinite intervals. Fields
that are monotonically increasing should return +/-infinity and all
others should return NULL. For Intervals, the fields are the same as
timestamps plus the hour and day fields since those don't overflow into
the next highest field.
I think this patch is just about ready for review, except for the
following two questions:
1. Should finite checks on intervals only look at months or all three
fields?
2. Should we make the error messages for adding/subtracting infinite
values more generic or leave them as is?
My opinions are
1. We should only look at months.
2. We should make the errors more generic.
Anyone else have any thoughts?
- Joe
On Sat, Jan 7, 2023 at 3:04 PM Joseph Koshakow <koshy44@gmail.com> wrote:
On Thu, Jan 5, 2023 at 11:30 PM jian he <jian.universality@gmail.com> wrote:
On Fri, Jan 6, 2023 at 6:54 AM Joseph Koshakow <koshy44@gmail.com> wrote:
Looks like some of the error messages have changed and we
have some issues with parsing "+infinity" after rebasing.There is a commit 2ceea5adb02603ef52579b568ca2c5aebed87358
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=2ceea5adb02603ef52579b568ca2c5aebed87358
if you pull this commit then you can do select interval '+infinity', even though I don't know why.It turns out that I was just misreading the error. The test was
expecting us to fail on "+infinity" but we succeeded. I just removed
that test case.pgindent. Looks like some of the error messages have changed
The conditions for checking valid addition/subtraction between infinite
values were missing some cases which explains the change in error
messages. I've updated the logic and removed duplicate checks.I removed the extract/date_part tests since they were duplicated in a
test above. I also converted the DO command tests to using SQL with
joins so it more closely matches the existing tests.I've updated the extract/date_part logic for infinite intervals. Fields
that are monotonically increasing should return +/-infinity and all
others should return NULL. For Intervals, the fields are the same as
timestamps plus the hour and day fields since those don't overflow into
the next highest field.I think this patch is just about ready for review, except for the
following two questions:
1. Should finite checks on intervals only look at months or all three
fields?
2. Should we make the error messages for adding/subtracting infinite
values more generic or leave them as is?My opinions are
1. We should only look at months.
2. We should make the errors more generic.Anyone else have any thoughts?
- Joe
Oops I forgot the actual patch. Please see attached.
Attachments:
v9-0001-Support-infinite-interval.patchtext/x-patch; charset=US-ASCII; name=v9-0001-Support-infinite-interval.patchDownload
From 4ea7c98d47dcbff1313a5013572cc79839e4417e Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 17 Dec 2022 14:21:26 -0500
Subject: [PATCH] This is WIP.
TODOs
1. Should we just use the months field to test for infinity?
2. Should the error messages for adding different sign infinties be "interval out of range"?
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 20 ++
src/backend/utils/adt/datetime.c | 14 +-
src/backend/utils/adt/timestamp.c | 448 ++++++++++++++++++++----
src/include/datatype/timestamp.h | 21 ++
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 466 +++++++++++++++++++++++--
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 130 ++++++-
10 files changed, 1002 insertions(+), 116 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index fdffba4442..2bcf959f70 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2316,7 +2316,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3bf8d021c3..7ddf76da4a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9369,7 +9369,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10256,7 +10256,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 99171d9c92..8334b9053f 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2067,6 +2067,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2085,6 +2090,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2599,6 +2609,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2621,6 +2636,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index d166613895..4192e7a74b 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -70,7 +70,7 @@ static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
const char *abbr, pg_tz *tzp,
int *offset, int *isdst);
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra);
+ DateTimeErrorExtra * extra);
const int day_tab[2][13] =
@@ -978,7 +978,7 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
int
DecodeDateTime(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -1928,7 +1928,7 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
int
DecodeTimeOnly(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -3233,7 +3233,7 @@ DecodeTimezone(const char *str, int *tzp)
int
DecodeTimezoneAbbrev(int field, const char *lowtoken,
int *ftype, int *offset, pg_tz **tz,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
const datetkn *tp;
@@ -3635,6 +3635,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;
@@ -4040,7 +4042,7 @@ DecodeUnits(int field, const char *lowtoken, int *val)
* separate SQLSTATE codes, so ...
*/
void
-DateTimeParseError(int dterr, DateTimeErrorExtra *extra,
+DateTimeParseError(int dterr, DateTimeErrorExtra * extra,
const char *str, const char *datatype,
Node *escontext)
{
@@ -4919,7 +4921,7 @@ InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
*/
static pg_tz *
FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
DynamicZoneAbbrev *dtza;
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 928c330897..189b7cbfea 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 interval_um_internal(Interval *interval, Interval *result);
/* 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)
{
@@ -2027,6 +2060,8 @@ 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;
}
@@ -2044,6 +2079,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2077,7 +2114,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2769,6 +2808,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))
@@ -2844,6 +2886,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,
@@ -2882,6 +2927,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))
@@ -2920,7 +2968,32 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND
+ (timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite values with opposite signs")));
+ 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("cannot add infinite values with opposite signs")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -2999,9 +3072,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3026,7 +3097,32 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND
+ (timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite values with opposite signs")));
+ 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("cannot add infinite values with opposite signs")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3109,15 +3205,37 @@ timestamptz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamptz_pl_interval,
TimestampGetDatum(timestamp),
PointerGetDatum(&tspan));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
Datum
interval_um(PG_FUNCTION_ARGS)
@@ -3126,23 +3244,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3186,28 +3288,62 @@ 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")));
+ /*
+ * 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))
+ {
+ if INTERVAL_IS_NOEND
+ (span2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite values with opposite signs")));
+ 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("cannot add infinite values with opposite signs")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (INTERVAL_NOT_FINITE(span2))
+ memcpy(result, span2, sizeof(Interval));
+ else
+ {
+ 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")));
- 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->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")));
+ 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3220,28 +3356,64 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ if INTERVAL_IS_NOBEGIN
+ (span2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite values with the same signs")));
+ 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("cannot subtract infinite values with the same signs")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (INTERVAL_IS_NOBEGIN(span2))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(span2))
+ INTERVAL_NOBEGIN(result);
+ else
+ {
+ 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->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")));
+ 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3265,6 +3437,24 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3325,6 +3515,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3356,6 +3551,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3377,6 +3595,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3910,6 +4133,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4365,6 +4593,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);
@@ -5224,6 +5458,60 @@ extract_timestamptz(PG_FUNCTION_ARGS)
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
*/
@@ -5247,6 +5535,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r)
+ {
+ 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);
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 21a37e21e9..333103efb1 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -160,6 +160,27 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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->time) == PG_INT64_MIN && (i->day) == PG_INT32_MIN && (i->month) == PG_INT32_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->time) == PG_INT64_MAX && (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/horology.out b/src/test/regress/expected/horology.out
index de73683690..10b86963ee 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -939,6 +939,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1050,6 +1051,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1157,6 +1159,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1432,6 +1435,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1599,7 +1603,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 579e92e7b3..2aa9b59ef2 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,7 +268,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 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +865,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 +877,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;
@@ -1578,31 +1625,31 @@ LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
-- 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
@@ -1681,19 +1728,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1767,7 +1816,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1776,3 +1827,350 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT date '-infinity' + interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: cannot subtract infinite values with the same signs
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: cannot subtract infinite values with the same signs
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp '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: cannot add infinite values with opposite signs
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: cannot add infinite values with opposite signs
+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: cannot add infinite values with opposite signs
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: cannot add infinite values with opposite signs
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: cannot add infinite values with opposite signs
+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' / '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)
+
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 2724a2bbc7..2494d852a6 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -173,14 +173,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -253,11 +256,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 0517b5b82b..742095382a 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -509,13 +513,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
-- 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;
@@ -578,3 +582,119 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp '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 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' / '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);
--
2.34.1
On Sat, Jan 7, 2023 at 3:05 PM Joseph Koshakow <koshy44@gmail.com> wrote:
On Sat, Jan 7, 2023 at 3:04 PM Joseph Koshakow <koshy44@gmail.com> wrote:
I think this patch is just about ready for review, except for the
following two questions:
1. Should finite checks on intervals only look at months or all three
fields?
2. Should we make the error messages for adding/subtracting infinite
values more generic or leave them as is?My opinions are
1. We should only look at months.
2. We should make the errors more generic.Anyone else have any thoughts?
Here's a patch with the more generic error messages.
- Joe
Attachments:
v10-0001-Support-infinite-interval.patchtext/x-patch; charset=US-ASCII; name=v10-0001-Support-infinite-interval.patchDownload
From 6ed93bc20db57cea2d692e9288d97b66f4a526dc Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 17 Dec 2022 14:21:26 -0500
Subject: [PATCH] This is WIP.
TODOs
1. Should we just use the months field to test for infinity?
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 20 ++
src/backend/utils/adt/datetime.c | 14 +-
src/backend/utils/adt/timestamp.c | 448 ++++++++++++++++++++----
src/include/datatype/timestamp.h | 21 ++
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 466 +++++++++++++++++++++++--
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 130 ++++++-
10 files changed, 1002 insertions(+), 116 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index fdffba4442..2bcf959f70 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2316,7 +2316,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 3bf8d021c3..7ddf76da4a 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9369,7 +9369,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10256,7 +10256,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 99171d9c92..8334b9053f 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2067,6 +2067,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2085,6 +2090,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2599,6 +2609,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2621,6 +2636,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index d166613895..4192e7a74b 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -70,7 +70,7 @@ static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
const char *abbr, pg_tz *tzp,
int *offset, int *isdst);
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra);
+ DateTimeErrorExtra * extra);
const int day_tab[2][13] =
@@ -978,7 +978,7 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
int
DecodeDateTime(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -1928,7 +1928,7 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
int
DecodeTimeOnly(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -3233,7 +3233,7 @@ DecodeTimezone(const char *str, int *tzp)
int
DecodeTimezoneAbbrev(int field, const char *lowtoken,
int *ftype, int *offset, pg_tz **tz,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
const datetkn *tp;
@@ -3635,6 +3635,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;
@@ -4040,7 +4042,7 @@ DecodeUnits(int field, const char *lowtoken, int *val)
* separate SQLSTATE codes, so ...
*/
void
-DateTimeParseError(int dterr, DateTimeErrorExtra *extra,
+DateTimeParseError(int dterr, DateTimeErrorExtra * extra,
const char *str, const char *datatype,
Node *escontext)
{
@@ -4919,7 +4921,7 @@ InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
*/
static pg_tz *
FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
DynamicZoneAbbrev *dtza;
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 928c330897..d08696de97 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 interval_um_internal(Interval *interval, Interval *result);
/* 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)
{
@@ -2027,6 +2060,8 @@ 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;
}
@@ -2044,6 +2079,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2077,7 +2114,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2769,6 +2808,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))
@@ -2844,6 +2886,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,
@@ -2882,6 +2927,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))
@@ -2920,7 +2968,32 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND
+ (timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -2999,9 +3072,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3026,7 +3097,32 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND
+ (timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3109,15 +3205,37 @@ timestamptz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamptz_pl_interval,
TimestampGetDatum(timestamp),
PointerGetDatum(&tspan));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
Datum
interval_um(PG_FUNCTION_ARGS)
@@ -3126,23 +3244,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3186,28 +3288,62 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3220,28 +3356,64 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3265,6 +3437,24 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3325,6 +3515,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3356,6 +3551,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3377,6 +3595,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3910,6 +4133,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4365,6 +4593,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);
@@ -5224,6 +5458,60 @@ extract_timestamptz(PG_FUNCTION_ARGS)
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
*/
@@ -5247,6 +5535,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r)
+ {
+ 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);
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 21a37e21e9..333103efb1 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -160,6 +160,27 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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->time) == PG_INT64_MIN && (i->day) == PG_INT32_MIN && (i->month) == PG_INT32_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->time) == PG_INT64_MAX && (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/horology.out b/src/test/regress/expected/horology.out
index de73683690..10b86963ee 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -939,6 +939,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1050,6 +1051,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1157,6 +1159,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1432,6 +1435,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1599,7 +1603,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 579e92e7b3..fdad2def16 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,7 +268,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 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +865,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 +877,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;
@@ -1578,31 +1625,31 @@ LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
-- 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
@@ -1681,19 +1728,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1767,7 +1816,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1776,3 +1827,350 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp '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: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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' / '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)
+
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 2724a2bbc7..2494d852a6 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -173,14 +173,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -253,11 +256,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 0517b5b82b..742095382a 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -509,13 +513,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
-- 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;
@@ -578,3 +582,119 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp '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 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' / '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);
--
2.34.1
On Sun, Jan 8, 2023 at 4:22 AM Joseph Koshakow <koshy44@gmail.com> wrote:
On Sat, Jan 7, 2023 at 3:05 PM Joseph Koshakow <koshy44@gmail.com> wrote:
On Sat, Jan 7, 2023 at 3:04 PM Joseph Koshakow <koshy44@gmail.com>
wrote:
I think this patch is just about ready for review, except for the
following two questions:
1. Should finite checks on intervals only look at months or all three
fields?
2. Should we make the error messages for adding/subtracting infinite
values more generic or leave them as is?My opinions are
1. We should only look at months.
2. We should make the errors more generic.Anyone else have any thoughts?
Here's a patch with the more generic error messages.
- Joe
HI.
I just found out another problem.
select * from generate_series(timestamp'-infinity', timestamp 'infinity',
interval 'infinity');
ERROR: timestamp out of range
select * from generate_series(timestamp'-infinity',timestamp 'infinity',
interval '-infinity'); --return following
generate_series
-----------------
(0 rows)
select * from generate_series(timestamp 'infinity',timestamp 'infinity',
interval 'infinity');
--will run all the time.
select * from generate_series(timestamp 'infinity',timestamp 'infinity',
interval '-infinity');
ERROR: timestamp out of range
select * from generate_series(timestamp'-infinity',timestamp'-infinity',
interval 'infinity');
ERROR: timestamp out of range
select * from generate_series(timestamp'-infinity',timestamp'-infinity',
interval '-infinity');
--will run all the time.
--
I recommend David Deutsch's <<The Beginning of Infinity>>
Jian
On Sun, Jan 8, 2023 at 11:17 PM jian he <jian.universality@gmail.com> wrote:
On Sun, Jan 8, 2023 at 4:22 AM Joseph Koshakow <koshy44@gmail.com> wrote:
On Sat, Jan 7, 2023 at 3:05 PM Joseph Koshakow <koshy44@gmail.com> wrote:
On Sat, Jan 7, 2023 at 3:04 PM Joseph Koshakow <koshy44@gmail.com> wrote:
I think this patch is just about ready for review, except for the
following two questions:
1. Should finite checks on intervals only look at months or all three
fields?
2. Should we make the error messages for adding/subtracting infinite
values more generic or leave them as is?My opinions are
1. We should only look at months.
2. We should make the errors more generic.Anyone else have any thoughts?
Here's a patch with the more generic error messages.
- Joe
HI.
I just found out another problem.
select * from generate_series(timestamp'-infinity', timestamp 'infinity', interval 'infinity');
ERROR: timestamp out of rangeselect * from generate_series(timestamp'-infinity',timestamp 'infinity', interval '-infinity'); --return following
generate_series
-----------------
(0 rows)select * from generate_series(timestamp 'infinity',timestamp 'infinity', interval 'infinity');
--will run all the time.select * from generate_series(timestamp 'infinity',timestamp 'infinity', interval '-infinity');
ERROR: timestamp out of rangeselect * from generate_series(timestamp'-infinity',timestamp'-infinity', interval 'infinity');
ERROR: timestamp out of rangeselect * from generate_series(timestamp'-infinity',timestamp'-infinity', interval '-infinity');
--will run all the time.
Good catch, I didn't think to check non date/time functions.
Unfortunately, I think you may have opened Pandoras box. I went through
pg_proc.dat and found the following functions that all involve
intervals. We should probably investigate all of them and make sure
that they handle infinite intervals properly.
{ oid => '1026', descr => 'adjust timestamp to new time zone',
proname => 'timezone', prorettype => 'timestamp',
proargtypes => 'interval timestamptz', prosrc => 'timestamptz_izone' },
{ oid => '4133', descr => 'window RANGE support',
proname => 'in_range', prorettype => 'bool',
proargtypes => 'date date interval bool bool',
prosrc => 'in_range_date_interval' },
{ oid => '1305', descr => 'intervals overlap?',
proname => 'overlaps', prolang => 'sql', proisstrict => 'f',
provolatile => 's', prorettype => 'bool',
proargtypes => 'timestamptz interval timestamptz interval',
prosrc => 'see system_functions.sql' },
{ oid => '1305', descr => 'intervals overlap?',
proname => 'overlaps', prolang => 'sql', proisstrict => 'f',
provolatile => 's', prorettype => 'bool',
proargtypes => 'timestamptz interval timestamptz interval',
prosrc => 'see system_functions.sql' },
{ oid => '1306', descr => 'intervals overlap?',
proname => 'overlaps', prolang => 'sql', proisstrict => 'f',
provolatile => 's', prorettype => 'bool',
proargtypes => 'timestamptz timestamptz timestamptz interval',
prosrc => 'see system_functions.sql' },
{ oid => '1307', descr => 'intervals overlap?',
proname => 'overlaps', prolang => 'sql', proisstrict => 'f',
provolatile => 's', prorettype => 'bool',
proargtypes => 'timestamptz interval timestamptz timestamptz',
prosrc => 'see system_functions.sql' },
{ oid => '1308', descr => 'intervals overlap?',
proname => 'overlaps', proisstrict => 'f', prorettype => 'bool',
proargtypes => 'time time time time', prosrc => 'overlaps_time' },
{ oid => '1309', descr => 'intervals overlap?',
proname => 'overlaps', prolang => 'sql', proisstrict => 'f',
prorettype => 'bool', proargtypes => 'time interval time interval',
prosrc => 'see system_functions.sql' },
{ oid => '1310', descr => 'intervals overlap?',
proname => 'overlaps', prolang => 'sql', proisstrict => 'f',
prorettype => 'bool', proargtypes => 'time time time interval',
prosrc => 'see system_functions.sql' },
{ oid => '1311', descr => 'intervals overlap?',
proname => 'overlaps', prolang => 'sql', proisstrict => 'f',
prorettype => 'bool', proargtypes => 'time interval time time',
prosrc => 'see system_functions.sql' },
{ oid => '1386',
descr => 'date difference from today preserving months and years',
proname => 'age', prolang => 'sql', provolatile => 's',
prorettype => 'interval', proargtypes => 'timestamptz',
prosrc => 'see system_functions.sql' },
{ oid => '2042', descr => 'intervals overlap?',
proname => 'overlaps', prolang => 'sql', proisstrict => 'f',
prorettype => 'bool', proargtypes => 'timestamp interval timestamp interval',
prosrc => 'see system_functions.sql' },
{ oid => '2043', descr => 'intervals overlap?',
proname => 'overlaps', prolang => 'sql', proisstrict => 'f',
prorettype => 'bool', proargtypes => 'timestamp timestamp timestamp interval',
prosrc => 'see system_functions.sql' },
{ oid => '2044', descr => 'intervals overlap?',
proname => 'overlaps', prolang => 'sql', proisstrict => 'f',
prorettype => 'bool', proargtypes => 'timestamp interval timestamp timestamp',
prosrc => 'see system_functions.sql' },
{ oid => '4134', descr => 'window RANGE support',
proname => 'in_range', prorettype => 'bool',
proargtypes => 'timestamp timestamp interval bool bool',
prosrc => 'in_range_timestamp_interval' },
{ oid => '4135', descr => 'window RANGE support',
proname => 'in_range', provolatile => 's', prorettype => 'bool',
proargtypes => 'timestamptz timestamptz interval bool bool',
prosrc => 'in_range_timestamptz_interval' },
{ oid => '4136', descr => 'window RANGE support',
proname => 'in_range', prorettype => 'bool',
proargtypes => 'interval interval interval bool bool',
prosrc => 'in_range_interval_interval' },
{ oid => '4137', descr => 'window RANGE support',
proname => 'in_range', prorettype => 'bool',
proargtypes => 'time time interval bool bool',
prosrc => 'in_range_time_interval' },
{ oid => '4138', descr => 'window RANGE support',
proname => 'in_range', prorettype => 'bool',
proargtypes => 'timetz timetz interval bool bool',
prosrc => 'in_range_timetz_interval' },
{ oid => '2058', descr => 'date difference preserving months and years',
proname => 'age', prorettype => 'interval',
proargtypes => 'timestamp timestamp', prosrc => 'timestamp_age' },
{ oid => '2059',
descr => 'date difference from today preserving months and years',
proname => 'age', prolang => 'sql', provolatile => 's',
prorettype => 'interval', proargtypes => 'timestamp',
prosrc => 'see system_functions.sql' },
{ oid => '2070', descr => 'adjust timestamp to new time zone',
proname => 'timezone', prorettype => 'timestamptz',
proargtypes => 'interval timestamp', prosrc => 'timestamp_izone' },
{ oid => '3935', descr => 'sleep for the specified interval',
proname => 'pg_sleep_for', prolang => 'sql', provolatile => 'v',
prorettype => 'void', proargtypes => 'interval',
prosrc => 'see system_functions.sql' },
{ oid => '2599', descr => 'get the available time zone abbreviations',
proname => 'pg_timezone_abbrevs', prorows => '1000', proretset => 't',
provolatile => 's', prorettype => 'record', proargtypes => '',
proallargtypes => '{text,interval,bool}', proargmodes => '{o,o,o}',
proargnames => '{abbrev,utc_offset,is_dst}',
prosrc => 'pg_timezone_abbrevs' },
{ oid => '2856', descr => 'get the available time zone names',
proname => 'pg_timezone_names', prorows => '1000', proretset => 't',
provolatile => 's', prorettype => 'record', proargtypes => '',
proallargtypes => '{text,text,interval,bool}', proargmodes => '{o,o,o,o}',
proargnames => '{name,abbrev,utc_offset,is_dst}',
prosrc => 'pg_timezone_names' },
{ oid => '939', descr => 'non-persistent series generator',
proname => 'generate_series', prorows => '1000', proretset => 't',
provolatile => 's', prorettype => 'timestamptz',
proargtypes => 'timestamptz timestamptz interval',
prosrc => 'generate_series_timestamptz' },
{ oid => '3976', descr => 'continuous distribution percentile',
proname => 'percentile_cont', prokind => 'a', proisstrict => 'f',
prorettype => 'interval', proargtypes => 'float8 interval',
prosrc => 'aggregate_dummy' },
{ oid => '3977', descr => 'aggregate final function',
proname => 'percentile_cont_interval_final', proisstrict => 'f',
prorettype => 'interval', proargtypes => 'internal float8',
prosrc => 'percentile_cont_interval_final' },
{ oid => '3982', descr => 'multiple continuous percentiles',
proname => 'percentile_cont', prokind => 'a', proisstrict => 'f',
prorettype => '_interval', proargtypes => '_float8 interval',
prosrc => 'aggregate_dummy' },
{ oid => '3983', descr => 'aggregate final function',
proname => 'percentile_cont_interval_multi_final', proisstrict => 'f',
prorettype => '_interval', proargtypes => 'internal _float8',
prosrc => 'percentile_cont_interval_multi_final' },
- Joe
Ok, I've updated the patch to handle every function that inputs or
outputs intervals, as well as added some tests. In the process I
noticed that some of the existing date/timestamp/timestamptz don't
handle infinite values properly. For example,
postgres=# SELECT age('infinity'::timestamp);
age
--------------------------------------------------
-292253 years -11 mons -26 days -04:00:54.775807
(1 row)
It might be worth going through all those functions separately
and making sure they are correct.
I also added some overflow handling to make_interval.
I also added handling of infinite timestamp subtraction.
At this point the patch is ready for review again except for the one
outstanding question of: Should finite checks on intervals only look at
months or all three fields?
- Joe
Attachments:
v11-0001-Support-infinite-interval.patchtext/x-patch; charset=US-ASCII; name=v11-0001-Support-infinite-interval.patchDownload
From 23868228ad2c0be57408b38db76bced85ab83cb1 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 17 Dec 2022 14:21:26 -0500
Subject: [PATCH] This is WIP.
TODOs
1. Should we just use the months field to test for infinity?
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 32 +
src/backend/utils/adt/datetime.c | 39 +-
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 695 ++++++++++++++++++----
src/include/common/int.h | 18 +
src/include/datatype/timestamp.h | 21 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 503 ++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 149 ++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
18 files changed, 1461 insertions(+), 193 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 467b49b199..d782d23574 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2316,7 +2316,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b8dac9ef46..36b31f7163 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9393,7 +9393,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10280,7 +10280,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 99171d9c92..dc271f663c 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2023,6 +2023,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2067,6 +2072,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2085,6 +2095,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2599,6 +2614,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2621,6 +2641,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3140,6 +3165,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index d166613895..608d562b48 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -50,7 +50,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -70,7 +69,7 @@ static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
const char *abbr, pg_tz *tzp,
int *offset, int *isdst);
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra);
+ DateTimeErrorExtra * extra);
const int day_tab[2][13] =
@@ -514,22 +513,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -620,7 +603,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -978,7 +961,7 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
int
DecodeDateTime(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -1928,7 +1911,7 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
int
DecodeTimeOnly(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -2853,9 +2836,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
@@ -3233,7 +3216,7 @@ DecodeTimezone(const char *str, int *tzp)
int
DecodeTimezoneAbbrev(int field, const char *lowtoken,
int *ftype, int *offset, pg_tz **tz,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
const datetkn *tp;
@@ -3635,6 +3618,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;
@@ -4040,7 +4025,7 @@ DecodeUnits(int field, const char *lowtoken, int *val)
* separate SQLSTATE codes, so ...
*/
void
-DateTimeParseError(int dterr, DateTimeErrorExtra *extra,
+DateTimeParseError(int dterr, DateTimeErrorExtra * extra,
const char *str, const char *datatype,
Node *escontext)
{
@@ -4919,7 +4904,7 @@ InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
*/
static pg_tz *
FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
DynamicZoneAbbrev *dtza;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index a4b524ea3a..d9dca76f96 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 57de51f0db..ab80812ae5 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 928c330897..6d406d5763 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -79,6 +80,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* 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.
@@ -1520,13 +1542,35 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, 7, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+
+ result->time = secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1545,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2027,6 +2082,8 @@ 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;
}
@@ -2044,6 +2101,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2077,7 +2136,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2208,6 +2269,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2703,43 +2787,70 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- result->time = dt1 - dt2;
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN
+ (dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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
+ {
+ result->time = dt1 - dt2;
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2769,6 +2880,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))
@@ -2844,6 +2958,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,
@@ -2882,6 +2999,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))
@@ -2920,7 +3040,32 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND
+ (timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -2999,9 +3144,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3026,7 +3169,32 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND
+ (timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3109,15 +3277,37 @@ timestamptz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamptz_pl_interval,
TimestampGetDatum(timestamp),
PointerGetDatum(&tspan));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
Datum
interval_um(PG_FUNCTION_ARGS)
@@ -3126,23 +3316,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3186,28 +3360,62 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3220,28 +3428,64 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3265,6 +3509,24 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3325,6 +3587,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3356,6 +3623,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3377,6 +3667,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3664,8 +3959,37 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN
+ (dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3784,8 +4108,37 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN
+ (dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3910,6 +4263,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4093,6 +4451,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4365,6 +4728,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);
@@ -5224,6 +5593,60 @@ extract_timestamptz(PG_FUNCTION_ARGS)
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
*/
@@ -5247,6 +5670,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r)
+ {
+ 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);
@@ -5516,6 +5967,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5773,6 +6231,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5839,6 +6304,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5918,6 +6388,11 @@ generate_series_timestamptz(PG_FUNCTION_ARGS)
(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);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..53cc4ce4ce 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,15 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
@@ -254,6 +263,15 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 21a37e21e9..333103efb1 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -160,6 +160,27 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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->time) == PG_INT64_MIN && (i->day) == PG_INT32_MIN && (i->month) == PG_INT32_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->time) == PG_INT64_MAX && (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/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 42f802bb9d..20eb024dc8 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -112,6 +112,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -121,6 +122,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index de73683690..10b86963ee 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -939,6 +939,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1050,6 +1051,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1157,6 +1159,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1432,6 +1435,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1599,7 +1603,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 579e92e7b3..686394c25b 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,7 +268,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 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +865,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 +877,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;
@@ -1576,33 +1623,38 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+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
@@ -1681,19 +1733,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1767,7 +1821,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1776,3 +1832,382 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp '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: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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' / '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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index edc6912e7a..d85359b28f 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2116,3 +2116,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 00379fd0fd..97230af014 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2459,6 +2459,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
--
-- Test behavior with a dynamic (time-varying) timezone abbreviation.
-- These tests rely on the knowledge that MSK (Europe/Moscow standard time)
@@ -3091,3 +3095,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 2724a2bbc7..2494d852a6 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -173,14 +173,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -253,11 +256,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 0517b5b82b..ed563666b0 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -507,15 +511,19 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+
-- 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;
@@ -578,3 +586,134 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp '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 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' / '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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index 1d580f77f1..e0fdfd7d74 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -393,3 +393,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 4905dd0831..598c07f064 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -454,6 +454,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
--
-- Test behavior with a dynamic (time-varying) timezone abbreviation.
@@ -596,3 +598,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
On Sat, Jan 14, 2023 at 4:22 PM Joseph Koshakow <koshy44@gmail.com> wrote:
At this point the patch is ready for review again except for the one
outstanding question of: Should finite checks on intervals only look at
months or all three fields?- Joe
I've gone ahead and updated the patch to only look at the months field.
I'll submit this email and patch to the Feb commitfest.
- Joe
Attachments:
v12-0001-Support-infinite-interval.patchtext/x-patch; charset=US-ASCII; name=v12-0001-Support-infinite-interval.patchDownload
From 123cdf534cc1a0e9a44e7dc8641d23e2c5b09e31 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 17 Dec 2022 14:21:26 -0500
Subject: [PATCH] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 32 +
src/backend/utils/adt/datetime.c | 39 +-
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 705 ++++++++++++++++++----
src/include/common/int.h | 18 +
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 563 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 174 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
18 files changed, 1516 insertions(+), 231 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 467b49b199..d782d23574 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2316,7 +2316,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index b8dac9ef46..36b31f7163 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9393,7 +9393,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10280,7 +10280,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 99171d9c92..dc271f663c 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2023,6 +2023,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2067,6 +2072,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2085,6 +2095,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2599,6 +2614,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2621,6 +2641,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3140,6 +3165,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index d166613895..608d562b48 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -50,7 +50,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -70,7 +69,7 @@ static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
const char *abbr, pg_tz *tzp,
int *offset, int *isdst);
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra);
+ DateTimeErrorExtra * extra);
const int day_tab[2][13] =
@@ -514,22 +513,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -620,7 +603,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -978,7 +961,7 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
int
DecodeDateTime(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -1928,7 +1911,7 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
int
DecodeTimeOnly(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -2853,9 +2836,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
@@ -3233,7 +3216,7 @@ DecodeTimezone(const char *str, int *tzp)
int
DecodeTimezoneAbbrev(int field, const char *lowtoken,
int *ftype, int *offset, pg_tz **tz,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
const datetkn *tp;
@@ -3635,6 +3618,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;
@@ -4040,7 +4025,7 @@ DecodeUnits(int field, const char *lowtoken, int *val)
* separate SQLSTATE codes, so ...
*/
void
-DateTimeParseError(int dterr, DateTimeErrorExtra *extra,
+DateTimeParseError(int dterr, DateTimeErrorExtra * extra,
const char *str, const char *datatype,
Node *escontext)
{
@@ -4919,7 +4904,7 @@ InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
*/
static pg_tz *
FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
DynamicZoneAbbrev *dtza;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index a4b524ea3a..d9dca76f96 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 57de51f0db..ab80812ae5 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 928c330897..51438035e7 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -79,6 +80,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* 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.
@@ -1520,13 +1542,35 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, 7, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+
+ result->time = secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1545,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2027,6 +2082,8 @@ 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;
}
@@ -2044,6 +2101,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2077,7 +2136,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2208,6 +2269,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2703,43 +2787,70 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- result->time = dt1 - dt2;
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN
+ (dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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
+ {
+ result->time = dt1 - dt2;
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2769,6 +2880,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))
@@ -2821,6 +2935,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2844,6 +2963,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,
@@ -2882,6 +3004,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))
@@ -2900,6 +3025,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2920,7 +3050,32 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND
+ (timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -2999,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3026,7 +3179,32 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND
+ (timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3109,15 +3287,37 @@ timestamptz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamptz_pl_interval,
TimestampGetDatum(timestamp),
PointerGetDatum(&tspan));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
Datum
interval_um(PG_FUNCTION_ARGS)
@@ -3126,23 +3326,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3186,28 +3370,62 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3220,28 +3438,64 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3265,6 +3519,24 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3325,6 +3597,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3356,6 +3633,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3377,6 +3677,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3664,8 +3969,37 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN
+ (dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3784,8 +4118,37 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN
+ (dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3910,6 +4273,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4093,6 +4461,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4365,6 +4738,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);
@@ -5224,6 +5603,60 @@ extract_timestamptz(PG_FUNCTION_ARGS)
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
*/
@@ -5247,6 +5680,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r)
+ {
+ 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);
@@ -5516,6 +5977,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5773,6 +6241,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5839,6 +6314,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5918,6 +6398,11 @@ generate_series_timestamptz(PG_FUNCTION_ARGS)
(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);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..53cc4ce4ce 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,15 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
@@ -254,6 +263,15 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 21a37e21e9..fc37810c45 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -160,6 +160,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index 42f802bb9d..20eb024dc8 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -112,6 +112,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -121,6 +122,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index de73683690..10b86963ee 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -939,6 +939,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1050,6 +1051,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1157,6 +1159,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1432,6 +1435,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1599,7 +1603,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 579e92e7b3..ecb0747323 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1576,33 +1629,38 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+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
@@ -1681,19 +1739,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1767,7 +1827,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1776,3 +1838,382 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp '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: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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' / '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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index edc6912e7a..d85359b28f 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2116,3 +2116,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 00379fd0fd..97230af014 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2459,6 +2459,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
--
-- Test behavior with a dynamic (time-varying) timezone abbreviation.
-- These tests rely on the knowledge that MSK (Europe/Moscow standard time)
@@ -3091,3 +3095,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 2724a2bbc7..2494d852a6 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -173,14 +173,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -253,11 +256,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 0517b5b82b..92c4bde930 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -507,15 +514,19 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+
-- 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;
@@ -578,3 +589,134 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp '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 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' / '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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index 1d580f77f1..e0fdfd7d74 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -393,3 +393,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 4905dd0831..598c07f064 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -454,6 +454,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
--
-- Test behavior with a dynamic (time-varying) timezone abbreviation.
@@ -596,3 +598,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
On Sun, 15 Jan 2023 at 11:44, Joseph Koshakow <koshy44@gmail.com> wrote:
On Sat, Jan 14, 2023 at 4:22 PM Joseph Koshakow <koshy44@gmail.com> wrote:
I've gone ahead and updated the patch to only look at the months field.
I'll submit this email and patch to the Feb commitfest.
It looks like this patch needs a (perhaps trivial) rebase.
It sounds like all the design questions are resolved so perhaps this
can be set to Ready for Committer once it's rebased?
--
Gregory Stark
As Commitfest Manager
On Wed, Mar 1, 2023 at 3:03 PM Gregory Stark (as CFM) <stark.cfm@gmail.com>
wrote:
It looks like this patch needs a (perhaps trivial) rebase.
Attached is a rebased patch.
It sounds like all the design questions are resolved so perhaps this
can be set to Ready for Committer once it's rebased?
There hasn't really been a review of this patch yet. It's just been
mostly me talking to myself in this thread, and a couple of
contributions from jian.
- Joe Koshakow
Attachments:
v13-0001-Add-infinite-interval-values.patchtext/x-patch; charset=US-ASCII; name=v13-0001-Add-infinite-interval-values.patchDownload
From aaaa1b35e2b96bcf69431bbd8720523163de10cf Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 17 Dec 2022 14:21:26 -0500
Subject: [PATCH] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 32 +
src/backend/utils/adt/datetime.c | 39 +-
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 711 ++++++++++++++++++----
src/include/common/int.h | 18 +
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 563 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 174 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
18 files changed, 1519 insertions(+), 234 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 467b49b199..d782d23574 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2316,7 +2316,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 97b3f1c1a6..c83f38d263 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9393,7 +9393,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10280,7 +10280,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 99171d9c92..dc271f663c 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2023,6 +2023,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2067,6 +2072,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2085,6 +2095,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2599,6 +2614,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2621,6 +2641,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3140,6 +3165,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 01660637a2..462df43e2e 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -50,7 +50,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -70,7 +69,7 @@ static bool DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t,
const char *abbr, pg_tz *tzp,
int *offset, int *isdst);
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra);
+ DateTimeErrorExtra * extra);
const int day_tab[2][13] =
@@ -514,22 +513,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -620,7 +603,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -978,7 +961,7 @@ ParseDateTime(const char *timestr, char *workbuf, size_t buflen,
int
DecodeDateTime(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -1928,7 +1911,7 @@ DetermineTimeZoneAbbrevOffsetInternal(pg_time_t t, const char *abbr, pg_tz *tzp,
int
DecodeTimeOnly(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
tmask,
@@ -2853,9 +2836,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
@@ -3233,7 +3216,7 @@ DecodeTimezone(const char *str, int *tzp)
int
DecodeTimezoneAbbrev(int field, const char *lowtoken,
int *ftype, int *offset, pg_tz **tz,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
const datetkn *tp;
@@ -3635,6 +3618,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;
@@ -4032,7 +4017,7 @@ DecodeUnits(int field, const char *lowtoken, int *val)
* separate SQLSTATE codes, so ...
*/
void
-DateTimeParseError(int dterr, DateTimeErrorExtra *extra,
+DateTimeParseError(int dterr, DateTimeErrorExtra * extra,
const char *str, const char *datatype,
Node *escontext)
{
@@ -4911,7 +4896,7 @@ InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *tbl)
*/
static pg_tz *
FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
DynamicZoneAbbrev *dtza;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index f3f4db5ef6..0157de73f0 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fe37e65af0..e2cc6049f2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index de93db89d4..0b3b2ec94e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -79,6 +80,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* 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.
@@ -1520,13 +1542,35 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, 7, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+
+ result->time = secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1545,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2032,6 +2087,8 @@ 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;
}
@@ -2049,6 +2106,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2082,7 +2141,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2213,6 +2274,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2708,46 +2792,73 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN
+ (dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2777,6 +2888,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))
@@ -2829,6 +2943,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2852,6 +2971,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,
@@ -2890,6 +3012,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))
@@ -2908,6 +3033,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2928,7 +3058,32 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND
+ (timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3007,9 +3162,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3034,7 +3187,32 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND
+ (timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3117,15 +3295,37 @@ timestamptz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamptz_pl_interval,
TimestampGetDatum(timestamp),
PointerGetDatum(&tspan));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
Datum
interval_um(PG_FUNCTION_ARGS)
@@ -3134,23 +3334,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3194,28 +3378,62 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3228,28 +3446,64 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3273,6 +3527,24 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3333,6 +3605,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3364,6 +3641,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3385,6 +3685,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3672,8 +3977,37 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN
+ (dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3792,8 +4126,37 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN
+ (dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3918,6 +4281,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4101,6 +4469,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4373,6 +4746,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);
@@ -5232,6 +5611,60 @@ extract_timestamptz(PG_FUNCTION_ARGS)
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
*/
@@ -5255,6 +5688,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r)
+ {
+ 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);
@@ -5524,6 +5985,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5781,6 +6249,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5847,6 +6322,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5926,6 +6406,11 @@ generate_series_timestamptz(PG_FUNCTION_ARGS)
(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);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..53cc4ce4ce 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,15 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
@@ -254,6 +263,15 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..5caf5dcfbe 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -167,6 +167,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edd59dc432..bcb54a7238 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -114,6 +114,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -123,6 +124,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index de73683690..10b86963ee 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -939,6 +939,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1050,6 +1051,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1157,6 +1159,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1432,6 +1435,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1599,7 +1603,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..55d65d17d0 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1587,33 +1640,38 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+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
@@ -1692,19 +1750,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1778,7 +1838,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1787,3 +1849,382 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp '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: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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' / '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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 91d7c1f5cc..e6c81eb7d2 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
--
-- Test behavior with a dynamic (time-varying) timezone abbreviation.
-- These tests rely on the knowledge that MSK (Europe/Moscow standard time)
@@ -3100,3 +3104,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 2724a2bbc7..2494d852a6 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -173,14 +173,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -253,11 +256,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..1e1d8560bf 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -511,15 +518,19 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+
-- 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;
@@ -582,3 +593,134 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp '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 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' / '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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index ae9ee4b56a..4545187af0 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
--
-- Test behavior with a dynamic (time-varying) timezone abbreviation.
@@ -600,3 +602,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
Hi Joseph,
Thanks for working on the patch. Sorry for taking so long to review
this patch. But here's it finally, review of code changes
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const datetkn *tp,
- DateTimeErrorExtra *extra);
+ DateTimeErrorExtra * extra);
There are a lot of these diffs. PG code doesn't leave an extra space between
variable name and *.
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
I think this is a good change, since we are moving the function to int.h where
it belongs. We could separate these kind of changes into another patch for easy
review.
+
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, 7, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
I think such changes are also good, but probably a separate patch for ease of
review.
secs = rint(secs * USECS_PER_SEC);
- result->time = hours mj* ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+
+ result->time = secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE *
USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR *
USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
shouldn't this be
secs = rint(secs);
result->time = 0;
pg_mul_add_s64_overflow(secs, USECS_PER_SEC, &result->time) to catch
overflow error early?
+ if TIMESTAMP_IS_NOBEGIN
+ (dt2)
Better be written as if (TIMESTAMP_IS_NOBEGIN(dt2))? There are more corrections
like this.
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
Probably, I added these kind of checks. But I don't remember if those are
defensive checks or whether it's really possible that the arithmetic above
these lines can yield an non-finite interval.
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
If this error ever gets to the user, it could be confusing. Can we elaborate by
adding context e.g. errcontext("while negating an interval") or some such?
-
- 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);
Shouldn't we incorporate these checks in interval_um_internal()? I don't think
INTERVAL_NOT_FINITE() covers all of those.
+ /*
+ * 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.
+ */
I think we need someone to validate these assumptions and similar assumptions
in interval_pl(). Googling gives confusing results in some cases. I have not
looked for IEEE standard around this specificallly.
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r)
I see that this code is very similar to the corresponding code in timestamp and
timestamptz, so it's bound to be correct. But I always thought float equality
is unreliable. if (r) is equivalent to if (r == 0.0) so it will not work as
intended. But may be (float) 0.0 is a special value for which equality holds
true.
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
I think this needs a prologue similar to int64_multiply_add(), that the patch
removes. Similarly for pg_mul_add_s32_overflow().
On Thu, Mar 2, 2023 at 3:51 AM Joseph Koshakow <koshy44@gmail.com> wrote:
On Wed, Mar 1, 2023 at 3:03 PM Gregory Stark (as CFM) <stark.cfm@gmail.com> wrote:
It looks like this patch needs a (perhaps trivial) rebase.
Attached is a rebased patch.
It sounds like all the design questions are resolved so perhaps this
can be set to Ready for Committer once it's rebased?There hasn't really been a review of this patch yet. It's just been
mostly me talking to myself in this thread, and a couple of
contributions from jian.- Joe Koshakow
--
Best Wishes,
Ashutosh Bapat
On Thu, Mar 9, 2023 at 12:42 PM Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
wrote:
static pg_tz *FetchDynamicTimeZone(TimeZoneAbbrevTable *tbl, const
datetkn *tp,
- DateTimeErrorExtra *extra); + DateTimeErrorExtra * extra);There are a lot of these diffs. PG code doesn't leave an extra space
between
variable name and *.
Those appeared from running pg_indent. I've removed them all.
/* Handle the integer part */ - if (!int64_multiply_add(val, scale, &itm_in->tm_usec)) + if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))I think this is a good change, since we are moving the function to
int.h where
it belongs. We could separate these kind of changes into another patch
for easy
review.
I've separated this out into another patch attached to this email.
Should I start a new email thread or is it ok to include it in this
one?
+ + result->day = days; + if (pg_mul_add_s32_overflow(weeks, 7, &result->day)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range")));I think such changes are also good, but probably a separate patch for
ease of
review.
I've separated a patch for this too, which I've also included in this
email.
secs = rint(secs * USECS_PER_SEC); - result->time = hours mj* ((int64) SECS_PER_HOUR * USECS_PER_SEC) + - mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) + - (int64) secs; + + result->time = secs; + if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range"))); + if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range")));shouldn't this be
secs = rint(secs);
result->time = 0;
pg_mul_add_s64_overflow(secs, USECS_PER_SEC, &result->time) to
catch
overflow error early?
The problem is that `secs = rint(secs)` rounds the seconds too early
and loses any fractional seconds. Do we have an overflow detecting
multiplication function for floats?
+ if TIMESTAMP_IS_NOBEGIN
+ (dt2)Better be written as if (TIMESTAMP_IS_NOBEGIN(dt2))? There are more
corrections
like this.
I think this may have also been done by pg_indent, I've reverted all
the examples of this.
+ if (INTERVAL_NOT_FINITE(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range")));Probably, I added these kind of checks. But I don't remember if those
are
defensive checks or whether it's really possible that the arithmetic
above
these lines can yield an non-finite interval.
These checks appear in `make_interval`, `justify_X`,
`interval_um_internal`, `interval_pl`, `interval_mi`, `interval_mul`,
`interval_div`. For all of these it's possible that the interval
overflows/underflows the non-finite ranges, but does not
overflow/underflow the data type. For example
`SELECT INTERVAL '2147483646 months' + INTERVAL '1 month'` would error
on this check.
+ else + { + result->time = -interval->time; + result->day = -interval->day; + result->month = -interval->month; + + if (INTERVAL_NOT_FINITE(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range")));If this error ever gets to the user, it could be confusing. Can we
elaborate by
adding context e.g. errcontext("while negating an interval") or some
such?
Done.
-
- 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);Shouldn't we incorporate these checks in interval_um_internal()? I
don't think
INTERVAL_NOT_FINITE() covers all of those.
I replaced these checks with the following:
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN
|| interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
I think this covers the same overflow check but is maybe a bit more
obvious. Unless, there's something I'm missing?
+ /* + * 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. + */I think we need someone to validate these assumptions and similar
assumptions
in interval_pl(). Googling gives confusing results in some cases. I
have not
looked for IEEE standard around this specificallly.
I used to Python and Rust to check my assumptions on the IEEE standard:
Python:
float('inf') + float('inf')
inf
float('-inf') + float('inf')
nan
float('inf') + float('-inf')
nan
float('-inf') + float('-inf')
-inf
float('inf') - float('inf')
nan
float('-inf') - float('inf')
-inf
float('inf') - float('-inf')
inf
float('-inf') - float('-inf')
nan
Rust:
inf + inf = inf
-inf + inf = NaN
inf + -inf = NaN
-inf + -inf = -inf
inf - inf = NaN
-inf - inf = -inf
inf - -inf = inf
-inf - -inf = NaN
I'll try and look up the actual standard and see what it says.
+ if (INTERVAL_NOT_FINITE(interval)) + { + double r = NonFiniteIntervalPart(type, val, lowunits, +
INTERVAL_IS_NOBEGIN(interval),
+ false); + + if (r)I see that this code is very similar to the corresponding code in
timestamp and
timestamptz, so it's bound to be correct. But I always thought float
equality
is unreliable. if (r) is equivalent to if (r == 0.0) so it will not
work as
intended. But may be (float) 0.0 is a special value for which equality
holds
true.
I'm not familiar with float equality being unreliable, but I'm by no
means a C or float expert. Can you link me to some docs/explanation?
+static inline bool +pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)I think this needs a prologue similar to int64_multiply_add(), that
the patch
removes. Similarly for pg_mul_add_s32_overflow().
I've added this to the first patch.
Thanks for the review! Sorry for the delayed response.
- Joe Koshakow
Attachments:
0001-Move-integer-helper-function-to-int.h.patchtext/x-patch; charset=US-ASCII; name=0001-Move-integer-helper-function-to-int.h.patchDownload
From ba97dfc52e7b5b967feba9008019c00977f3948e Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 12:26:28 -0400
Subject: [PATCH 1/3] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index be2e55bb29..64f28a85b0 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.34.1
0002-Check-for-overflow-in-make_interval.patchtext/x-patch; charset=US-ASCII; name=0002-Check-for-overflow-in-make_interval.patchDownload
From 2952414253f3a76d7b2983b9eaf57de0c5237c0e Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 12:38:58 -0400
Subject: [PATCH 2/3] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 24 +++++++++++++++++++-----
src/include/common/int.h | 13 +++++++++++++
src/test/regress/expected/interval.out | 5 +++++
src/test/regress/sql/interval.sql | 4 ++++
4 files changed, 41 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index c266d0d02e..de80a7e5b5 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1501,13 +1501,27 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, 7, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ result->time = secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..27bfb8ba9b 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,11 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..f1abf08501 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,10 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.34.1
0003-Add-infinite-interval-values.patchtext/x-patch; charset=US-ASCII; name=0003-Add-infinite-interval-values.patchDownload
From a847277bd0ce4c8aa965685bd4a7af9ef86a6940 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/3] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 32 +
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 674 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 559 ++++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 170 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1451 insertions(+), 202 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 4df8bd1b64..df4cd287ce 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2316,7 +2316,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 15314aa3ee..4c74efc320 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9428,7 +9428,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10315,7 +10315,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index a163fbb4ab..5b4ba76eed 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2023,6 +2023,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2067,6 +2072,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2085,6 +2095,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2599,6 +2614,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2621,6 +2641,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3113,6 +3138,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 64f28a85b0..f77bf1d5eb 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3550,6 +3550,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/formatting.c b/src/backend/utils/adt/formatting.c
index e6246dc44b..97693a68ce 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0|| INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fe37e65af0..e2cc6049f2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index de80a7e5b5..b0a59425ef 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -79,6 +80,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -924,6 +926,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);
@@ -946,8 +956,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);
@@ -1333,6 +1348,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.
@@ -1506,6 +1528,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, 7, &result->day))
ereport(ERROR,
@@ -1523,6 +1546,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1540,6 +1568,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2027,6 +2066,8 @@ 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;
}
@@ -2044,6 +2085,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2077,7 +2120,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2208,6 +2253,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2703,46 +2771,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN(dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2772,6 +2865,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))
@@ -2824,6 +2920,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2847,6 +2948,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,
@@ -2885,6 +2989,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))
@@ -2903,6 +3010,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2923,7 +3035,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND(timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3002,9 +3137,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3029,7 +3162,30 @@ timestamptz_pl_interval(PG_FUNCTION_ARGS)
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND(timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3112,15 +3268,39 @@ timestamptz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamptz_pl_interval,
TimestampGetDatum(timestamp),
PointerGetDatum(&tspan));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while negating an interval")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while negating an interval")));
+ }
+}
Datum
interval_um(PG_FUNCTION_ARGS)
@@ -3129,23 +3309,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3189,27 +3353,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3223,27 +3420,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3268,6 +3500,24 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3328,6 +3578,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3359,6 +3614,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3380,6 +3658,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3667,8 +3950,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN(dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3787,8 +4097,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN(dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3913,6 +4250,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4096,6 +4438,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4337,6 +4684,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);
@@ -5195,6 +5548,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5219,6 +5625,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r)
+ {
+ 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);
@@ -5461,6 +5895,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5691,6 +6132,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5757,6 +6205,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5836,6 +6289,11 @@ generate_series_timestamptz(PG_FUNCTION_ARGS)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..5caf5dcfbe 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -167,6 +167,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edd59dc432..bcb54a7238 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -114,6 +114,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -123,6 +124,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..33842f9fbe 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1048,6 +1048,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1159,6 +1160,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1268,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,6 +1544,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1708,7 +1712,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 27bfb8ba9b..0adfe5f9fb 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1594,31 +1647,31 @@ select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.77
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
@@ -1697,19 +1750,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1783,7 +1838,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1792,3 +1849,383 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp '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: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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
+CONTEXT: while negating an interval
+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' / '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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 91d7c1f5cc..e6c81eb7d2 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
--
-- Test behavior with a dynamic (time-varying) timezone abbreviation.
-- These tests rely on the knowledge that MSK (Europe/Moscow standard time)
@@ -3100,3 +3104,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..1d0ab322c0 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -207,14 +207,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -287,11 +290,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index f1abf08501..1e1d8560bf 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -517,13 +524,13 @@ select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.77
-- 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;
@@ -586,3 +593,134 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp '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 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' / '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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index ae9ee4b56a..4545187af0 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
--
-- Test behavior with a dynamic (time-varying) timezone abbreviation.
@@ -600,3 +602,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
Joseph Koshakow <koshy44@gmail.com> writes:
On Thu, Mar 9, 2023 at 12:42 PM Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
wrote:There are a lot of these diffs. PG code doesn't leave an extra space
between variable name and *.
Those appeared from running pg_indent. I've removed them all.
More specifically, those are from running pg_indent with an obsolete
typedefs list. Good practice is to fetch an up-to-date list from
the buildfarm:
curl https://buildfarm.postgresql.org/cgi-bin/typedefs.pl -o .../typedefs.list
and use that. (If your patch adds any typedefs, you can then add them
to that list.) There's been talk of trying harder to keep
src/tools/pgindent/typedefs.list up to date, but not much has happened
yet.
I've separated this out into another patch attached to this email.
Should I start a new email thread or is it ok to include it in this
one?
Having separate threads with interdependent patches is generally a
bad idea :-( ... the cfbot certainly won't cope.
I see that this code is very similar to the corresponding code in
timestamp and
timestamptz, so it's bound to be correct. But I always thought float
equality
is unreliable. if (r) is equivalent to if (r == 0.0) so it will not
work as
intended. But may be (float) 0.0 is a special value for which equality
holds
true.
I'm not familiar with float equality being unreliable, but I'm by no
means a C or float expert. Can you link me to some docs/explanation?
The specific issue with float zero is that plus zero and minus zero
are distinct concepts with distinct bit patterns, but the IEEE spec
says that they compare as equal. The C standard says about "if":
[#1] The controlling expression of an if statement shall
have scalar type.
[#2] In both forms, the first substatement is executed if
the expression compares unequal to 0. In the else form, the
second substatement is executed if the expression compares
equal to 0.
so it sure looks to me like a float control expression is valid and
minus zero should be treated as "false". Nonetheless, personally
I'd consider this to be poor style and would write "r != 0" or
"r != 0.0" rather than depending on that.
BTW, this may already need a rebase over 75bd846b6.
regards, tom lane
On Sat, Mar 18, 2023 at 3:08 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Joseph Koshakow <koshy44@gmail.com> writes:
On Thu, Mar 9, 2023 at 12:42 PM Ashutosh Bapat <
ashutosh.bapat.oss@gmail.com>
wrote:
There are a lot of these diffs. PG code doesn't leave an extra space
between variable name and *.Those appeared from running pg_indent. I've removed them all.
More specifically, those are from running pg_indent with an obsolete
typedefs list. Good practice is to fetch an up-to-date list from
the buildfarm:curl https://buildfarm.postgresql.org/cgi-bin/typedefs.pl -o
.../typedefs.list
and use that. (If your patch adds any typedefs, you can then add them
to that list.) There's been talk of trying harder to keep
src/tools/pgindent/typedefs.list up to date, but not much has happened
yet.
I must be doing something wrong because even after doing that I get the
same strange formatting. Specifically from the root directory I ran
curl https://buildfarm.postgresql.org/cgi-bin/typedefs.pl -o
src/tools/pgindent/typedefs.list
src/tools/pgindent/pgindent src/backend/utils/adt/datetime.c
src/include/common/int.h src/backend/utils/adt/timestamp.c
src/backend/utils/adt/date.c src/backend/utils/adt/formatting.c
src/backend/utils/adt/selfuncs.c src/include/datatype/timestamp.h
src/include/utils/timestamp.h
The specific issue with float zero is that plus zero and minus zero
are distinct concepts with distinct bit patterns, but the IEEE spec
says that they compare as equal. The C standard says about "if":[#1] The controlling expression of an if statement shall
have scalar type.
[#2] In both forms, the first substatement is executed if
the expression compares unequal to 0. In the else form, the
second substatement is executed if the expression compares
equal to 0.so it sure looks to me like a float control expression is valid and
minus zero should be treated as "false". Nonetheless, personally
I'd consider this to be poor style and would write "r != 0" or
"r != 0.0" rather than depending on that.
Thanks for the info, I've updated the three instances of the check to
be "r != 0.0"
BTW, this may already need a rebase over 75bd846b6.
The patches in this email should be rebased over master.
- Joe Koshakow
Attachments:
v1-0002-Check-for-overflow-in-make_interval.patchtext/x-patch; charset=US-ASCII; name=v1-0002-Check-for-overflow-in-make_interval.patchDownload
From da22f9b3d55433c408f04056eecf0fddf60f01c9 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 12:38:58 -0400
Subject: [PATCH 2/3] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 24 +++++++++++++++++++-----
src/include/common/int.h | 13 +++++++++++++
src/test/regress/expected/interval.out | 5 +++++
src/test/regress/sql/interval.sql | 4 ++++
4 files changed, 41 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index aaadc68ae6..b79af28ae3 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1517,13 +1517,27 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, 7, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ result->time = secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..27bfb8ba9b 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,11 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..f1abf08501 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,10 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.34.1
v15-0003-Add-infinite-interval-values.patchtext/x-patch; charset=US-ASCII; name=v15-0003-Add-infinite-interval-values.patchDownload
From e6edf1cf90d315918688a66374a76f48f806da94 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/3] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 32 +
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 679 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 559 ++++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 170 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1454 insertions(+), 204 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 4df8bd1b64..df4cd287ce 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2316,7 +2316,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a3a13b895f..33fa3e6670 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9472,7 +9472,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10369,7 +10369,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index a163fbb4ab..5b4ba76eed 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2023,6 +2023,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2067,6 +2072,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2085,6 +2095,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2599,6 +2614,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2621,6 +2641,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3113,6 +3138,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 64f28a85b0..f77bf1d5eb 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3550,6 +3550,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/formatting.c b/src/backend/utils/adt/formatting.c
index e6246dc44b..8c739833dc 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fe37e65af0..e2cc6049f2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index b79af28ae3..5f5b621e51 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -940,6 +942,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);
@@ -962,8 +972,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);
@@ -1349,6 +1364,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.
@@ -1522,6 +1544,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, 7, &result->day))
ereport(ERROR,
@@ -1539,6 +1562,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1556,6 +1584,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2043,6 +2082,8 @@ 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;
}
@@ -2060,6 +2101,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2093,7 +2136,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2224,6 +2269,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2719,46 +2787,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN(dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2788,6 +2881,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))
@@ -2840,6 +2936,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2863,6 +2964,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,
@@ -2901,6 +3005,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))
@@ -2919,6 +3026,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2939,7 +3051,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND(timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3018,9 +3153,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3047,7 +3180,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND(timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3137,9 +3293,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3190,6 +3344,33 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while negating an interval")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while negating an interval")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3197,23 +3378,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3257,27 +3422,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3291,27 +3489,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3336,6 +3569,24 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3396,6 +3647,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3427,6 +3683,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3448,6 +3727,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3731,8 +4015,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN(dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3851,8 +4162,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if TIMESTAMP_IS_NOBEGIN(dt2)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3977,6 +4315,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4160,6 +4503,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4398,6 +4746,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);
@@ -4742,7 +5096,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5016,7 +5370,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5256,6 +5610,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5280,6 +5687,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5522,6 +5957,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5752,6 +6194,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5818,6 +6267,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5900,6 +6354,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..5caf5dcfbe 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -167,6 +167,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edd59dc432..bcb54a7238 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -114,6 +114,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -123,6 +124,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..33842f9fbe 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1048,6 +1048,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1159,6 +1160,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1268,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,6 +1544,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1708,7 +1712,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 27bfb8ba9b..0adfe5f9fb 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1594,31 +1647,31 @@ select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.77
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
@@ -1697,19 +1750,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1783,7 +1838,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1792,3 +1849,383 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp '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: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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
+CONTEXT: while negating an interval
+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' / '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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..1d0ab322c0 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -207,14 +207,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -287,11 +290,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index f1abf08501..1e1d8560bf 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -517,13 +524,13 @@ select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.77
-- 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;
@@ -586,3 +593,134 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp '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 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' / '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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
v1-0001-Move-integer-helper-function-to-int.h.patchtext/x-patch; charset=US-ASCII; name=v1-0001-Move-integer-helper-function-to-int.h.patchDownload
From 565687d49f7a5042726da1312ddbb9b1571049b9 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 12:26:28 -0400
Subject: [PATCH 1/3] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index be2e55bb29..64f28a85b0 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.34.1
Joseph Koshakow <koshy44@gmail.com> writes:
On Sat, Mar 18, 2023 at 3:08 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
More specifically, those are from running pg_indent with an obsolete
typedefs list.
I must be doing something wrong because even after doing that I get the
same strange formatting. Specifically from the root directory I ran
Hmm, I dunno what's going on there. When I do this:
curl https://buildfarm.postgresql.org/cgi-bin/typedefs.pl -o
src/tools/pgindent/typedefs.list
I end up with a plausible set of updates, notably
$ git diff
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 097f42e1b3..667f8e13ed 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
...
@@ -545,10 +548,12 @@ DataDumperPtr
DataPageDeleteStack
DatabaseInfo
DateADT
+DateTimeErrorExtra
Datum
DatumTupleFields
DbInfo
DbInfoArr
+DbLocaleInfo
DeClonePtrType
DeadLockState
DeallocateStmt
so it sure ought to know DateTimeErrorExtra is a typedef.
I then tried pgindent'ing datetime.c and timestamp.c,
and it did not want to change either file. I do get
diffs like
DecodeDateTime(char **field, int *ftype, int nf,
int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp,
- DateTimeErrorExtra *extra)
+ DateTimeErrorExtra * extra)
{
int fmask = 0,
if I try to pgindent datetime.c with typedefs.list as it
stands in HEAD. That's pretty much pgindent's normal
behavior when it doesn't recognize a name as a typedef.
regards, tom lane
On Sat, Mar 18, 2023 at 3:55 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Joseph Koshakow <koshy44@gmail.com> writes:
On Sat, Mar 18, 2023 at 3:08 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
More specifically, those are from running pg_indent with an obsolete
typedefs list.I must be doing something wrong because even after doing that I get
the
same strange formatting. Specifically from the root directory I ran
Hmm, I dunno what's going on there. When I do this:
curl https://buildfarm.postgresql.org/cgi-bin/typedefs.pl -o
src/tools/pgindent/typedefs.listI end up with a plausible set of updates, notably
$ git diff
diff --git a/src/tools/pgindent/typedefs.list
b/src/tools/pgindent/typedefs.list
index 097f42e1b3..667f8e13ed 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list ... @@ -545,10 +548,12 @@ DataDumperPtr DataPageDeleteStack DatabaseInfo DateADT +DateTimeErrorExtra Datum DatumTupleFields DbInfo DbInfoArr +DbLocaleInfo DeClonePtrType DeadLockState DeallocateStmtso it sure ought to know DateTimeErrorExtra is a typedef.
I then tried pgindent'ing datetime.c and timestamp.c,
and it did not want to change either file. I do get
diffs like
DecodeDateTime(char **field, int *ftype, int nf, int *dtype, struct pg_tm *tm, fsec_t *fsec, int *tzp, - DateTimeErrorExtra *extra) + DateTimeErrorExtra * extra) { int fmask = 0,if I try to pgindent datetime.c with typedefs.list as it
stands in HEAD. That's pretty much pgindent's normal
behavior when it doesn't recognize a name as a typedef.
I must have been doing something wrong because I tried again today and
it worked fine. However, I go get a lot of changes like the following:
- if TIMESTAMP_IS_NOBEGIN(dt2)
- ereport(ERROR,
-
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of
range")));
+ if TIMESTAMP_IS_NOBEGIN
+ (dt2)
+ ereport(ERROR,
+
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of
range")));
Should I keep these pgindent changes or keep it the way I have it?
- Joe Koshakow
Joseph Koshakow <koshy44@gmail.com> writes:
I must have been doing something wrong because I tried again today and
it worked fine. However, I go get a lot of changes like the following:
- if TIMESTAMP_IS_NOBEGIN(dt2) - ereport(ERROR, - (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), - errmsg("timestamp out of range"))); + if TIMESTAMP_IS_NOBEGIN + (dt2) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("timestamp out of range")));
Should I keep these pgindent changes or keep it the way I have it?
Did you actually write "if TIMESTAMP_IS_NOBEGIN(dt2)" and not
"if (TIMESTAMP_IS_NOBEGIN(dt2))"? If the former, I'm not surprised
that pgindent gets confused. The parentheses are required by the
C standard. Your code might accidentally work because the macro
has parentheses internally, but call sites have no business
knowing that. For example, it would be completely legit to change
TIMESTAMP_IS_NOBEGIN to be a plain function, and then this would be
syntactically incorrect.
regards, tom lane
On Sun, Mar 19, 2023 at 5:13 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Did you actually write "if TIMESTAMP_IS_NOBEGIN(dt2)" and not
"if (TIMESTAMP_IS_NOBEGIN(dt2))"? If the former, I'm not surprised
that pgindent gets confused. The parentheses are required by the
C standard. Your code might accidentally work because the macro
has parentheses internally, but call sites have no business
knowing that. For example, it would be completely legit to change
TIMESTAMP_IS_NOBEGIN to be a plain function, and then this would be
syntactically incorrect.
Oh duh. I've been doing too much Rust development and did this without
thinking. I've attached a patch with a fix.
- Joe Koshakow
Attachments:
v16-0003-Add-infinite-interval-values.patchtext/x-patch; charset=US-ASCII; name=v16-0003-Add-infinite-interval-values.patchDownload
From d3543e7c410f83cbe3f3f3df9715025bc767fc5f Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/3] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 32 +
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 679 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 559 ++++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 170 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1454 insertions(+), 204 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index faf0d74104..694af4000d 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2321,7 +2321,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a3a13b895f..33fa3e6670 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9472,7 +9472,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10369,7 +10369,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index a163fbb4ab..5b4ba76eed 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2023,6 +2023,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2067,6 +2072,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2085,6 +2095,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2599,6 +2614,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2621,6 +2641,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3113,6 +3138,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 64f28a85b0..f77bf1d5eb 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3550,6 +3550,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/formatting.c b/src/backend/utils/adt/formatting.c
index e6246dc44b..8c739833dc 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fe37e65af0..e2cc6049f2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index b79af28ae3..c006e27dc0 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -940,6 +942,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);
@@ -962,8 +972,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);
@@ -1349,6 +1364,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.
@@ -1522,6 +1544,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, 7, &result->day))
ereport(ERROR,
@@ -1539,6 +1562,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1556,6 +1584,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2043,6 +2082,8 @@ 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;
}
@@ -2060,6 +2101,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2093,7 +2136,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2224,6 +2269,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2719,46 +2787,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2788,6 +2881,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))
@@ -2840,6 +2936,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2863,6 +2964,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,
@@ -2901,6 +3005,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))
@@ -2919,6 +3026,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2939,7 +3051,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3018,9 +3153,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3047,7 +3180,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3137,9 +3293,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3190,6 +3344,33 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while negating an interval")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while negating an interval")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3197,23 +3378,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3257,27 +3422,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3291,27 +3489,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3336,6 +3569,24 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3396,6 +3647,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3427,6 +3683,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3448,6 +3727,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3731,8 +4015,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3851,8 +4162,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3977,6 +4315,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4160,6 +4503,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4398,6 +4746,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);
@@ -4742,7 +5096,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5016,7 +5370,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5256,6 +5610,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5280,6 +5687,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5522,6 +5957,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5752,6 +6194,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5818,6 +6267,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5900,6 +6354,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..5caf5dcfbe 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -167,6 +167,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edd59dc432..bcb54a7238 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -114,6 +114,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -123,6 +124,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..33842f9fbe 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1048,6 +1048,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1159,6 +1160,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1268,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,6 +1544,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1708,7 +1712,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 27bfb8ba9b..0adfe5f9fb 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1594,31 +1647,31 @@ select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.77
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
@@ -1697,19 +1750,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1783,7 +1838,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1792,3 +1849,383 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp '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: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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
+CONTEXT: while negating an interval
+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' / '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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..1d0ab322c0 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -207,14 +207,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -287,11 +290,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index f1abf08501..1e1d8560bf 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -517,13 +524,13 @@ select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.77
-- 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;
@@ -586,3 +593,134 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp '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 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' / '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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
On Sun, Mar 19, 2023 at 1:04 AM Joseph Koshakow <koshy44@gmail.com> wrote:
The patches in this email should be rebased over master.
Reviewed 0001 -
Looks good to me. The new function is properly placed along with other
signed 64 bit functions. All existing calls to int64_multiply_add()
have been replaced with the new function and negated the result.
Reviewed 0002
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, 7, &result->day))
You don't need to do this, but looks like we can add DAYS_PER_WEEK macro and
use it here.
The first two patches look good to me; ready for a committer. Can be
committed independent of the third patch.
Will look at the third patch soon.
--
Best Wishes,
Ashutosh Bapat
On Fri, Mar 24, 2023 at 9:43 AM Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
wrote:
You don't need to do this, but looks like we can add DAYS_PER_WEEK
macro and
use it here.
I've attached a patch with this new macro. There's probably tons of
places it can be used instead of hardcoding the number 7, but I'll save
that for a future patch.
- Joe Koshakow
Attachments:
v2-0001-Move-integer-helper-function-to-int.h.patchtext/x-patch; charset=US-ASCII; name=v2-0001-Move-integer-helper-function-to-int.h.patchDownload
From 41fa5de65c757d72331aff6bb626fab76390e9db Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 12:26:28 -0400
Subject: [PATCH 1/2] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index be2e55bb29..64f28a85b0 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.34.1
v2-0002-Check-for-overflow-in-make_interval.patchtext/x-patch; charset=US-ASCII; name=v2-0002-Check-for-overflow-in-make_interval.patchDownload
From 242ffd232bef606c9c948f0ee9980152fb9e3bec Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 12:38:58 -0400
Subject: [PATCH 2/2] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 24 +++++++++++++++++++-----
src/include/common/int.h | 13 +++++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 5 +++++
src/test/regress/sql/interval.sql | 4 ++++
5 files changed, 42 insertions(+), 5 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index aaadc68ae6..ccf0019a3c 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1517,13 +1517,27 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ result->time = secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..27bfb8ba9b 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,11 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..f1abf08501 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,10 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.34.1
In terms of adding/subtracting infinities, the IEEE standard is pay
walled and I don't have a copy. I tried finding information online but
I also wasn't able to find anything useful. I additionally checked to see
the results of C++, C, and Java, and they all match which increases my
confidence that we're doing the right thing. Does anyone happen to have
a copy of the standard and can confirm?
- Joe Koshakow
Joseph Koshakow <koshy44@gmail.com> writes:
In terms of adding/subtracting infinities, the IEEE standard is pay
walled and I don't have a copy. I tried finding information online but
I also wasn't able to find anything useful. I additionally checked to see
the results of C++, C, and Java, and they all match which increases my
confidence that we're doing the right thing. Does anyone happen to have
a copy of the standard and can confirm?
I think you can take it as read that simple C test programs on modern
platforms will exhibit IEEE-compliant handling of float infinities.
regards, tom lane
On Sat, 25 Mar 2023 at 15:59, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Joseph Koshakow <koshy44@gmail.com> writes:
In terms of adding/subtracting infinities, the IEEE standard is pay
walled and I don't have a copy. I tried finding information online but
I also wasn't able to find anything useful. I additionally checked to see
the results of C++, C, and Java, and they all match which increases my
confidence that we're doing the right thing. Does anyone happen to have
a copy of the standard and can confirm?I think you can take it as read that simple C test programs on modern
platforms will exhibit IEEE-compliant handling of float infinities.
Additionally, the Java language specification claims to follow IEEE 754:
https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.18.2
So either C and Java agree with each other and with the spec, or they
disagree in the same way even while at least one of them explicitly claims
to be following the spec. I think you're on pretty firm ground.
On Sat, Mar 25, 2023 at 9:13 PM Joseph Koshakow <koshy44@gmail.com> wrote:
On Fri, Mar 24, 2023 at 9:43 AM Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> wrote:
You don't need to do this, but looks like we can add DAYS_PER_WEEK macro and
use it here.I've attached a patch with this new macro. There's probably tons of
places it can be used instead of hardcoding the number 7, but I'll save
that for a future patch.
Thanks. Yes, changing other existing usages is out of scope for this patch.
Looks good to me.
--
Best Wishes,
Ashutosh Bapat
On Sun, Mar 19, 2023 at 12:18 AM Joseph Koshakow <koshy44@gmail.com> wrote:
The problem is that `secs = rint(secs)` rounds the seconds too early
and loses any fractional seconds. Do we have an overflow detecting
multiplication function for floats?
We have float8_mul() which checks for overflow. typedef double float8;
+ if (INTERVAL_NOT_FINITE(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range")));Probably, I added these kind of checks. But I don't remember if those are
defensive checks or whether it's really possible that the arithmetic above
these lines can yield an non-finite interval.These checks appear in `make_interval`, `justify_X`,
`interval_um_internal`, `interval_pl`, `interval_mi`, `interval_mul`,
`interval_div`. For all of these it's possible that the interval
overflows/underflows the non-finite ranges, but does not
overflow/underflow the data type. For example
`SELECT INTERVAL '2147483646 months' + INTERVAL '1 month'` would error
on this check.
Without this patch
postgres@64807=#SELECT INTERVAL '2147483646 months' + INTERVAL '1 month';
?column?
------------------------
178956970 years 7 mons
(1 row)
That result looks correct
postgres@64807=#select 178956970 * 12 + 7;
?column?
------------
2147483647
(1 row)
So some backward compatibility break. I don't think we can avoid the
backward compatibility break without expanding interval structure and
thus causing on-disk breakage. But we can reduce the chances of
breaking, if we change INTERVAL_NOT_FINITE to check all the three
fields, instead of just month.
+ else + { + result->time = -interval->time; + result->day = -interval->day; + result->month = -interval->month; + + if (INTERVAL_NOT_FINITE(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range")));If this error ever gets to the user, it could be confusing. Can we elaborate by
adding context e.g. errcontext("while negating an interval") or some such?Done.
Thanks. Can we add relevant contexts at similar other places?
Also if we use all the three fields, we will need to add such checks
in interval_justify_hours()
I replaced these checks with the following:
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range")));I think this covers the same overflow check but is maybe a bit more
obvious. Unless, there's something I'm missing?
Thanks. Your current version is closer to int4um().
Some more review comments in the following email.
--
Best Wishes,
Ashutosh Bapat
On Sun, Mar 26, 2023 at 1:28 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
I think you can take it as read that simple C test programs on modern
platforms will exhibit IEEE-compliant handling of float infinities.
For the record, I tried the attached. It gives a warning at compilation time.
$gcc float_inf.c
float_inf.c: In function ‘main’:
float_inf.c:10:17: warning: division by zero [-Wdiv-by-zero]
10 | float inf = 1.0/0;
| ^
float_inf.c:11:20: warning: division by zero [-Wdiv-by-zero]
11 | float n_inf = -1.0/0;
| ^
$ ./a.out
inf = inf
-inf = -inf
inf + inf = inf
inf + -inf = -nan
-inf + inf = -nan
-inf + -inf = -inf
inf - inf = -nan
inf - -inf = inf
-inf - inf = -inf
-inf - -inf = -nan
float 0.0 equals 0.0
float 1.0 equals 1.0
5.0 * inf = inf
5.0 * - inf = -inf
5.0 / inf = 0.000000
5.0 / - inf = -0.000000
inf / 5.0 = inf
- inf / 5.0 = -inf
The changes in the patch are compliant with the observations above.
--
Best Wishes,
Ashutosh Bapat
Attachments:
On Mon, Mar 20, 2023 at 3:16 AM Joseph Koshakow <koshy44@gmail.com> wrote:
On Sun, Mar 19, 2023 at 5:13 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Did you actually write "if TIMESTAMP_IS_NOBEGIN(dt2)" and not
"if (TIMESTAMP_IS_NOBEGIN(dt2))"? If the former, I'm not surprised
that pgindent gets confused. The parentheses are required by the
C standard. Your code might accidentally work because the macro
has parentheses internally, but call sites have no business
knowing that. For example, it would be completely legit to change
TIMESTAMP_IS_NOBEGIN to be a plain function, and then this would be
syntactically incorrect.Oh duh. I've been doing too much Rust development and did this without
thinking. I've attached a patch with a fix.
Thanks for fixing this.
On this latest patch, I have one code comment
@@ -3047,7 +3180,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if TIMESTAMP_IS_NOEND(timestamp)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
This code is duplicated in timestamp_pl_interval(). We could create a function
to encode the infinity handling rules and then call it in these two places. The
argument types are different, Timestamp and TimestampTz viz. which map to in64,
so shouldn't be a problem. But it will be slightly unreadable. Or use macros
but then it will be difficult to debug.
What do you think?
Next I will review the test changes and also make sure that every
operator that interval as one of its operands or the result has been
covered in the code. This is the following list
#select oprname, oprcode from pg_operator where oprleft =
'interval'::regtype or oprright = 'interval'::regtype or oprresult =
'interval'::regtype;
oprname | oprcode
---------+-------------------------
+ | date_pl_interval
- | date_mi_interval
+ | timestamptz_pl_interval
- | timestamptz_mi
- | timestamptz_mi_interval
= | interval_eq
<> | interval_ne
< | interval_lt
<= | interval_le
| interval_gt
= | interval_ge
- | interval_um
+ | interval_pl
- | interval_mi
- | time_mi_time
* | interval_mul
* | mul_d_interval
/ | interval_div
+ | time_pl_interval
- | time_mi_interval
+ | timetz_pl_interval
- | timetz_mi_interval
+ | interval_pl_time
+ | timestamp_pl_interval
- | timestamp_mi
- | timestamp_mi_interval
+ | interval_pl_date
+ | interval_pl_timetz
+ | interval_pl_timestamp
+ | interval_pl_timestamptz
(30 rows)
--
Best Wishes,
Ashutosh Bapat
On Tue, Mar 28, 2023 at 7:17 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
make sure that every
operator that interval as one of its operands or the result has been
covered in the code.
time_mi_time - do we want to add an Assert to make sure that this
function does not produce an Interval structure which looks like
non-finite interval?
multiplying an interval by infinity throws an error
#select '5 days'::interval * 'infinity'::float8;
2023-03-29 19:40:15.797 IST [136240] ERROR: interval out of range
2023-03-29 19:40:15.797 IST [136240] STATEMENT: select '5
days'::interval * 'infinity'::float8;
ERROR: interval out of range
I think this should produce an infinite interval now. Attached patch
to fix this, to be applied on top of your patch. With the patch
#select '5 days'::interval * 'infinity'::float8;
?column?
----------
infinity
(1 row)
Going through the tests now.
--
Best Wishes,
Ashutosh Bapat
Attachments:
interval_mul_inf.patchtext/x-patch; charset=US-ASCII; name=interval_mul_inf.patchDownload
commit e345924db6e6b531f98124159731560a38eae7d0
Author: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Fri Mar 31 15:41:25 2023 +0530
fixup! Add infinite interval values
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index c006e27dc0..79842d3e0d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -3566,6 +3566,7 @@ interval_mul(PG_FUNCTION_ARGS)
int32 orig_month = span->month,
orig_day = span->day;
Interval *result;
+ int is_factor_inf = isinf(factor);
result = (Interval *) palloc(sizeof(Interval));
@@ -3574,6 +3575,15 @@ interval_mul(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (is_factor_inf != 0)
+ {
+ if (is_factor_inf < 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ INTERVAL_NOEND(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+
/*
* Multiplying infinite interval by finite number keeps it infinite but
* may change the sign.
I hurried too much on the previous patch. It introduced other
problems. Attached is a better patch and also fixes problem below
#select 'infinity'::interval * 0;
?column?
----------
infinity
(1 row)
with the patch we see
#select 'infinity'::interval * 0;
2023-03-31 18:00:43.131 IST [240892] ERROR: interval out of range
2023-03-31 18:00:43.131 IST [240892] STATEMENT: select
'infinity'::interval * 0;
ERROR: interval out of range
which looks more appropriate given 0 * inf = Nan for float.
There's some way to avoid separate checks for infinite-ness of
interval and factor and use a single block using some integer
arithmetic. But I think this is more readable. So I avoided doing
that. Let me know if this works for you.
Also added some test cases.
--
Best Wishes,
Ashutosh Bapat
On Fri, Mar 31, 2023 at 3:46 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
Show quoted text
On Tue, Mar 28, 2023 at 7:17 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:make sure that every
operator that interval as one of its operands or the result has been
covered in the code.time_mi_time - do we want to add an Assert to make sure that this
function does not produce an Interval structure which looks like
non-finite interval?multiplying an interval by infinity throws an error
#select '5 days'::interval * 'infinity'::float8;
2023-03-29 19:40:15.797 IST [136240] ERROR: interval out of range
2023-03-29 19:40:15.797 IST [136240] STATEMENT: select '5
days'::interval * 'infinity'::float8;
ERROR: interval out of rangeI think this should produce an infinite interval now. Attached patch
to fix this, to be applied on top of your patch. With the patch
#select '5 days'::interval * 'infinity'::float8;
?column?
----------
infinity
(1 row)Going through the tests now.
--
Best Wishes,
Ashutosh Bapat
Attachments:
interval_mul_fixes.patchtext/x-patch; charset=US-ASCII; name=interval_mul_fixes.patchDownload
commit 29d8501bb0e1b727abc81e862185770fd8e3a6c9
Author: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Fri Mar 31 18:02:25 2023 +0530
fixup! Add infinite interval values
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index c006e27dc0..cd05c61de3 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -3566,6 +3566,7 @@ interval_mul(PG_FUNCTION_ARGS)
int32 orig_month = span->month,
orig_day = span->day;
Interval *result;
+ int is_factor_inf = isinf(factor);
result = (Interval *) palloc(sizeof(Interval));
@@ -3580,12 +3581,35 @@ interval_mul(PG_FUNCTION_ARGS)
*/
if (INTERVAL_NOT_FINITE(span))
{
- if (factor < 0.0)
+ 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);
}
+ else if (is_factor_inf)
+ {
+ Interval zero;
+ int result_is_inf;
+
+ memset(&zero, 0, sizeof(zero));
+ result_is_inf = interval_cmp_internal(span, &zero) * is_factor_inf;
+
+ if (result_is_inf == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_is_inf < 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ INTERVAL_NOEND(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
result_double = span->month * factor;
if (isnan(result_double) ||
The problem is that `secs = rint(secs)` rounds the seconds too early
and loses any fractional seconds. Do we have an overflow detecting
multiplication function for floats?We have float8_mul() which checks for overflow. typedef double float8;
I've updated patch 2 to use this. I also realized that the implicit
cast from double to int64 can also result in an overflow. For example,
even after adding float8_mul() we can see this:
SELECT make_interval(0, 0, 0, 0, 0,
0,17976931348623157000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000);
make_interval
--------------------------
-2562047788:00:54.775808
(1 row)
So I added a check for FLOAT8_FITS_IN_INT64() and a test with this
scenario.
Without this patch
postgres@64807=#SELECT INTERVAL '2147483646 months' + INTERVAL '1 month';
?column?
------------------------
178956970 years 7 mons
(1 row)That result looks correct
postgres@64807=#select 178956970 * 12 + 7;
?column?
------------
2147483647(1 row)
So some backward compatibility break. I don't think we can avoid the
backward compatibility break without expanding interval structure and
thus causing on-disk breakage. But we can reduce the chances of
breaking, if we change INTERVAL_NOT_FINITE to check all the three
fields, instead of just month.
For what it's worth I think that 2147483647 months only became a valid
interval in v15 as part of this commit [0]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=e39f9904671082c5ad3a2c5acbdbd028fa93bf35. It's also outside of the
documented valid range [1]https://www.postgresql.org/docs/15/datatype-datetime.html, which is
[-178000000 years, 178000000 years] or
[-14833333 months, 14833333 months].
The rationale for only checking the month's field is that it's faster
than checking all three fields, though I'm not entirely sure if it's
the right trade-off. Any thoughts on this?
+ else + { + result->time = -interval->time; + result->day = -interval->day; + result->month = -interval->month; + + if (INTERVAL_NOT_FINITE(result)) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval out of range")));If this error ever gets to the user, it could be confusing. Can we
elaborate by
adding context e.g. errcontext("while negating an interval") or
some such?
Done.
Thanks. Can we add relevant contexts at similar other places?
I've added an errcontext to all the errors of the form "X out of
range". My one concern is that some of the messages can be slightly
confusing. For example date arithmetic is converted to timestamp
arithmetic, so the errcontext talks about timestamps even though the
actual operation used dates. For example,
SELECT date 'infinity' + interval '-infinity';
ERROR: interval out of range
CONTEXT: while adding an interval and timestamp
Also if we use all the three fields, we will need to add such checks
in interval_justify_hours()
I added these for now because even if we stick to just using the month
field, it will be good future proofing.
@@ -3047,7 +3180,30 @@ timestamptz_pl_interval_internal(TimestampTz
timestamp,
TimestampTz result;
int tz;- 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. + */ + if (INTERVAL_IS_NOBEGIN(span)) + { + if TIMESTAMP_IS_NOEND(timestamp) + ereport(ERROR, + (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), + errmsg("interval 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("interval out of range"))); + else + TIMESTAMP_NOEND(result); + } + else if (TIMESTAMP_NOT_FINITE(timestamp))This code is duplicated in timestamp_pl_interval(). We could create > a
function
to encode the infinity handling rules and then call it in these two >
places. The
argument types are different, Timestamp and TimestampTz viz. which map to
in64,
so shouldn't be a problem. But it will be slightly unreadable. Or use
macros
but then it will be difficult to debug.
What do you think?
I was hoping that I could come up with a macro that we could re-use for
all the similar logic. If that doesn't work then I'll try the helper
functions. I'll update the patch in a follow-up email to give myself some
time to think about this.
time_mi_time - do we want to add an Assert to make sure that this
function does not produce an Interval structure which looks like
non-finite interval?
Since the month and day field of the interval result is hard-coded as
0, it's not possible to produce a non-finite interval result, but I
don't think it would hurt. I've added an assert to the end.
There's some way to avoid separate checks for infinite-ness of
interval and factor and use a single block using some integer
arithmetic. But I think this is more readable. So I avoided doing
that. Let me know if this works for you.
I think the patch looks good, I've combined it with the existing patch.
Also added some test cases.
I didn't see any tests in the patch, did you forget to include it?
I've attached the updated patches. I've also rebased them against main.
Thanks,
Joe Koshakow
[0]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=e39f9904671082c5ad3a2c5acbdbd028fa93bf35
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=e39f9904671082c5ad3a2c5acbdbd028fa93bf35
[1]: https://www.postgresql.org/docs/15/datatype-datetime.html
On Fri, Mar 31, 2023 at 8:46 AM Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
wrote:
Show quoted text
I hurried too much on the previous patch. It introduced other
problems. Attached is a better patch and also fixes problem below
#select 'infinity'::interval * 0;
?column?
----------
infinity
(1 row)with the patch we see
#select 'infinity'::interval * 0;
2023-03-31 18:00:43.131 IST [240892] ERROR: interval out of range
2023-03-31 18:00:43.131 IST [240892] STATEMENT: select
'infinity'::interval * 0;
ERROR: interval out of rangewhich looks more appropriate given 0 * inf = Nan for float.
There's some way to avoid separate checks for infinite-ness of
interval and factor and use a single block using some integer
arithmetic. But I think this is more readable. So I avoided doing
that. Let me know if this works for you.Also added some test cases.
--
Best Wishes,
Ashutosh BapatOn Fri, Mar 31, 2023 at 3:46 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:On Tue, Mar 28, 2023 at 7:17 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:make sure that every
operator that interval as one of its operands or the result has been
covered in the code.time_mi_time - do we want to add an Assert to make sure that this
function does not produce an Interval structure which looks like
non-finite interval?multiplying an interval by infinity throws an error
#select '5 days'::interval * 'infinity'::float8;
2023-03-29 19:40:15.797 IST [136240] ERROR: interval out of range
2023-03-29 19:40:15.797 IST [136240] STATEMENT: select '5
days'::interval * 'infinity'::float8;
ERROR: interval out of rangeI think this should produce an infinite interval now. Attached patch
to fix this, to be applied on top of your patch. With the patch
#select '5 days'::interval * 'infinity'::float8;
?column?
----------
infinity
(1 row)Going through the tests now.
--
Best Wishes,
Ashutosh Bapat
Attachments:
v3-0002-Check-for-overflow-in-make_interval.patchtext/x-patch; charset=US-ASCII; name=v3-0002-Check-for-overflow-in-make_interval.patchDownload
From 765aa1ebf9de5e5d48e1c588f7bde70074374979 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/3] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index aaadc68ae6..5c25e12795 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1517,13 +1517,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..db5c7b83ba 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..434fd87a35 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.34.1
v3-0001-Move-integer-helper-function-to-int.h.patchtext/x-patch; charset=US-ASCII; name=v3-0001-Move-integer-helper-function-to-int.h.patchDownload
From f6bf9c201a94a0b338dff520442ac5e8d2922c89 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/3] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index be2e55bb29..64f28a85b0 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.34.1
v17-0003-Add-infinite-interval-values.patchtext/x-patch; charset=US-ASCII; name=v17-0003-Add-infinite-interval-values.patchDownload
From 4947a39a755a93ddd3fd5c19610f91594af4ee5a Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/3] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 35 +
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 821 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 606 ++++++++++++++--
src/test/regress/expected/timestamp.out | 67 ++
src/test/regress/expected/timestamptz.out | 67 ++
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 170 ++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1629 insertions(+), 231 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index f6b867f344..2b454e7fbc 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2321,7 +2321,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 918a492234..13f7033572 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9472,7 +9472,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10369,7 +10369,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index a163fbb4ab..cea2590ba9 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2023,6 +2023,12 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range"),
+ errcontext("while converting interval to time")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2054,6 +2060,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2067,6 +2075,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2085,6 +2098,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2599,6 +2617,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2621,6 +2644,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3113,6 +3141,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 64f28a85b0..f77bf1d5eb 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3550,6 +3550,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/formatting.c b/src/backend/utils/adt/formatting.c
index e6246dc44b..8c739833dc 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fe37e65af0..e2cc6049f2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 5c25e12795..78133dfb17 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -940,6 +942,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);
@@ -962,8 +972,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);
@@ -1349,6 +1364,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.
@@ -1514,34 +1536,47 @@ make_interval(PG_FUNCTION_ARGS)
if (isinf(secs) || isnan(secs))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while making an interval")));
result = (Interval *) palloc(sizeof(Interval));
result->month = months;
if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while making an interval")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while making an interval")));
secs = rint(float8_mul(secs, USECS_PER_SEC));
if (!FLOAT8_FITS_IN_INT64(secs))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while making an interval")));
result->time = (int64) secs;
if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while making an interval")));
if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while making an interval")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while making an interval")));
PG_RETURN_INTERVAL_P(result);
}
@@ -1560,6 +1595,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2047,6 +2093,8 @@ 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;
}
@@ -2064,6 +2112,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2097,7 +2147,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2228,6 +2280,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2723,46 +2798,74 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range"),
+ errcontext("while subtracting timestamps")));
+ 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("timestamp out of range"),
+ errcontext("while subtracting timestamps")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else
+ {
+ if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while subtracting timestamps")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2792,6 +2895,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))
@@ -2801,7 +2907,8 @@ interval_justify_interval(PG_FUNCTION_ARGS)
if (pg_add_s32_overflow(result->month, wholemonth, &result->month))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while justifying an interval")));
}
/*
@@ -2818,7 +2925,8 @@ interval_justify_interval(PG_FUNCTION_ARGS)
if (pg_add_s32_overflow(result->month, wholemonth, &result->month))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while justifying an interval")));
if (result->month > 0 &&
(result->day < 0 || (result->day == 0 && result->time < 0)))
@@ -2844,6 +2952,12 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while justifying an interval")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2867,11 +2981,15 @@ 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,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while justifying an interval hours")));
if (result->day > 0 && result->time < 0)
{
@@ -2884,6 +3002,12 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while justifying an interval hours")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2905,12 +3029,16 @@ 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))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while justifying an interval days")));
if (result->month > 0 && result->day < 0)
{
@@ -2923,6 +3051,12 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while justifying an interval days")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2943,7 +3077,32 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while adding an interval and timestamp")));
+ 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("interval out of range"),
+ errcontext("while adding an interval and timestamp")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -2956,7 +3115,8 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("timestamp out of range"),
+ errcontext("while adding an interval and timestamp")));
tm->tm_mon += span->month;
if (tm->tm_mon > MONTHS_PER_YEAR)
@@ -2977,7 +3137,8 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
if (tm2timestamp(tm, fsec, NULL, ×tamp) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("timestamp out of range"),
+ errcontext("while adding an interval and timestamp")));
}
if (span->day != 0)
@@ -2990,7 +3151,8 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
if (timestamp2tm(timestamp, NULL, tm, &fsec, NULL, NULL) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("timestamp out of range"),
+ errcontext("while adding an interval and timestamp")));
/* Add days by converting to and from Julian */
julian = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + span->day;
@@ -2999,7 +3161,8 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
if (tm2timestamp(tm, fsec, NULL, ×tamp) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("timestamp out of range"),
+ errcontext("while adding an interval and timestamp")));
}
timestamp += span->time;
@@ -3007,7 +3170,8 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
if (!IS_VALID_TIMESTAMP(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("timestamp out of range"),
+ errcontext("while adding an interval and timestamp")));
result = timestamp;
}
@@ -3022,9 +3186,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3051,7 +3213,32 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while adding an interval and timestamptz")));
+ 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("interval out of range"),
+ errcontext("while adding an interval and timestamptz")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3068,7 +3255,8 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, attimezone) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("timestamp out of range"),
+ errcontext("while adding an interval and timestamptz")));
tm->tm_mon += span->month;
if (tm->tm_mon > MONTHS_PER_YEAR)
@@ -3091,7 +3279,8 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("timestamp out of range"),
+ errcontext("while adding an interval and timestamptz")));
}
if (span->day != 0)
@@ -3104,7 +3293,8 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
if (timestamp2tm(timestamp, &tz, tm, &fsec, NULL, attimezone) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("timestamp out of range"),
+ errcontext("while adding an interval and timestamptz")));
/* Add days by converting to and from Julian */
julian = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) + span->day;
@@ -3115,7 +3305,8 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
if (tm2timestamp(tm, fsec, &tz, ×tamp) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("timestamp out of range"),
+ errcontext("while adding an interval and timestamptz")));
}
timestamp += span->time;
@@ -3123,7 +3314,8 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
if (!IS_VALID_TIMESTAMP(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("timestamp out of range"),
+ errcontext("while adding an interval and timestamptz")));
result = timestamp;
}
@@ -3141,9 +3333,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3194,6 +3384,33 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while negating an interval")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while negating an interval")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3201,23 +3418,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3261,27 +3462,66 @@ 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")));
+ /*
+ * 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))
+ {
+ if (INTERVAL_IS_NOEND(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while adding intervals")));
+ 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"),
+ errcontext("while adding intervals")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (INTERVAL_NOT_FINITE(span2))
+ memcpy(result, span2, sizeof(Interval));
+ else
+ {
+ 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"),
+ errcontext("while adding intervals")));
- 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->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"),
+ errcontext("while adding intervals")));
- 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")));
+ 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"),
+ errcontext("while adding intervals")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while adding intervals")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3295,27 +3535,68 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ if (INTERVAL_IS_NOBEGIN(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while subtracting intervals")));
+ 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"),
+ errcontext("while subtracting intervals")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (INTERVAL_IS_NOBEGIN(span2))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(span2))
+ INTERVAL_NOBEGIN(result);
+ else
+ {
+ 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"),
+ errcontext("while subtracting intervals")));
- 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->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"),
+ errcontext("while subtracting intervals")));
- 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")));
+ 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"),
+ errcontext("while subtracting intervals")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while subtracting intervals")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3337,15 +3618,59 @@ interval_mul(PG_FUNCTION_ARGS)
int32 orig_month = span->month,
orig_day = span->day;
Interval *result;
+ int is_factor_inf = isinf(factor);
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while multiplying an interval and float8")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ 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);
+ }
+ else if (is_factor_inf)
+ {
+ Interval zero;
+ int result_is_inf;
+
+ memset(&zero, 0, sizeof(zero));
+ result_is_inf = interval_cmp_internal(span, &zero) * is_factor_inf;
+
+ if (result_is_inf == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_is_inf < 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)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while multiplying an interval and float8")));
result->month = (int32) result_double;
result_double = span->day * factor;
@@ -3353,7 +3678,8 @@ interval_mul(PG_FUNCTION_ARGS)
result_double > INT_MAX || result_double < INT_MIN)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while multiplying an interval and float8")));
result->day = (int32) result_double;
/*
@@ -3397,9 +3723,16 @@ interval_mul(PG_FUNCTION_ARGS)
if (isnan(result_double) || !FLOAT8_FITS_IN_INT64(result_double))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while multiplying an interval and float8")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while multiplying an interval and float8")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3431,6 +3764,31 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while dividing an interval by float8")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (isinf(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while dividing an interval by float8")));
+
+ 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);
@@ -3452,6 +3810,12 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"),
+ errcontext("while dividing an interval by float8")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3735,8 +4099,37 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range"),
+ errcontext("while executing timestamp_age")));
+ 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("timestamp out of range"),
+ errcontext("while executing timestamp_age")));
+ 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;
@@ -3819,12 +4212,14 @@ timestamp_age(PG_FUNCTION_ARGS)
if (itm2interval(tm, result) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while executing timestamp_age")));
}
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("timestamp out of range"),
+ errcontext("while executing timestamp_age")));
PG_RETURN_INTERVAL_P(result);
}
@@ -3855,8 +4250,37 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range"),
+ errcontext("while executing timestamptz_age")));
+ 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("timestamp out of range"),
+ errcontext("while executing timestamptz_age")));
+ 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;
@@ -3943,12 +4367,14 @@ timestamptz_age(PG_FUNCTION_ARGS)
if (itm2interval(tm, result) != 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("interval out of range"),
+ errcontext("while executing timestamptz_age")));
}
else
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("timestamp out of range"),
+ errcontext("while executing timestamptz_age")));
PG_RETURN_INTERVAL_P(result);
}
@@ -3981,6 +4407,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4164,6 +4595,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4402,6 +4838,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);
@@ -4746,7 +5188,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5020,7 +5462,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5260,6 +5702,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5284,6 +5779,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5526,6 +6049,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5756,6 +6286,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5822,6 +6359,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5904,6 +6446,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edd59dc432..bcb54a7238 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -114,6 +114,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -123,6 +124,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..33842f9fbe 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1048,6 +1048,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1159,6 +1160,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1268,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,6 +1544,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1708,7 +1712,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index db5c7b83ba..3610755fa9 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -266,22 +309,23 @@ LINE 1: INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483648 years'...
-- Test edge-case overflow detection in interval multiplication
select extract(epoch from '256 microseconds'::interval * (2^55)::float8);
ERROR: interval out of range
+CONTEXT: while multiplying an interval and float8
SELECT r1.*, r2.*
FROM INTERVAL_TBL_OF r1, INTERVAL_TBL_OF r2
WHERE r1.f1 > r2.f1
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +340,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +430,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,8 +475,13 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+CONTEXT: while justifying an interval hours
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+CONTEXT: while justifying an interval days
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
+CONTEXT: while justifying an interval days
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
1 month -1 hour
@@ -450,26 +501,36 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+CONTEXT: while justifying an interval
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+CONTEXT: while justifying an interval
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+CONTEXT: while justifying an interval
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+CONTEXT: while justifying an interval
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+CONTEXT: while justifying an interval
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
+CONTEXT: while justifying an interval
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
SET IntervalStyle TO postgres;
@@ -820,8 +881,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 +893,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;
@@ -1590,37 +1653,40 @@ LINE 1: select interval '-2147483648 months -2147483648 days -922337...
-- overflowing using make_interval
select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
ERROR: interval out of range
+CONTEXT: while making an interval
select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
ERROR: interval out of range
+CONTEXT: while making an interval
SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
ERROR: interval out of range
+CONTEXT: while making an 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';
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
@@ -1673,8 +1739,10 @@ select make_interval(months := 'NaN'::float::int);
ERROR: integer out of range
select make_interval(secs := 'inf');
ERROR: interval out of range
+CONTEXT: while making an interval
select make_interval(secs := 'NaN');
ERROR: interval out of range
+CONTEXT: while making an interval
select make_interval(secs := 7e12);
make_interval
------------------------------------
@@ -1699,19 +1767,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1855,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,3 +1866,415 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+CONTEXT: while adding an interval and timestamp
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+CONTEXT: while adding an interval and timestamp
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+CONTEXT: while adding an interval and timestamp
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+CONTEXT: while adding an interval and timestamp
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+CONTEXT: while adding intervals
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+CONTEXT: while adding intervals
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+CONTEXT: while adding intervals
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+CONTEXT: while adding intervals
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+CONTEXT: while subtracting intervals
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+CONTEXT: while subtracting intervals
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+CONTEXT: while subtracting intervals
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+CONTEXT: while subtracting intervals
+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: interval out of range
+CONTEXT: while adding an interval and timestamp
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+CONTEXT: while adding an interval and timestamp
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+CONTEXT: while adding an interval and timestamp
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+CONTEXT: while adding an interval and timestamp
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+CONTEXT: while adding an interval and timestamptz
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+CONTEXT: while adding an interval and timestamptz
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+CONTEXT: while adding an interval and timestamptz
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+CONTEXT: while adding an interval and timestamptz
+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
+CONTEXT: while negating an interval
+SELECT interval 'infinity' * 'nan';
+ERROR: interval out of range
+CONTEXT: while multiplying an interval and float8
+SELECT interval '-infinity' * 'nan';
+ERROR: interval out of range
+CONTEXT: while multiplying an interval and float8
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+ERROR: interval out of range
+CONTEXT: while multiplying an interval and float8
+SELECT interval 'infinity' / 'infinity';
+ERROR: interval out of range
+CONTEXT: while dividing an interval by float8
+SELECT interval 'infinity' / '-infinity';
+ERROR: interval out of range
+CONTEXT: while dividing an interval by float8
+SELECT interval 'infinity' / 'nan';
+ERROR: interval out of range
+CONTEXT: while dividing an interval by float8
+SELECT interval '-infinity' / 'infinity';
+ERROR: interval out of range
+CONTEXT: while dividing an interval by float8
+SELECT interval '-infinity' / '-infinity';
+ERROR: interval out of range
+CONTEXT: while dividing an interval by float8
+SELECT interval '-infinity' / 'nan';
+ERROR: interval out of range
+CONTEXT: while dividing an interval by float8
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+ERROR: interval out of range
+CONTEXT: while dividing an interval by float8
+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: time out of range
+CONTEXT: while converting interval to time
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+CONTEXT: while converting interval to time
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..b586f30ba1 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -1216,6 +1216,7 @@ SELECT timestamp '294276-12-31 23:59:59' - timestamp '1999-12-23 19:59:04.224193
SELECT timestamp '294276-12-31 23:59:59' - timestamp '1999-12-23 19:59:04.224192' AS overflows;
ERROR: interval out of range
+CONTEXT: while subtracting timestamps
-- TO_CHAR()
SELECT to_char(d1, 'DAY Day day DY Dy dy MONTH Month month RM MON Mon mon')
FROM TIMESTAMP_TBL;
@@ -2125,3 +2126,69 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp out of range
+CONTEXT: while subtracting timestamps
+select timestamp 'infinity' - timestamp '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '-infinity';
+ERROR: timestamp out of range
+CONTEXT: while subtracting timestamps
+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: timestamp out of range
+CONTEXT: while executing timestamp_age
+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: timestamp out of range
+CONTEXT: while executing timestamp_age
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..c532c96958 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -1340,6 +1340,7 @@ SELECT timestamptz '294276-12-31 23:59:59 UTC' - timestamptz '1999-12-23 19:59:0
SELECT timestamptz '294276-12-31 23:59:59 UTC' - timestamptz '1999-12-23 19:59:04.224192 UTC' AS overflows;
ERROR: interval out of range
+CONTEXT: while subtracting timestamps
-- TO_CHAR()
SELECT to_char(d1, 'DAY Day day DY Dy dy MONTH Month month RM MON Mon mon')
FROM TIMESTAMPTZ_TBL;
@@ -2468,6 +2469,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3159,65 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp out of range
+CONTEXT: while subtracting timestamps
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+ERROR: timestamp out of range
+CONTEXT: while subtracting timestamps
+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: timestamp out of range
+CONTEXT: while executing timestamptz_age
+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: timestamp out of range
+CONTEXT: while executing timestamptz_age
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..1d0ab322c0 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -207,14 +207,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -287,11 +290,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 434fd87a35..dab0e07999 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -587,3 +594,134 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp '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 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' / '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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
This code is duplicated in timestamp_pl_interval(). We could create a
function
to encode the infinity handling rules and then call it in these two
places. The
argument types are different, Timestamp and TimestampTz viz. which map
to in64,
so shouldn't be a problem. But it will be slightly unreadable. Or use
macros
but then it will be difficult to debug.
What do you think?
I was hoping that I could come up with a macro that we could re-use for
all the similar logic. If that doesn't work then I'll try the helper
functions. I'll update the patch in a follow-up email to give myself some
time to think about this.
So I checked where are all the places that we do arithmetic between two
potentially infinite values, and it's at the top of the following
functions:
- timestamp_mi()
- timestamp_pl_interval()
- timestamptz_pl_interval_internal()
- interval_pl()
- interval_mi()
- timestamp_age()
- timestamptz_age()
I was able to get an extremely generic macro to work, but it was very
ugly and unmaintainable in my view. Instead I took the following steps
to clean this up:
- I rewrote interval_mi() to be implemented in terms of interval_um()
and interval_pl().
- I abstracted the infinite arithmetic from timestamp_mi(),
timestamp_age(), and timestamptz_age() into a helper function called
infinite_timestamp_mi_internal()
- I abstracted the infinite arithmetic from timestamp_pl_interval() and
timestamptz_pl_interval_internal() into a helper function called
infinite_timestamp_pl_interval_internal()
The helper functions return a bool to indicate if they set the result.
An alternative approach would be to check for finiteness in either of
the inputs, then call the helper function which would have a void
return type. I think this alternative approach would be slightly more
readable, but involve duplicate finiteness checks before and during the
helper function.
I've attached a patch with these changes that is meant to be applied
over the previous three patches. Let me know what you think.
With this patch I believe that I've addressed all open comments except
for the discussion around whether we should check just the months field
or all three fields for finiteness. Please let me know if I've missed
something.
Thanks,
Joe Koshakow
Attachments:
0004-Clean-up-infinity-arithmetic.patchtext/x-patch; charset=US-ASCII; name=0004-Clean-up-infinity-arithmetic.patchDownload
From e50d4ca6321c58d216d563f74502356d721c2b4b Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sun, 2 Apr 2023 17:15:01 -0400
Subject: [PATCH 4/4] Clean up infinity arithmetic
---
src/backend/utils/adt/timestamp.c | 254 +++++++---------------
src/test/regress/expected/interval.out | 16 +-
src/test/regress/expected/timestamp.out | 4 +-
src/test/regress/expected/timestamptz.out | 4 +-
4 files changed, 86 insertions(+), 192 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 78133dfb17..50a47f3778 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -2788,16 +2788,15 @@ timestamp_larger(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(result);
}
-
-Datum
-timestamp_mi(PG_FUNCTION_ARGS)
+/* Helper function to perform subtraction between two potentially infinite
+ * timestamps.
+ *
+ * Returns true if either dt1 or dt1 were infinite and result was set,
+ * false otherwise.
+ */
+bool
+infinite_timestamp_mi_internal(Timestamp dt1, Timestamp dt2, Interval *result)
{
- Timestamp dt1 = PG_GETARG_TIMESTAMP(0);
- Timestamp dt2 = PG_GETARG_TIMESTAMP(1);
- Interval *result;
-
- result = (Interval *) palloc(sizeof(Interval));
-
/*
* Subtracting two infinite timestamps with different signs results in an
* infinite interval with the same sign as the left operand. Subtracting
@@ -2812,6 +2811,7 @@ timestamp_mi(PG_FUNCTION_ARGS)
errcontext("while subtracting timestamps")));
else
INTERVAL_NOBEGIN(result);
+ return true;
}
else if (TIMESTAMP_IS_NOEND(dt1))
{
@@ -2822,11 +2822,34 @@ timestamp_mi(PG_FUNCTION_ARGS)
errcontext("while subtracting timestamps")));
else
INTERVAL_NOEND(result);
+ return true;
}
else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ {
INTERVAL_NOEND(result);
+ return true;
+ }
else if (TIMESTAMP_IS_NOEND(dt2))
+ {
INTERVAL_NOBEGIN(result);
+ return true;
+ }
+ else
+ return false;
+}
+
+Datum
+timestamp_mi(PG_FUNCTION_ARGS)
+{
+ Timestamp dt1 = PG_GETARG_TIMESTAMP(0);
+ Timestamp dt2 = PG_GETARG_TIMESTAMP(1);
+ Interval *result;
+
+ result = (Interval *) palloc(sizeof(Interval));
+
+ if (infinite_timestamp_mi_internal(dt1, dt2, result))
+ {
+ }
else
{
if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
@@ -3060,23 +3083,15 @@ interval_justify_days(PG_FUNCTION_ARGS)
PG_RETURN_INTERVAL_P(result);
}
-/* timestamp_pl_interval()
- * Add an interval to a timestamp data type.
- * Note that interval has provisions for qualitative year/month and day
- * units, so try to do the right thing with them.
- * To add a month, increment the month, and use the same day of month.
- * Then, if the next month has fewer days, set the day of month
- * to the last day of month.
- * To add a day, increment the mday, and use the same time of day.
- * Lastly, add in the "quantitative time".
+/* Helper function to perform addition between a potentially infinite
+ * timestamp and a potentially infinite interval.
+ *
+ * Returns true if either dt1 or dt1 were infinite and result was set,
+ * false otherwise.
*/
-Datum
-timestamp_pl_interval(PG_FUNCTION_ARGS)
+bool
+infinite_timestamp_pl_interval_internal(Timestamp timestamp, Interval *span, Timestamp *result)
{
- Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
- Interval *span = PG_GETARG_INTERVAL_P(1);
- Timestamp result;
-
/*
* Adding two infinites with the same sign results in an infinite
* timestamp with the same sign. Adding two infintes with different signs
@@ -3090,7 +3105,8 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range"),
errcontext("while adding an interval and timestamp")));
else
- TIMESTAMP_NOBEGIN(result);
+ TIMESTAMP_NOBEGIN(*result);
+ return true;
}
else if (INTERVAL_IS_NOEND(span))
{
@@ -3100,11 +3116,36 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range"),
errcontext("while adding an interval and timestamp")));
else
- TIMESTAMP_NOEND(result);
+ TIMESTAMP_NOEND(*result);
+ return true;
}
else if (TIMESTAMP_NOT_FINITE(timestamp))
- result = timestamp;
+ {
+ *result = timestamp;
+ return true;
+ }
else
+ return false;
+}
+
+/* timestamp_pl_interval()
+ * Add an interval to a timestamp data type.
+ * Note that interval has provisions for qualitative year/month and day
+ * units, so try to do the right thing with them.
+ * To add a month, increment the month, and use the same day of month.
+ * Then, if the next month has fewer days, set the day of month
+ * to the last day of month.
+ * To add a day, increment the mday, and use the same time of day.
+ * Lastly, add in the "quantitative time".
+ */
+Datum
+timestamp_pl_interval(PG_FUNCTION_ARGS)
+{
+ Timestamp timestamp = PG_GETARG_TIMESTAMP(0);
+ Interval *span = PG_GETARG_INTERVAL_P(1);
+ Timestamp result;
+
+ if (!infinite_timestamp_pl_interval_internal(timestamp, span, &result))
{
if (span->month != 0)
{
@@ -3193,7 +3234,6 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
PointerGetDatum(&tspan));
}
-
/* timestamptz_pl_interval_internal()
* Add an interval to a timestamptz, in the given (or session) timezone.
*
@@ -3213,34 +3253,7 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- /*
- * Adding two infinites with the same sign results in an infinite
- * timestamp with the same sign. Adding two infintes with different signs
- * results in an error.
- */
- if (INTERVAL_IS_NOBEGIN(span))
- {
- if (TIMESTAMP_IS_NOEND(timestamp))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range"),
- errcontext("while adding an interval and timestamptz")));
- 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("interval out of range"),
- errcontext("while adding an interval and timestamptz")));
- else
- TIMESTAMP_NOEND(result);
- }
- else if (TIMESTAMP_NOT_FINITE(timestamp))
- result = timestamp;
- else
+ if (!infinite_timestamp_pl_interval_internal(timestamp, span, &result))
{
/* Use session timezone if caller asks for default */
if (attimezone == NULL)
@@ -3529,76 +3542,9 @@ interval_pl(PG_FUNCTION_ARGS)
Datum
interval_mi(PG_FUNCTION_ARGS)
{
- Interval *span1 = PG_GETARG_INTERVAL_P(0);
- Interval *span2 = PG_GETARG_INTERVAL_P(1);
- Interval *result;
-
- 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))
- {
- if (INTERVAL_IS_NOBEGIN(span2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range"),
- errcontext("while subtracting intervals")));
- 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"),
- errcontext("while subtracting intervals")));
- else
- INTERVAL_NOEND(result);
- }
- else if (INTERVAL_IS_NOBEGIN(span2))
- INTERVAL_NOEND(result);
- else if (INTERVAL_IS_NOEND(span2))
- INTERVAL_NOBEGIN(result);
- else
- {
- 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"),
- errcontext("while subtracting intervals")));
-
- 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"),
- errcontext("while subtracting intervals")));
-
- 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"),
- errcontext("while subtracting intervals")));
-
- if (INTERVAL_NOT_FINITE(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range"),
- errcontext("while subtracting intervals")));
- }
-
- PG_RETURN_INTERVAL_P(result);
+ return DirectFunctionCall2(interval_pl,
+ PG_GETARG_INTERVAL_P(0),
+ DirectFunctionCall1(interval_um, PG_GETARG_INTERVAL_P(1)));
}
/*
@@ -4099,35 +4045,9 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- /*
- * Subtracting two infinite timestamps with different signs results in an
- * infinite interval with the same sign as the left operand. Subtracting
- * two infinte timestamps with the same sign results in an error.
- */
- if (TIMESTAMP_IS_NOBEGIN(dt1))
+ if (infinite_timestamp_mi_internal(dt1, dt2, result))
{
- if (TIMESTAMP_IS_NOBEGIN(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range"),
- errcontext("while executing timestamp_age")));
- 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("timestamp out of range"),
- errcontext("while executing timestamp_age")));
- 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)
{
@@ -4250,35 +4170,9 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- /*
- * Subtracting two infinite timestamps with different signs results in an
- * infinite interval with the same sign as the left operand. Subtracting
- * two infinte timestamps with the same sign results in an error.
- */
- if (TIMESTAMP_IS_NOBEGIN(dt1))
+ if (infinite_timestamp_mi_internal(dt1, dt2, result))
{
- if (TIMESTAMP_IS_NOBEGIN(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range"),
- errcontext("while executing timestamptz_age")));
- 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("timestamp out of range"),
- errcontext("while executing timestamptz_age")));
- 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)
{
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 3610755fa9..9cf23e2bbb 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1983,7 +1983,7 @@ ERROR: interval out of range
CONTEXT: while adding intervals
SELECT interval 'infinity' - interval 'infinity';
ERROR: interval out of range
-CONTEXT: while subtracting intervals
+CONTEXT: while adding intervals
SELECT interval 'infinity' - interval '-infinity';
?column?
----------
@@ -1998,7 +1998,7 @@ SELECT interval '-infinity' - interval 'infinity';
SELECT interval '-infinity' - interval '-infinity';
ERROR: interval out of range
-CONTEXT: while subtracting intervals
+CONTEXT: while adding intervals
SELECT interval 'infinity' - interval '10 days';
?column?
----------
@@ -2013,10 +2013,10 @@ SELECT interval '-infinity' - interval '10 days';
SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
ERROR: interval out of range
-CONTEXT: while subtracting intervals
+CONTEXT: while adding intervals
SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
ERROR: interval out of range
-CONTEXT: while subtracting intervals
+CONTEXT: while adding intervals
SELECT timestamp '1995-08-06 12:30:15' + interval 'infinity';
?column?
----------
@@ -2109,10 +2109,10 @@ SELECT timestamptz 'infinity' + interval 'infinity';
SELECT timestamptz 'infinity' + interval '-infinity';
ERROR: interval out of range
-CONTEXT: while adding an interval and timestamptz
+CONTEXT: while adding an interval and timestamp
SELECT timestamptz '-infinity' + interval 'infinity';
ERROR: interval out of range
-CONTEXT: while adding an interval and timestamptz
+CONTEXT: while adding an interval and timestamp
SELECT timestamptz '-infinity' + interval '-infinity';
?column?
-----------
@@ -2121,7 +2121,7 @@ SELECT timestamptz '-infinity' + interval '-infinity';
SELECT timestamptz 'infinity' - interval 'infinity';
ERROR: interval out of range
-CONTEXT: while adding an interval and timestamptz
+CONTEXT: while adding an interval and timestamp
SELECT timestamptz 'infinity' - interval '-infinity';
?column?
----------
@@ -2136,7 +2136,7 @@ SELECT timestamptz '-infinity' - interval 'infinity';
SELECT timestamptz '-infinity' - interval '-infinity';
ERROR: interval out of range
-CONTEXT: while adding an interval and timestamptz
+CONTEXT: while adding an interval and timestamp
SELECT time '11:27:42' + interval 'infinity';
ERROR: cannot add infinite interval to time
SELECT time '11:27:42' + interval '-infinity';
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index b586f30ba1..470c2bf52c 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2176,7 +2176,7 @@ select age(timestamp '-infinity');
select age(timestamp 'infinity', timestamp 'infinity');
ERROR: timestamp out of range
-CONTEXT: while executing timestamp_age
+CONTEXT: while subtracting timestamps
select age(timestamp 'infinity', timestamp '-infinity');
age
----------
@@ -2191,4 +2191,4 @@ select age(timestamp '-infinity', timestamp 'infinity');
select age(timestamp '-infinity', timestamp '-infinity');
ERROR: timestamp out of range
-CONTEXT: while executing timestamp_age
+CONTEXT: while subtracting timestamps
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index c532c96958..e98359872e 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -3205,7 +3205,7 @@ SELECT age(timestamptz '-infinity');
SELECT age(timestamptz 'infinity', timestamptz 'infinity');
ERROR: timestamp out of range
-CONTEXT: while executing timestamptz_age
+CONTEXT: while subtracting timestamps
SELECT age(timestamptz 'infinity', timestamptz '-infinity');
age
----------
@@ -3220,4 +3220,4 @@ SELECT age(timestamptz '-infinity', timestamptz 'infinity');
SELECT age(timestamptz '-infinity', timestamptz '-infinity');
ERROR: timestamp out of range
-CONTEXT: while executing timestamptz_age
+CONTEXT: while subtracting timestamps
--
2.34.1
Joseph Koshakow <koshy44@gmail.com> writes:
I've attached a patch with these changes that is meant to be applied
over the previous three patches. Let me know what you think.
Does not really seem like an improvement to me --- I think it's
adding more complexity than it removes. The changes in CONTEXT
messages are definitely not an improvement; you might as well
not have the context messages at all as give misleading ones.
(Those context messages are added by the previous patches, no?
They do not really seem per project style, and I'm not sure
that they are helpful.)
regards, tom lane
On Sun, Apr 2, 2023 at 5:36 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Joseph Koshakow <koshy44@gmail.com> writes:
I've attached a patch with these changes that is meant to be applied
over the previous three patches. Let me know what you think.Does not really seem like an improvement to me --- I think it's
adding more complexity than it removes. The changes in CONTEXT
messages are definitely not an improvement; you might as well
not have the context messages at all as give misleading ones.
(Those context messages are added by the previous patches, no?
They do not really seem per project style, and I'm not sure
that they are helpful.)
Yes they were added in the previous patch,
v17-0003-Add-infinite-interval-values.patch. I also had the following
note about them.
I've added an errcontext to all the errors of the form "X out of
range". My one concern is that some of the messages can be slightly
confusing. For example date arithmetic is converted to timestamp
arithmetic, so the errcontext talks about timestamps even though the
actual operation used dates. For example,SELECT date 'infinity' + interval '-infinity';
ERROR: interval out of range
CONTEXT: while adding an interval and timestamp
I would be OK with removing all of the context messages or maybe only
keeping a select few, like the ones in interval_um.
How do you feel about redefining interval_mi in terms of interval_um
and interval_pl? That one felt like an improvement to me even outside
of the context of this change.
Thanks,
Joe Koshakow
Joseph Koshakow <koshy44@gmail.com> writes:
I've added an errcontext to all the errors of the form "X out of
range".
Please note the style guidelines [1]https://www.postgresql.org/docs/devel/error-message-reporting.html:
errcontext(const char *msg, ...) is not normally called directly from
an ereport message site; rather it is used in error_context_stack
callback functions to provide information about the context in which
an error occurred, such as the current location in a PL function.
If we should have this at all, which I doubt, it's probably
errdetail not errcontext.
How do you feel about redefining interval_mi in terms of interval_um
and interval_pl? That one felt like an improvement to me even outside
of the context of this change.
I did not think so. For one thing, it introduces integer-overflow
hazards that you would not have otherwise; ie, interval_um might have
to throw an error for INT_MIN input, even though the end result of
the calculation would have been in range.
regards, tom lane
[1]: https://www.postgresql.org/docs/devel/error-message-reporting.html
On Sun, Apr 2, 2023 at 6:54 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Joseph Koshakow <koshy44@gmail.com> writes:
I've added an errcontext to all the errors of the form "X out of
range".Please note the style guidelines [1]:
errcontext(const char *msg, ...) is not normally called directly
from
an ereport message site; rather it is used in error_context_stack
callback functions to provide information about the context in
which
an error occurred, such as the current location in a PL function.
If we should have this at all, which I doubt, it's probably
errdetail not errcontext.
I've attached a patch with all of the errcontext calls removed. None of
the existing out of range errors have an errdetail call so I think this
is more consistent. If we do want to add errdetail, then we should
probably do it in a later patch and add it to all out of range errors,
not just the ones related to infinity.
How do you feel about redefining interval_mi in terms of interval_um
and interval_pl? That one felt like an improvement to me even outside
of the context of this change.I did not think so. For one thing, it introduces integer-overflow
hazards that you would not have otherwise; ie, interval_um might have
to throw an error for INT_MIN input, even though the end result of
the calculation would have been in range.
Good point, I didn't think of that.
Thanks,
Joe Koshakow
Attachments:
v3-0001-Move-integer-helper-function-to-int.h.patchtext/x-patch; charset=US-ASCII; name=v3-0001-Move-integer-helper-function-to-int.h.patchDownload
From f6bf9c201a94a0b338dff520442ac5e8d2922c89 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/3] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index be2e55bb29..64f28a85b0 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.34.1
v3-0002-Check-for-overflow-in-make_interval.patchtext/x-patch; charset=US-ASCII; name=v3-0002-Check-for-overflow-in-make_interval.patchDownload
From 765aa1ebf9de5e5d48e1c588f7bde70074374979 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/3] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index aaadc68ae6..5c25e12795 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1517,13 +1517,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..db5c7b83ba 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..434fd87a35 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.34.1
v18-0003-Add-infinite-interval-values.patchtext/x-patch; charset=US-ASCII; name=v18-0003-Add-infinite-interval-values.patchDownload
From b0cf51ee8824bb531f8b90e087dbcbab1c507de7 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/3] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 706 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 6 +-
src/test/regress/expected/interval.out | 558 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 6 +-
src/test/regress/sql/interval.sql | 170 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1482 insertions(+), 204 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index f6b867f344..2b454e7fbc 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2321,7 +2321,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 918a492234..13f7033572 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9472,7 +9472,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10369,7 +10369,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index a163fbb4ab..77931a567f 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2023,6 +2023,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2054,6 +2059,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2067,6 +2074,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2085,6 +2097,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2599,6 +2616,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2621,6 +2643,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3113,6 +3140,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 64f28a85b0..f77bf1d5eb 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3550,6 +3550,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/formatting.c b/src/backend/utils/adt/formatting.c
index e6246dc44b..8c739833dc 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fe37e65af0..e2cc6049f2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 5c25e12795..43853d11ca 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -940,6 +942,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);
@@ -962,8 +972,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);
@@ -1349,6 +1364,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.
@@ -1522,6 +1544,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1543,6 +1566,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1560,6 +1588,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2047,6 +2086,8 @@ 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;
}
@@ -2064,6 +2105,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2097,7 +2140,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2228,6 +2273,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2723,46 +2791,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2792,6 +2885,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))
@@ -2844,6 +2940,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2867,6 +2968,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,
@@ -2884,6 +2988,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2905,6 +3014,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))
@@ -2923,6 +3035,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2943,7 +3060,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3022,9 +3162,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3051,7 +3189,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3141,9 +3302,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3194,6 +3353,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3201,23 +3385,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3261,27 +3429,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3295,27 +3496,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3337,9 +3573,51 @@ interval_mul(PG_FUNCTION_ARGS)
int32 orig_month = span->month,
orig_day = span->day;
Interval *result;
+ int is_factor_inf = isinf(factor);
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ 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);
+ }
+ else if (is_factor_inf)
+ {
+ Interval zero;
+ int result_is_inf;
+
+ memset(&zero, 0, sizeof(zero));
+ result_is_inf = interval_cmp_internal(span, &zero) * is_factor_inf;
+
+ if (result_is_inf == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_is_inf < 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)
@@ -3400,6 +3678,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3431,6 +3714,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3452,6 +3758,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3735,8 +4046,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3855,8 +4193,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3981,6 +4346,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4164,6 +4534,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4402,6 +4777,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);
@@ -4746,7 +5127,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5020,7 +5401,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5260,6 +5641,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5284,6 +5718,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5526,6 +5988,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5756,6 +6225,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5822,6 +6298,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5904,6 +6385,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edd59dc432..bcb54a7238 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -114,6 +114,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -123,6 +124,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..33842f9fbe 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1048,6 +1048,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------------------+-----------+----------------------------+----------------------------
@@ -1159,6 +1160,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1268,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,6 +1544,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1708,7 +1712,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index db5c7b83ba..48a29f8c6a 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,3 +1851,382 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp '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: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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' / '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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..1d0ab322c0 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -207,14 +207,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND 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 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
@@ -287,11 +290,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 434fd87a35..dab0e07999 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -587,3 +594,134 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp '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 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' / '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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
Hi Joseph,
thanks for addressing comments.
On Sat, Apr 1, 2023 at 10:53 PM Joseph Koshakow <koshy44@gmail.com> wrote:
So I added a check for FLOAT8_FITS_IN_INT64() and a test with this
scenario.
I like that. Thanks.
For what it's worth I think that 2147483647 months only became a valid
interval in v15 as part of this commit [0]. It's also outside of the
documented valid range [1], which is
[-178000000 years, 178000000 years] or
[-14833333 months, 14833333 months].
you mean +/-2136000000 months :). In that sense the current code
actually fixes a bug introduced in v15. So I am fine with it.
The rationale for only checking the month's field is that it's faster
than checking all three fields, though I'm not entirely sure if it's
the right trade-off. Any thoughts on this?
Hmm, comparing one integer is certainly faster than comparing three.
We do that check at least once per interval operation. So the thrice
CPU cycles might show some impact when millions of rows are processed.
Given that we have clear documentation of bounds, just using months
field is fine. If needed we can always expand it later.
There's some way to avoid separate checks for infinite-ness of
interval and factor and use a single block using some integer
arithmetic. But I think this is more readable. So I avoided doing
that. Let me know if this works for you.I think the patch looks good, I've combined it with the existing patch.
Also added some test cases.
I didn't see any tests in the patch, did you forget to include it?
Sorry I forgot to include those. Attached.
Please see my reply to your latest email as well.
--
Best Wishes,
Ashutosh Bapat
Attachments:
infinite_interval_arith.patchapplication/x-patch; name=infinite_interval_arith.patchDownload
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 0adfe5f9fb..ebb4208ad7 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -2161,6 +2161,26 @@ 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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 1e1d8560bf..549ceb57c1 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -690,6 +690,12 @@ 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';
Hi Joseph,
On Mon, Apr 3, 2023 at 6:02 AM Joseph Koshakow <koshy44@gmail.com> wrote:
I've attached a patch with all of the errcontext calls removed. None of
the existing out of range errors have an errdetail call so I think this
is more consistent. If we do want to add errdetail, then we should
probably do it in a later patch and add it to all out of range errors,
not just the ones related to infinity.
Hmm, I realize my errcontext suggestion was in wrong direction. We can
use errdetail if required in future. But not for this patch.
Here are comments on the test and output.
+ infinity | | |
| | Infinity | Infinity | | | Infinity |
Infinity | Infinity | Infinity | Infinity
+ -infinity | | |
| | -Infinity | -Infinity | | | -Infinity |
-Infinity | -Infinity | -Infinity | -Infinity
This is more for my education. It looks like for oscillating units we report
NULL here but for monotonically increasing units we report infinity. I came
across those terms in the code. But I didn't find definitions of those terms.
Can you please point me to the document/resources defining those terms.
diff --git a/src/test/regress/sql/horology.sql
b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..1d0ab322c0 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -207,14 +207,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS
"add", t.d1 - i.f1 AS "subtract"
FROM TIMESTAMP_TBL t, INTERVAL_TBL i
WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01'
AND i.f1 BETWEEN '00:00' AND '23:00'
+ AND isfinite(i.f1)
I removed this and it did not have any effect on results. I think the
isfinite(i.f1) is already covered by the two existing conditions.
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
@@ -287,11 +290,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
IIUC, the isfinite() conditions are added to avoid any changes to the
output due to new
values added to INTERVAL_TBL. Instead, it might be a good idea to not add these
conditions and avoid extra queries testing infinity arithmetic in interval.sql,
timestamptz.sql and timestamp.sql like below
+
+-- infinite intervals
... some lines folded
+
+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';
... block truncated
With that I have reviewed the entire patch-set. Once you address these
comments, we can mark it as ready for committer. I already see Tom
looking at the patch. So that might be just a formality.
--
Best Wishes,
Ashutosh Bapat
On Mon, Apr 3, 2023 at 10:11 AM Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
wrote:
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity + -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -InfinityThis is more for my education. It looks like for oscillating units we
report
NULL here but for monotonically increasing units we report infinity. I
came
across those terms in the code. But I didn't find definitions of those
terms.
Can you please point me to the document/resources defining those terms.
I was also unable to find a definition of oscillating or monotonically
increasing in this context. I used the existing timestamps and dates
code to form my own definition:
If there exists an two intervals with the same sign, such that adding
them together results in an interval with a unit that is less than the
unit of at least one of the original intervals, then that unit is
oscillating. Otherwise it is monotonically increasing.
So for example `INTERVAL '30 seconds' + INTERVAL '30 seconds'` results
in an interval with 0 seconds, so seconds are oscillating. You couldn't
find a similar example for days or hours, so they're monotonically
increasing.
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql index f7f8c8d2dd..1d0ab322c0 100644 --- a/src/test/regress/sql/horology.sql +++ b/src/test/regress/sql/horology.sql @@ -207,14 +207,17 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract" FROM TIMESTAMP_TBL t, INTERVAL_TBL i WHERE t.d1 BETWEEN '1990-01-01' AND '2001-01-01' AND i.f1 BETWEEN '00:00' AND '23:00' + AND isfinite(i.f1)I removed this and it did not have any effect on results. I think the
isfinite(i.f1) is already covered by the two existing conditions.
Thanks for pointing this out, I've removed this in the attached patch.
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 @@ -287,11 +290,12 @@ SELECT f1 AS "timestamp"SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";IIUC, the isfinite() conditions are added to avoid any changes to the
output due to new
values added to INTERVAL_TBL. Instead, it might be a good idea to not
add these
conditions and avoid extra queries testing infinity arithmetic in
interval.sql,
timestamptz.sql and timestamp.sql like below
+
+-- infinite intervals... some lines folded
+ +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';... block truncated
I originally tried that, but the issue here is that errors propagate
through the whole query. So if one row produces an error then no rows
are produced and instead a single error is returned. So the rows that
would execute, for example,
SELECT date 'infinity' + interval '-infinity' would cause the entire
query to error out. If you have any suggestions to get around this
please let me know.
With that I have reviewed the entire patch-set. Once you address these
comments, we can mark it as ready for committer. I already see Tom
looking at the patch. So that might be just a formality.
Thanks so much for taking the time to review this!
Thanks,
Joe Koshakow
Attachments:
v3-0001-Move-integer-helper-function-to-int.h.patchtext/x-patch; charset=US-ASCII; name=v3-0001-Move-integer-helper-function-to-int.h.patchDownload
From f6bf9c201a94a0b338dff520442ac5e8d2922c89 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/3] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index be2e55bb29..64f28a85b0 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.34.1
v19-0003-Add-infinite-interval-values.patchtext/x-patch; charset=US-ASCII; name=v19-0003-Add-infinite-interval-values.patchDownload
From 34c3589f1010b978ac15d78fc8bd684e157803f5 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/3] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 706 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 5 +-
src/test/regress/expected/interval.out | 578 ++++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 176 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1506 insertions(+), 204 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index f6b867f344..2b454e7fbc 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2321,7 +2321,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 918a492234..13f7033572 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9472,7 +9472,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10369,7 +10369,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index a163fbb4ab..77931a567f 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2023,6 +2023,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2054,6 +2059,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2067,6 +2074,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2085,6 +2097,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2599,6 +2616,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2621,6 +2643,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3113,6 +3140,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 64f28a85b0..f77bf1d5eb 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3550,6 +3550,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/formatting.c b/src/backend/utils/adt/formatting.c
index e6246dc44b..8c739833dc 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fe37e65af0..e2cc6049f2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 5c25e12795..43853d11ca 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -940,6 +942,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);
@@ -962,8 +972,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);
@@ -1349,6 +1364,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.
@@ -1522,6 +1544,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1543,6 +1566,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1560,6 +1588,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2047,6 +2086,8 @@ 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;
}
@@ -2064,6 +2105,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2097,7 +2140,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2228,6 +2273,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2723,46 +2791,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2792,6 +2885,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))
@@ -2844,6 +2940,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2867,6 +2968,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,
@@ -2884,6 +2988,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2905,6 +3014,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))
@@ -2923,6 +3035,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2943,7 +3060,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3022,9 +3162,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3051,7 +3189,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3141,9 +3302,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3194,6 +3353,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3201,23 +3385,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3261,27 +3429,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3295,27 +3496,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3337,9 +3573,51 @@ interval_mul(PG_FUNCTION_ARGS)
int32 orig_month = span->month,
orig_day = span->day;
Interval *result;
+ int is_factor_inf = isinf(factor);
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ 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);
+ }
+ else if (is_factor_inf)
+ {
+ Interval zero;
+ int result_is_inf;
+
+ memset(&zero, 0, sizeof(zero));
+ result_is_inf = interval_cmp_internal(span, &zero) * is_factor_inf;
+
+ if (result_is_inf == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_is_inf < 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)
@@ -3400,6 +3678,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3431,6 +3714,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3452,6 +3758,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3735,8 +4046,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3855,8 +4193,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3981,6 +4346,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4164,6 +4534,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4402,6 +4777,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);
@@ -4746,7 +5127,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5020,7 +5401,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5260,6 +5641,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5284,6 +5718,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5526,6 +5988,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5756,6 +6225,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5822,6 +6298,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5904,6 +6385,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edd59dc432..bcb54a7238 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -114,6 +114,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -123,6 +124,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..d54a8eb801 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,6 +1543,7 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
@@ -1708,7 +1711,7 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index db5c7b83ba..25ec3ac2e2 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,3 +1851,402 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp '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: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..8417238aae 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 434fd87a35..f24609532f 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -587,3 +594,140 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp '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 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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
v3-0002-Check-for-overflow-in-make_interval.patchtext/x-patch; charset=US-ASCII; name=v3-0002-Check-for-overflow-in-make_interval.patchDownload
From 765aa1ebf9de5e5d48e1c588f7bde70074374979 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/3] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index aaadc68ae6..5c25e12795 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1517,13 +1517,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..db5c7b83ba 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..434fd87a35 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.34.1
On Sat, Apr 8, 2023 at 8:54 PM Joseph Koshakow <koshy44@gmail.com> wrote:
I was also unable to find a definition of oscillating or monotonically
increasing in this context. I used the existing timestamps and dates
code to form my own definition:If there exists an two intervals with the same sign, such that adding
them together results in an interval with a unit that is less than the
unit of at least one of the original intervals, then that unit is
oscillating. Otherwise it is monotonically increasing.So for example `INTERVAL '30 seconds' + INTERVAL '30 seconds'` results
in an interval with 0 seconds, so seconds are oscillating. You couldn't
find a similar example for days or hours, so they're monotonically
increasing.
Thanks for the explanation with an example. Makes sense considering
that the hours and days are not convertible to their wider units
without temporal context.
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;
Those two are operations with Time which does not allow infinity. So I
think this is fine.
-- SQL9x OVERLAPS operator @@ -287,11 +290,12 @@ SELECT f1 AS "timestamp"SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1)
ORDER BY plus, "timestamp", "interval";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)
+ WHERE isfinite(t.f1)
ORDER BY minus, "timestamp", "interval";I originally tried that, but the issue here is that errors propagate
through the whole query. So if one row produces an error then no rows
are produced and instead a single error is returned. So the rows that
would execute, for example,
SELECT date 'infinity' + interval '-infinity' would cause the entire
query to error out. If you have any suggestions to get around this
please let me know.
I modified this to WHERE isfinite(t.f1) or isfinite(d.f1). The output
contains a lot of additions with infinity::interval but that might be
ok. No errors. We could further improve it to allow operations between
infinity which do not result in error e.g, both operands being same
signed for plus and opposite signed for minus. But I think we can
leave this to the committer's judgement. Which route to choose.
With that I have reviewed the entire patch-set. Once you address these
comments, we can mark it as ready for committer. I already see Tom
looking at the patch. So that might be just a formality.Thanks so much for taking the time to review this!
My pleasure. I am very much interested to see this being part of code.
Given that the last commit fest for v16 has ended, let's target this
for v17. I will mark this as ready for committer now.
--
Best Wishes,
Ashutosh Bapat
On Wed, Apr 12, 2023 at 9:11 AM Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
wrote:
I modified this to WHERE isfinite(t.f1) or isfinite(d.f1). The output
contains a lot of additions with infinity::interval but that might be
ok. No errors.
Attached is a patch with this testing change.
Thanks,
Joe Koshakow
Attachments:
v3-0001-Move-integer-helper-function-to-int.h.patchtext/x-patch; charset=US-ASCII; name=v3-0001-Move-integer-helper-function-to-int.h.patchDownload
From f6bf9c201a94a0b338dff520442ac5e8d2922c89 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/3] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index be2e55bb29..64f28a85b0 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.34.1
v20-0003-Add-infinite-interval-values.patchtext/x-patch; charset=US-ASCII; name=v20-0003-Add-infinite-interval-values.patchDownload
From d166531ece4668e9f56ab2deedf5ab27513950c2 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/3] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 706 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1544 insertions(+), 206 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index f6b867f344..2b454e7fbc 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2321,7 +2321,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 918a492234..13f7033572 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9472,7 +9472,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10369,7 +10369,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index a163fbb4ab..77931a567f 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2023,6 +2023,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2054,6 +2059,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2067,6 +2074,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2085,6 +2097,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2599,6 +2616,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2621,6 +2643,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3113,6 +3140,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 64f28a85b0..f77bf1d5eb 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3550,6 +3550,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/formatting.c b/src/backend/utils/adt/formatting.c
index e6246dc44b..8c739833dc 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index fe37e65af0..e2cc6049f2 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 5c25e12795..43853d11ca 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -940,6 +942,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);
@@ -962,8 +972,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);
@@ -1349,6 +1364,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.
@@ -1522,6 +1544,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1543,6 +1566,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1560,6 +1588,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2047,6 +2086,8 @@ 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;
}
@@ -2064,6 +2105,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2097,7 +2140,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2228,6 +2273,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2723,46 +2791,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2792,6 +2885,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))
@@ -2844,6 +2940,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2867,6 +2968,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,
@@ -2884,6 +2988,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2905,6 +3014,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))
@@ -2923,6 +3035,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2943,7 +3060,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3022,9 +3162,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3051,7 +3189,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3141,9 +3302,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3194,6 +3353,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3201,23 +3385,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3261,27 +3429,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3295,27 +3496,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3337,9 +3573,51 @@ interval_mul(PG_FUNCTION_ARGS)
int32 orig_month = span->month,
orig_day = span->day;
Interval *result;
+ int is_factor_inf = isinf(factor);
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ 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);
+ }
+ else if (is_factor_inf)
+ {
+ Interval zero;
+ int result_is_inf;
+
+ memset(&zero, 0, sizeof(zero));
+ result_is_inf = interval_cmp_internal(span, &zero) * is_factor_inf;
+
+ if (result_is_inf == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_is_inf < 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)
@@ -3400,6 +3678,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3431,6 +3714,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3452,6 +3758,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3735,8 +4046,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3855,8 +4193,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3981,6 +4346,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4164,6 +4534,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4402,6 +4777,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);
@@ -4746,7 +5127,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5020,7 +5401,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5260,6 +5641,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5284,6 +5718,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5526,6 +5988,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5756,6 +6225,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5822,6 +6298,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5904,6 +6385,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index edd59dc432..bcb54a7238 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -114,6 +114,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -123,6 +124,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index db5c7b83ba..4bbbab6431 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,3 +1851,378 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 434fd87a35..26e8c131fd 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -587,3 +594,136 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
v3-0002-Check-for-overflow-in-make_interval.patchtext/x-patch; charset=US-ASCII; name=v3-0002-Check-for-overflow-in-make_interval.patchDownload
From 765aa1ebf9de5e5d48e1c588f7bde70074374979 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/3] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index aaadc68ae6..5c25e12795 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1517,13 +1517,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..db5c7b83ba 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..434fd87a35 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.34.1
Looks like cfbot didn't like the names of these patches. It tried to
apply v20-0003 first and that failed. Attached patches with names in
sequential order. Let's see if that makes cfbot happy.
On Thu, Apr 13, 2023 at 12:05 AM Joseph Koshakow <koshy44@gmail.com> wrote:
On Wed, Apr 12, 2023 at 9:11 AM Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> wrote:
I modified this to WHERE isfinite(t.f1) or isfinite(d.f1). The output
contains a lot of additions with infinity::interval but that might be
ok. No errors.Attached is a patch with this testing change.
Thanks,
Joe Koshakow
--
Best Wishes,
Ashutosh Bapat
Attachments:
0002-Check-for-overflow-in-make_interval-20230623application/octet-stream; name=0002-Check-for-overflow-in-make_interval-20230623Download
From acef69449a1c8f81f6b69f580585bf6923e84d51 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/3] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec5a..3ab9b6918e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1518,13 +1518,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..db5c7b83ba 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..434fd87a35 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.25.1
0001-Move-integer-helper-function-to-int.h-20230623application/octet-stream; name=0001-Move-integer-helper-function-to-int.h-20230623Download
From 9472024ef0de1056e91c559277fc0deedd7a79e8 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/3] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 5d8d583ddc..dc79cbb4db 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.25.1
0003-Add-infinite-interval-values-20230623application/octet-stream; name=0003-Add-infinite-interval-values-20230623Download
From 779987d14fd71bb8b87c720f78ed4b042d5e9b50 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/3] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 706 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1544 insertions(+), 206 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 8d32a8c9c5..507f3cb1cc 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,7 +2320,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5a47ce4343..8303aad35f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9472,7 +9472,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10369,7 +10369,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de2c..e854a8cd1e 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index dc79cbb4db..c39fd37b7f 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3550,6 +3550,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/formatting.c b/src/backend/utils/adt/formatting.c
index e6246dc44b..8c739833dc 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..6e87173846 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3ab9b6918e..1223a9775d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1523,6 +1545,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1544,6 +1567,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1561,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2039,6 +2078,8 @@ 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;
}
@@ -2056,6 +2097,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2089,7 +2132,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2220,6 +2265,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2715,46 +2783,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2784,6 +2877,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))
@@ -2836,6 +2932,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2859,6 +2960,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,
@@ -2876,6 +2980,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2897,6 +3006,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))
@@ -2915,6 +3027,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2935,7 +3052,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3014,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3043,7 +3181,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3133,9 +3294,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3186,6 +3345,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3193,23 +3377,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3253,27 +3421,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3287,27 +3488,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3329,9 +3565,51 @@ interval_mul(PG_FUNCTION_ARGS)
int32 orig_month = span->month,
orig_day = span->day;
Interval *result;
+ int is_factor_inf = isinf(factor);
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ 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);
+ }
+ else if (is_factor_inf)
+ {
+ Interval zero;
+ int result_is_inf;
+
+ memset(&zero, 0, sizeof(zero));
+ result_is_inf = interval_cmp_internal(span, &zero) * is_factor_inf;
+
+ if (result_is_inf == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_is_inf < 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)
@@ -3392,6 +3670,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3423,6 +3706,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3444,6 +3750,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3727,8 +4038,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3847,8 +4185,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3973,6 +4338,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4156,6 +4526,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4394,6 +4769,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);
@@ -4738,7 +5119,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5012,7 +5393,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5252,6 +5633,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5276,6 +5710,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5518,6 +5980,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5748,6 +6217,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5814,6 +6290,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5896,6 +6377,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8c9..fe713fb6dd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index db5c7b83ba..4bbbab6431 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,3 +1851,378 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 434fd87a35..26e8c131fd 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -587,3 +594,136 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.25.1
Resending with .patch at the end in case cfbot needs that too.
On Fri, Jun 23, 2023 at 12:57 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
Looks like cfbot didn't like the names of these patches. It tried to
apply v20-0003 first and that failed. Attached patches with names in
sequential order. Let's see if that makes cfbot happy.On Thu, Apr 13, 2023 at 12:05 AM Joseph Koshakow <koshy44@gmail.com> wrote:
On Wed, Apr 12, 2023 at 9:11 AM Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> wrote:
I modified this to WHERE isfinite(t.f1) or isfinite(d.f1). The output
contains a lot of additions with infinity::interval but that might be
ok. No errors.Attached is a patch with this testing change.
Thanks,
Joe Koshakow--
Best Wishes,
Ashutosh Bapat
--
Best Wishes,
Ashutosh Bapat
Attachments:
0001-Move-integer-helper-function-to-int.h-20230623.patchtext/x-patch; charset=US-ASCII; name=0001-Move-integer-helper-function-to-int.h-20230623.patchDownload
From 9472024ef0de1056e91c559277fc0deedd7a79e8 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/3] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 5d8d583ddc..dc79cbb4db 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.25.1
0002-Check-for-overflow-in-make_interval-20230623.patchtext/x-patch; charset=US-ASCII; name=0002-Check-for-overflow-in-make_interval-20230623.patchDownload
From acef69449a1c8f81f6b69f580585bf6923e84d51 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/3] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec5a..3ab9b6918e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1518,13 +1518,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..db5c7b83ba 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..434fd87a35 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.25.1
0003-Add-infinite-interval-values-20230623.patchtext/x-patch; charset=US-ASCII; name=0003-Add-infinite-interval-values-20230623.patchDownload
From 779987d14fd71bb8b87c720f78ed4b042d5e9b50 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/3] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 706 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1544 insertions(+), 206 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 8d32a8c9c5..507f3cb1cc 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,7 +2320,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5a47ce4343..8303aad35f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9472,7 +9472,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10369,7 +10369,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de2c..e854a8cd1e 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index dc79cbb4db..c39fd37b7f 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3550,6 +3550,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/formatting.c b/src/backend/utils/adt/formatting.c
index e6246dc44b..8c739833dc 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..6e87173846 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3ab9b6918e..1223a9775d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1523,6 +1545,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1544,6 +1567,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1561,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2039,6 +2078,8 @@ 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;
}
@@ -2056,6 +2097,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2089,7 +2132,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2220,6 +2265,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2715,46 +2783,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2784,6 +2877,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))
@@ -2836,6 +2932,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2859,6 +2960,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,
@@ -2876,6 +2980,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2897,6 +3006,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))
@@ -2915,6 +3027,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2935,7 +3052,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3014,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3043,7 +3181,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3133,9 +3294,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3186,6 +3345,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3193,23 +3377,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3253,27 +3421,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3287,27 +3488,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3329,9 +3565,51 @@ interval_mul(PG_FUNCTION_ARGS)
int32 orig_month = span->month,
orig_day = span->day;
Interval *result;
+ int is_factor_inf = isinf(factor);
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ 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);
+ }
+ else if (is_factor_inf)
+ {
+ Interval zero;
+ int result_is_inf;
+
+ memset(&zero, 0, sizeof(zero));
+ result_is_inf = interval_cmp_internal(span, &zero) * is_factor_inf;
+
+ if (result_is_inf == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_is_inf < 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)
@@ -3392,6 +3670,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3423,6 +3706,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3444,6 +3750,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3727,8 +4038,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3847,8 +4185,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3973,6 +4338,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4156,6 +4526,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4394,6 +4769,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);
@@ -4738,7 +5119,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5012,7 +5393,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5252,6 +5633,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5276,6 +5710,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5518,6 +5980,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5748,6 +6217,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5814,6 +6290,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5896,6 +6377,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8c9..fe713fb6dd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index db5c7b83ba..4bbbab6431 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,3 +1851,378 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 434fd87a35..26e8c131fd 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -587,3 +594,136 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.25.1
Fixed assertion in time_mi_time(). It needed to assert that the result
is FINITE but it was doing the other way round and that triggered some
failures in cfbot.
On Fri, Jun 23, 2023 at 1:13 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
Resending with .patch at the end in case cfbot needs that too.
On Fri, Jun 23, 2023 at 12:57 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:Looks like cfbot didn't like the names of these patches. It tried to
apply v20-0003 first and that failed. Attached patches with names in
sequential order. Let's see if that makes cfbot happy.On Thu, Apr 13, 2023 at 12:05 AM Joseph Koshakow <koshy44@gmail.com> wrote:
On Wed, Apr 12, 2023 at 9:11 AM Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> wrote:
I modified this to WHERE isfinite(t.f1) or isfinite(d.f1). The output
contains a lot of additions with infinity::interval but that might be
ok. No errors.Attached is a patch with this testing change.
Thanks,
Joe Koshakow--
Best Wishes,
Ashutosh Bapat--
Best Wishes,
Ashutosh Bapat
--
Best Wishes,
Ashutosh Bapat
Attachments:
0002-Check-for-overflow-in-make_interval-20230627.patchtext/x-patch; charset=US-ASCII; name=0002-Check-for-overflow-in-make_interval-20230627.patchDownload
From acef69449a1c8f81f6b69f580585bf6923e84d51 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/3] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec5a..3ab9b6918e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1518,13 +1518,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..db5c7b83ba 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..434fd87a35 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.25.1
0001-Move-integer-helper-function-to-int.h-20230627.patchtext/x-patch; charset=US-ASCII; name=0001-Move-integer-helper-function-to-int.h-20230627.patchDownload
From 9472024ef0de1056e91c559277fc0deedd7a79e8 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/3] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 5d8d583ddc..dc79cbb4db 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.25.1
0003-Add-infinite-interval-values-20230627.patchtext/x-patch; charset=US-ASCII; name=0003-Add-infinite-interval-values-20230627.patchDownload
From c76bb70c489d3f49acefa69ec0eee5d18771ecc8 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/3] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 706 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1544 insertions(+), 206 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 8d32a8c9c5..507f3cb1cc 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,7 +2320,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5a47ce4343..8303aad35f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9472,7 +9472,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10369,7 +10369,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de2c..f2107dd967 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(!INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index dc79cbb4db..c39fd37b7f 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3550,6 +3550,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/formatting.c b/src/backend/utils/adt/formatting.c
index e6246dc44b..8c739833dc 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..6e87173846 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3ab9b6918e..1223a9775d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1523,6 +1545,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1544,6 +1567,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1561,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2039,6 +2078,8 @@ 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;
}
@@ -2056,6 +2097,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2089,7 +2132,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2220,6 +2265,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2715,46 +2783,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2784,6 +2877,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))
@@ -2836,6 +2932,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2859,6 +2960,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,
@@ -2876,6 +2980,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2897,6 +3006,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))
@@ -2915,6 +3027,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2935,7 +3052,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3014,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3043,7 +3181,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3133,9 +3294,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3186,6 +3345,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3193,23 +3377,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3253,27 +3421,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3287,27 +3488,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3329,9 +3565,51 @@ interval_mul(PG_FUNCTION_ARGS)
int32 orig_month = span->month,
orig_day = span->day;
Interval *result;
+ int is_factor_inf = isinf(factor);
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying infinite interval by finite number keeps it infinite but
+ * may change the sign.
+ */
+ 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);
+ }
+ else if (is_factor_inf)
+ {
+ Interval zero;
+ int result_is_inf;
+
+ memset(&zero, 0, sizeof(zero));
+ result_is_inf = interval_cmp_internal(span, &zero) * is_factor_inf;
+
+ if (result_is_inf == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_is_inf < 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)
@@ -3392,6 +3670,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3423,6 +3706,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3444,6 +3750,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3727,8 +4038,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3847,8 +4185,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3973,6 +4338,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4156,6 +4526,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4394,6 +4769,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);
@@ -4738,7 +5119,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5012,7 +5393,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5252,6 +5633,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5276,6 +5710,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5518,6 +5980,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5748,6 +6217,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5814,6 +6290,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5896,6 +6377,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8c9..fe713fb6dd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index db5c7b83ba..4bbbab6431 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,3 +1851,378 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 434fd87a35..26e8c131fd 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -587,3 +594,136 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.25.1
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> writes:
Fixed assertion in time_mi_time(). It needed to assert that the result
is FINITE but it was doing the other way round and that triggered some
failures in cfbot.
It's still not passing in the cfbot, at least not on any non-Linux
platforms. I believe the reason is that the patch thinks isinf()
delivers a three-way result, but per POSIX you can only expect
zero or nonzero (ie, finite or not).
regards, tom lane
On Sat, Jul 8, 2023 at 1:50 PM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> writes:
Fixed assertion in time_mi_time(). It needed to assert that the result
is FINITE but it was doing the other way round and that triggered some
failures in cfbot.
It's still not passing in the cfbot, at least not on any non-Linux
platforms. I believe the reason is that the patch thinks isinf()
delivers a three-way result, but per POSIX you can only expect
zero or nonzero (ie, finite or not).
That looks right to me. I've updated the patch to fix this issue.
Thanks,
Joe Koshakow
Attachments:
0001-Move-integer-helper-function-to-int.h-20230708.patchtext/x-patch; charset=US-ASCII; name=0001-Move-integer-helper-function-to-int.h-20230708.patchDownload
From 7da4853f9a68242087ecdc95a17ab02e95cfda92 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/3] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 5d8d583ddc..dc79cbb4db 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.34.1
0003-Add-infinite-interval-values-20230708.patchtext/x-patch; charset=US-ASCII; name=0003-Add-infinite-interval-values-20230708.patchDownload
From 98fb6780e3c3599523de19284cb0434e25b1ca82 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/3] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 708 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1546 insertions(+), 206 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 8d32a8c9c5..507f3cb1cc 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,7 +2320,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0b62e0c828..c14b30040f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9472,7 +9472,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10369,7 +10369,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de2c..f2107dd967 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(!INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index dc79cbb4db..c39fd37b7f 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3550,6 +3550,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/formatting.c b/src/backend/utils/adt/formatting.c
index e6246dc44b..8c739833dc 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..6e87173846 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3ab9b6918e..509e03ed04 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1523,6 +1545,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1544,6 +1567,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1561,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2039,6 +2078,8 @@ 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;
}
@@ -2056,6 +2097,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2089,7 +2132,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2220,6 +2265,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2715,46 +2783,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2784,6 +2877,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))
@@ -2836,6 +2932,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2859,6 +2960,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,
@@ -2876,6 +2980,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2897,6 +3006,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))
@@ -2915,6 +3027,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2935,7 +3052,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3014,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3043,7 +3181,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3133,9 +3294,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3186,6 +3345,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3193,23 +3377,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3253,27 +3421,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3287,27 +3488,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3329,9 +3565,53 @@ interval_mul(PG_FUNCTION_ARGS)
int32 orig_month = span->month,
orig_day = span->day;
Interval *result;
+ int is_factor_inf = isinf(factor);
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results in an
+ * infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ Interval zero;
+ int result_is_inf;
+ int factor_sign;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ memset(&zero, 0, sizeof(zero));
+ result_is_inf = interval_cmp_internal(span, &zero) * factor_sign;
+
+ if (result_is_inf == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_is_inf < 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)
@@ -3392,6 +3672,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3423,6 +3708,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3444,6 +3752,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3727,8 +4040,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3847,8 +4187,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3973,6 +4340,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4156,6 +4528,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4394,6 +4771,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);
@@ -4738,7 +5121,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5012,7 +5395,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5252,6 +5635,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5276,6 +5712,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5518,6 +5982,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5748,6 +6219,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5814,6 +6292,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5896,6 +6379,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8c9..fe713fb6dd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index db5c7b83ba..4bbbab6431 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,3 +1851,378 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 434fd87a35..26e8c131fd 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -587,3 +594,136 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
0002-Check-for-overflow-in-make_interval-20230708.patchtext/x-patch; charset=US-ASCII; name=0002-Check-for-overflow-in-make_interval-20230708.patchDownload
From 28743c5390a76b91abc6ff9ad37d4efb81bca040 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/3] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec5a..3ab9b6918e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1518,13 +1518,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..db5c7b83ba 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..434fd87a35 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.34.1
On Sat, Jul 8, 2023 at 2:38 PM Joseph Koshakow <koshy44@gmail.com> wrote:
I've updated the patch to fix this issue.
That seems to have fixed the cfbot failures. Though I left in an
unused variable. Here's another set of patches with the compiler
warnings fixed.
Thanks,
Joe Koshakow
Attachments:
0001-Move-integer-helper-function-to-int.h-20230708.patchtext/x-patch; charset=US-ASCII; name=0001-Move-integer-helper-function-to-int.h-20230708.patchDownload
From 7da4853f9a68242087ecdc95a17ab02e95cfda92 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/3] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 5d8d583ddc..dc79cbb4db 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.34.1
0003-Add-infinite-interval-values-20230708.patchtext/x-patch; charset=US-ASCII; name=0003-Add-infinite-interval-values-20230708.patchDownload
From 41064a220e04855bcd1070941069b6054c4fa0ee Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/3] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 707 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1545 insertions(+), 206 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 8d32a8c9c5..507f3cb1cc 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,7 +2320,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 0b62e0c828..c14b30040f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9472,7 +9472,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10369,7 +10369,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de2c..f2107dd967 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(!INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index dc79cbb4db..c39fd37b7f 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3550,6 +3550,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/formatting.c b/src/backend/utils/adt/formatting.c
index e6246dc44b..8c739833dc 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..6e87173846 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3ab9b6918e..b95346f6ec 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1523,6 +1545,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1544,6 +1567,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1561,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2039,6 +2078,8 @@ 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;
}
@@ -2056,6 +2097,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2089,7 +2132,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2220,6 +2265,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2715,46 +2783,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2784,6 +2877,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))
@@ -2836,6 +2932,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2859,6 +2960,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,
@@ -2876,6 +2980,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2897,6 +3006,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))
@@ -2915,6 +3027,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2935,7 +3052,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3014,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3043,7 +3181,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3133,9 +3294,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3186,6 +3345,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3193,23 +3377,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3253,27 +3421,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3287,27 +3488,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3332,6 +3568,49 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results in an
+ * infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ Interval zero;
+ int factor_sign,
+ result_is_inf;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ memset(&zero, 0, sizeof(zero));
+ result_is_inf = interval_cmp_internal(span, &zero) * factor_sign;
+
+ if (result_is_inf == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_is_inf < 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)
@@ -3392,6 +3671,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3423,6 +3707,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3444,6 +3751,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3727,8 +4039,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3847,8 +4186,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3973,6 +4339,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4156,6 +4527,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4394,6 +4770,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);
@@ -4738,7 +5120,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5012,7 +5394,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5252,6 +5634,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5276,6 +5711,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5518,6 +5981,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5748,6 +6218,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5814,6 +6291,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5896,6 +6378,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8c9..fe713fb6dd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index db5c7b83ba..4bbbab6431 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,3 +1851,378 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 434fd87a35..26e8c131fd 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -587,3 +594,136 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
0002-Check-for-overflow-in-make_interval-20230708.patchtext/x-patch; charset=US-ASCII; name=0002-Check-for-overflow-in-make_interval-20230708.patchDownload
From 28743c5390a76b91abc6ff9ad37d4efb81bca040 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/3] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec5a..3ab9b6918e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1518,13 +1518,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..db5c7b83ba 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..434fd87a35 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.34.1
The patches still apply. But here's a rebased version with one white
space error fixed. Also ran pgindent.
On Sun, Jul 9, 2023 at 12:58 AM Joseph Koshakow <koshy44@gmail.com> wrote:
On Sat, Jul 8, 2023 at 2:38 PM Joseph Koshakow <koshy44@gmail.com> wrote:
I've updated the patch to fix this issue.
That seems to have fixed the cfbot failures. Though I left in an
unused variable. Here's another set of patches with the compiler
warnings fixed.Thanks,
Joe Koshakow
--
Best Wishes,
Ashutosh Bapat
Attachments:
0001-Move-integer-helper-function-to-int.h-20230824.patchtext/x-patch; charset=US-ASCII; name=0001-Move-integer-helper-function-to-int.h-20230824.patchDownload
From 9a1a712d296e394d39f21f5047f922177563ce7b Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/3] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 5d8d583ddc..dc79cbb4db 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.25.1
0002-Check-for-overflow-in-make_interval-20230824.patchtext/x-patch; charset=US-ASCII; name=0002-Check-for-overflow-in-make_interval-20230824.patchDownload
From 9bba1af3f9411cf109368817214e987d9b89237a Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/3] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec5a..3ab9b6918e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1518,13 +1518,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 28b71d9681..db5c7b83ba 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 56feda1a3d..434fd87a35 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.25.1
0003-Add-infinite-interval-values-20230824.patchtext/x-patch; charset=US-ASCII; name=0003-Add-infinite-interval-values-20230824.patchDownload
From f506833c3b79cc7f6aae3e5e21aee445e83079a1 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/3] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 707 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1545 insertions(+), 206 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 8d32a8c9c5..507f3cb1cc 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,7 +2320,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 5a47ce4343..8303aad35f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9472,7 +9472,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10369,7 +10369,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de2c..f2107dd967 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(!INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index dc79cbb4db..c39fd37b7f 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3550,6 +3550,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/formatting.c b/src/backend/utils/adt/formatting.c
index e6246dc44b..8c739833dc 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4145,7 +4145,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..6e87173846 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3ab9b6918e..02587e5f93 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1523,6 +1545,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1544,6 +1567,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1561,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2039,6 +2078,8 @@ 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;
}
@@ -2056,6 +2097,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2089,7 +2132,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2220,6 +2265,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2715,46 +2783,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2784,6 +2877,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))
@@ -2836,6 +2932,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2859,6 +2960,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,
@@ -2876,6 +2980,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2897,6 +3006,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))
@@ -2915,6 +3027,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2935,7 +3052,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3014,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3043,7 +3181,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3133,9 +3294,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3186,6 +3345,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3193,23 +3377,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3253,27 +3421,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3287,27 +3488,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3332,6 +3568,49 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results
+ * in an infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ Interval zero;
+ int factor_sign,
+ result_is_inf;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ memset(&zero, 0, sizeof(zero));
+ result_is_inf = interval_cmp_internal(span, &zero) * factor_sign;
+
+ if (result_is_inf == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_is_inf < 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)
@@ -3392,6 +3671,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3423,6 +3707,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3444,6 +3751,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3727,8 +4039,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3847,8 +4186,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3973,6 +4339,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4156,6 +4527,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4394,6 +4770,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);
@@ -4738,7 +5120,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5012,7 +5394,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5252,6 +5634,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5276,6 +5711,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5518,6 +5981,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5748,6 +6218,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5814,6 +6291,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5896,6 +6378,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8c9..fe713fb6dd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index db5c7b83ba..4bbbab6431 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,3 +1851,378 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 434fd87a35..26e8c131fd 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -587,3 +594,136 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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');
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.25.1
On Thu, 24 Aug 2023 at 14:51, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
The patches still apply. But here's a rebased version with one white
space error fixed. Also ran pgindent.
This needs another rebase, and it looks like the infinite interval
input code is broken.
I took a quick look, and had a couple of other review comments:
1). In interval_mul(), I think "result_sign" would be a more accurate
name than "result_is_inf" for the local variable.
2). interval_accum() and interval_accum_inv() don't work correctly
with infinite intervals. To make them work, they need to count the
number of infinities seen, to allow them to be subtracted off by the
inverse function (similar to the code in numeric.c, except for the
NaN-handling, which will need to be different). Consider, for example:
SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
FROM (VALUES ('1 day'::interval),
('3 days'::interval),
('infinity'::timestamptz - now()),
('4 days'::interval),
('6 days'::interval)) v(x);
ERROR: interval out of range
as compared to:
SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
FROM (VALUES (1::numeric),
(3::numeric),
('infinity'::numeric),
(4::numeric),
(6::numeric)) v(x);
x | avg
----------+--------------------
1 | 2.0000000000000000
3 | Infinity
Infinity | Infinity
4 | 5.0000000000000000
6 | 6.0000000000000000
(5 rows)
Regards,
Dean
On Tue, Sep 12, 2023 at 2:39 PM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Thu, 24 Aug 2023 at 14:51, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:The patches still apply. But here's a rebased version with one white
space error fixed. Also ran pgindent.This needs another rebase,
Fixed.
and it looks like the infinite interval
input code is broken.
The code required to handle 'infinity' as an input value was removed by
d6d1430f404386162831bc32906ad174b2007776. I have added a separate
commit which reverts that commit as 0004, which should be merged into
0003.
I took a quick look, and had a couple of other review comments:
1). In interval_mul(), I think "result_sign" would be a more accurate
name than "result_is_inf" for the local variable.
Fixed as part of 0003.
2). interval_accum() and interval_accum_inv() don't work correctly
with infinite intervals. To make them work, they need to count the
number of infinities seen, to allow them to be subtracted off by the
inverse function (similar to the code in numeric.c, except for the
NaN-handling, which will need to be different). Consider, for example:SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
FROM (VALUES ('1 day'::interval),
('3 days'::interval),
('infinity'::timestamptz - now()),
('4 days'::interval),
('6 days'::interval)) v(x);
ERROR: interval out of rangeas compared to:
SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING)
FROM (VALUES (1::numeric),
(3::numeric),
('infinity'::numeric),
(4::numeric),
(6::numeric)) v(x);x | avg
----------+--------------------
1 | 2.0000000000000000
3 | Infinity
Infinity | Infinity
4 | 5.0000000000000000
6 | 6.0000000000000000
(5 rows)
Nice catch. I agree that we need to do something similar to
numeric_accum and numeric_accum_inv. As part of that also add test for
window aggregates on interval data type. We might also need some fix
to sum(). I am planning to work on this next week but in case somebody
else wants to pick this up here are patches with other things fixed.
--
Best Wishes,
Ashutosh Bapat
Attachments:
0002-Check-for-overflow-in-make_interval-20230913.patchtext/x-patch; charset=US-ASCII; name=0002-Check-for-overflow-in-make_interval-20230913.patchDownload
From 7e5ec988a6a300ff458a13b514920471433a1408 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/4] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec5a..3ab9b6918e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1518,13 +1518,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index c0ca8e041b..96db07c3b4 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 038fc508d0..f328b8914d 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.25.1
0001-Move-integer-helper-function-to-int.h-20230913.patchtext/x-patch; charset=US-ASCII; name=0001-Move-integer-helper-function-to-int.h-20230913.patchDownload
From 74d2b7e42f72c2727f35aec9a1d68a8a6e738bdd Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/4] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..48fc0d5942 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.25.1
0004-Revert-Remove-dead-code-in-DecodeInterval-20230913.patchtext/x-patch; charset=US-ASCII; name=0004-Revert-Remove-dead-code-in-DecodeInterval-20230913.patchDownload
From c3c8708c0ddfa1c1e256af46c153ba740606dac3 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 13 Sep 2023 12:43:49 +0530
Subject: [PATCH 4/4] Revert "Remove dead code in DecodeInterval()"
This reverts commit d6d1430f404386162831bc32906ad174b2007776 since
RESERVE is required to support infinite intervals. 'infinity' is saved
as a RESERVE in datetktbl[], thus required to parse
'infinity'::interval.
Ashutosh Bapat
---
src/backend/utils/adt/datetime.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 5fed7e3dd1..5b8b080a32 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3582,6 +3582,11 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
--
2.25.1
0003-Add-infinite-interval-values-20230913.patchtext/x-patch; charset=US-ASCII; name=0003-Add-infinite-interval-values-20230913.patchDownload
From 9d95c38c39c026719505934031c372429c4a2793 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/4] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 707 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1545 insertions(+), 206 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 8d32a8c9c5..507f3cb1cc 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,7 +2320,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 24ad87f910..7786853743 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9537,7 +9537,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10434,7 +10434,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de2c..f2107dd967 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(!INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 48fc0d5942..5fed7e3dd1 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3557,6 +3557,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index e27ea8ef97..e9e85503f8 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..6e87173846 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3ab9b6918e..3100ad610f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1523,6 +1545,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1544,6 +1567,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1561,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2039,6 +2078,8 @@ 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;
}
@@ -2056,6 +2097,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2089,7 +2132,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2220,6 +2265,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2715,46 +2783,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2784,6 +2877,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))
@@ -2836,6 +2932,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2859,6 +2960,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,
@@ -2876,6 +2980,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2897,6 +3006,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))
@@ -2915,6 +3027,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2935,7 +3052,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3014,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3043,7 +3181,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3133,9 +3294,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3186,6 +3345,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3193,23 +3377,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3253,27 +3421,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3287,27 +3488,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3332,6 +3568,49 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results
+ * in an infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ Interval zero;
+ int factor_sign,
+ result_sign;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ memset(&zero, 0, sizeof(zero));
+ result_sign = interval_cmp_internal(span, &zero) * factor_sign;
+
+ if (result_sign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_sign < 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)
@@ -3392,6 +3671,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3423,6 +3707,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3444,6 +3751,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3727,8 +4039,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3847,8 +4186,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3973,6 +4339,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4156,6 +4527,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4394,6 +4770,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);
@@ -4738,7 +5120,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5012,7 +5394,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5252,6 +5634,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5276,6 +5711,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5518,6 +5981,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5748,6 +6218,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5814,6 +6291,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5896,6 +6378,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8c9..fe713fb6dd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 96db07c3b4..a4630d17ef 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,6 +1851,381 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+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"
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index f328b8914d..023cf3761a 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -588,6 +595,139 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.25.1
On Wed, Sep 13, 2023 at 6:13 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
to sum(). I am planning to work on this next week but in case somebody
else wants to pick this up here are patches with other things fixed.--
Best Wishes,
Ashutosh Bapat
hi. some doc issues.
- <literal>decade</literal>, <literal>century</literal>, and
<literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and
<literal>millennium</literal>
+ for all types and <literal>hour</literal> and
<literal>day</literal> just for <type>interval</type>).
The above part seems not right. some fields do not apply to interval data types.
test case:
SELECT EXTRACT(epoch FROM interval 'infinity') as epoch
,EXTRACT(YEAR FROM interval 'infinity') as year
,EXTRACT(decade FROM interval 'infinity') as decade
,EXTRACT(century FROM interval 'infinity') as century
,EXTRACT(millennium FROM interval 'infinity') as millennium
,EXTRACT(month FROM interval 'infinity') as mon
,EXTRACT(day FROM interval 'infinity') as day
,EXTRACT(hour FROM interval 'infinity') as hour
,EXTRACT(min FROM interval 'infinity') as min
,EXTRACT(second FROM interval 'infinity') as sec;
--------------------
- <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>
it seems we have forgotten to mention the -infinity case, we can fix
the doc together, since <type>timestamptz</type> also applies to
+/-infinity.
hi.
fixed the doc special value inf/-inf reference. didn't fix the EXTRACT
function doc issue.
I refactor the avg(interval), sum(interval), so moving aggregate,
plain aggregate both work with +inf/-inf.
no performance degradation, in fact, some performance gains.
--setup for test performance.
create unlogged table interval_aggtest AS
select g::int as a
,make_interval(years => g % 100, days => g % 100, hours => g %
200 , secs => random()::numeric(3,2) *100 ) as b
from generate_series(1, 100_000) g;
--use foreign data wrapper to copy exact content to interval_aggtest_no_patch
create unlogged table interval_aggtest_no_patch AS
select * from interval_aggtest;
--queryA
explain (analyze, costs off, buffers)
SELECT a, avg(b) OVER(ROWS BETWEEN 1 preceding AND 2 FOLLOWING)
from interval_aggtest \watch i=0.1 c=10
--queryB
explain (analyze, costs off, buffers)
SELECT a, avg(b) OVER(ROWS BETWEEN 1 preceding AND 2 FOLLOWING)
from interval_aggtest_no_patch \watch i=0.1 c=10
--queryC
explain (analyze, costs off, buffers)
SELECT a, sum(b) OVER(ROWS BETWEEN 1 preceding AND 2 FOLLOWING)
from interval_aggtest \watch i=0.1 c=10
--queryD
explain (analyze, costs off, buffers)
SELECT a, sum(b) OVER(ROWS BETWEEN 1 preceding AND 2 FOLLOWING)
from interval_aggtest_no_patch \watch i=0.1 c=10
--queryE
explain (analyze, costs off, buffers)
SELECT sum(b), avg(b)
from interval_aggtest \watch i=0.1 c=10
--queryF
explain (analyze, costs off, buffers)
SELECT sum(b), avg(b)
from interval_aggtest_no_patch \watch i=0.1 c=10
queryA execute 10 time, last executed time(ms) 748.258
queryB execute 10 time, last executed time(ms) 1059.750
queryC execute 10 time, last executed time(ms) 697.887
queryD execute 10 time, last executed time(ms) 708.462
queryE execute 10 time, last executed time(ms) 156.237
queryF execute 10 time, last executed time(ms) 405.451
---------------------------------------------------------------------
The result seems right, I am not %100 sure the code it's correct.
That's the best I can think of. You can work based on that.
Attachments:
v20-0005-doc-for-special-interval-value.patchtext/x-patch; charset=US-ASCII; name=v20-0005-doc-for-special-interval-value.patchDownload
From 71908c3d8a06f405c27e2e09927c66bbafb5d80b Mon Sep 17 00:00:00 2001
From: pgaddict <jian.universality@gmail.com>
Date: Fri, 15 Sep 2023 16:38:03 +0800
Subject: [PATCH v20 5/6] doc for special interval value.
interval now have special values: infinity and -infinity.
document it.
---
doc/src/sgml/datatype.sgml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 507f3cb1..c2aac35f 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,12 +2320,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>timestamptz</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>timestamptz</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
--
2.34.1
v20-0004-Revert-Remove-dead-code-in-DecodeInterval.patchtext/x-patch; charset=US-ASCII; name=v20-0004-Revert-Remove-dead-code-in-DecodeInterval.patchDownload
From 17cee14a9eae900ff168dd40de30d9a370e5bd4e Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 13 Sep 2023 12:43:49 +0530
Subject: [PATCH v20 4/6] Revert "Remove dead code in DecodeInterval()"
This reverts commit d6d1430f404386162831bc32906ad174b2007776 since
RESERVE is required to support infinite intervals. 'infinity' is saved
as a RESERVE in datetktbl[], thus required to parse
'infinity'::interval.
Ashutosh Bapat
---
src/backend/utils/adt/datetime.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 5fed7e3d..5b8b080a 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3582,6 +3582,11 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
--
2.34.1
v20-0002-Check-for-overflow-in-make_interval.patchtext/x-patch; charset=US-ASCII; name=v20-0002-Check-for-overflow-in-make_interval.patchDownload
From 93ffbeb2b10cec642e855b960d76aba62f8a2496 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH v20 2/6] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec..3ab9b691 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1518,13 +1518,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65..48ef4955 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89..1a639058 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index c0ca8e04..96db07c3 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 038fc508..f328b891 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.34.1
v20-0003-Add-infinite-interval-values.patchtext/x-patch; charset=US-ASCII; name=v20-0003-Add-infinite-interval-values.patchDownload
From 6984586effbaad33b895b7c41064835f5f10a0b3 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH v20 3/6] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 709 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1546 insertions(+), 207 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 8d32a8c9..507f3cb1 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,7 +2320,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 24ad87f9..77868537 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9537,7 +9537,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10434,7 +10434,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de..f2107dd9 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(!INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 48fc0d59..5fed7e3d 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3557,6 +3557,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index e27ea8ef..e9e85503 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd007..6e871738 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3ab9b691..3100ad61 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1523,6 +1545,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1544,6 +1567,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1561,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2039,6 +2078,8 @@ 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;
}
@@ -2056,6 +2097,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2089,7 +2132,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2220,6 +2265,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2715,46 +2783,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->month = 0;
- result->day = 0;
-
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
*/
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ result->month = 0;
+ result->day = 0;
+
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2784,6 +2877,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))
@@ -2836,6 +2932,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2859,6 +2960,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,
@@ -2876,6 +2980,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2897,6 +3006,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))
@@ -2915,6 +3027,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2935,7 +3052,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3014,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3043,7 +3181,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3133,9 +3294,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3186,6 +3345,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3193,23 +3377,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3253,27 +3421,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3287,27 +3488,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3332,6 +3568,49 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results
+ * in an infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ Interval zero;
+ int factor_sign,
+ result_sign;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ memset(&zero, 0, sizeof(zero));
+ result_sign = interval_cmp_internal(span, &zero) * factor_sign;
+
+ if (result_sign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_sign < 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)
@@ -3392,6 +3671,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3423,6 +3707,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3444,6 +3751,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3727,8 +4039,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3847,8 +4186,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3973,6 +4339,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4156,6 +4527,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4394,6 +4770,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);
@@ -4738,7 +5120,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5012,7 +5394,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5252,6 +5634,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5276,6 +5711,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5518,6 +5981,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5748,6 +6218,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5814,6 +6291,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5896,6 +6378,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a639058..f9bd30d2 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8..fe713fb6 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30..eaf44b96 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 96db07c3..a4630d17 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,6 +1851,381 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+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"
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c..53542e07 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c..825202d5 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2..0b4071f3 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index f328b891..023cf376 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -588,6 +595,139 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9c..ea12ffd1 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d04..7876225c 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
v20-0001-Move-integer-helper-function-to-int.h.patchtext/x-patch; charset=US-ASCII; name=v20-0001-Move-integer-helper-function-to-int.h.patchDownload
From 6b10acce408d5967ab67e2d6d52342ac3ebc905c Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH v20 1/6] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37..48fc0d59 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 45080089..81726c65 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.34.1
v20-0006-refactor-avg-interval-sum-interval-aggregate.patchtext/x-patch; charset=US-ASCII; name=v20-0006-refactor-avg-interval-sum-interval-aggregate.patchDownload
From 0d1ca2f1ee2ef76bab1927b59a2f45b0f20d5c79 Mon Sep 17 00:00:00 2001
From: pgaddict <jian.universality@gmail.com>
Date: Fri, 15 Sep 2023 17:28:29 +0800
Subject: [PATCH v20 6/6] refactor avg(interval), sum(interval) aggregate.
to make avg(interval), sum(interval) window function also work
when intervals have special values: inf/-inf,
We need a state to live through the whole aggregate function cycle.
the state will accumulate or discard value.
the aggregate transition and inverse transition function associated
with interval type will update the state all the time.
the aggregate final function will compute the final result.
We already do the same logic in many data types like numeric.
so invent a new struct: IntervalAggState.
it will be initialized in aggregate memory context
(I don't know when it will be destroyed).
For avg(interval), sum(interval) every time accumulate a new interval value,
change the state struct inner
elements value.
In IntervalAggStat we also track the number of special values.
In the end, using the aggregate final function returns the result.
To make window function work,
we need to have an inverse transition function that can discard an interval from the state.
tests include order by and non-order by window function case.
also invent the sum(interval) aggregate final function.
---
src/backend/utils/adt/timestamp.c | 455 +++++++++++++++++++--------
src/include/catalog/pg_aggregate.dat | 21 +-
src/include/catalog/pg_proc.dat | 21 +-
src/test/regress/expected/window.out | 64 ++++
src/test/regress/sql/window.sql | 45 +++
5 files changed, 452 insertions(+), 154 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3100ad61..8fe1b60d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -73,7 +73,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+typedef struct IntervalAggState
+{
+ MemoryContext agg_context; /* context we're calculating in */
+ int64 N; /* count of processed numbers */
+ Interval sumX; /* sum of processed intervals */
+ /* These counts are *not* included in N! Use INF_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +Inf values */
+ int64 nInfcount; /* count of -Inf values */
+} IntervalAggState;
+#define INF_TOTAL_COUNT(na) \
+ ((na)->N + (na)->pInfcount + (na)->nInfcount)
+
+static void do_interval_accum(IntervalAggState *state, Interval *newval);
+static IntervalAggState *makeIntervalAggState(FunctionCallInfo fcinfo);
static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
static Timestamp dt2local(Timestamp dt, int timezone);
static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
@@ -3860,161 +3874,328 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for a interval aggregate function that needs to compute
+ * sum, count, avg.
+ */
+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));
+ state->agg_context = agg_context;
+
+ MemoryContextSwitchTo(old_context);
+
+ return state;
+}
+
+/*
+ * actual do the interval accumualation.
+ * if to be added interval value is not finite then record.
+ * state's infinity/-infinity count, else do interval addidition.
+ */
+static void
+do_interval_accum(IntervalAggState *state, Interval *newval)
+{
+
+ Interval *X;
+ Interval *p;
+/* i think if temp as interval pointer, following interval addition will not work.*/
+ Interval temp;
+ MemoryContext old_context;
+
+ X = (Interval *) palloc(sizeof(Interval));
+
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN (newval))
+ {
+ state->nInfcount++;
+ return;
+ }
+
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount++;
+ return;
+ }
+
+ X->day = newval->day;
+ X->month = newval->month;
+ X->time = newval->time;
+
+ temp.day = state->sumX.day;
+ temp.month = state->sumX.month;
+ temp.time = state->sumX.time;
+
+ /* The rest of this needs to work in the aggregate context */
+ old_context = MemoryContextSwitchTo(state->agg_context);
+
+ state->N++;
+
+ /* Accumulate sums */
+ p = &state->sumX;
+ {
+ p->month = temp.month + X->month;
+ /* overflow check copied from int4pl */
+ if (SAMESIGN(temp.month, X->month) &&
+ !SAMESIGN(p->month, temp.month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ p->day = temp.day + X->day;
+ if (SAMESIGN(temp.day, X->day) &&
+ !SAMESIGN(p->day, temp.day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ p->time = temp.time + X->time;
+ if (SAMESIGN(temp.time, X->time) &&
+ !SAMESIGN(p->time, temp.time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+
+ MemoryContextSwitchTo(old_context);
+ return;
+}
+
+/*
+ * transition function for AVG(interval) aggregate.
+ */
+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 to conmbine 2 interval aggreagte state.
+ */
+Datum
+interval_avg_combine(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state1;
+ IntervalAggState *state2;
+ MemoryContext agg_context;
+ MemoryContext old_context;
+
+ if (!AggCheckCallContext(fcinfo, &agg_context))
+ elog(ERROR, "aggregate function called in non-aggregate context");
+
+ 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);
+
+ /* manually copy all fields from state2 to state1 */
+ if (state1 == NULL)
+ {
+ old_context = MemoryContextSwitchTo(agg_context);
+
+ state1 = makeIntervalAggState(fcinfo);
+ state1->agg_context = CurrentMemoryContext;
+
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
+
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
+
+ MemoryContextSwitchTo(old_context);
+
+ PG_RETURN_POINTER(state1);
+ }
+
+ state1->N += state2->N;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
+
+ if (state2->N > 0)
+ {
+ /* The rest of this needs to work in the aggregate context */
+ old_context = MemoryContextSwitchTo(agg_context);
+
+ /* Accumulate interval values */
+ do_interval_accum(state1, &state2->sumX);
+
+ MemoryContextSwitchTo(old_context);
+ }
+ PG_RETURN_POINTER(state1);
+}
+
+static bool
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+ Interval *X;
+ MemoryContext old_context;
+
+ X = (Interval *) palloc(sizeof(Interval));
+
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN (newval))
+ {
+ state->nInfcount--;
+ return true;
+ }
+
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount--;
+ return true;
+ }
+
+ /* the value to be discarded is not special. assign to X */
+ X->day = -newval->day;
+ X->month = -newval->month;
+ X->time = -newval->time;
+
+ state->N--;
+ if (state->N > 0)
+ {
+ /* The rest of this needs to work in the aggregate context */
+ old_context = MemoryContextSwitchTo(state->agg_context);
+
+ do_interval_accum(state,X);
+
+ /* do_interval_accum increase state->N by one, we need decrease one */
+ state->N--;
+ MemoryContextSwitchTo(old_context);
+ }
+ else
+ {
+ /*now all values discarded, reset now */
+ memset(&state->sumX, 0, sizeof(state->sumX));
+ }
+
+ return true;
+}
+
+/*
+ * Generic inverse transition function for interval aggregates
*
- * 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.
*/
-
-Datum
-interval_accum(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;
-
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
-
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
-
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
-
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
-
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
-
- PG_RETURN_ARRAYTYPE_P(result);
-}
-
-Datum
-interval_combine(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;
-
- Interval *newsum;
- ArrayType *result;
-
- deconstruct_array(transarray1,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums1, NULL, &ndatums1);
- if (ndatums1 != 2)
- elog(ERROR, "expected 2-element interval array");
-
- sum1 = *(DatumGetIntervalP(transdatums1[0]));
- N1 = *(DatumGetIntervalP(transdatums1[1]));
-
- deconstruct_array(transarray2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums2, NULL, &ndatums2);
- if (ndatums2 != 2)
- elog(ERROR, "expected 2-element interval array");
-
- sum2 = *(DatumGetIntervalP(transdatums2[0]));
- N2 = *(DatumGetIntervalP(transdatums2[1]));
-
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sum1),
- IntervalPGetDatum(&sum2)));
- N1.time += N2.time;
-
- transdatums1[0] = IntervalPGetDatum(newsum);
- transdatums1[1] = IntervalPGetDatum(&N1);
-
- result = construct_array(transdatums1, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
-
- PG_RETURN_ARRAYTYPE_P(result);
-}
-
Datum
interval_accum_inv(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Should not get here with no state */
+ if (state == NULL)
+ elog(ERROR, "interval_accum_inv called with NULL state");
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ if (!PG_ARGISNULL(1))
+ {
+ /* If we fail to perform the inverse transition, return NULL */
+ if (!do_interval_discard(state, PG_GETARG_INTERVAL_P(1)))
+ PG_RETURN_NULL();
+ }
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
-
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
-
- PG_RETURN_ARRAYTYPE_P(result);
+ PG_RETURN_POINTER(state);
}
+/* avg(interval) aggregate final function */
Datum
interval_avg(PG_FUNCTION_ARGS)
{
- ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
- Datum *transdatums;
- int ndatums;
- Interval sumX,
- N;
-
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
-
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
-
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ IntervalAggState *state;
+ double N_datum;
+ Interval *sumX;
+
+ sumX = (Interval *) palloc(sizeof(Interval));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ if (state == NULL || INF_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
+ /* adding plus and minus infinities gives error */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range."))); /* maybe we should make it more verbose */
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ N_datum = (double) state->N;
+ sumX = &state->sumX;
+
+ PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX), Float8GetDatum(N_datum)));
}
+/* sum(interval) aggregate final function */
+Datum
+interval_sum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ Interval *sumX;
+
+ sumX = (Interval *) palloc(sizeof(Interval));
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || INF_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
+
+ /* adding plus and minus infinities should yield error */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range."))); /* maybe we should make it more verbose */
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ sumX = &state->sumX;
+ PG_RETURN_INTERVAL_P(sumX);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d..905359b3 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,12 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
+ aggmtranstype => 'internal'
+},
# sum
{ aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
@@ -72,10 +72,11 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
+ aggmtranstype => 'internal'},
{ aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
aggserialfn => 'numeric_avg_serialize',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc61..92857368 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -2356,6 +2356,7 @@
{ oid => '1169',
proname => 'interval_pl', prorettype => 'interval',
proargtypes => 'interval interval', prosrc => 'interval_pl' },
+
{ oid => '1170',
proname => 'interval_mi', prorettype => 'interval',
proargtypes => 'interval interval', prosrc => 'interval_mi' },
@@ -4914,17 +4915,23 @@
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_accum_inv', proisstrict => 'f',
+ prorettype => 'internal', proargtypes => 'internal interval',
+ prosrc => 'interval_accum_inv' },
{ oid => '1844', descr => 'aggregate final function',
proname => 'interval_avg', prorettype => 'interval',
- proargtypes => '_interval', prosrc => 'interval_avg' },
+ proargtypes => 'internal', prosrc => 'interval_avg' },
+{ oid => '8069', 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' },
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df1..85b21d7a 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,6 +4375,70 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev1_curr_sum | curr_next_avg | prev1_curr_avg
+-----------+---------------+----------------+---------------+----------------
+ -infinity | -infinity | -infinity | -infinity | -infinity
+ @ 6 days | infinity | -infinity | infinity | -infinity
+ infinity | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity
+ | | infinity | | infinity
+ | | | |
+(6 rows)
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | curr_next_sum | prev1_curr_avg | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum
+-----------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
+ | infinity | infinity | | | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | @ 13 days | infinity | infinity | infinity | infinity
+ @ 7 days | @ 7 days | @ 7 days | @ 6 days 12 hours | @ 13 days | -infinity | -infinity
+ | -infinity | -infinity | @ 7 days | @ 7 days | -infinity | -infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity | -infinity | -infinity
+(7 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
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92a..d23370a1 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,6 +1591,51 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('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);
--
2.34.1
On Thu, Sep 14, 2023 at 11:58 AM jian he <jian.universality@gmail.com> wrote:
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>). + <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal> + for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
It seems you have changed a paragraph from
https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-INTERVAL-INPUT.
But that section is only for interval "8.5.4. Interval Input ". So
mentioning " ... for all types ..." wouldn't fit the section's title.
I don't see why it needs to be changed.
The above part seems not right. some fields do not apply to interval data types.
test case:
SELECT EXTRACT(epoch FROM interval 'infinity') as epoch
,EXTRACT(YEAR FROM interval 'infinity') as year
,EXTRACT(decade FROM interval 'infinity') as decade
,EXTRACT(century FROM interval 'infinity') as century
,EXTRACT(millennium FROM interval 'infinity') as millennium
,EXTRACT(month FROM interval 'infinity') as mon
,EXTRACT(day FROM interval 'infinity') as day
,EXTRACT(hour FROM interval 'infinity') as hour
,EXTRACT(min FROM interval 'infinity') as min
,EXTRACT(second FROM interval 'infinity') as sec;
For this query, I get output
#SELECT EXTRACT(epoch FROM interval 'infinity') as epoch
,EXTRACT(YEAR FROM interval 'infinity') as year
,EXTRACT(decade FROM interval 'infinity') as decade
,EXTRACT(century FROM interval 'infinity') as century
,EXTRACT(millennium FROM interval 'infinity') as millennium
,EXTRACT(month FROM interval 'infinity') as mon
,EXTRACT(day FROM timestamp 'infinity') as day
,EXTRACT(hour FROM interval 'infinity') as hour
,EXTRACT(min FROM interval 'infinity') as min
,EXTRACT(second FROM interval 'infinity') as sec;
epoch | year | decade | century | millennium | mon | day |
hour | min | sec
----------+----------+----------+----------+------------+-----+-----+----------+-----+-----
Infinity | Infinity | Infinity | Infinity | Infinity | | |
Infinity | |
EXTRACT( .... FROM interval '[-]infinity') is implemented similar to
EXTRACT (... FROM timestamp '[-]infinity). Hence this is the output.
This has been discussed earlier [1]/messages/by-id/CAExHW5ut4bR4KSNWAhXb_EZ8PyY=J100guA6ZumNhvoia1ZRjw@mail.gmail.com.
--------------------
- <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>it seems we have forgotten to mention the -infinity case, we can fix
the doc together, since <type>timestamptz</type> also applies to
+/-infinity.
Your point about -infinity is right. But timestamp corresponds to both
timestamp with and without timezone as per table 8.9 on the same page
. https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME-TABLE.
So I don't see a need to specify timestamptz separately.
[1]: /messages/by-id/CAExHW5ut4bR4KSNWAhXb_EZ8PyY=J100guA6ZumNhvoia1ZRjw@mail.gmail.com
--
Best Wishes,
Ashutosh Bapat
On Wed, 13 Sept 2023 at 11:13, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
On Tue, Sep 12, 2023 at 2:39 PM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
and it looks like the infinite interval
input code is broken.The code required to handle 'infinity' as an input value was removed by
d6d1430f404386162831bc32906ad174b2007776. I have added a separate
commit which reverts that commit as 0004, which should be merged into
0003.
I think that simply reverting d6d1430f404386162831bc32906ad174b2007776
is not sufficient. This does not make it clear what the point is of
the code in the "case RESERV" block. That code really should check the
value returned by DecodeSpecial(), otherwise invalid inputs are not
caught until later, and the error reported is not ideal. For example:
select interval 'now';
ERROR: unexpected dtype 12 while parsing interval "now"
So DecodeInterval() should return DTERR_BAD_FORMAT in such cases (see
similar code in DecodeTimeOnly(), for example).
I'd also suggest a comment to indicate why itm_in isn't updated in
this case (see similar case in DecodeDateTime(), for example).
Another point to consider is what should happen if "ago" is specified
with infinite inputs. As it stands, it is accepted, but does nothing:
select interval 'infinity ago';
interval
----------
infinity
(1 row)
select interval '-infinity ago';
interval
-----------
-infinity
(1 row)
This could be made to invert the sign, as it does for finite inputs,
but I think perhaps it would be better to simply reject such inputs.
Regards,
Dean
On Sat, 16 Sept 2023 at 01:00, jian he <jian.universality@gmail.com> wrote:
I refactor the avg(interval), sum(interval), so moving aggregate,
plain aggregate both work with +inf/-inf.
no performance degradation, in fact, some performance gains.
I haven't reviewed this part in any detail yet, but I can confirm that
there are some impressive performance improvements for avg(). However,
for me, sum() seems to be consistently a few percent slower with this
patch.
The introduction of an internal transition state struct seems like a
promising approach, but I think there is more to be gained by
eliminating per-row pallocs, and IntervalAggState's MemoryContext
(interval addition, unlike numeric addition, doesn't require memory
allocation, right?).
Also, this needs to include serialization and deserialization
functions, otherwise these aggregates will no longer be able to use
parallel workers. That makes a big difference to queryE, if the size
of the test data is scaled up.
This comment:
+ int64 N; /* count of processed numbers */
should be "count of processed intervals".
Regards,
Dean
On Tue, Sep 19, 2023 at 7:14 PM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
I haven't reviewed this part in any detail yet, but I can confirm that
there are some impressive performance improvements for avg(). However,
for me, sum() seems to be consistently a few percent slower with this
patch.
Now there should be no degradation.
The introduction of an internal transition state struct seems like a
promising approach, but I think there is more to be gained by
eliminating per-row pallocs, and IntervalAggState's MemoryContext
(interval addition, unlike numeric addition, doesn't require memory
allocation, right?).
"eliminating per-row pallocs"
I guess I understand. If not , please point it out.
IntervalAggState's MemoryContext
(interval addition, unlike numeric addition, doesn't require memory
allocation, right?).
if I remove IntervalAggState's element: MemoryContext, it will not work.
so I don't understand what the above sentence means...... Sorry. (it's
my problem)
Also, this needs to include serialization and deserialization
functions, otherwise these aggregates will no longer be able to use
parallel workers. That makes a big difference to queryE, if the size
of the test data is scaled up.
I tried, but failed. sum(interval) result is correct, but
avg(interval) result is wrong.
Datum
interval_avg_serialize(PG_FUNCTION_ARGS)
{
IntervalAggState *state;
StringInfoData buf;
bytea *result;
/* Ensure we disallow calling when not in aggregate context */
if (!AggCheckCallContext(fcinfo, NULL))
elog(ERROR, "aggregate function called in non-aggregate context");
state = (IntervalAggState *) PG_GETARG_POINTER(0);
pq_begintypsend(&buf);
/* N */
pq_sendint64(&buf, state->N);
/* Interval struct elements, one by one. */
pq_sendint64(&buf, state->sumX.time);
pq_sendint32(&buf, state->sumX.day);
pq_sendint32(&buf, state->sumX.month);
/* pInfcount */
pq_sendint64(&buf, state->pInfcount);
/* nInfcount */
pq_sendint64(&buf, state->nInfcount);
result = pq_endtypsend(&buf);
PG_RETURN_BYTEA_P(result);
}
SELECT sum(b) ,avg(b)
,avg(b) = sum(b)/count(*) as should_be_true
,avg(b) * count(*) = sum(b) as should_be_true_too
from interval_aggtest_1m; --1million row.
The above query expects two bool columns to return true, but actually
both returns false.(spend some time found out parallel mode will make
the number of rows to 1_000_002, should be 1_000_0000).
This comment:
+ int64 N; /* count of processed numbers */
should be "count of processed intervals".
fixed.
Attachments:
v21-0001-Move-integer-helper-function-to-int.h.patchtext/x-patch; charset=US-ASCII; name=v21-0001-Move-integer-helper-function-to-int.h.patchDownload
From 076611d9813cbb6fc62f61f8dcf773ef8de2f515 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH v21 1/6] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37..48fc0d59 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 45080089..81726c65 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.34.1
v21-0002-Check-for-overflow-in-make_interval.patchtext/x-patch; charset=US-ASCII; name=v21-0002-Check-for-overflow-in-make_interval.patchDownload
From 45d801e4fc8f0de9aa4039f315e8e922493db187 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH v21 2/6] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec..3ab9b691 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1518,13 +1518,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65..48ef4955 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89..1a639058 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index c0ca8e04..96db07c3 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 038fc508..f328b891 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.34.1
v21-0005-doc-for-special-interval-value.patchtext/x-patch; charset=US-ASCII; name=v21-0005-doc-for-special-interval-value.patchDownload
From 2c61ea5039a5d603c8c723157b9fdf1c196117af Mon Sep 17 00:00:00 2001
From: pgaddict <jian.universality@gmail.com>
Date: Fri, 15 Sep 2023 16:38:03 +0800
Subject: [PATCH v21 5/6] doc for special interval value.
interval now have special values: infinity and -infinity.
document it.
---
doc/src/sgml/datatype.sgml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 507f3cb1..c2aac35f 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,12 +2320,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type>, <type>interval</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>
--
2.34.1
v21-0003-Add-infinite-interval-values.patchtext/x-patch; charset=US-ASCII; name=v21-0003-Add-infinite-interval-values.patchDownload
From 7992f2ec0dc2c957822ca0fe6280700e297a7501 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH v21 3/6] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 2 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 709 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1546 insertions(+), 207 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 8d32a8c9..507f3cb1 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,7 +2320,7 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 24ad87f9..77868537 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9537,7 +9537,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10434,7 +10434,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de..f2107dd9 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(!INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 48fc0d59..5fed7e3d 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3557,6 +3557,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index e27ea8ef..e9e85503 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd007..6e871738 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3ab9b691..3100ad61 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1523,6 +1545,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1544,6 +1567,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1561,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2039,6 +2078,8 @@ 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;
}
@@ -2056,6 +2097,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2089,7 +2132,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2220,6 +2265,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2715,46 +2783,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->month = 0;
- result->day = 0;
-
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
*/
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ result->month = 0;
+ result->day = 0;
+
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2784,6 +2877,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))
@@ -2836,6 +2932,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2859,6 +2960,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,
@@ -2876,6 +2980,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2897,6 +3006,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))
@@ -2915,6 +3027,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2935,7 +3052,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3014,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3043,7 +3181,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3133,9 +3294,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3186,6 +3345,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3193,23 +3377,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3253,27 +3421,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3287,27 +3488,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3332,6 +3568,49 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results
+ * in an infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ Interval zero;
+ int factor_sign,
+ result_sign;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ memset(&zero, 0, sizeof(zero));
+ result_sign = interval_cmp_internal(span, &zero) * factor_sign;
+
+ if (result_sign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_sign < 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)
@@ -3392,6 +3671,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3423,6 +3707,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3444,6 +3751,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3727,8 +4039,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3847,8 +4186,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3973,6 +4339,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4156,6 +4527,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4394,6 +4770,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);
@@ -4738,7 +5120,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5012,7 +5394,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5252,6 +5634,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5276,6 +5711,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5518,6 +5981,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5748,6 +6218,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5814,6 +6291,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5896,6 +6378,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a639058..f9bd30d2 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8..fe713fb6 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30..eaf44b96 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 96db07c3..a4630d17 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,6 +1851,381 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+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"
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c..53542e07 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c..825202d5 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2..0b4071f3 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index f328b891..023cf376 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -588,6 +595,139 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9c..ea12ffd1 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d04..7876225c 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.34.1
v21-0004-Revert-Remove-dead-code-in-DecodeInterval.patchtext/x-patch; charset=US-ASCII; name=v21-0004-Revert-Remove-dead-code-in-DecodeInterval.patchDownload
From c5bd5799e35a33cf647b2a957972285dd885b9cb Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 13 Sep 2023 12:43:49 +0530
Subject: [PATCH v21 4/6] Revert "Remove dead code in DecodeInterval()"
This reverts commit d6d1430f404386162831bc32906ad174b2007776 since
RESERVE is required to support infinite intervals. 'infinity' is saved
as a RESERVE in datetktbl[], thus required to parse
'infinity'::interval.
Ashutosh Bapat
---
src/backend/utils/adt/datetime.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 5fed7e3d..5b8b080a 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3582,6 +3582,11 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
--
2.34.1
v21-0006-refactor-avg-interval-sum-interval-aggregate.patchtext/x-patch; charset=US-ASCII; name=v21-0006-refactor-avg-interval-sum-interval-aggregate.patchDownload
From adc1e06fa74d00f3ab069434ba720486393b44d3 Mon Sep 17 00:00:00 2001
From: pgaddict <jian.universality@gmail.com>
Date: Wed, 20 Sep 2023 18:08:33 +0800
Subject: [PATCH v21 6/6] refactor avg(interval), sum(interval) aggregate.
To make avg(interval), sum(interval) window function also work
when intervals have special values: inf/-inf,
We need a state to live through the whole aggregate function cycle.
the state will accumulate or discard value.
the aggregate transition and inverse transition function associated
with interval type will update the state all the time.
the aggregate final function will compute the final result.
We already do the same logic in many data types like numeric.
so invent a new struct: IntervalAggState.
it will be initialized in aggregate memory context
(I don't know when it will be destroyed).
For avg(interval), sum(interval) every time accumulate a new interval value,
change the state struct inner
elements value.
In IntervalAggStat we also track the number of special values.
In the end, using the aggregate final function returns the result.
To make window function work,
we need to have an inverse transition function that can discard an interval from the state.
tests include order by and non-order by window function case.
also invent the sum(interval) aggregate final function.
---
src/backend/utils/adt/timestamp.c | 452 +++++++++++++++++++--------
src/include/catalog/pg_aggregate.dat | 21 +-
src/include/catalog/pg_proc.dat | 21 +-
src/test/regress/expected/window.out | 64 ++++
src/test/regress/sql/window.sql | 45 +++
5 files changed, 449 insertions(+), 154 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3100ad61..d9fc812b 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -73,7 +73,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+typedef struct IntervalAggState
+{
+ MemoryContext agg_context; /* context we're calculating in */
+ int64 N; /* count of processed intervals */
+ Interval sumX; /* sum of processed intervals */
+ /* These counts are *not* included in N! Use INF_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +Inf values */
+ int64 nInfcount; /* count of -Inf values */
+} IntervalAggState;
+#define INF_TOTAL_COUNT(na) \
+ ((na)->N + (na)->pInfcount + (na)->nInfcount)
+
+static void do_interval_accum(IntervalAggState *state, Interval *newval);
+static IntervalAggState *makeIntervalAggState(FunctionCallInfo fcinfo);
static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
static Timestamp dt2local(Timestamp dt, int timezone);
static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
@@ -3860,161 +3874,325 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for a interval aggregate function that needs to compute
+ * sum, count, avg.
+ */
+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));
+ state->agg_context = agg_context;
+
+ MemoryContextSwitchTo(old_context);
+
+ return state;
+}
+
+/*
+ * actual do the interval accumualation.
+ * if to be added interval value is not finite then record.
+ * state's infinity/-infinity count, else do interval addidition.
+ */
+static void
+do_interval_accum(IntervalAggState *state, Interval *newval)
+{
+
+ Interval X;
+ Interval *p;
+/* i think if temp as interval pointer, following interval addition will not work.*/
+ Interval temp;
+ MemoryContext old_context;
+
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN (newval))
+ {
+ state->nInfcount++;
+ return;
+ }
+
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount++;
+ return;
+ }
+
+ X.day = newval->day;
+ X.month = newval->month;
+ X.time = newval->time;
+
+ temp.day = state->sumX.day;
+ temp.month = state->sumX.month;
+ temp.time = state->sumX.time;
+
+ /* The rest of this needs to work in the aggregate context */
+ old_context = MemoryContextSwitchTo(state->agg_context);
+
+ state->N++;
+
+ /* Accumulate interval struct field */
+ p = &state->sumX;
+ {
+ /* overflow check copied from int8pl */
+ if (unlikely(pg_add_s64_overflow(temp.time, X.time, &p->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ /* overflow check copied from int4pl */
+ p->month = temp.month + X.month;
+ if (SAMESIGN(temp.month, X.month) &&
+ !SAMESIGN(p->month, temp.month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ p->day = temp.day + X.day;
+ if (SAMESIGN(temp.day, X.day) &&
+ !SAMESIGN(p->day, temp.day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+
+ MemoryContextSwitchTo(old_context);
+ return;
+}
+
+/*
+ * transition function for AVG(interval) aggregate.
+ */
+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 to conmbine 2 interval aggreagte state.
+ */
+Datum
+interval_avg_combine(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state1;
+ IntervalAggState *state2;
+ MemoryContext agg_context;
+ MemoryContext old_context;
+
+ if (!AggCheckCallContext(fcinfo, &agg_context))
+ elog(ERROR, "aggregate function called in non-aggregate context");
+
+ 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);
+
+ /* manually copy all fields from state2 to state1 */
+ if (state1 == NULL)
+ {
+ old_context = MemoryContextSwitchTo(agg_context);
+
+ state1 = makeIntervalAggState(fcinfo);
+ state1->agg_context = CurrentMemoryContext;
+
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
+ state1->sumX.time = state2->sumX.time;
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+
+ MemoryContextSwitchTo(old_context);
+
+ PG_RETURN_POINTER(state1);
+ }
+
+ state1->N += state2->N;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
+
+ if (state2->N > 0)
+ {
+ /* The rest of this needs to work in the aggregate context */
+ old_context = MemoryContextSwitchTo(agg_context);
+
+ /* Accumulate interval values */
+ do_interval_accum(state1, &state2->sumX);
+
+ MemoryContextSwitchTo(old_context);
+ }
+ PG_RETURN_POINTER(state1);
+}
+
+static bool
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+ Interval X;
+ MemoryContext old_context;
+
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN (newval))
+ {
+ state->nInfcount--;
+ return true;
+ }
+
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount--;
+ return true;
+ }
+
+ /* the value to be discarded is not special. assign to X */
+ X.time = -newval->time;
+ X.day = -newval->day;
+ X.month = -newval->month;
+
+ state->N--;
+ if (state->N > 0)
+ {
+ /* The rest of this needs to work in the aggregate context */
+ old_context = MemoryContextSwitchTo(state->agg_context);
+
+ do_interval_accum(state,&X);
+
+ /* do_interval_accum increase state->N by one, we need decrease one */
+ state->N--;
+ MemoryContextSwitchTo(old_context);
+ }
+ else
+ {
+ /*now all values discarded, reset now.
+ * due to NULL, div by zero cases
+ * we can only memset interval struct.
+ */
+ memset(&state->sumX, 0, sizeof(state->sumX));
+ }
+
+ return true;
+}
+
+/*
+ * Generic inverse transition function for interval aggregates
*
- * 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.
*/
-
-Datum
-interval_accum(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;
-
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
-
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
-
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
-
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
-
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
-
- PG_RETURN_ARRAYTYPE_P(result);
-}
-
-Datum
-interval_combine(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;
-
- Interval *newsum;
- ArrayType *result;
-
- deconstruct_array(transarray1,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums1, NULL, &ndatums1);
- if (ndatums1 != 2)
- elog(ERROR, "expected 2-element interval array");
-
- sum1 = *(DatumGetIntervalP(transdatums1[0]));
- N1 = *(DatumGetIntervalP(transdatums1[1]));
-
- deconstruct_array(transarray2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums2, NULL, &ndatums2);
- if (ndatums2 != 2)
- elog(ERROR, "expected 2-element interval array");
-
- sum2 = *(DatumGetIntervalP(transdatums2[0]));
- N2 = *(DatumGetIntervalP(transdatums2[1]));
-
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sum1),
- IntervalPGetDatum(&sum2)));
- N1.time += N2.time;
-
- transdatums1[0] = IntervalPGetDatum(newsum);
- transdatums1[1] = IntervalPGetDatum(&N1);
-
- result = construct_array(transdatums1, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
-
- PG_RETURN_ARRAYTYPE_P(result);
-}
-
Datum
interval_accum_inv(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ if (state == NULL)
+ elog(ERROR, "interval_accum_inv called with NULL state");
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ if (!PG_ARGISNULL(1))
+ {
+ /* If we fail to perform the inverse transition, return NULL */
+ if (!do_interval_discard(state, PG_GETARG_INTERVAL_P(1)))
+ PG_RETURN_NULL();
+ }
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
-
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
-
- PG_RETURN_ARRAYTYPE_P(result);
+ PG_RETURN_POINTER(state);
}
+/* avg(interval) aggregate final function */
Datum
interval_avg(PG_FUNCTION_ARGS)
{
- ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
- Datum *transdatums;
- int ndatums;
- Interval sumX,
- N;
-
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
-
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
-
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ IntervalAggState *state;
+ int64 N_datum;
+ Interval *sumX;
+ Datum d;
+
+ sumX = (Interval *) palloc(sizeof(Interval));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ if (state == NULL || INF_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
+ /* adding plus and minus infinities gives error */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range."))); /* maybe we should make it more verbose */
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ N_datum = state->N;
+ sumX = &state->sumX;
+ d = DirectFunctionCall1(i8tod,Int64GetDatum(N_datum));
+
+ PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX), d));
}
+/* sum(interval) aggregate final function */
+Datum
+interval_sum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ Interval *sumX;
+
+ sumX = (Interval *) palloc(sizeof(Interval));
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || INF_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
+
+ /* adding plus and minus infinities should yield error */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range."))); /* maybe we should make it more verbose */
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ sumX = &state->sumX;
+ PG_RETURN_INTERVAL_P(sumX);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d..905359b3 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,12 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
+ aggmtranstype => 'internal'
+},
# sum
{ aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
@@ -72,10 +72,11 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
+ aggmtranstype => 'internal'},
{ aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
aggserialfn => 'numeric_avg_serialize',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc61..92857368 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -2356,6 +2356,7 @@
{ oid => '1169',
proname => 'interval_pl', prorettype => 'interval',
proargtypes => 'interval interval', prosrc => 'interval_pl' },
+
{ oid => '1170',
proname => 'interval_mi', prorettype => 'interval',
proargtypes => 'interval interval', prosrc => 'interval_mi' },
@@ -4914,17 +4915,23 @@
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_accum_inv', proisstrict => 'f',
+ prorettype => 'internal', proargtypes => 'internal interval',
+ prosrc => 'interval_accum_inv' },
{ oid => '1844', descr => 'aggregate final function',
proname => 'interval_avg', prorettype => 'interval',
- proargtypes => '_interval', prosrc => 'interval_avg' },
+ proargtypes => 'internal', prosrc => 'interval_avg' },
+{ oid => '8069', 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' },
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df1..85b21d7a 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,6 +4375,70 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev1_curr_sum | curr_next_avg | prev1_curr_avg
+-----------+---------------+----------------+---------------+----------------
+ -infinity | -infinity | -infinity | -infinity | -infinity
+ @ 6 days | infinity | -infinity | infinity | -infinity
+ infinity | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity
+ | | infinity | | infinity
+ | | | |
+(6 rows)
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | curr_next_sum | prev1_curr_avg | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum
+-----------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
+ | infinity | infinity | | | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | @ 13 days | infinity | infinity | infinity | infinity
+ @ 7 days | @ 7 days | @ 7 days | @ 6 days 12 hours | @ 13 days | -infinity | -infinity
+ | -infinity | -infinity | @ 7 days | @ 7 days | -infinity | -infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity | -infinity | -infinity
+(7 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
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92a..d23370a1 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,6 +1591,51 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('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);
--
2.34.1
On Wed, 20 Sept 2023 at 11:27, jian he <jian.universality@gmail.com> wrote:
if I remove IntervalAggState's element: MemoryContext, it will not work.
so I don't understand what the above sentence means...... Sorry. (it's
my problem)
I don't see why it won't work. The point is to try to simplify
do_interval_accum() as much as possible. Looking at the current code,
I see a few places that could be simpler:
+ X.day = newval->day;
+ X.month = newval->month;
+ X.time = newval->time;
+
+ temp.day = state->sumX.day;
+ temp.month = state->sumX.month;
+ temp.time = state->sumX.time;
Why do we need these local variables X and temp? It could just add the
values from newval directly to those in state->sumX.
+ /* The rest of this needs to work in the aggregate context */
+ old_context = MemoryContextSwitchTo(state->agg_context);
Why? It's not allocating any memory here, so I don't see a need to
switch context.
So basically, do_interval_accum() could be simplified to:
static void
do_interval_accum(IntervalAggState *state, Interval *newval)
{
/* Count infinite intervals separately from all else */
if (INTERVAL_IS_NOBEGIN (newval))
{
state->nInfcount++;
return;
}
if (INTERVAL_IS_NOEND(newval))
{
state->pInfcount++;
return;
}
/* Update count of finite intervals */
state->N++;
/* Update sum of finite intervals */
if (unlikely(pg_add_s32_overflow(state->sumX.month, newval->month,
&state->sumX.month)))
ereport(ERROR,
errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range"));
if (unlikely(pg_add_s32_overflow(state->sumX.day, newval->day,
&state->sumX.day)))
ereport(ERROR,
errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range"));
if (unlikely(pg_add_s64_overflow(state->sumX.time, newval->time,
&state->sumX.time)))
ereport(ERROR,
errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range"));
return;
}
and that can be further refactored, as described below, and similarly
for do_interval_discard(), except using pg_sub_s32/64_overflow().
Also, this needs to include serialization and deserialization
functions, otherwise these aggregates will no longer be able to use
parallel workers. That makes a big difference to queryE, if the size
of the test data is scaled up.I tried, but failed. sum(interval) result is correct, but
avg(interval) result is wrong.SELECT sum(b) ,avg(b)
,avg(b) = sum(b)/count(*) as should_be_true
,avg(b) * count(*) = sum(b) as should_be_true_too
from interval_aggtest_1m; --1million row.
The above query expects two bool columns to return true, but actually
both returns false.(spend some time found out parallel mode will make
the number of rows to 1_000_002, should be 1_000_0000).
I think the reason for your wrong results is this code in
interval_avg_combine():
+ if (state2->N > 0)
+ {
+ /* The rest of this needs to work in the aggregate context */
+ old_context = MemoryContextSwitchTo(agg_context);
+
+ /* Accumulate interval values */
+ do_interval_accum(state1, &state2->sumX);
+
+ MemoryContextSwitchTo(old_context);
+ }
The problem is that using do_interval_accum() to add the 2 sums
together also adds 1 to the count N, making it incorrect. This code
should only be adding state2->sumX to state1->sumX, not touching
state1->N. And, as in do_interval_accum(), there is no need to switch
memory context.
Given that there are multiple places in this file that need to add
intervals, I think it makes sense to further refactor, and add a local
function to add 2 finite intervals, along the lines of the code above.
This can then be called from do_interval_accum(),
interval_avg_combine(), and interval_pl(). And similarly for
subtracting 2 finite intervals.
Regards,
Dean
On Wed, 20 Sept 2023 at 13:09, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
So basically, do_interval_accum() could be simplified to:
Oh, and I guess it also needs an INTERVAL_NOT_FINITE() check, to make
sure that finite values don't sum to our representation of infinity,
as in interval_pl().
Regards,
Dean
On Mon, Sep 18, 2023 at 5:09 PM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Wed, 13 Sept 2023 at 11:13, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:On Tue, Sep 12, 2023 at 2:39 PM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
and it looks like the infinite interval
input code is broken.The code required to handle 'infinity' as an input value was removed by
d6d1430f404386162831bc32906ad174b2007776. I have added a separate
commit which reverts that commit as 0004, which should be merged into
0003.I think that simply reverting d6d1430f404386162831bc32906ad174b2007776
is not sufficient. This does not make it clear what the point is of
the code in the "case RESERV" block. That code really should check the
value returned by DecodeSpecial(), otherwise invalid inputs are not
caught until later, and the error reported is not ideal. For example:select interval 'now';
ERROR: unexpected dtype 12 while parsing interval "now"So DecodeInterval() should return DTERR_BAD_FORMAT in such cases (see
similar code in DecodeTimeOnly(), for example).
The since the code was there earlier, I missed that part. Sorry.
I'd also suggest a comment to indicate why itm_in isn't updated in
this case (see similar case in DecodeDateTime(), for example).
Added but in the function prologue since it's part of the API.
Another point to consider is what should happen if "ago" is specified
with infinite inputs. As it stands, it is accepted, but does nothing:select interval 'infinity ago';
interval
----------
infinity
(1 row)select interval '-infinity ago';
interval
-----------
-infinity
(1 row)This could be made to invert the sign, as it does for finite inputs,
but I think perhaps it would be better to simply reject such inputs.
Fixed this. After studying what DecodeInterval is doing, I think it's
better to treat all infinity specifications similar to "ago". They
need to be the last part of the input string. Rest of the code makes
sure that nothing preceds infinity specification as other case blocks
do not handle RESERVE or DTK_LATE and DTK_EARLY. This means that
"+infinity", "-infinity" or "infinity" can be the only allowed word as
a valid interval input when either of them is specified.
I will post these changes in another email along with other patches.
--
Best Wishes,
Ashutosh Bapat
On Wed, Sep 20, 2023 at 5:39 PM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Wed, 20 Sept 2023 at 11:27, jian he <jian.universality@gmail.com> wrote:
if I remove IntervalAggState's element: MemoryContext, it will not work.
so I don't understand what the above sentence means...... Sorry. (it's
my problem)I don't see why it won't work. The point is to try to simplify
do_interval_accum() as much as possible. Looking at the current code,
I see a few places that could be simpler:+ X.day = newval->day; + X.month = newval->month; + X.time = newval->time; + + temp.day = state->sumX.day; + temp.month = state->sumX.month; + temp.time = state->sumX.time;Why do we need these local variables X and temp? It could just add the
values from newval directly to those in state->sumX.+ /* The rest of this needs to work in the aggregate context */ + old_context = MemoryContextSwitchTo(state->agg_context);Why? It's not allocating any memory here, so I don't see a need to
switch context.So basically, do_interval_accum() could be simplified to:
static void
do_interval_accum(IntervalAggState *state, Interval *newval)
{
/* Count infinite intervals separately from all else */
if (INTERVAL_IS_NOBEGIN (newval))
{
state->nInfcount++;
return;
}
if (INTERVAL_IS_NOEND(newval))
{
state->pInfcount++;
return;
}/* Update count of finite intervals */
state->N++;/* Update sum of finite intervals */
if (unlikely(pg_add_s32_overflow(state->sumX.month, newval->month,
&state->sumX.month)))
ereport(ERROR,
errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range"));if (unlikely(pg_add_s32_overflow(state->sumX.day, newval->day,
&state->sumX.day)))
ereport(ERROR,
errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range"));if (unlikely(pg_add_s64_overflow(state->sumX.time, newval->time,
&state->sumX.time)))
ereport(ERROR,
errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range"));return;
}and that can be further refactored, as described below, and similarly
for do_interval_discard(), except using pg_sub_s32/64_overflow().Also, this needs to include serialization and deserialization
functions, otherwise these aggregates will no longer be able to use
parallel workers. That makes a big difference to queryE, if the size
of the test data is scaled up.I tried, but failed. sum(interval) result is correct, but
avg(interval) result is wrong.SELECT sum(b) ,avg(b)
,avg(b) = sum(b)/count(*) as should_be_true
,avg(b) * count(*) = sum(b) as should_be_true_too
from interval_aggtest_1m; --1million row.
The above query expects two bool columns to return true, but actually
both returns false.(spend some time found out parallel mode will make
the number of rows to 1_000_002, should be 1_000_0000).I think the reason for your wrong results is this code in
interval_avg_combine():+ if (state2->N > 0) + { + /* The rest of this needs to work in the aggregate context */ + old_context = MemoryContextSwitchTo(agg_context); + + /* Accumulate interval values */ + do_interval_accum(state1, &state2->sumX); + + MemoryContextSwitchTo(old_context); + }The problem is that using do_interval_accum() to add the 2 sums
together also adds 1 to the count N, making it incorrect. This code
should only be adding state2->sumX to state1->sumX, not touching
state1->N. And, as in do_interval_accum(), there is no need to switch
memory context.Given that there are multiple places in this file that need to add
intervals, I think it makes sense to further refactor, and add a local
function to add 2 finite intervals, along the lines of the code above.
This can then be called from do_interval_accum(),
interval_avg_combine(), and interval_pl(). And similarly for
subtracting 2 finite intervals.
I was working on refactoring Jian's patches but forgot to mention it
there. I think the patchset attached has addressed all your comments.
But they do not implement serialization and deserialization yet. I
will take a look at Jian's patch for the same and incorporate/refactor
those changes.
Jian,
I don't understand why there's two sets of test queries, one with
ORDER BY and one without? Does ORDER BY specification make any
difference in the testing?
Patches are thus
0001, 0002 are same
0003 - earlier 0003 + incorporated doc changes suggested by Jian
0004 - fixes DecodeInterval()
0005 - Refactored Jian's code fixing window functions. Does not
contain the changes for serialization and deserialization. Jian,
please let me know if I have missed anything else.
In my testing, I saw the timings for queries as below
Query A: 145.164 ms
Query B: 222.419 ms
Query C: 136.995 ms
Query D: 146.893 ms
Query E: 38.053 ms
Query F: 80.112 ms
I didn't see degradation in case of sum().
--
Best Wishes,
Ashutosh Bapat
Attachments:
0004-Introduce-infinity-interval-specification-i-20230920.patchtext/x-patch; charset=US-ASCII; name=0004-Introduce-infinity-interval-specification-i-20230920.patchDownload
From bb51c8162c6ae84d94e3ab42de55fa6198aea2fe Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 13 Sep 2023 12:43:49 +0530
Subject: [PATCH 4/5] Introduce 'infinity' interval specification in
DecodeInterval()
This reverts commit d6d1430f404386162831bc32906ad174b2007776 since
RESERVE is required to support infinite intervals. 'infinity' is saved
as a RESERVE in datetktbl[], thus required to parse
'infinity'::interval.
Infinte interval specification i.e. "+infinity", "-infinity" or
"infinity" does not require anything other that one of the three
strings. Fix DecodeInterval() to disallow anything else.
"ago" may be used to revrse the sign of infinity specification like how
ago reverts the sign of finite intervals. But that is not as intuitive
as it is with finite intervals. Hence disallows "infinity ago" as well.
Ashutosh Bapat
---
src/backend/utils/adt/datetime.c | 24 ++++++++++++++++
src/test/regress/expected/interval.out | 39 ++++++++++++++++++++++++++
src/test/regress/sql/interval.sql | 14 +++++++++
3 files changed, 77 insertions(+)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 5fed7e3dd1..b25bd6a551 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3254,6 +3254,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3582,6 +3585,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+
+ /*
+ * Only reserved words corresponding to infinite
+ * intervals are accepted.
+ */
+ if (uval != DTK_LATE && uval != DTK_EARLY)
+ return DTERR_BAD_FORMAT;
+
+ /*
+ * infinity can not be followed by anything else. We
+ * could allow "ago" to reverse the sign of infinity
+ * but that signed infinity is more intuitive.
+ */
+ if (i != nf - 1)
+ return DTERR_BAD_FORMAT;
+
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index a4630d17ef..d61b07f38e 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -2244,3 +2244,42 @@ SELECT INTERVAL '1 year months days 5 hours';
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 023cf3761a..183ddfaf31 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -735,3 +735,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
--
2.25.1
0002-Check-for-overflow-in-make_interval-20230920.patchtext/x-patch; charset=US-ASCII; name=0002-Check-for-overflow-in-make_interval-20230920.patchDownload
From 7e5ec988a6a300ff458a13b514920471433a1408 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/5] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec5a..3ab9b6918e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1518,13 +1518,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index c0ca8e041b..96db07c3b4 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 038fc508d0..f328b8914d 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.25.1
0001-Move-integer-helper-function-to-int.h-20230920.patchtext/x-patch; charset=US-ASCII; name=0001-Move-integer-helper-function-to-int.h-20230920.patchDownload
From 74d2b7e42f72c2727f35aec9a1d68a8a6e738bdd Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/5] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..48fc0d5942 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.25.1
0005-Support-infinite-interval-values-in-sum-and-20230920.patchtext/x-patch; charset=US-ASCII; name=0005-Support-infinite-interval-values-in-sum-and-20230920.patchDownload
From 292e252183d4238eb96cab556cbadd224877c8b4 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 20 Sep 2023 15:41:00 +0530
Subject: [PATCH 5/5] Support infinite interval values in sum and avg window
functions
Results of arithmetic operations involing both infinity and -infinity
intervals are undefined. Sliding window functions
accumulate and discard values as the window slides through the data.
Even if the data may have both infinities, none of windows may have both
of them together and thus produce definite results. Hence we can not use
normal operator functions to accumulate and discard values. Instead we
maintain a count of both infinities seen respectively in an aggregate
state and compute the aggregate for each window based on the accumulated
finite value and the counts of infinities, all of which are updated as
the window slides through the data.
Initial patch by Jian He, significantly edited by Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 407 ++++++++++++++++++---------
src/include/catalog/pg_aggregate.dat | 23 +-
src/include/catalog/pg_proc.dat | 20 +-
src/test/regress/expected/window.out | 62 ++++
src/test/regress/sql/window.sql | 43 +++
src/tools/pgindent/typedefs.list | 1 +
6 files changed, 400 insertions(+), 156 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3100ad610f..c6dc2d4423 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -73,6 +73,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+/*
+ * The transition datatype for interval aggregates is declared as Internal. It's
+ * a pointer to a NumericAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+ int64 N; /* count of processed intervals */
+ Interval sumX; /* sum of processed intervals */
+ /* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +Inf values */
+ int64 nInfcount; /* count of -Inf values */
+} 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);
@@ -82,6 +97,8 @@ static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
static void interval_um_internal(Interval *interval, Interval *result);
+static void finite_interval_pl(Interval *result, Interval *span1,
+ Interval *span2);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -3447,34 +3464,7 @@ interval_pl(PG_FUNCTION_ARGS)
else if (INTERVAL_NOT_FINITE(span2))
memcpy(result, span2, sizeof(Interval));
else
- {
- 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")));
-
- 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")));
-
- if (INTERVAL_NOT_FINITE(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- }
+ finite_interval_pl(result, span1, span2);
PG_RETURN_INTERVAL_P(result);
}
@@ -3860,161 +3850,300 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function that needs to compute
+ * sum and count.
+ */
+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;
+}
+
+/*
+ * Function to add two finite intervals.
*
- * 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.
+ * We handle non-finite intervals in different ways when accumulating intervals
+ * and adding two intervals respectively. But the addition of finite interval
+ * has same implementation in both these cases.
*/
+static void
+finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
-Datum
-interval_accum(PG_FUNCTION_ARGS)
+ 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")));
+
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+/*
+ * Accumulate a new input value for interval aggregate functions.
+ */
+static void
+do_interval_accum(IntervalAggState *state, Interval *newval)
{
- 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;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ Interval temp;
+
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount++;
+ return;
+ }
+
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount++;
+ return;
+ }
+
+ memcpy(&temp, &state->sumX, sizeof(Interval));
+ finite_interval_pl(&state->sumX, &temp, newval);
+ state->N++;
+}
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+/*
+ * Transition function for interval aggregates.
+ */
+Datum
+interval_avg_accum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ /* Create the state data on the first call */
+ if (state == NULL)
+ state = makeIntervalAggState(fcinfo);
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ if (!PG_ARGISNULL(1))
+ do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
- PG_RETURN_ARRAYTYPE_P(result);
+ PG_RETURN_POINTER(state);
}
+/*
+ * Combine function for interval aggregates.
+ */
Datum
-interval_combine(PG_FUNCTION_ARGS)
+interval_avg_combine(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 *state1;
+ IntervalAggState *state2;
+
+ state1 = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+ state2 = PG_ARGISNULL(1) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(1);
- Interval *newsum;
- ArrayType *result;
+ if (state2 == NULL)
+ PG_RETURN_POINTER(state1);
+
+ /* manually copy all fields from state2 to state1 */
+ if (state1 == NULL)
+ {
+ state1 = makeIntervalAggState(fcinfo);
- deconstruct_array(transarray1,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums1, NULL, &ndatums1);
- if (ndatums1 != 2)
- elog(ERROR, "expected 2-element interval array");
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
- sum1 = *(DatumGetIntervalP(transdatums1[0]));
- N1 = *(DatumGetIntervalP(transdatums1[1]));
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
- deconstruct_array(transarray2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums2, NULL, &ndatums2);
- if (ndatums2 != 2)
- elog(ERROR, "expected 2-element interval array");
+ PG_RETURN_POINTER(state1);
+ }
+
+ state1->N += state2->N;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
+
+ if (state2->N > 0)
+ {
+ Interval temp;
+
+ /* Accumulate interval values */
+ memcpy(&temp, &state1->sumX, sizeof(Interval));
+ finite_interval_pl(&state1->sumX, &temp, &state2->sumX);
+ }
+
+ PG_RETURN_POINTER(state1);
+}
- sum2 = *(DatumGetIntervalP(transdatums2[0]));
- N2 = *(DatumGetIntervalP(transdatums2[1]));
+/*
+ * Remove the given interval value from the aggregated state.
+ */
+static void
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount--;
+ return;
+ }
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sum1),
- IntervalPGetDatum(&sum2)));
- N1.time += N2.time;
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount--;
+ return;
+ }
- transdatums1[0] = IntervalPGetDatum(newsum);
- transdatums1[1] = IntervalPGetDatum(&N1);
+ /* Handle to be discarded finite value. */
+ if (state->N > 0)
+ {
+ Interval temp;
+ Interval neg_val;
- result = construct_array(transdatums1, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ neg_val.day = -newval->day;
+ neg_val.month = -newval->month;
+ neg_val.time = -newval->time;
- PG_RETURN_ARRAYTYPE_P(result);
+ memcpy(&temp, &state->sumX, sizeof(Interval));
+ finite_interval_pl(&state->sumX, &temp, &neg_val);
+ }
+ else
+ {
+ /* All values discarded, reset the state */
+ memset(&state->sumX, 0, sizeof(state->sumX));
+ }
+ state->N--;
}
+/*
+ * Generic inverse transition function for interval aggregates
+ */
Datum
interval_accum_inv(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Should not get here with no state */
+ if (state == NULL)
+ elog(ERROR, "interval_accum_inv called with NULL state");
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ if (!PG_ARGISNULL(1))
+ do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ PG_RETURN_POINTER(state);
+}
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ double N_datum;
+ Interval *sumX;
+
+ sumX = (Interval *) palloc(sizeof(Interval));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
- PG_RETURN_ARRAYTYPE_P(result);
+ /* adding plus and minus infinities gives error */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ N_datum = (double) state->N;
+ sumX = &state->sumX;
+
+ PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX),
+ Float8GetDatum(N_datum)));
}
+/* sum(interval) aggregate final function */
Datum
-interval_avg(PG_FUNCTION_ARGS)
+interval_sum(PG_FUNCTION_ARGS)
{
- ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
- Datum *transdatums;
- int ndatums;
- Interval sumX,
- N;
+ IntervalAggState *state;
+ Interval *result;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ result = (Interval *) palloc(sizeof(Interval));
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
-}
+ /* adding plus and minus interval infinities is not possible */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ memcpy(result, &state->sumX, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e2087d7be1 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
+ aggmtranstype => 'internal', aggtransspace => '128',
+ aggmtransspace => '128'
+},
# sum
{ aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
@@ -72,10 +73,12 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
+ aggmtranstype => 'internal', aggtransspace => '128',
+ aggmtransspace => '128'},
{ aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
aggserialfn => 'numeric_avg_serialize',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..f8eca58ddf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4914,17 +4914,23 @@
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_accum_inv', proisstrict => 'f',
+ prorettype => 'internal', proargtypes => 'internal interval',
+ prosrc => 'interval_accum_inv' },
{ oid => '1844', descr => 'aggregate final function',
proname => 'interval_avg', prorettype => 'interval',
- proargtypes => '_interval', prosrc => 'interval_avg' },
+ proargtypes => 'internal', prosrc => 'interval_avg' },
+{ oid => '8069', 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' },
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..45262924cd 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,6 +4375,68 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev1_curr_sum
+-----------+---------------+----------------
+ -infinity | -infinity | -infinity
+ @ 6 days | infinity | -infinity
+ infinity | infinity | infinity
+ infinity | infinity | infinity
+ | | infinity
+ | |
+(6 rows)
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | curr_next_sum | prev1_curr_avg | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum
+-----------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
+ | infinity | infinity | | | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | @ 13 days | infinity | infinity | infinity | infinity
+ @ 7 days | @ 7 days | @ 7 days | @ 6 days 12 hours | @ 13 days | -infinity | -infinity
+ | -infinity | -infinity | @ 7 days | @ 7 days | -infinity | -infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity | -infinity | -infinity
+(7 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
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..d00423946d 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,6 +1591,49 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('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);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f3d8a2a855..4df61a69ca 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1204,6 +1204,7 @@ IntegerSet
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup
--
2.25.1
0003-Add-infinite-interval-values-20230920.patchtext/x-patch; charset=US-ASCII; name=0003-Add-infinite-interval-values-20230920.patchDownload
From 9442b707b6aec49da063ae1714716325cef826dd Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/5] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 4 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 707 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1546 insertions(+), 207 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 8d32a8c9c5..557b81cae8 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,12 +2320,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 24ad87f910..7786853743 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9537,7 +9537,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10434,7 +10434,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de2c..f2107dd967 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(!INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 48fc0d5942..5fed7e3dd1 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3557,6 +3557,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index e27ea8ef97..e9e85503f8 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..6e87173846 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3ab9b6918e..3100ad610f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1523,6 +1545,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1544,6 +1567,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1561,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2039,6 +2078,8 @@ 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;
}
@@ -2056,6 +2097,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2089,7 +2132,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2220,6 +2265,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2715,46 +2783,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2784,6 +2877,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))
@@ -2836,6 +2932,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2859,6 +2960,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,
@@ -2876,6 +2980,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2897,6 +3006,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))
@@ -2915,6 +3027,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2935,7 +3052,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3014,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3043,7 +3181,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3133,9 +3294,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3186,6 +3345,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3193,23 +3377,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3253,27 +3421,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3287,27 +3488,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3332,6 +3568,49 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results
+ * in an infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ Interval zero;
+ int factor_sign,
+ result_sign;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ memset(&zero, 0, sizeof(zero));
+ result_sign = interval_cmp_internal(span, &zero) * factor_sign;
+
+ if (result_sign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_sign < 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)
@@ -3392,6 +3671,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3423,6 +3707,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3444,6 +3751,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3727,8 +4039,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3847,8 +4186,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3973,6 +4339,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4156,6 +4527,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4394,6 +4770,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);
@@ -4738,7 +5120,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5012,7 +5394,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5252,6 +5634,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5276,6 +5711,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5518,6 +5981,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5748,6 +6218,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5814,6 +6291,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5896,6 +6378,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8c9..fe713fb6dd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 96db07c3b4..a4630d17ef 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,6 +1851,381 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+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"
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index f328b8914d..023cf3761a 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -588,6 +595,139 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.25.1
On Wed, Sep 20, 2023 at 5:44 PM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Wed, 20 Sept 2023 at 13:09, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
So basically, do_interval_accum() could be simplified to:
Oh, and I guess it also needs an INTERVAL_NOT_FINITE() check, to make
sure that finite values don't sum to our representation of infinity,
as in interval_pl().
Fixed in the latest patch set.
--
Best Wishes,
Ashutosh Bapat
On Wed, 20 Sept 2023 at 15:00, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
0005 - Refactored Jian's code fixing window functions. Does not
contain the changes for serialization and deserialization. Jian,
please let me know if I have missed anything else.
That looks a lot neater. One thing I don't care for is this code
pattern in finite_interval_pl():
+ result->month = span1->month + span2->month;
+ /* overflow check copied from int4pl */
+ if (SAMESIGN(span1->month, span2->month) &&
+ !SAMESIGN(result->month, span1->month))
+ ereport(ERROR,
The problem is that this is a bug waiting to happen for anyone who
uses this function with "result" pointing to the same Interval struct
as "span1" or "span2". I understand that the current code avoids this
by careful use of temporary Interval structs, but it's still a pretty
ugly pattern. This can be avoided by using pg_add_s32/64_overflow(),
which then allows the callers to be simplified, getting rid of the
temporary Interval structs and memcpy()'s.
Also, in do_interval_discard(), this seems a bit risky:
+ neg_val.day = -newval->day;
+ neg_val.month = -newval->month;
+ neg_val.time = -newval->time;
because it could in theory turn a finite large negative interval into
an infinite one (-INT_MAX -> INT_MAX), leading to an assertion failure
in finite_interval_pl(). Now maybe that's not possible for some other
reasons, but I think we may as well do the same refactoring for
interval_mi() as we're doing for interval_pl() -- i.e., introduce a
finite_interval_mi() function, making the addition and subtraction
code match, and removing the need for neg_val in
do_interval_discard().
Regards,
Dean
On Wed, 20 Sept 2023 at 15:00, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:0005 - Refactored Jian's code fixing window functions. Does not
contain the changes for serialization and deserialization. Jian,
please let me know if I have missed anything else.
attached serialization and deserialization function.
Also, in do_interval_discard(), this seems a bit risky:
+ neg_val.day = -newval->day; + neg_val.month = -newval->month; + neg_val.time = -newval->time;
we already have interval negate function, So I changed to interval_um_internal.
based on 20230920 patches. I have made the attached changes.
The serialization do make big difference when configure to parallel mode.
Attachments:
interval-serialization-and-deserialization-20220921.patchtext/x-patch; charset=US-ASCII; name=interval-serialization-and-deserialization-20220921.patchDownload
From bff5e3dfa8607a8b45aa287a7c55fda9d984f339 Mon Sep 17 00:00:00 2001
From: pgaddict <jian.universality@gmail.com>
Date: Thu, 21 Sep 2023 10:05:21 +0800
Subject: [PATCH v22 7/7] interval aggregate serializatinn and deserialization
add interval aggregate serialization and deserialization function.
fix a typo.
change manually negate a interval to use interval_um_internal.
---
src/backend/utils/adt/timestamp.c | 86 +++++++++++++++++++++++++---
src/include/catalog/pg_aggregate.dat | 4 ++
src/include/catalog/pg_proc.dat | 6 ++
3 files changed, 88 insertions(+), 8 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index c6dc2d44..2b86171a 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -75,7 +75,7 @@ typedef struct
/*
* The transition datatype for interval aggregates is declared as Internal. It's
- * a pointer to a NumericAggState allocated in the aggregate context.
+ * a pointer to a IntervalAggState allocated in the aggregate context.
*/
typedef struct IntervalAggState
{
@@ -3984,10 +3984,7 @@ interval_avg_combine(PG_FUNCTION_ARGS)
state1->N = state2->N;
state1->pInfcount = state2->pInfcount;
state1->nInfcount = state2->nInfcount;
-
- state1->sumX.day = state2->sumX.day;
- state1->sumX.month = state2->sumX.month;
- state1->sumX.time = state2->sumX.time;
+ memcpy(&state1->sumX, &state2->sumX, sizeof(Interval));
PG_RETURN_POINTER(state1);
}
@@ -4008,6 +4005,81 @@ interval_avg_combine(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(state1);
}
+/*
+ * interval_avg_serialize
+ * Serialize IntervalAggState for interval aggregates.
+ */
+Datum
+interval_avg_serialize(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ StringInfoData buf;
+ bytea *result;
+ /* Ensure we disallow calling when not in aggregate context */
+ if (!AggCheckCallContext(fcinfo, NULL))
+ elog(ERROR, "aggregate function called in non-aggregate context");
+ state = (IntervalAggState *) PG_GETARG_POINTER(0);
+ pq_begintypsend(&buf);
+ /* N */
+ pq_sendint64(&buf, state->N);
+ /* Interval struct elements, one by one. */
+ pq_sendint64(&buf, state->sumX.time);
+ pq_sendint32(&buf, state->sumX.day);
+ pq_sendint32(&buf, state->sumX.month);
+ /* pInfcount */
+ pq_sendint64(&buf, state->pInfcount);
+ /* nInfcount */
+ pq_sendint64(&buf, state->nInfcount);
+ result = pq_endtypsend(&buf);
+ PG_RETURN_BYTEA_P(result);
+}
+
+/*
+ * interval_avg_deserialize
+ * Deserialize bytea into IntervalAggState for interval aggregates.
+ */
+Datum
+interval_avg_deserialize(PG_FUNCTION_ARGS)
+{
+ 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);
+
+ /*
+ * Copy the bytea into a StringInfo so that we can "receive" it using the
+ * standard recv-function infrastructure.
+ */
+ initStringInfo(&buf);
+ appendBinaryStringInfo(&buf,
+ VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
+
+ result = makeIntervalAggState(fcinfo);
+
+ /* N */
+ result->N = pq_getmsgint64(&buf);
+
+ /* Interval struct elements, one by one. */
+ result->sumX.time = pq_getmsgint64(&buf);
+ result->sumX.day = pq_getmsgint(&buf, sizeof(result->sumX.day));
+ result->sumX.month = pq_getmsgint(&buf, sizeof(result->sumX.month));
+
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
+
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
+
+ pq_getmsgend(&buf);
+ pfree(buf.data);
+
+ PG_RETURN_POINTER(result);
+}
+
/*
* Remove the given interval value from the aggregated state.
*/
@@ -4034,9 +4106,7 @@ do_interval_discard(IntervalAggState *state, Interval *newval)
Interval temp;
Interval neg_val;
- neg_val.day = -newval->day;
- neg_val.month = -newval->month;
- neg_val.time = -newval->time;
+ interval_um_internal(newval, &neg_val);
memcpy(&temp, &state->sumX, sizeof(Interval));
finite_interval_pl(&state->sumX, &temp, &neg_val);
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index e2087d7b..0e62c3f7 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -45,6 +45,8 @@
aggtranstype => '_float8', agginitval => '{0,0,0}' },
{ 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_accum_inv',
aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
aggmtranstype => 'internal', aggtransspace => '128',
@@ -75,6 +77,8 @@
aggtranstype => 'money', aggmtranstype => 'money' },
{ 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_accum_inv',
aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
aggmtranstype => 'internal', aggtransspace => '128',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f8eca58d..486573d6 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4943,6 +4943,12 @@
{ oid => '3571', descr => 'aggregate transition function',
proname => 'int4_avg_accum_inv', prorettype => '_int8',
proargtypes => '_int8 int4', prosrc => 'int4_avg_accum_inv' },
+{ oid => '3813', descr => 'aggregate serial function',
+ proname => 'interval_avg_serialize', prorettype => 'bytea',
+ proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
+{ oid => '3814', descr => 'aggregate deserial function',
+ proname => 'interval_avg_deserialize', prorettype => 'internal',
+ proargtypes => 'bytea internal', prosrc => 'interval_avg_deserialize' },
{ oid => '1964', descr => 'aggregate final function',
proname => 'int8_avg', prorettype => 'numeric', proargtypes => '_int8',
prosrc => 'int8_avg' },
--
2.34.1
On Wed, Sep 20, 2023 at 8:23 PM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Wed, 20 Sept 2023 at 15:00, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:0005 - Refactored Jian's code fixing window functions. Does not
contain the changes for serialization and deserialization. Jian,
please let me know if I have missed anything else.That looks a lot neater. One thing I don't care for is this code
pattern in finite_interval_pl():+ result->month = span1->month + span2->month; + /* overflow check copied from int4pl */ + if (SAMESIGN(span1->month, span2->month) && + !SAMESIGN(result->month, span1->month)) + ereport(ERROR,The problem is that this is a bug waiting to happen for anyone who
uses this function with "result" pointing to the same Interval struct
as "span1" or "span2". I understand that the current code avoids this
by careful use of temporary Interval structs, but it's still a pretty
ugly pattern. This can be avoided by using pg_add_s32/64_overflow(),
which then allows the callers to be simplified, getting rid of the
temporary Interval structs and memcpy()'s.
That's a good idea. Done.
Also, in do_interval_discard(), this seems a bit risky:
+ neg_val.day = -newval->day; + neg_val.month = -newval->month; + neg_val.time = -newval->time;because it could in theory turn a finite large negative interval into
an infinite one (-INT_MAX -> INT_MAX), leading to an assertion failure
in finite_interval_pl(). Now maybe that's not possible for some other
reasons, but I think we may as well do the same refactoring for
interval_mi() as we're doing for interval_pl() -- i.e., introduce a
finite_interval_mi() function, making the addition and subtraction
code match, and removing the need for neg_val in
do_interval_discard().
Your suspicion is correct. It did throw an error. Added tests for the
same. Introduced finite_interval_mi() which uses
pg_sub_s32/s64_overflow() functions.
I will send updated patches with my next reply.
--
Best Wishes,
Ashutosh Bapat
On Thu, Sep 21, 2023 at 8:35 AM jian he <jian.universality@gmail.com> wrote:
On Wed, 20 Sept 2023 at 15:00, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:0005 - Refactored Jian's code fixing window functions. Does not
contain the changes for serialization and deserialization. Jian,
please let me know if I have missed anything else.attached serialization and deserialization function.
Thanks. They look good. I have incorporated those in the attached patch set.
One thing I didn't understand though is the use of
makeIntervalAggState() in interval_avg_deserialize(). In all other
deserialization functions like numeric_avg_deserialize() we create the
Agg State in CurrentMemoryContext but makeIntervalAggState() creates
it in aggcontext. And it works. We could change the code to allocate
agg state in aggcontext. Not a big change. But I did not find any
explanation as to why we use CurrentMemoryContext in other places.
Dean, do you have any idea?
Also, in do_interval_discard(), this seems a bit risky:
+ neg_val.day = -newval->day; + neg_val.month = -newval->month; + neg_val.time = -newval->time;we already have interval negate function, So I changed to interval_um_internal.
based on 20230920 patches. I have made the attached changes.
I didn't use this since it still requires neg_val variable and the
implementation for finite interval subtraction would still be differ
in interval_mi and do_interval_discard().
The serialization do make big difference when configure to parallel mode.
Yes. On my machine queryE shows following timings, that's huge change
because of parallel query.
with the ser/deser functions: 112.193 ms
without those functions: 272.759 ms.
Before the introduction of Internal IntervalAggState, there were no
serialize, deserialize functions. I wonder how did parallel query
work. Did it just use serialize/deserialize functions of _interval?
The attached patches are thus
0001 - 0005 - same as the last patch set.
Dean, if you are fine with the changes in 0004, I would like to merge
that into 0003.
0006 - uses pg_add/sub_s32/64_overflow functions in finite_interval_pl
and also introduces finite_interval_mi as suggested by Dean.
0007 - implements serialization and deserialization functions, but
uses aggcontext for deser.
Once we are fine with the last three patches, they need to be merged into 0003.
--
Best Wishes,
Ashutosh Bapat
Attachments:
0004-Introduce-infinity-interval-specification-i-20230921.patchtext/x-patch; charset=US-ASCII; name=0004-Introduce-infinity-interval-specification-i-20230921.patchDownload
From bb51c8162c6ae84d94e3ab42de55fa6198aea2fe Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 13 Sep 2023 12:43:49 +0530
Subject: [PATCH 4/7] Introduce 'infinity' interval specification in
DecodeInterval()
This reverts commit d6d1430f404386162831bc32906ad174b2007776 since
RESERVE is required to support infinite intervals. 'infinity' is saved
as a RESERVE in datetktbl[], thus required to parse
'infinity'::interval.
Infinte interval specification i.e. "+infinity", "-infinity" or
"infinity" does not require anything other that one of the three
strings. Fix DecodeInterval() to disallow anything else.
"ago" may be used to revrse the sign of infinity specification like how
ago reverts the sign of finite intervals. But that is not as intuitive
as it is with finite intervals. Hence disallows "infinity ago" as well.
Ashutosh Bapat
---
src/backend/utils/adt/datetime.c | 24 ++++++++++++++++
src/test/regress/expected/interval.out | 39 ++++++++++++++++++++++++++
src/test/regress/sql/interval.sql | 14 +++++++++
3 files changed, 77 insertions(+)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 5fed7e3dd1..b25bd6a551 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3254,6 +3254,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3582,6 +3585,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+
+ /*
+ * Only reserved words corresponding to infinite
+ * intervals are accepted.
+ */
+ if (uval != DTK_LATE && uval != DTK_EARLY)
+ return DTERR_BAD_FORMAT;
+
+ /*
+ * infinity can not be followed by anything else. We
+ * could allow "ago" to reverse the sign of infinity
+ * but that signed infinity is more intuitive.
+ */
+ if (i != nf - 1)
+ return DTERR_BAD_FORMAT;
+
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index a4630d17ef..d61b07f38e 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -2244,3 +2244,42 @@ SELECT INTERVAL '1 year months days 5 hours';
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 023cf3761a..183ddfaf31 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -735,3 +735,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
--
2.25.1
0007-Implement-serialization-functions-for-inter-20230921.patchtext/x-patch; charset=US-ASCII; name=0007-Implement-serialization-functions-for-inter-20230921.patchDownload
From 6f49ea0ab7cbe8c4cca6850d8253cddf86c7dd7c Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Thu, 21 Sep 2023 16:52:30 +0530
Subject: [PATCH 7/7] Implement serialization functions for interval aggregate
state
Now that interval aggregates use transition state which of date type Internal,
parallel aggregates require these separate functions.
Jian He, reviewed by Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 78 ++++++++++++++++++++++++++++
src/include/catalog/pg_aggregate.dat | 4 ++
src/include/catalog/pg_proc.dat | 6 +++
3 files changed, 88 insertions(+)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 17415cc4bd..31fd5d9e07 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -4004,6 +4004,84 @@ interval_avg_combine(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(state1);
}
+/*
+ * interval_avg_serialize
+ * Serialize IntervalAggState for interval aggregates.
+ */
+Datum
+interval_avg_serialize(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ StringInfoData buf;
+ bytea *result;
+
+ /* Ensure we disallow calling when not in aggregate context */
+ if (!AggCheckCallContext(fcinfo, NULL))
+ elog(ERROR, "aggregate function called in non-aggregate context");
+
+ /* TODO: Handle NULL inputs? */
+ state = (IntervalAggState *) PG_GETARG_POINTER(0);
+ pq_begintypsend(&buf);
+ /* N */
+ pq_sendint64(&buf, state->N);
+ /* Finite interval value */
+ pq_sendint64(&buf, state->sumX.time);
+ pq_sendint32(&buf, state->sumX.day);
+ pq_sendint32(&buf, state->sumX.month);
+ /* pInfcount */
+ pq_sendint64(&buf, state->pInfcount);
+ /* nInfcount */
+ pq_sendint64(&buf, state->nInfcount);
+ result = pq_endtypsend(&buf);
+ PG_RETURN_BYTEA_P(result);
+}
+
+/*
+ * interval_avg_deserialize
+ * Deserialize bytea into IntervalAggState for interval aggregates.
+ */
+Datum
+interval_avg_deserialize(PG_FUNCTION_ARGS)
+{
+ 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);
+
+ /*
+ * Copy the bytea into a StringInfo so that we can "receive" it using the
+ * standard recv-function infrastructure.
+ */
+ initStringInfo(&buf);
+ appendBinaryStringInfo(&buf,
+ VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
+
+ result = makeIntervalAggState(fcinfo);
+
+ /* N */
+ result->N = pq_getmsgint64(&buf);
+
+ /* Interval struct elements, one by one. */
+ result->sumX.time = pq_getmsgint64(&buf);
+ result->sumX.day = pq_getmsgint(&buf, sizeof(result->sumX.day));
+ result->sumX.month = pq_getmsgint(&buf, sizeof(result->sumX.month));
+
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
+
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
+
+ pq_getmsgend(&buf);
+ pfree(buf.data);
+
+ PG_RETURN_POINTER(result);
+}
+
/*
* Remove the given interval value from the aggregated state.
*/
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index e2087d7be1..0e62c3f7a6 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -45,6 +45,8 @@
aggtranstype => '_float8', agginitval => '{0,0,0}' },
{ 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_accum_inv',
aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
aggmtranstype => 'internal', aggtransspace => '128',
@@ -75,6 +77,8 @@
aggtranstype => 'money', aggmtranstype => 'money' },
{ 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_accum_inv',
aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
aggmtranstype => 'internal', aggtransspace => '128',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f8eca58ddf..34da5f3687 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4925,6 +4925,12 @@
proname => 'interval_accum_inv', proisstrict => 'f',
prorettype => 'internal', proargtypes => 'internal interval',
prosrc => 'interval_accum_inv' },
+{ oid => '3813', descr => 'aggregate serial function',
+ proname => 'interval_avg_serialize', prorettype => 'bytea',
+ proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
+{ oid => '3814', 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 => 'internal', prosrc => 'interval_avg' },
--
2.25.1
0005-Support-infinite-interval-values-in-sum-and-20230921.patchtext/x-patch; charset=US-ASCII; name=0005-Support-infinite-interval-values-in-sum-and-20230921.patchDownload
From c96cb360f5ce4a71f24f9dd12fbc10b9713f89b8 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 20 Sep 2023 15:41:00 +0530
Subject: [PATCH 5/7] Support infinite interval values in sum and avg window
functions
Results of arithmetic operations involing both infinity and -infinity
intervals are undefined. Sliding window functions
accumulate and discard values as the window slides through the data.
Even if the data may have both infinities, none of windows may have both
of them together and thus produce definite results. Hence we can not use
normal operator functions to accumulate and discard values. Instead we
maintain a count of both infinities seen respectively in an aggregate
state and compute the aggregate for each window based on the accumulated
finite value and the counts of infinities, all of which are updated as
the window slides through the data.
Initial patch by Jian He, significantly edited by Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 407 ++++++++++++++++++---------
src/include/catalog/pg_aggregate.dat | 23 +-
src/include/catalog/pg_proc.dat | 20 +-
src/test/regress/expected/window.out | 62 ++++
src/test/regress/sql/window.sql | 43 +++
src/tools/pgindent/typedefs.list | 1 +
6 files changed, 400 insertions(+), 156 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3100ad610f..21478c387c 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -73,6 +73,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+/*
+ * The transition datatype for interval aggregates is declared as Internal. It's
+ * a pointer to a IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+ int64 N; /* count of processed intervals */
+ Interval sumX; /* sum of processed intervals */
+ /* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +Inf values */
+ int64 nInfcount; /* count of -Inf values */
+} 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);
@@ -82,6 +97,8 @@ static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
static void interval_um_internal(Interval *interval, Interval *result);
+static void finite_interval_pl(Interval *result, Interval *span1,
+ Interval *span2);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -3447,34 +3464,7 @@ interval_pl(PG_FUNCTION_ARGS)
else if (INTERVAL_NOT_FINITE(span2))
memcpy(result, span2, sizeof(Interval));
else
- {
- 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")));
-
- 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")));
-
- if (INTERVAL_NOT_FINITE(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- }
+ finite_interval_pl(result, span1, span2);
PG_RETURN_INTERVAL_P(result);
}
@@ -3860,161 +3850,300 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function that needs to compute
+ * sum and count.
+ */
+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;
+}
+
+/*
+ * Function to add two finite intervals.
*
- * 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.
+ * We handle non-finite intervals in different ways when accumulating intervals
+ * and adding two intervals respectively. But the addition of finite interval
+ * has same implementation in both these cases.
*/
+static void
+finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
-Datum
-interval_accum(PG_FUNCTION_ARGS)
+ 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")));
+
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+/*
+ * Accumulate a new input value for interval aggregate functions.
+ */
+static void
+do_interval_accum(IntervalAggState *state, Interval *newval)
{
- 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;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ Interval temp;
+
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount++;
+ return;
+ }
+
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount++;
+ return;
+ }
+
+ memcpy(&temp, &state->sumX, sizeof(Interval));
+ finite_interval_pl(&state->sumX, &temp, newval);
+ state->N++;
+}
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+/*
+ * Transition function for interval aggregates.
+ */
+Datum
+interval_avg_accum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ /* Create the state data on the first call */
+ if (state == NULL)
+ state = makeIntervalAggState(fcinfo);
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ if (!PG_ARGISNULL(1))
+ do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
- PG_RETURN_ARRAYTYPE_P(result);
+ PG_RETURN_POINTER(state);
}
+/*
+ * Combine function for interval aggregates.
+ */
Datum
-interval_combine(PG_FUNCTION_ARGS)
+interval_avg_combine(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 *state1;
+ IntervalAggState *state2;
+
+ state1 = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+ state2 = PG_ARGISNULL(1) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(1);
- Interval *newsum;
- ArrayType *result;
+ if (state2 == NULL)
+ PG_RETURN_POINTER(state1);
+
+ /* manually copy all fields from state2 to state1 */
+ if (state1 == NULL)
+ {
+ state1 = makeIntervalAggState(fcinfo);
- deconstruct_array(transarray1,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums1, NULL, &ndatums1);
- if (ndatums1 != 2)
- elog(ERROR, "expected 2-element interval array");
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
- sum1 = *(DatumGetIntervalP(transdatums1[0]));
- N1 = *(DatumGetIntervalP(transdatums1[1]));
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
- deconstruct_array(transarray2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums2, NULL, &ndatums2);
- if (ndatums2 != 2)
- elog(ERROR, "expected 2-element interval array");
+ PG_RETURN_POINTER(state1);
+ }
+
+ state1->N += state2->N;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
+
+ if (state2->N > 0)
+ {
+ Interval temp;
+
+ /* Accumulate interval values */
+ memcpy(&temp, &state1->sumX, sizeof(Interval));
+ finite_interval_pl(&state1->sumX, &temp, &state2->sumX);
+ }
+
+ PG_RETURN_POINTER(state1);
+}
- sum2 = *(DatumGetIntervalP(transdatums2[0]));
- N2 = *(DatumGetIntervalP(transdatums2[1]));
+/*
+ * Remove the given interval value from the aggregated state.
+ */
+static void
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount--;
+ return;
+ }
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sum1),
- IntervalPGetDatum(&sum2)));
- N1.time += N2.time;
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount--;
+ return;
+ }
- transdatums1[0] = IntervalPGetDatum(newsum);
- transdatums1[1] = IntervalPGetDatum(&N1);
+ /* Handle to be discarded finite value. */
+ if (state->N > 0)
+ {
+ Interval temp;
+ Interval neg_val;
- result = construct_array(transdatums1, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ neg_val.day = -newval->day;
+ neg_val.month = -newval->month;
+ neg_val.time = -newval->time;
- PG_RETURN_ARRAYTYPE_P(result);
+ memcpy(&temp, &state->sumX, sizeof(Interval));
+ finite_interval_pl(&state->sumX, &temp, &neg_val);
+ }
+ else
+ {
+ /* All values discarded, reset the state */
+ memset(&state->sumX, 0, sizeof(state->sumX));
+ }
+ state->N--;
}
+/*
+ * Generic inverse transition function for interval aggregates
+ */
Datum
interval_accum_inv(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Should not get here with no state */
+ if (state == NULL)
+ elog(ERROR, "interval_accum_inv called with NULL state");
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ if (!PG_ARGISNULL(1))
+ do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ PG_RETURN_POINTER(state);
+}
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ double N_datum;
+ Interval *sumX;
+
+ sumX = (Interval *) palloc(sizeof(Interval));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
- PG_RETURN_ARRAYTYPE_P(result);
+ /* adding plus and minus infinities gives error */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ N_datum = (double) state->N;
+ sumX = &state->sumX;
+
+ PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX),
+ Float8GetDatum(N_datum)));
}
+/* sum(interval) aggregate final function */
Datum
-interval_avg(PG_FUNCTION_ARGS)
+interval_sum(PG_FUNCTION_ARGS)
{
- ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
- Datum *transdatums;
- int ndatums;
- Interval sumX,
- N;
+ IntervalAggState *state;
+ Interval *result;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ result = (Interval *) palloc(sizeof(Interval));
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
-}
+ /* adding plus and minus interval infinities is not possible */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ memcpy(result, &state->sumX, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e2087d7be1 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
+ aggmtranstype => 'internal', aggtransspace => '128',
+ aggmtransspace => '128'
+},
# sum
{ aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
@@ -72,10 +73,12 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
+ aggmtranstype => 'internal', aggtransspace => '128',
+ aggmtransspace => '128'},
{ aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
aggserialfn => 'numeric_avg_serialize',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..f8eca58ddf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4914,17 +4914,23 @@
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_accum_inv', proisstrict => 'f',
+ prorettype => 'internal', proargtypes => 'internal interval',
+ prosrc => 'interval_accum_inv' },
{ oid => '1844', descr => 'aggregate final function',
proname => 'interval_avg', prorettype => 'interval',
- proargtypes => '_interval', prosrc => 'interval_avg' },
+ proargtypes => 'internal', prosrc => 'interval_avg' },
+{ oid => '8069', 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' },
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..45262924cd 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,6 +4375,68 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev1_curr_sum
+-----------+---------------+----------------
+ -infinity | -infinity | -infinity
+ @ 6 days | infinity | -infinity
+ infinity | infinity | infinity
+ infinity | infinity | infinity
+ | | infinity
+ | |
+(6 rows)
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | curr_next_sum | prev1_curr_avg | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum
+-----------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
+ | infinity | infinity | | | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | @ 13 days | infinity | infinity | infinity | infinity
+ @ 7 days | @ 7 days | @ 7 days | @ 6 days 12 hours | @ 13 days | -infinity | -infinity
+ | -infinity | -infinity | @ 7 days | @ 7 days | -infinity | -infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity | -infinity | -infinity
+(7 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
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..d00423946d 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,6 +1591,49 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('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);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f3d8a2a855..4df61a69ca 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1204,6 +1204,7 @@ IntegerSet
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup
--
2.25.1
0003-Add-infinite-interval-values-20230921.patchtext/x-patch; charset=US-ASCII; name=0003-Add-infinite-interval-values-20230921.patchDownload
From 9442b707b6aec49da063ae1714716325cef826dd Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/7] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 4 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 707 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1546 insertions(+), 207 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 8d32a8c9c5..557b81cae8 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,12 +2320,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 24ad87f910..7786853743 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9537,7 +9537,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10434,7 +10434,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de2c..f2107dd967 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(!INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 48fc0d5942..5fed7e3dd1 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3557,6 +3557,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index e27ea8ef97..e9e85503f8 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..6e87173846 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3ab9b6918e..3100ad610f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1523,6 +1545,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1544,6 +1567,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1561,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2039,6 +2078,8 @@ 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;
}
@@ -2056,6 +2097,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2089,7 +2132,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2220,6 +2265,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2715,46 +2783,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2784,6 +2877,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))
@@ -2836,6 +2932,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2859,6 +2960,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,
@@ -2876,6 +2980,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2897,6 +3006,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))
@@ -2915,6 +3027,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2935,7 +3052,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3014,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3043,7 +3181,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3133,9 +3294,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3186,6 +3345,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3193,23 +3377,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3253,27 +3421,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3287,27 +3488,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3332,6 +3568,49 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results
+ * in an infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ Interval zero;
+ int factor_sign,
+ result_sign;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ memset(&zero, 0, sizeof(zero));
+ result_sign = interval_cmp_internal(span, &zero) * factor_sign;
+
+ if (result_sign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_sign < 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)
@@ -3392,6 +3671,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3423,6 +3707,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3444,6 +3751,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3727,8 +4039,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3847,8 +4186,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3973,6 +4339,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4156,6 +4527,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4394,6 +4770,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);
@@ -4738,7 +5120,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5012,7 +5394,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5252,6 +5634,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5276,6 +5711,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5518,6 +5981,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5748,6 +6218,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5814,6 +6291,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5896,6 +6378,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8c9..fe713fb6dd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 96db07c3b4..a4630d17ef 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,6 +1851,381 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+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"
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index f328b8914d..023cf3761a 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -588,6 +595,139 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.25.1
0001-Move-integer-helper-function-to-int.h-20230921.patchtext/x-patch; charset=US-ASCII; name=0001-Move-integer-helper-function-to-int.h-20230921.patchDownload
From 74d2b7e42f72c2727f35aec9a1d68a8a6e738bdd Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/7] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..48fc0d5942 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.25.1
0002-Check-for-overflow-in-make_interval-20230921.patchtext/x-patch; charset=US-ASCII; name=0002-Check-for-overflow-in-make_interval-20230921.patchDownload
From 7e5ec988a6a300ff458a13b514920471433a1408 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/7] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec5a..3ab9b6918e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1518,13 +1518,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index c0ca8e041b..96db07c3b4 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 038fc508d0..f328b8914d 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.25.1
0006-Use-integer-overflow-checking-routines-to-a-20230921.patchtext/x-patch; charset=US-ASCII; name=0006-Use-integer-overflow-checking-routines-to-a-20230921.patchDownload
From 12fbd8b15c963e1e69294ade4f6f5af6ef7b27c5 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Thu, 21 Sep 2023 15:34:33 +0530
Subject: [PATCH 6/7] Use integer overflow checking routines to add and
subtract finite intervals
Use pg_add/sub_s32/64_overflow() routines in finite_interval_pl() and
finite_interval_mi() instead of adding overflow checks in those
functions.
Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 121 +++++++++++--------------
src/test/regress/expected/interval.out | 11 +++
src/test/regress/expected/window.out | 54 ++++-------
src/test/regress/sql/interval.sql | 3 +
src/test/regress/sql/window.sql | 22 ++---
5 files changed, 95 insertions(+), 116 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 21478c387c..17415cc4bd 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -99,6 +99,8 @@ static Timestamp timestamptz2timestamp(TimestampTz timestamp);
static void interval_um_internal(Interval *interval, Interval *result);
static void finite_interval_pl(Interval *result, Interval *span1,
Interval *span2);
+static void finite_interval_mi(Interval *result, Interval *span1,
+ Interval *span2);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -3506,34 +3508,7 @@ interval_mi(PG_FUNCTION_ARGS)
else if (INTERVAL_IS_NOEND(span2))
INTERVAL_NOBEGIN(result);
else
- {
- 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")));
-
- if (INTERVAL_NOT_FINITE(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- }
+ finite_interval_mi(result, span1, span2);
PG_RETURN_INTERVAL_P(result);
}
@@ -3873,11 +3848,18 @@ makeIntervalAggState(FunctionCallInfo fcinfo)
}
/*
- * Function to add two finite intervals.
+ * Functions to add or subtract two finite intervals.
+ *
+ * We handle non-finite intervals in different ways when accumulating/discarding
+ * intervals and in actual mathematical operations respectively. But the
+ * addition or subtraction of finite intervals have same implementation
+ * respectively in both these cases.
+ *
+ * Addition/Subtraction of span1 and span2 is stored in the result. It is fine
+ * to pass either of span1 or span2 as a result pointer. In such a case result
+ * will overwrite the given value.
*
- * We handle non-finite intervals in different ways when accumulating intervals
- * and adding two intervals respectively. But the addition of finite interval
- * has same implementation in both these cases.
+ * If the result is not a valid interval, the function throws an error.
*/
static void
finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
@@ -3885,24 +3867,44 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
Assert(!INTERVAL_NOT_FINITE(span1));
Assert(!INTERVAL_NOT_FINITE(span2));
- result->month = span1->month + span2->month;
- /* overflow check copied from int4pl */
- if (SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
+ if (pg_add_s32_overflow(span1->month, span2->month, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+static void
+finite_interval_mi(Interval *result, Interval *span1, Interval *span2)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
+
+ if (pg_sub_s32_overflow(span1->month, span2->month, &result->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))
+ if (pg_sub_s32_overflow(span1->day, span2->day, &result->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))
+ if (pg_sub_s64_overflow(span1->time, span2->time, &result->time))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
@@ -3919,9 +3921,6 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
static void
do_interval_accum(IntervalAggState *state, Interval *newval)
{
-
- Interval temp;
-
/* Count -infinity inputs separately from all else */
if (INTERVAL_IS_NOBEGIN(newval))
{
@@ -3936,8 +3935,7 @@ do_interval_accum(IntervalAggState *state, Interval *newval)
return;
}
- memcpy(&temp, &state->sumX, sizeof(Interval));
- finite_interval_pl(&state->sumX, &temp, newval);
+ finite_interval_pl(&state->sumX, &state->sumX, newval);
state->N++;
}
@@ -3963,6 +3961,9 @@ interval_avg_accum(PG_FUNCTION_ARGS)
/*
* Combine function for interval aggregates.
+ *
+ * Combine the given internal aggregate states and place the combination in the
+ * first argument.
*/
Datum
interval_avg_combine(PG_FUNCTION_ARGS)
@@ -3976,9 +3977,9 @@ interval_avg_combine(PG_FUNCTION_ARGS)
if (state2 == NULL)
PG_RETURN_POINTER(state1);
- /* manually copy all fields from state2 to state1 */
if (state1 == NULL)
{
+ /* manually copy all fields from state2 to state1 */
state1 = makeIntervalAggState(fcinfo);
state1->N = state2->N;
@@ -3996,14 +3997,9 @@ interval_avg_combine(PG_FUNCTION_ARGS)
state1->pInfcount += state2->pInfcount;
state1->nInfcount += state2->nInfcount;
+ /* Accumulate finite interval values, if any. */
if (state2->N > 0)
- {
- Interval temp;
-
- /* Accumulate interval values */
- memcpy(&temp, &state1->sumX, sizeof(Interval));
- finite_interval_pl(&state1->sumX, &temp, &state2->sumX);
- }
+ finite_interval_pl(&state1->sumX, &state1->sumX, &state2->sumX);
PG_RETURN_POINTER(state1);
}
@@ -4029,24 +4025,15 @@ do_interval_discard(IntervalAggState *state, Interval *newval)
}
/* Handle to be discarded finite value. */
+ state->N--;
if (state->N > 0)
- {
- Interval temp;
- Interval neg_val;
-
- neg_val.day = -newval->day;
- neg_val.month = -newval->month;
- neg_val.time = -newval->time;
-
- memcpy(&temp, &state->sumX, sizeof(Interval));
- finite_interval_pl(&state->sumX, &temp, &neg_val);
- }
+ finite_interval_mi(&state->sumX, &state->sumX, newval);
else
{
/* All values discarded, reset the state */
+ Assert(state->N == 0);
memset(&state->sumX, 0, sizeof(state->sumX));
}
- state->N--;
}
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index d61b07f38e..c5587fc903 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -347,6 +347,17 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
(5 rows)
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column?
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- Floating point arithmetic rounding errors can lead to unexpected results,
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 45262924cd..63f36e069e 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,51 +4375,37 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
---order by.
-SELECT x
- ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
-FROM (VALUES (NULL::interval),
- ('infinity'::interval),
- ('infinity'::timestamptz - now()),
- ('6 days'::interval),
- (NULL::interval),
- ('-infinity'::interval)) v(x);
- x | curr_next_avg | prev1_curr_sum
------------+---------------+----------------
- -infinity | -infinity | -infinity
- @ 6 days | infinity | -infinity
- infinity | infinity | infinity
- infinity | infinity | infinity
- | | infinity
- | |
-(6 rows)
-
---no order by.
+-- window function over interval, infinity and extreme values test the
+-- behaviour of accumulation and elimination of these values as the window
+-- slides.
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
+ ('2147483647 days 2147483646 months'), -- extreme interval value
('infinity'::timestamptz - now()),
+ ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
(NULL::interval),
('-infinity'::interval)) v(x);
- x | curr_next_avg | curr_next_sum | prev1_curr_avg | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum
------------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
- | infinity | infinity | | | infinity | infinity
- infinity | infinity | infinity | infinity | infinity | infinity | infinity
- infinity | infinity | infinity | infinity | infinity | infinity | infinity
- @ 6 days | @ 6 days 12 hours | @ 13 days | infinity | infinity | infinity | infinity
- @ 7 days | @ 7 days | @ 7 days | @ 6 days 12 hours | @ 13 days | -infinity | -infinity
- | -infinity | -infinity | @ 7 days | @ 7 days | -infinity | -infinity
- -infinity | -infinity | -infinity | -infinity | -infinity | -infinity | -infinity
-(7 rows)
+ x | curr_next_avg | prev1_curr_avg | curr_next_sum | prev1_curr_sum
+----------------------------------------------+-------------------+-------------------+---------------+----------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 6 mons 2147483647 days | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 7 mons 2147483648 days ago | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
+(10 rows)
--should fail.
SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 183ddfaf31..e31c5c3d98 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -99,6 +99,9 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index d00423946d..6dd9885949 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,28 +1591,20 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
---order by.
-SELECT x
- ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
-FROM (VALUES (NULL::interval),
- ('infinity'::interval),
- ('infinity'::timestamptz - now()),
- ('6 days'::interval),
- (NULL::interval),
- ('-infinity'::interval)) v(x);
-
---no order by.
+-- window function over interval, infinity and extreme values test the
+-- behaviour of accumulation and elimination of these values as the window
+-- slides.
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
+ ('2147483647 days 2147483646 months'), -- extreme interval value
('infinity'::timestamptz - now()),
+ ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
(NULL::interval),
--
2.25.1
On Thu, Sep 21, 2023 at 7:21 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
One thing I didn't understand though is the use of
makeIntervalAggState() in interval_avg_deserialize(). In all other
deserialization functions like numeric_avg_deserialize() we create the
Agg State in CurrentMemoryContext but makeIntervalAggState() creates
it in aggcontext. And it works. We could change the code to allocate
agg state in aggcontext. Not a big change. But I did not find any
explanation as to why we use CurrentMemoryContext in other places.
Dean, do you have any idea?
Following code in ExecInterpExpr makes it clear that the
deserialization function is be executed in per tuple memory context.
Whereas the aggregate's context is different from this context and may
lives longer that the context in which deserialization is expected to
happen.
/* evaluate aggregate deserialization function (non-strict portion) */
EEO_CASE(EEOP_AGG_DESERIALIZE)
{
FunctionCallInfo fcinfo = op->d.agg_deserialize.fcinfo_data;
AggState *aggstate = castNode(AggState, state->parent);
MemoryContext oldContext;
/*
* We run the deserialization functions in per-input-tuple memory
* context.
*/
oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
fcinfo->isnull = false;
*op->resvalue = FunctionCallInvoke(fcinfo);
*op->resnull = fcinfo->isnull;
MemoryContextSwitchTo(oldContext);
Hence I have changed interval_avg_deserialize() in 0007 to use
CurrentMemoryContext instead of aggcontext. Rest of the patches are
same as previous set.
--
Best Wishes,
Ashutosh Bapat
Attachments:
0001-Move-integer-helper-function-to-int.h-20230922.patchtext/x-patch; charset=US-ASCII; name=0001-Move-integer-helper-function-to-int.h-20230922.patchDownload
From 74d2b7e42f72c2727f35aec9a1d68a8a6e738bdd Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:24 -0400
Subject: [PATCH 1/7] Move integer helper function to int.h
---
src/backend/utils/adt/datetime.c | 25 ++++---------------------
src/include/common/int.h | 13 +++++++++++++
2 files changed, 17 insertions(+), 21 deletions(-)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..48fc0d5942 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -51,7 +51,6 @@ static int DecodeDate(char *str, int fmask, int *tmask, bool *is2digits,
struct pg_tm *tm);
static char *AppendSeconds(char *cp, int sec, fsec_t fsec,
int precision, bool fillzeros);
-static bool int64_multiply_add(int64 val, int64 multiplier, int64 *sum);
static bool AdjustFractMicroseconds(double frac, int64 scale,
struct pg_itm_in *itm_in);
static bool AdjustFractDays(double frac, int scale,
@@ -515,22 +514,6 @@ AppendTimestampSeconds(char *cp, struct pg_tm *tm, fsec_t fsec)
return AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, true);
}
-
-/*
- * Add val * multiplier to *sum.
- * Returns true if successful, false on overflow.
- */
-static bool
-int64_multiply_add(int64 val, int64 multiplier, int64 *sum)
-{
- int64 product;
-
- if (pg_mul_s64_overflow(val, multiplier, &product) ||
- pg_add_s64_overflow(*sum, product, sum))
- return false;
- return true;
-}
-
/*
* Multiply frac by scale (to produce microseconds) and add to itm_in->tm_usec.
* Returns true if successful, false if itm_in overflows.
@@ -621,7 +604,7 @@ AdjustMicroseconds(int64 val, double fval, int64 scale,
struct pg_itm_in *itm_in)
{
/* Handle the integer part */
- if (!int64_multiply_add(val, scale, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(val, scale, &itm_in->tm_usec))
return false;
/* Handle the float part */
return AdjustFractMicroseconds(fval, scale, itm_in);
@@ -2701,9 +2684,9 @@ DecodeTimeForInterval(char *str, int fmask, int range,
return dterr;
itm_in->tm_usec = itm.tm_usec;
- if (!int64_multiply_add(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
- !int64_multiply_add(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
+ if (pg_mul_add_s64_overflow(itm.tm_hour, USECS_PER_HOUR, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_min, USECS_PER_MINUTE, &itm_in->tm_usec) ||
+ pg_mul_add_s64_overflow(itm.tm_sec, USECS_PER_SEC, &itm_in->tm_usec))
return DTERR_FIELD_OVERFLOW;
return 0;
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..81726c65f7 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -254,6 +254,19 @@ pg_mul_s64_overflow(int64 a, int64 b, int64 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s64_overflow(int64 val, int64 multiplier, int64 *sum)
+{
+ int64 product;
+
+ return pg_mul_s64_overflow(val, multiplier, &product) ||
+ pg_add_s64_overflow(*sum, product, sum);
+}
+
/*------------------------------------------------------------------------
* Overflow routines for unsigned integers
*------------------------------------------------------------------------
--
2.25.1
0002-Check-for-overflow-in-make_interval-20230922.patchtext/x-patch; charset=US-ASCII; name=0002-Check-for-overflow-in-make_interval-20230922.patchDownload
From 7e5ec988a6a300ff458a13b514920471433a1408 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 2/7] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 30 ++++++++++++++++++++------
src/include/common/int.h | 13 +++++++++++
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 7 ++++++
src/test/regress/sql/interval.sql | 5 +++++
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec5a..3ab9b6918e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1518,13 +1518,31 @@ make_interval(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
+ result->month = months;
+ if (pg_mul_add_s32_overflow(years, MONTHS_PER_YEAR, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->day = days;
+ if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ result->time = (int64) secs;
+ if (pg_mul_add_s64_overflow(mins, ((int64) SECS_PER_MINUTE * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ if (pg_mul_add_s64_overflow(hours, ((int64) SECS_PER_HOUR * USECS_PER_SEC), &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
PG_RETURN_INTERVAL_P(result);
}
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 81726c65f7..48ef495551 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -154,6 +154,19 @@ pg_mul_s32_overflow(int32 a, int32 b, int32 *result)
#endif
}
+/*
+ * Add val * multiplier to *sum.
+ * Returns false if successful, true on overflow.
+ */
+static inline bool
+pg_mul_add_s32_overflow(int32 val, int32 multiplier, int32 *sum)
+{
+ int32 product;
+
+ return pg_mul_s32_overflow(val, multiplier, &product) ||
+ pg_add_s32_overflow(*sum, product, sum);
+}
+
/*
* INT64
*/
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index c0ca8e041b..96db07c3b4 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,13 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+ERROR: interval out of range
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+ERROR: interval out of range
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+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';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 038fc508d0..f328b8914d 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,11 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(1, 2147483647, 2147483647, 1, 1, 1, 9223372036854.7759);
+select make_interval(-1, -2147483648, -2147483648, -1, -1, -1, -9223372036854.7759);
+SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.25.1
0005-Support-infinite-interval-values-in-sum-and-20230922.patchtext/x-patch; charset=US-ASCII; name=0005-Support-infinite-interval-values-in-sum-and-20230922.patchDownload
From c96cb360f5ce4a71f24f9dd12fbc10b9713f89b8 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 20 Sep 2023 15:41:00 +0530
Subject: [PATCH 5/7] Support infinite interval values in sum and avg window
functions
Results of arithmetic operations involing both infinity and -infinity
intervals are undefined. Sliding window functions
accumulate and discard values as the window slides through the data.
Even if the data may have both infinities, none of windows may have both
of them together and thus produce definite results. Hence we can not use
normal operator functions to accumulate and discard values. Instead we
maintain a count of both infinities seen respectively in an aggregate
state and compute the aggregate for each window based on the accumulated
finite value and the counts of infinities, all of which are updated as
the window slides through the data.
Initial patch by Jian He, significantly edited by Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 407 ++++++++++++++++++---------
src/include/catalog/pg_aggregate.dat | 23 +-
src/include/catalog/pg_proc.dat | 20 +-
src/test/regress/expected/window.out | 62 ++++
src/test/regress/sql/window.sql | 43 +++
src/tools/pgindent/typedefs.list | 1 +
6 files changed, 400 insertions(+), 156 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3100ad610f..21478c387c 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -73,6 +73,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+/*
+ * The transition datatype for interval aggregates is declared as Internal. It's
+ * a pointer to a IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+ int64 N; /* count of processed intervals */
+ Interval sumX; /* sum of processed intervals */
+ /* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +Inf values */
+ int64 nInfcount; /* count of -Inf values */
+} 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);
@@ -82,6 +97,8 @@ static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
static void interval_um_internal(Interval *interval, Interval *result);
+static void finite_interval_pl(Interval *result, Interval *span1,
+ Interval *span2);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -3447,34 +3464,7 @@ interval_pl(PG_FUNCTION_ARGS)
else if (INTERVAL_NOT_FINITE(span2))
memcpy(result, span2, sizeof(Interval));
else
- {
- 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")));
-
- 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")));
-
- if (INTERVAL_NOT_FINITE(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- }
+ finite_interval_pl(result, span1, span2);
PG_RETURN_INTERVAL_P(result);
}
@@ -3860,161 +3850,300 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function that needs to compute
+ * sum and count.
+ */
+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;
+}
+
+/*
+ * Function to add two finite intervals.
*
- * 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.
+ * We handle non-finite intervals in different ways when accumulating intervals
+ * and adding two intervals respectively. But the addition of finite interval
+ * has same implementation in both these cases.
*/
+static void
+finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
-Datum
-interval_accum(PG_FUNCTION_ARGS)
+ 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")));
+
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+/*
+ * Accumulate a new input value for interval aggregate functions.
+ */
+static void
+do_interval_accum(IntervalAggState *state, Interval *newval)
{
- 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;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ Interval temp;
+
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount++;
+ return;
+ }
+
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount++;
+ return;
+ }
+
+ memcpy(&temp, &state->sumX, sizeof(Interval));
+ finite_interval_pl(&state->sumX, &temp, newval);
+ state->N++;
+}
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+/*
+ * Transition function for interval aggregates.
+ */
+Datum
+interval_avg_accum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ /* Create the state data on the first call */
+ if (state == NULL)
+ state = makeIntervalAggState(fcinfo);
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ if (!PG_ARGISNULL(1))
+ do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
- PG_RETURN_ARRAYTYPE_P(result);
+ PG_RETURN_POINTER(state);
}
+/*
+ * Combine function for interval aggregates.
+ */
Datum
-interval_combine(PG_FUNCTION_ARGS)
+interval_avg_combine(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 *state1;
+ IntervalAggState *state2;
+
+ state1 = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+ state2 = PG_ARGISNULL(1) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(1);
- Interval *newsum;
- ArrayType *result;
+ if (state2 == NULL)
+ PG_RETURN_POINTER(state1);
+
+ /* manually copy all fields from state2 to state1 */
+ if (state1 == NULL)
+ {
+ state1 = makeIntervalAggState(fcinfo);
- deconstruct_array(transarray1,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums1, NULL, &ndatums1);
- if (ndatums1 != 2)
- elog(ERROR, "expected 2-element interval array");
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
- sum1 = *(DatumGetIntervalP(transdatums1[0]));
- N1 = *(DatumGetIntervalP(transdatums1[1]));
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
- deconstruct_array(transarray2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums2, NULL, &ndatums2);
- if (ndatums2 != 2)
- elog(ERROR, "expected 2-element interval array");
+ PG_RETURN_POINTER(state1);
+ }
+
+ state1->N += state2->N;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
+
+ if (state2->N > 0)
+ {
+ Interval temp;
+
+ /* Accumulate interval values */
+ memcpy(&temp, &state1->sumX, sizeof(Interval));
+ finite_interval_pl(&state1->sumX, &temp, &state2->sumX);
+ }
+
+ PG_RETURN_POINTER(state1);
+}
- sum2 = *(DatumGetIntervalP(transdatums2[0]));
- N2 = *(DatumGetIntervalP(transdatums2[1]));
+/*
+ * Remove the given interval value from the aggregated state.
+ */
+static void
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount--;
+ return;
+ }
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sum1),
- IntervalPGetDatum(&sum2)));
- N1.time += N2.time;
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount--;
+ return;
+ }
- transdatums1[0] = IntervalPGetDatum(newsum);
- transdatums1[1] = IntervalPGetDatum(&N1);
+ /* Handle to be discarded finite value. */
+ if (state->N > 0)
+ {
+ Interval temp;
+ Interval neg_val;
- result = construct_array(transdatums1, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ neg_val.day = -newval->day;
+ neg_val.month = -newval->month;
+ neg_val.time = -newval->time;
- PG_RETURN_ARRAYTYPE_P(result);
+ memcpy(&temp, &state->sumX, sizeof(Interval));
+ finite_interval_pl(&state->sumX, &temp, &neg_val);
+ }
+ else
+ {
+ /* All values discarded, reset the state */
+ memset(&state->sumX, 0, sizeof(state->sumX));
+ }
+ state->N--;
}
+/*
+ * Generic inverse transition function for interval aggregates
+ */
Datum
interval_accum_inv(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Should not get here with no state */
+ if (state == NULL)
+ elog(ERROR, "interval_accum_inv called with NULL state");
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ if (!PG_ARGISNULL(1))
+ do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ PG_RETURN_POINTER(state);
+}
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ double N_datum;
+ Interval *sumX;
+
+ sumX = (Interval *) palloc(sizeof(Interval));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
- PG_RETURN_ARRAYTYPE_P(result);
+ /* adding plus and minus infinities gives error */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ N_datum = (double) state->N;
+ sumX = &state->sumX;
+
+ PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX),
+ Float8GetDatum(N_datum)));
}
+/* sum(interval) aggregate final function */
Datum
-interval_avg(PG_FUNCTION_ARGS)
+interval_sum(PG_FUNCTION_ARGS)
{
- ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
- Datum *transdatums;
- int ndatums;
- Interval sumX,
- N;
+ IntervalAggState *state;
+ Interval *result;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ result = (Interval *) palloc(sizeof(Interval));
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
-}
+ /* adding plus and minus interval infinities is not possible */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ memcpy(result, &state->sumX, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e2087d7be1 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
+ aggmtranstype => 'internal', aggtransspace => '128',
+ aggmtransspace => '128'
+},
# sum
{ aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
@@ -72,10 +73,12 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
+ aggmtranstype => 'internal', aggtransspace => '128',
+ aggmtransspace => '128'},
{ aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
aggserialfn => 'numeric_avg_serialize',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..f8eca58ddf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4914,17 +4914,23 @@
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_accum_inv', proisstrict => 'f',
+ prorettype => 'internal', proargtypes => 'internal interval',
+ prosrc => 'interval_accum_inv' },
{ oid => '1844', descr => 'aggregate final function',
proname => 'interval_avg', prorettype => 'interval',
- proargtypes => '_interval', prosrc => 'interval_avg' },
+ proargtypes => 'internal', prosrc => 'interval_avg' },
+{ oid => '8069', 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' },
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..45262924cd 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,6 +4375,68 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev1_curr_sum
+-----------+---------------+----------------
+ -infinity | -infinity | -infinity
+ @ 6 days | infinity | -infinity
+ infinity | infinity | infinity
+ infinity | infinity | infinity
+ | | infinity
+ | |
+(6 rows)
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | curr_next_sum | prev1_curr_avg | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum
+-----------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
+ | infinity | infinity | | | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | @ 13 days | infinity | infinity | infinity | infinity
+ @ 7 days | @ 7 days | @ 7 days | @ 6 days 12 hours | @ 13 days | -infinity | -infinity
+ | -infinity | -infinity | @ 7 days | @ 7 days | -infinity | -infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity | -infinity | -infinity
+(7 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
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..d00423946d 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,6 +1591,49 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('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);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f3d8a2a855..4df61a69ca 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1204,6 +1204,7 @@ IntegerSet
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup
--
2.25.1
0004-Introduce-infinity-interval-specification-i-20230922.patchtext/x-patch; charset=US-ASCII; name=0004-Introduce-infinity-interval-specification-i-20230922.patchDownload
From bb51c8162c6ae84d94e3ab42de55fa6198aea2fe Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 13 Sep 2023 12:43:49 +0530
Subject: [PATCH 4/7] Introduce 'infinity' interval specification in
DecodeInterval()
This reverts commit d6d1430f404386162831bc32906ad174b2007776 since
RESERVE is required to support infinite intervals. 'infinity' is saved
as a RESERVE in datetktbl[], thus required to parse
'infinity'::interval.
Infinte interval specification i.e. "+infinity", "-infinity" or
"infinity" does not require anything other that one of the three
strings. Fix DecodeInterval() to disallow anything else.
"ago" may be used to revrse the sign of infinity specification like how
ago reverts the sign of finite intervals. But that is not as intuitive
as it is with finite intervals. Hence disallows "infinity ago" as well.
Ashutosh Bapat
---
src/backend/utils/adt/datetime.c | 24 ++++++++++++++++
src/test/regress/expected/interval.out | 39 ++++++++++++++++++++++++++
src/test/regress/sql/interval.sql | 14 +++++++++
3 files changed, 77 insertions(+)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 5fed7e3dd1..b25bd6a551 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3254,6 +3254,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3582,6 +3585,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+
+ /*
+ * Only reserved words corresponding to infinite
+ * intervals are accepted.
+ */
+ if (uval != DTK_LATE && uval != DTK_EARLY)
+ return DTERR_BAD_FORMAT;
+
+ /*
+ * infinity can not be followed by anything else. We
+ * could allow "ago" to reverse the sign of infinity
+ * but that signed infinity is more intuitive.
+ */
+ if (i != nf - 1)
+ return DTERR_BAD_FORMAT;
+
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index a4630d17ef..d61b07f38e 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -2244,3 +2244,42 @@ SELECT INTERVAL '1 year months days 5 hours';
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 023cf3761a..183ddfaf31 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -735,3 +735,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
--
2.25.1
0006-Use-integer-overflow-checking-routines-to-a-20230922.patchtext/x-patch; charset=US-ASCII; name=0006-Use-integer-overflow-checking-routines-to-a-20230922.patchDownload
From 12fbd8b15c963e1e69294ade4f6f5af6ef7b27c5 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Thu, 21 Sep 2023 15:34:33 +0530
Subject: [PATCH 6/7] Use integer overflow checking routines to add and
subtract finite intervals
Use pg_add/sub_s32/64_overflow() routines in finite_interval_pl() and
finite_interval_mi() instead of adding overflow checks in those
functions.
Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 121 +++++++++++--------------
src/test/regress/expected/interval.out | 11 +++
src/test/regress/expected/window.out | 54 ++++-------
src/test/regress/sql/interval.sql | 3 +
src/test/regress/sql/window.sql | 22 ++---
5 files changed, 95 insertions(+), 116 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 21478c387c..17415cc4bd 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -99,6 +99,8 @@ static Timestamp timestamptz2timestamp(TimestampTz timestamp);
static void interval_um_internal(Interval *interval, Interval *result);
static void finite_interval_pl(Interval *result, Interval *span1,
Interval *span2);
+static void finite_interval_mi(Interval *result, Interval *span1,
+ Interval *span2);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -3506,34 +3508,7 @@ interval_mi(PG_FUNCTION_ARGS)
else if (INTERVAL_IS_NOEND(span2))
INTERVAL_NOBEGIN(result);
else
- {
- 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")));
-
- if (INTERVAL_NOT_FINITE(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- }
+ finite_interval_mi(result, span1, span2);
PG_RETURN_INTERVAL_P(result);
}
@@ -3873,11 +3848,18 @@ makeIntervalAggState(FunctionCallInfo fcinfo)
}
/*
- * Function to add two finite intervals.
+ * Functions to add or subtract two finite intervals.
+ *
+ * We handle non-finite intervals in different ways when accumulating/discarding
+ * intervals and in actual mathematical operations respectively. But the
+ * addition or subtraction of finite intervals have same implementation
+ * respectively in both these cases.
+ *
+ * Addition/Subtraction of span1 and span2 is stored in the result. It is fine
+ * to pass either of span1 or span2 as a result pointer. In such a case result
+ * will overwrite the given value.
*
- * We handle non-finite intervals in different ways when accumulating intervals
- * and adding two intervals respectively. But the addition of finite interval
- * has same implementation in both these cases.
+ * If the result is not a valid interval, the function throws an error.
*/
static void
finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
@@ -3885,24 +3867,44 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
Assert(!INTERVAL_NOT_FINITE(span1));
Assert(!INTERVAL_NOT_FINITE(span2));
- result->month = span1->month + span2->month;
- /* overflow check copied from int4pl */
- if (SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
+ if (pg_add_s32_overflow(span1->month, span2->month, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+static void
+finite_interval_mi(Interval *result, Interval *span1, Interval *span2)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
+
+ if (pg_sub_s32_overflow(span1->month, span2->month, &result->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))
+ if (pg_sub_s32_overflow(span1->day, span2->day, &result->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))
+ if (pg_sub_s64_overflow(span1->time, span2->time, &result->time))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
@@ -3919,9 +3921,6 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
static void
do_interval_accum(IntervalAggState *state, Interval *newval)
{
-
- Interval temp;
-
/* Count -infinity inputs separately from all else */
if (INTERVAL_IS_NOBEGIN(newval))
{
@@ -3936,8 +3935,7 @@ do_interval_accum(IntervalAggState *state, Interval *newval)
return;
}
- memcpy(&temp, &state->sumX, sizeof(Interval));
- finite_interval_pl(&state->sumX, &temp, newval);
+ finite_interval_pl(&state->sumX, &state->sumX, newval);
state->N++;
}
@@ -3963,6 +3961,9 @@ interval_avg_accum(PG_FUNCTION_ARGS)
/*
* Combine function for interval aggregates.
+ *
+ * Combine the given internal aggregate states and place the combination in the
+ * first argument.
*/
Datum
interval_avg_combine(PG_FUNCTION_ARGS)
@@ -3976,9 +3977,9 @@ interval_avg_combine(PG_FUNCTION_ARGS)
if (state2 == NULL)
PG_RETURN_POINTER(state1);
- /* manually copy all fields from state2 to state1 */
if (state1 == NULL)
{
+ /* manually copy all fields from state2 to state1 */
state1 = makeIntervalAggState(fcinfo);
state1->N = state2->N;
@@ -3996,14 +3997,9 @@ interval_avg_combine(PG_FUNCTION_ARGS)
state1->pInfcount += state2->pInfcount;
state1->nInfcount += state2->nInfcount;
+ /* Accumulate finite interval values, if any. */
if (state2->N > 0)
- {
- Interval temp;
-
- /* Accumulate interval values */
- memcpy(&temp, &state1->sumX, sizeof(Interval));
- finite_interval_pl(&state1->sumX, &temp, &state2->sumX);
- }
+ finite_interval_pl(&state1->sumX, &state1->sumX, &state2->sumX);
PG_RETURN_POINTER(state1);
}
@@ -4029,24 +4025,15 @@ do_interval_discard(IntervalAggState *state, Interval *newval)
}
/* Handle to be discarded finite value. */
+ state->N--;
if (state->N > 0)
- {
- Interval temp;
- Interval neg_val;
-
- neg_val.day = -newval->day;
- neg_val.month = -newval->month;
- neg_val.time = -newval->time;
-
- memcpy(&temp, &state->sumX, sizeof(Interval));
- finite_interval_pl(&state->sumX, &temp, &neg_val);
- }
+ finite_interval_mi(&state->sumX, &state->sumX, newval);
else
{
/* All values discarded, reset the state */
+ Assert(state->N == 0);
memset(&state->sumX, 0, sizeof(state->sumX));
}
- state->N--;
}
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index d61b07f38e..c5587fc903 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -347,6 +347,17 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
(5 rows)
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column?
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- Floating point arithmetic rounding errors can lead to unexpected results,
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 45262924cd..63f36e069e 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,51 +4375,37 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
---order by.
-SELECT x
- ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
-FROM (VALUES (NULL::interval),
- ('infinity'::interval),
- ('infinity'::timestamptz - now()),
- ('6 days'::interval),
- (NULL::interval),
- ('-infinity'::interval)) v(x);
- x | curr_next_avg | prev1_curr_sum
------------+---------------+----------------
- -infinity | -infinity | -infinity
- @ 6 days | infinity | -infinity
- infinity | infinity | infinity
- infinity | infinity | infinity
- | | infinity
- | |
-(6 rows)
-
---no order by.
+-- window function over interval, infinity and extreme values test the
+-- behaviour of accumulation and elimination of these values as the window
+-- slides.
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
+ ('2147483647 days 2147483646 months'), -- extreme interval value
('infinity'::timestamptz - now()),
+ ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
(NULL::interval),
('-infinity'::interval)) v(x);
- x | curr_next_avg | curr_next_sum | prev1_curr_avg | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum
------------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
- | infinity | infinity | | | infinity | infinity
- infinity | infinity | infinity | infinity | infinity | infinity | infinity
- infinity | infinity | infinity | infinity | infinity | infinity | infinity
- @ 6 days | @ 6 days 12 hours | @ 13 days | infinity | infinity | infinity | infinity
- @ 7 days | @ 7 days | @ 7 days | @ 6 days 12 hours | @ 13 days | -infinity | -infinity
- | -infinity | -infinity | @ 7 days | @ 7 days | -infinity | -infinity
- -infinity | -infinity | -infinity | -infinity | -infinity | -infinity | -infinity
-(7 rows)
+ x | curr_next_avg | prev1_curr_avg | curr_next_sum | prev1_curr_sum
+----------------------------------------------+-------------------+-------------------+---------------+----------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 6 mons 2147483647 days | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 7 mons 2147483648 days ago | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
+(10 rows)
--should fail.
SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 183ddfaf31..e31c5c3d98 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -99,6 +99,9 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index d00423946d..6dd9885949 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,28 +1591,20 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
---order by.
-SELECT x
- ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
-FROM (VALUES (NULL::interval),
- ('infinity'::interval),
- ('infinity'::timestamptz - now()),
- ('6 days'::interval),
- (NULL::interval),
- ('-infinity'::interval)) v(x);
-
---no order by.
+-- window function over interval, infinity and extreme values test the
+-- behaviour of accumulation and elimination of these values as the window
+-- slides.
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
+ ('2147483647 days 2147483646 months'), -- extreme interval value
('infinity'::timestamptz - now()),
+ ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
(NULL::interval),
--
2.25.1
0003-Add-infinite-interval-values-20230922.patchtext/x-patch; charset=US-ASCII; name=0003-Add-infinite-interval-values-20230922.patchDownload
From 9442b707b6aec49da063ae1714716325cef826dd Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 3/7] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 4 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 707 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1546 insertions(+), 207 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 8d32a8c9c5..557b81cae8 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,12 +2320,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 24ad87f910..7786853743 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9537,7 +9537,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10434,7 +10434,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de2c..f2107dd967 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(!INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 48fc0d5942..5fed7e3dd1 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3557,6 +3557,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index e27ea8ef97..e9e85503f8 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..6e87173846 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 3ab9b6918e..3100ad610f 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1523,6 +1545,7 @@ make_interval(PG_FUNCTION_ARGS)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+
result->day = days;
if (pg_mul_add_s32_overflow(weeks, DAYS_PER_WEEK, &result->day))
ereport(ERROR,
@@ -1544,6 +1567,11 @@ make_interval(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -1561,6 +1589,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2039,6 +2078,8 @@ 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;
}
@@ -2056,6 +2097,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2089,7 +2132,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2220,6 +2265,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2715,46 +2783,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2784,6 +2877,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))
@@ -2836,6 +2932,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2859,6 +2960,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,
@@ -2876,6 +2980,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2897,6 +3006,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))
@@ -2915,6 +3027,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2935,7 +3052,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3014,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3043,7 +3181,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3133,9 +3294,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3186,6 +3345,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3193,23 +3377,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3253,27 +3421,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3287,27 +3488,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3332,6 +3568,49 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results
+ * in an infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ Interval zero;
+ int factor_sign,
+ result_sign;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ memset(&zero, 0, sizeof(zero));
+ result_sign = interval_cmp_internal(span, &zero) * factor_sign;
+
+ if (result_sign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_sign < 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)
@@ -3392,6 +3671,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3423,6 +3707,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ 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);
@@ -3444,6 +3751,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3727,8 +4039,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3847,8 +4186,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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;
@@ -3973,6 +4339,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4156,6 +4527,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4394,6 +4770,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);
@@ -4738,7 +5120,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5012,7 +5394,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5252,6 +5634,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ 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.
@@ -5276,6 +5711,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ 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);
@@ -5518,6 +5981,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5748,6 +6218,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5814,6 +6291,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5896,6 +6378,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8c9..fe713fb6dd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 96db07c3b4..a4630d17ef 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,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;
@@ -1596,31 +1649,31 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
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
@@ -1699,19 +1752,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1785,7 +1840,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1794,6 +1851,381 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+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"
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index f328b8914d..023cf3761a 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -518,13 +525,13 @@ SELECT make_interval(0, 0, 0, 0, 0, 0, 9223372036854.776000);
-- 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;
@@ -588,6 +595,139 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.25.1
0007-Implement-serialization-functions-for-inter-20230922.patchtext/x-patch; charset=US-ASCII; name=0007-Implement-serialization-functions-for-inter-20230922.patchDownload
From d7e41a0b6dc3bc7f1f514f1662281b75a29bddf0 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Thu, 21 Sep 2023 16:52:30 +0530
Subject: [PATCH 7/7] Implement serialization functions for interval aggregate
state
Now that interval aggregates use transition state which of date type Internal,
parallel aggregates require these separate functions.
Jian He and Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 86 +++++++++++++++++++++++++++-
src/include/catalog/pg_aggregate.dat | 4 ++
src/include/catalog/pg_proc.dat | 6 ++
3 files changed, 94 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 17415cc4bd..cffb5d4c61 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -3825,8 +3825,12 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * Prepare state data for an interval aggregate function that needs to compute
- * sum and count.
+ * Prepare state data for an interval aggregate function, that needs to compute
+ * sum and count, in the aggregate's memory context.
+ *
+ * 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)
@@ -4004,6 +4008,84 @@ interval_avg_combine(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(state1);
}
+/*
+ * interval_avg_serialize
+ * Serialize IntervalAggState for interval aggregates.
+ */
+Datum
+interval_avg_serialize(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ StringInfoData buf;
+ bytea *result;
+
+ /* Ensure we disallow calling when not in aggregate context */
+ if (!AggCheckCallContext(fcinfo, NULL))
+ elog(ERROR, "aggregate function called in non-aggregate context");
+
+ /* TODO: Handle NULL inputs? */
+ state = (IntervalAggState *) PG_GETARG_POINTER(0);
+ pq_begintypsend(&buf);
+ /* N */
+ pq_sendint64(&buf, state->N);
+ /* Finite interval value */
+ pq_sendint64(&buf, state->sumX.time);
+ pq_sendint32(&buf, state->sumX.day);
+ pq_sendint32(&buf, state->sumX.month);
+ /* pInfcount */
+ pq_sendint64(&buf, state->pInfcount);
+ /* nInfcount */
+ pq_sendint64(&buf, state->nInfcount);
+ result = pq_endtypsend(&buf);
+ PG_RETURN_BYTEA_P(result);
+}
+
+/*
+ * interval_avg_deserialize
+ * Deserialize bytea into IntervalAggState for interval aggregates.
+ */
+Datum
+interval_avg_deserialize(PG_FUNCTION_ARGS)
+{
+ 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);
+
+ /*
+ * Copy the bytea into a StringInfo so that we can "receive" it using the
+ * standard recv-function infrastructure.
+ */
+ initStringInfo(&buf);
+ appendBinaryStringInfo(&buf,
+ VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
+
+ result = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
+
+ /* N */
+ result->N = pq_getmsgint64(&buf);
+
+ /* Interval struct elements, one by one. */
+ result->sumX.time = pq_getmsgint64(&buf);
+ result->sumX.day = pq_getmsgint(&buf, sizeof(result->sumX.day));
+ result->sumX.month = pq_getmsgint(&buf, sizeof(result->sumX.month));
+
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
+
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
+
+ pq_getmsgend(&buf);
+ pfree(buf.data);
+
+ PG_RETURN_POINTER(result);
+}
+
/*
* Remove the given interval value from the aggregated state.
*/
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index e2087d7be1..0e62c3f7a6 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -45,6 +45,8 @@
aggtranstype => '_float8', agginitval => '{0,0,0}' },
{ 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_accum_inv',
aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
aggmtranstype => 'internal', aggtransspace => '128',
@@ -75,6 +77,8 @@
aggtranstype => 'money', aggmtranstype => 'money' },
{ 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_accum_inv',
aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
aggmtranstype => 'internal', aggtransspace => '128',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f8eca58ddf..34da5f3687 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4925,6 +4925,12 @@
proname => 'interval_accum_inv', proisstrict => 'f',
prorettype => 'internal', proargtypes => 'internal interval',
prosrc => 'interval_accum_inv' },
+{ oid => '3813', descr => 'aggregate serial function',
+ proname => 'interval_avg_serialize', prorettype => 'bytea',
+ proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
+{ oid => '3814', 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 => 'internal', prosrc => 'interval_avg' },
--
2.25.1
On Fri, 22 Sept 2023 at 08:49, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
Following code in ExecInterpExpr makes it clear that the
deserialization function is be executed in per tuple memory context.
Whereas the aggregate's context is different from this context and may
lives longer that the context in which deserialization is expected to
happen.
Right. I was about to reply, saying much the same thing, but it's
always better when you see it for yourself.
Hence I have changed interval_avg_deserialize() in 0007 to use
CurrentMemoryContext instead of aggcontext.
+1. And consistency with other deserialisation functions is good.
Rest of the patches are
same as previous set.
OK, I'll take a look.
Regards,
Dean
On Fri, Sep 22, 2023 at 3:49 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
On Thu, Sep 21, 2023 at 7:21 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:Hence I have changed interval_avg_deserialize() in 0007 to use
CurrentMemoryContext instead of aggcontext. Rest of the patches are
same as previous set.--
Best Wishes,
Ashutosh Bapat
/* TODO: Handle NULL inputs? */
since interval_avg_serialize is strict, so handle null would be like:
if (PG_ARGISNULL(0)) then PG_RETURN_NULL();
On Fri, Sep 22, 2023 at 2:35 PM jian he <jian.universality@gmail.com> wrote:
/* TODO: Handle NULL inputs? */
since interval_avg_serialize is strict, so handle null would be like:
if (PG_ARGISNULL(0)) then PG_RETURN_NULL();
That's automatically taken care of by the executor. Functions need to
handle NULL inputs if they are *not* strict.
#select proisstrict from pg_proc where proname = 'interval_avg_serialize';
proisstrict
-------------
t
(1 row)
#select proisstrict from pg_proc where proname = 'interval_avg_deserialize';
proisstrict
-------------
t
(1 row)
--
Best Wishes,
Ashutosh Bapat
On Fri, 22 Sept 2023 at 09:09, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Fri, 22 Sept 2023 at 08:49, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:Rest of the patches are
same as previous set.OK, I'll take a look.
I've been going over this in more detail, and I'm attaching my review
comments as an incremental patch to make it easier to see the changes.
Aside from some cosmetic stuff, I've made the following more
substantive changes:
1. I don't think it's really worth adding the
pg_mul_add_sNN_overflow() functions to int.h.
I thought about this for a while, and it looks to me as though they're
really only of very limited general use, and even this patch only used
them in a couple of places.
Looking around more widely, the majority of places that multiply then
add actually require a 4-argument function that computes result = a *
b + c. But even then, such functions would offer no performance
benefit, and wouldn't really do much to improve code readability at
call sites.
And there's another issue: someone using these functions might
reasonably expect them to return true if the result overflows, but
actually, as written, they also return true if the intermediate
product overflows, which isn't necessarily what's expected / wanted.
So I think it's best to just drop these functions, getting rid of
0001, and rewriting 0002 to just use the existing int.h functions.
After a little more copy-editing, I think that actually makes the code
in make_interval() more readable.
I think that part is now ready to commit, and I plan to push this fix
to make_interval() separately, since it's really a bug-fix, not
related to support for infinite intervals. In line with recent
precedent, I don't think it's worth back-patching though, since such
inputs are pretty unlikely in production.
2. The various in_range() functions needed adjusting to handle
infinite interval offsets.
For timestamp values, I followed the precedent set by the equivalent
float/numeric code. I.e., all (finite and non-finite) timestamps are
regarded as infinitely following -infinity and infinitely preceding
+infinity.
For time values, it's a bit different because no time values precede
or follow any other by more than 24 hours, so a window frame between
+inf following and +inf following is empty (whereas in the timestamp
case it contains +inf). Put another way, such a window frame is empty
because a time value can't be infinity.
3. I got rid of interval2timestamp_no_overflow() because I don't think
it really makes much sense to convert an interval to a timestamp, and
it's a bit of a hack anyway (as selfuncs.c itself admits). Actually, I
think it's OK to just leave selfuncs.c as it is. The existing code
will cope just fine with infinite intervals, since they aren't really
infinite, just larger than any others.
4. I tested pg_upgrade on a table with an interval with INT_MAX
months, and it was silently converted to infinity. I think that's
probably the best outcome (better than failing). However, this means
that we really should require all 3 fields of an interval to be
INT_MIN/MAX for it to be considered infinite, otherwise it would be
possible to have multiple internal representations of infinity that do
not compare as equal.
Similarly, interval_in() needs to accept such inputs, otherwise things
like pg_dump/restore from pre-17 databases could fail. But since it
now requires all 3 fields of the interval to be INT_MIN/MAX for it to
be infinite, the odds of that happening by accident are vanishingly
small in practice.
This approach also means that the range of allowed finite intervals is
only reduced by 1 microsecond at each end of the range, rather than a
whole month.
Also, it means that it is no longer necessary to change a number of
the regression tests (such as the justify_interval() tests) for values
near INT_MIN/MAX.
Overall, I think this is now pretty close to being ready for commit.
Regards,
Dean
Attachments:
v25-0001-Check-for-overflow-in-make_interval.patchtext/x-patch; charset=US-ASCII; name=v25-0001-Check-for-overflow-in-make_interval.patchDownload
From 102d37b219254f204c2af33b616ad8b90e11febb Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH v25 1/7] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 39 ++++++++++++++++++--------
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 27 ++++++++++++++++++
src/test/regress/sql/interval.sql | 15 ++++++++++
4 files changed, 71 insertions(+), 11 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec5a..7ddf64ad2e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1509,24 +1509,41 @@ make_interval(PG_FUNCTION_ARGS)
Interval *result;
/*
- * Reject out-of-range inputs. We really ought to check the integer
- * inputs as well, but it's not entirely clear what limits to apply.
+ * Reject out-of-range inputs. We reject any input values that cause
+ * integer overflow of the corresponding interval fields.
*/
if (isinf(secs) || isnan(secs))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ goto out_of_range;
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ /* years and months -> months */
+ if (pg_mul_s32_overflow(years, MONTHS_PER_YEAR, &result->month) ||
+ pg_add_s32_overflow(result->month, months, &result->month))
+ goto out_of_range;
+
+ /* weeks and days -> days */
+ if (pg_mul_s32_overflow(weeks, DAYS_PER_WEEK, &result->day) ||
+ pg_add_s32_overflow(result->day, days, &result->day))
+ goto out_of_range;
+
+ /* hours and mins -> usecs (cannot overflow 64-bit) */
+ result->time = hours * USECS_PER_HOUR + mins * USECS_PER_MINUTE;
+
+ /* secs -> usecs */
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs) ||
+ pg_add_s64_overflow(result->time, (int64) secs, &result->time))
+ goto out_of_range;
PG_RETURN_INTERVAL_P(result);
+
+out_of_range:
+ ereport(ERROR,
+ errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"));
+
+ PG_RETURN_NULL(); /* keep compiler quiet */
}
/* EncodeSpecialTimestamp()
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index c0ca8e041b..75d19d6594 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,33 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(years := 178956971);
+ERROR: interval out of range
+select make_interval(years := -178956971);
+ERROR: interval out of range
+select make_interval(years := 1, months := 2147483647);
+ERROR: interval out of range
+select make_interval(years := -1, months := -2147483648);
+ERROR: interval out of range
+select make_interval(weeks := 306783379);
+ERROR: interval out of range
+select make_interval(weeks := -306783379);
+ERROR: interval out of range
+select make_interval(weeks := 1, days := 2147483647);
+ERROR: interval out of range
+select make_interval(weeks := -1, days := -2147483648);
+ERROR: interval out of range
+select make_interval(secs := 1e308);
+ERROR: value out of range: overflow
+select make_interval(secs := 1e18);
+ERROR: interval out of range
+select make_interval(secs := -1e18);
+ERROR: interval out of range
+select make_interval(mins := 1, secs := 9223372036800.0);
+ERROR: interval out of range
+select make_interval(mins := -1, secs := -9223372036800.0);
+ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 038fc508d0..a0a373f08b 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,21 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(years := 178956971);
+select make_interval(years := -178956971);
+select make_interval(years := 1, months := 2147483647);
+select make_interval(years := -1, months := -2147483648);
+select make_interval(weeks := 306783379);
+select make_interval(weeks := -306783379);
+select make_interval(weeks := 1, days := 2147483647);
+select make_interval(weeks := -1, days := -2147483648);
+select make_interval(secs := 1e308);
+select make_interval(secs := 1e18);
+select make_interval(secs := -1e18);
+select make_interval(mins := 1, secs := 9223372036800.0);
+select make_interval(mins := -1, secs := -9223372036800.0);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.35.3
v25-0005-Use-integer-overflow-checking-routines-to-add-an.patchtext/x-patch; charset=US-ASCII; name=v25-0005-Use-integer-overflow-checking-routines-to-add-an.patchDownload
From bcd47fd63d1a602518864f0ac66b2e07806c3854 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Thu, 21 Sep 2023 15:34:33 +0530
Subject: [PATCH v25 5/7] Use integer overflow checking routines to add and
subtract finite intervals
Use pg_add/sub_s32/64_overflow() routines in finite_interval_pl() and
finite_interval_mi() instead of adding overflow checks in those
functions.
Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 121 +++++++++++--------------
src/test/regress/expected/interval.out | 11 +++
src/test/regress/expected/window.out | 54 ++++-------
src/test/regress/sql/interval.sql | 3 +
src/test/regress/sql/window.sql | 22 ++---
5 files changed, 95 insertions(+), 116 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 50fb793c13..8c1b0a774b 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -99,6 +99,8 @@ static Timestamp timestamptz2timestamp(TimestampTz timestamp);
static void interval_um_internal(Interval *interval, Interval *result);
static void finite_interval_pl(Interval *result, Interval *span1,
Interval *span2);
+static void finite_interval_mi(Interval *result, Interval *span1,
+ Interval *span2);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -3502,34 +3504,7 @@ interval_mi(PG_FUNCTION_ARGS)
else if (INTERVAL_IS_NOEND(span2))
INTERVAL_NOBEGIN(result);
else
- {
- 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")));
-
- if (INTERVAL_NOT_FINITE(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- }
+ finite_interval_mi(result, span1, span2);
PG_RETURN_INTERVAL_P(result);
}
@@ -3869,11 +3844,18 @@ makeIntervalAggState(FunctionCallInfo fcinfo)
}
/*
- * Function to add two finite intervals.
+ * Functions to add or subtract two finite intervals.
+ *
+ * We handle non-finite intervals in different ways when accumulating/discarding
+ * intervals and in actual mathematical operations respectively. But the
+ * addition or subtraction of finite intervals have same implementation
+ * respectively in both these cases.
+ *
+ * Addition/Subtraction of span1 and span2 is stored in the result. It is fine
+ * to pass either of span1 or span2 as a result pointer. In such a case result
+ * will overwrite the given value.
*
- * We handle non-finite intervals in different ways when accumulating intervals
- * and adding two intervals respectively. But the addition of finite interval
- * has same implementation in both these cases.
+ * If the result is not a valid interval, the function throws an error.
*/
static void
finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
@@ -3881,24 +3863,44 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
Assert(!INTERVAL_NOT_FINITE(span1));
Assert(!INTERVAL_NOT_FINITE(span2));
- result->month = span1->month + span2->month;
- /* overflow check copied from int4pl */
- if (SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
+ if (pg_add_s32_overflow(span1->month, span2->month, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+static void
+finite_interval_mi(Interval *result, Interval *span1, Interval *span2)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
+
+ if (pg_sub_s32_overflow(span1->month, span2->month, &result->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))
+ if (pg_sub_s32_overflow(span1->day, span2->day, &result->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))
+ if (pg_sub_s64_overflow(span1->time, span2->time, &result->time))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
@@ -3915,9 +3917,6 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
static void
do_interval_accum(IntervalAggState *state, Interval *newval)
{
-
- Interval temp;
-
/* Count -infinity inputs separately from all else */
if (INTERVAL_IS_NOBEGIN(newval))
{
@@ -3932,8 +3931,7 @@ do_interval_accum(IntervalAggState *state, Interval *newval)
return;
}
- memcpy(&temp, &state->sumX, sizeof(Interval));
- finite_interval_pl(&state->sumX, &temp, newval);
+ finite_interval_pl(&state->sumX, &state->sumX, newval);
state->N++;
}
@@ -3959,6 +3957,9 @@ interval_avg_accum(PG_FUNCTION_ARGS)
/*
* Combine function for interval aggregates.
+ *
+ * Combine the given internal aggregate states and place the combination in the
+ * first argument.
*/
Datum
interval_avg_combine(PG_FUNCTION_ARGS)
@@ -3972,9 +3973,9 @@ interval_avg_combine(PG_FUNCTION_ARGS)
if (state2 == NULL)
PG_RETURN_POINTER(state1);
- /* manually copy all fields from state2 to state1 */
if (state1 == NULL)
{
+ /* manually copy all fields from state2 to state1 */
state1 = makeIntervalAggState(fcinfo);
state1->N = state2->N;
@@ -3992,14 +3993,9 @@ interval_avg_combine(PG_FUNCTION_ARGS)
state1->pInfcount += state2->pInfcount;
state1->nInfcount += state2->nInfcount;
+ /* Accumulate finite interval values, if any. */
if (state2->N > 0)
- {
- Interval temp;
-
- /* Accumulate interval values */
- memcpy(&temp, &state1->sumX, sizeof(Interval));
- finite_interval_pl(&state1->sumX, &temp, &state2->sumX);
- }
+ finite_interval_pl(&state1->sumX, &state1->sumX, &state2->sumX);
PG_RETURN_POINTER(state1);
}
@@ -4025,24 +4021,15 @@ do_interval_discard(IntervalAggState *state, Interval *newval)
}
/* Handle to be discarded finite value. */
+ state->N--;
if (state->N > 0)
- {
- Interval temp;
- Interval neg_val;
-
- neg_val.day = -newval->day;
- neg_val.month = -newval->month;
- neg_val.time = -newval->time;
-
- memcpy(&temp, &state->sumX, sizeof(Interval));
- finite_interval_pl(&state->sumX, &temp, &neg_val);
- }
+ finite_interval_mi(&state->sumX, &state->sumX, newval);
else
{
/* All values discarded, reset the state */
+ Assert(state->N == 0);
memset(&state->sumX, 0, sizeof(state->sumX));
}
- state->N--;
}
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 135d683285..8bd72f60b0 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -347,6 +347,17 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
(5 rows)
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column?
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- Floating point arithmetic rounding errors can lead to unexpected results,
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 45262924cd..63f36e069e 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,51 +4375,37 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
---order by.
-SELECT x
- ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
-FROM (VALUES (NULL::interval),
- ('infinity'::interval),
- ('infinity'::timestamptz - now()),
- ('6 days'::interval),
- (NULL::interval),
- ('-infinity'::interval)) v(x);
- x | curr_next_avg | prev1_curr_sum
------------+---------------+----------------
- -infinity | -infinity | -infinity
- @ 6 days | infinity | -infinity
- infinity | infinity | infinity
- infinity | infinity | infinity
- | | infinity
- | |
-(6 rows)
-
---no order by.
+-- window function over interval, infinity and extreme values test the
+-- behaviour of accumulation and elimination of these values as the window
+-- slides.
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
+ ('2147483647 days 2147483646 months'), -- extreme interval value
('infinity'::timestamptz - now()),
+ ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
(NULL::interval),
('-infinity'::interval)) v(x);
- x | curr_next_avg | curr_next_sum | prev1_curr_avg | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum
------------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
- | infinity | infinity | | | infinity | infinity
- infinity | infinity | infinity | infinity | infinity | infinity | infinity
- infinity | infinity | infinity | infinity | infinity | infinity | infinity
- @ 6 days | @ 6 days 12 hours | @ 13 days | infinity | infinity | infinity | infinity
- @ 7 days | @ 7 days | @ 7 days | @ 6 days 12 hours | @ 13 days | -infinity | -infinity
- | -infinity | -infinity | @ 7 days | @ 7 days | -infinity | -infinity
- -infinity | -infinity | -infinity | -infinity | -infinity | -infinity | -infinity
-(7 rows)
+ x | curr_next_avg | prev1_curr_avg | curr_next_sum | prev1_curr_sum
+----------------------------------------------+-------------------+-------------------+---------------+----------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 6 mons 2147483647 days | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 7 mons 2147483648 days ago | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
+(10 rows)
--should fail.
SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 912803a1b0..414c12be1c 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -99,6 +99,9 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index d00423946d..6dd9885949 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,28 +1591,20 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
---order by.
-SELECT x
- ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
-FROM (VALUES (NULL::interval),
- ('infinity'::interval),
- ('infinity'::timestamptz - now()),
- ('6 days'::interval),
- (NULL::interval),
- ('-infinity'::interval)) v(x);
-
---no order by.
+-- window function over interval, infinity and extreme values test the
+-- behaviour of accumulation and elimination of these values as the window
+-- slides.
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
+ ('2147483647 days 2147483646 months'), -- extreme interval value
('infinity'::timestamptz - now()),
+ ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
(NULL::interval),
--
2.35.3
v25-0003-Introduce-infinity-interval-specification-in-Dec.patchtext/x-patch; charset=US-ASCII; name=v25-0003-Introduce-infinity-interval-specification-in-Dec.patchDownload
From 69d8e9779b2016c4a96262a24c22e10eccdfea0c Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 13 Sep 2023 12:43:49 +0530
Subject: [PATCH v25 3/7] Introduce 'infinity' interval specification in
DecodeInterval()
This reverts commit d6d1430f404386162831bc32906ad174b2007776 since
RESERVE is required to support infinite intervals. 'infinity' is saved
as a RESERVE in datetktbl[], thus required to parse
'infinity'::interval.
Infinte interval specification i.e. "+infinity", "-infinity" or
"infinity" does not require anything other that one of the three
strings. Fix DecodeInterval() to disallow anything else.
"ago" may be used to revrse the sign of infinity specification like how
ago reverts the sign of finite intervals. But that is not as intuitive
as it is with finite intervals. Hence disallows "infinity ago" as well.
Ashutosh Bapat
---
src/backend/utils/adt/datetime.c | 24 ++++++++++++++++
src/test/regress/expected/interval.out | 39 ++++++++++++++++++++++++++
src/test/regress/sql/interval.sql | 14 +++++++++
3 files changed, 77 insertions(+)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index ea2bc65f50..73dadb747c 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3271,6 +3271,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3599,6 +3602,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+
+ /*
+ * Only reserved words corresponding to infinite
+ * intervals are accepted.
+ */
+ if (uval != DTK_LATE && uval != DTK_EARLY)
+ return DTERR_BAD_FORMAT;
+
+ /*
+ * infinity can not be followed by anything else. We
+ * could allow "ago" to reverse the sign of infinity
+ * but that signed infinity is more intuitive.
+ */
+ if (i != nf - 1)
+ return DTERR_BAD_FORMAT;
+
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 5011fc4815..135d683285 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -2264,3 +2264,42 @@ SELECT INTERVAL '1 year months days 5 hours';
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 590e1000bd..912803a1b0 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -745,3 +745,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
--
2.35.3
v25-0004-Support-infinite-interval-values-in-sum-and-avg-.patchtext/x-patch; charset=US-ASCII; name=v25-0004-Support-infinite-interval-values-in-sum-and-avg-.patchDownload
From 1d2314afbbfb551fa841eee95861ffdfd81bfda5 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 20 Sep 2023 15:41:00 +0530
Subject: [PATCH v25 4/7] Support infinite interval values in sum and avg
window functions
Results of arithmetic operations involing both infinity and -infinity
intervals are undefined. Sliding window functions
accumulate and discard values as the window slides through the data.
Even if the data may have both infinities, none of windows may have both
of them together and thus produce definite results. Hence we can not use
normal operator functions to accumulate and discard values. Instead we
maintain a count of both infinities seen respectively in an aggregate
state and compute the aggregate for each window based on the accumulated
finite value and the counts of infinities, all of which are updated as
the window slides through the data.
Initial patch by Jian He, significantly edited by Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 407 ++++++++++++++++++---------
src/include/catalog/pg_aggregate.dat | 23 +-
src/include/catalog/pg_proc.dat | 20 +-
src/test/regress/expected/window.out | 62 ++++
src/test/regress/sql/window.sql | 43 +++
src/tools/pgindent/typedefs.list | 1 +
6 files changed, 400 insertions(+), 156 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index c52bdb68da..50fb793c13 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -73,6 +73,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+/*
+ * The transition datatype for interval aggregates is declared as Internal. It's
+ * a pointer to a IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+ int64 N; /* count of processed intervals */
+ Interval sumX; /* sum of processed intervals */
+ /* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +Inf values */
+ int64 nInfcount; /* count of -Inf values */
+} 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);
@@ -82,6 +97,8 @@ static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
static void interval_um_internal(Interval *interval, Interval *result);
+static void finite_interval_pl(Interval *result, Interval *span1,
+ Interval *span2);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -3443,34 +3460,7 @@ interval_pl(PG_FUNCTION_ARGS)
else if (INTERVAL_NOT_FINITE(span2))
memcpy(result, span2, sizeof(Interval));
else
- {
- 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")));
-
- 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")));
-
- if (INTERVAL_NOT_FINITE(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- }
+ finite_interval_pl(result, span1, span2);
PG_RETURN_INTERVAL_P(result);
}
@@ -3856,161 +3846,300 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function that needs to compute
+ * sum and count.
+ */
+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;
+}
+
+/*
+ * Function to add two finite intervals.
*
- * 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.
+ * We handle non-finite intervals in different ways when accumulating intervals
+ * and adding two intervals respectively. But the addition of finite interval
+ * has same implementation in both these cases.
*/
+static void
+finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
-Datum
-interval_accum(PG_FUNCTION_ARGS)
+ 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")));
+
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+/*
+ * Accumulate a new input value for interval aggregate functions.
+ */
+static void
+do_interval_accum(IntervalAggState *state, Interval *newval)
{
- 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;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ Interval temp;
+
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount++;
+ return;
+ }
+
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount++;
+ return;
+ }
+
+ memcpy(&temp, &state->sumX, sizeof(Interval));
+ finite_interval_pl(&state->sumX, &temp, newval);
+ state->N++;
+}
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+/*
+ * Transition function for interval aggregates.
+ */
+Datum
+interval_avg_accum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ /* Create the state data on the first call */
+ if (state == NULL)
+ state = makeIntervalAggState(fcinfo);
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ if (!PG_ARGISNULL(1))
+ do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
- PG_RETURN_ARRAYTYPE_P(result);
+ PG_RETURN_POINTER(state);
}
+/*
+ * Combine function for interval aggregates.
+ */
Datum
-interval_combine(PG_FUNCTION_ARGS)
+interval_avg_combine(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 *state1;
+ IntervalAggState *state2;
+
+ state1 = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+ state2 = PG_ARGISNULL(1) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(1);
- Interval *newsum;
- ArrayType *result;
+ if (state2 == NULL)
+ PG_RETURN_POINTER(state1);
+
+ /* manually copy all fields from state2 to state1 */
+ if (state1 == NULL)
+ {
+ state1 = makeIntervalAggState(fcinfo);
- deconstruct_array(transarray1,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums1, NULL, &ndatums1);
- if (ndatums1 != 2)
- elog(ERROR, "expected 2-element interval array");
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
- sum1 = *(DatumGetIntervalP(transdatums1[0]));
- N1 = *(DatumGetIntervalP(transdatums1[1]));
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
- deconstruct_array(transarray2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums2, NULL, &ndatums2);
- if (ndatums2 != 2)
- elog(ERROR, "expected 2-element interval array");
+ PG_RETURN_POINTER(state1);
+ }
+
+ state1->N += state2->N;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
+
+ if (state2->N > 0)
+ {
+ Interval temp;
+
+ /* Accumulate interval values */
+ memcpy(&temp, &state1->sumX, sizeof(Interval));
+ finite_interval_pl(&state1->sumX, &temp, &state2->sumX);
+ }
+
+ PG_RETURN_POINTER(state1);
+}
- sum2 = *(DatumGetIntervalP(transdatums2[0]));
- N2 = *(DatumGetIntervalP(transdatums2[1]));
+/*
+ * Remove the given interval value from the aggregated state.
+ */
+static void
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount--;
+ return;
+ }
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sum1),
- IntervalPGetDatum(&sum2)));
- N1.time += N2.time;
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount--;
+ return;
+ }
- transdatums1[0] = IntervalPGetDatum(newsum);
- transdatums1[1] = IntervalPGetDatum(&N1);
+ /* Handle to be discarded finite value. */
+ if (state->N > 0)
+ {
+ Interval temp;
+ Interval neg_val;
- result = construct_array(transdatums1, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ neg_val.day = -newval->day;
+ neg_val.month = -newval->month;
+ neg_val.time = -newval->time;
- PG_RETURN_ARRAYTYPE_P(result);
+ memcpy(&temp, &state->sumX, sizeof(Interval));
+ finite_interval_pl(&state->sumX, &temp, &neg_val);
+ }
+ else
+ {
+ /* All values discarded, reset the state */
+ memset(&state->sumX, 0, sizeof(state->sumX));
+ }
+ state->N--;
}
+/*
+ * Generic inverse transition function for interval aggregates
+ */
Datum
interval_accum_inv(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Should not get here with no state */
+ if (state == NULL)
+ elog(ERROR, "interval_accum_inv called with NULL state");
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ if (!PG_ARGISNULL(1))
+ do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ PG_RETURN_POINTER(state);
+}
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ double N_datum;
+ Interval *sumX;
+
+ sumX = (Interval *) palloc(sizeof(Interval));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
- PG_RETURN_ARRAYTYPE_P(result);
+ /* adding plus and minus infinities gives error */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ N_datum = (double) state->N;
+ sumX = &state->sumX;
+
+ PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX),
+ Float8GetDatum(N_datum)));
}
+/* sum(interval) aggregate final function */
Datum
-interval_avg(PG_FUNCTION_ARGS)
+interval_sum(PG_FUNCTION_ARGS)
{
- ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
- Datum *transdatums;
- int ndatums;
- Interval sumX,
- N;
+ IntervalAggState *state;
+ Interval *result;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ result = (Interval *) palloc(sizeof(Interval));
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
-}
+ /* adding plus and minus interval infinities is not possible */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ memcpy(result, &state->sumX, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e2087d7be1 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
+ aggmtranstype => 'internal', aggtransspace => '128',
+ aggmtransspace => '128'
+},
# sum
{ aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
@@ -72,10 +73,12 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
+ aggmtranstype => 'internal', aggtransspace => '128',
+ aggmtransspace => '128'},
{ aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
aggserialfn => 'numeric_avg_serialize',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f0b7b9cbd8..52ef90ba6c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4914,17 +4914,23 @@
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_accum_inv', proisstrict => 'f',
+ prorettype => 'internal', proargtypes => 'internal interval',
+ prosrc => 'interval_accum_inv' },
{ oid => '1844', descr => 'aggregate final function',
proname => 'interval_avg', prorettype => 'interval',
- proargtypes => '_interval', prosrc => 'interval_avg' },
+ proargtypes => 'internal', prosrc => 'interval_avg' },
+{ oid => '8069', 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' },
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..45262924cd 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,6 +4375,68 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev1_curr_sum
+-----------+---------------+----------------
+ -infinity | -infinity | -infinity
+ @ 6 days | infinity | -infinity
+ infinity | infinity | infinity
+ infinity | infinity | infinity
+ | | infinity
+ | |
+(6 rows)
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | curr_next_sum | prev1_curr_avg | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum
+-----------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
+ | infinity | infinity | | | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | @ 13 days | infinity | infinity | infinity | infinity
+ @ 7 days | @ 7 days | @ 7 days | @ 6 days 12 hours | @ 13 days | -infinity | -infinity
+ | -infinity | -infinity | @ 7 days | @ 7 days | -infinity | -infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity | -infinity | -infinity
+(7 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
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..d00423946d 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,6 +1591,49 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('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);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8de90c4958..5a81b42fd6 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1204,6 +1204,7 @@ IntegerSet
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup
--
2.35.3
v25-0002-Add-infinite-interval-values.patchtext/x-patch; charset=US-ASCII; name=v25-0002-Add-infinite-interval-values.patchDownload
From 06386aebc49feda9123098400c11d0dd78dee998 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH v25 2/7] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 4 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 704 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1543 insertions(+), 207 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 5d23765705..123d8207f8 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2321,12 +2321,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 24ad87f910..7786853743 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9537,7 +9537,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10434,7 +10434,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de2c..f2107dd967 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(!INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..ea2bc65f50 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3574,6 +3574,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index e27ea8ef97..e9e85503f8 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..6e87173846 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7ddf64ad2e..c52bdb68da 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1536,6 +1558,9 @@ make_interval(PG_FUNCTION_ARGS)
pg_add_s64_overflow(result->time, (int64) secs, &result->time))
goto out_of_range;
+ if (INTERVAL_NOT_FINITE(result))
+ goto out_of_range;
+
PG_RETURN_INTERVAL_P(result);
out_of_range:
@@ -1560,6 +1585,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2038,6 +2074,8 @@ 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;
}
@@ -2055,6 +2093,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2088,7 +2128,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2219,6 +2261,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2714,46 +2779,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2783,6 +2873,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))
@@ -2835,6 +2928,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2858,6 +2956,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,
@@ -2875,6 +2976,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2896,6 +3002,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))
@@ -2914,6 +3023,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2934,7 +3048,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3013,9 +3150,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3042,7 +3177,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3132,9 +3290,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3185,6 +3341,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3192,23 +3373,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3252,27 +3417,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3286,27 +3484,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3331,6 +3564,49 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results
+ * in an infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ Interval zero;
+ int factor_sign,
+ result_sign;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ memset(&zero, 0, sizeof(zero));
+ result_sign = interval_cmp_internal(span, &zero) * factor_sign;
+
+ if (result_sign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_sign < 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ INTERVAL_NOEND(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3391,6 +3667,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3422,6 +3703,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (isinf(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = (int32) (span->month / factor);
result->day = (int32) (span->day / factor);
@@ -3443,6 +3747,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3726,8 +4035,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3846,8 +4182,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3972,6 +4335,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4155,6 +4523,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4393,6 +4766,12 @@ interval_trunc(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ memcpy(result, interval, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
VARSIZE_ANY_EXHDR(units),
false);
@@ -4737,7 +5116,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5011,7 +5390,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5251,6 +5630,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ if ((type != UNITS) && (type != RESERV))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unit \"%s\" not recognized for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_YEAR:
+ case DTK_DECADE:
+ case DTK_CENTURY:
+ case DTK_MILLENNIUM:
+ case DTK_EPOCH:
+ if (isNegative)
+ return -get_float8_infinity();
+ else
+ return get_float8_infinity();
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unit \"%s\" not supported for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+ return 0.0; /* keep compiler quiet */
+ }
+}
/* interval_part() and extract_interval()
* Extract specified field from interval.
@@ -5275,6 +5707,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r != 0.0)
+ {
+ if (retnumeric)
+ {
+ if (r < 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("-Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ else if (r > 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ }
+ else
+ PG_RETURN_FLOAT8(r);
+ }
+ else
+ PG_RETURN_NULL();
+ }
+
if (type == UNITS)
{
interval2itm(*interval, tm);
@@ -5517,6 +5977,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5747,6 +6214,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5813,6 +6287,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5895,6 +6374,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8c9..fe713fb6dd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 75d19d6594..5011fc4815 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,9 @@ SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
6 years | 6 years | 6 years
5 mons | 5 mons | 00:00:00
5 mons 12:00:00 | 5 mons 12:00:00 | 00:00:00
-(10 rows)
+ infinity | infinity | infinity
+ -infinity | -infinity | -infinity
+(12 rows)
-- test inputting and outputting SQL standard interval literals
SET IntervalStyle TO sql_standard;
@@ -1616,31 +1669,31 @@ select make_interval(mins := -1, secs := -9223372036800.0);
ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
--------------------------------------------------------------------
- -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+ -178956970 years -7 mons -2147483648 days -2562047788:00:54.775808
(1 row)
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
---------------------------------------------------
- -178956970-8 -2147483648 -2562047788:00:54.775808
+ -178956970-7 -2147483648 -2562047788:00:54.775808
(1 row)
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
-----------------------------------------------------
- P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+ P-178956970Y-7M-2147483648DT-2562047788H-54.775808S
(1 row)
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
------------------------------------------------------------------------------
- @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+ @ 178956970 years 7 mons 2147483648 days 2562047788 hours 54.775808 secs ago
(1 row)
-- check that '30 days' equals '1 month' according to the hash function
@@ -1719,19 +1772,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1805,7 +1860,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1814,6 +1871,381 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+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"
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index a0a373f08b..590e1000bd 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -528,13 +535,13 @@ select make_interval(mins := -1, secs := -9223372036800.0);
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
-- check that '30 days' equals '1 month' according to the hash function
select '30 days'::interval = '1 month'::interval as t;
@@ -598,6 +605,139 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.35.3
v25-0006-Implement-serialization-functions-for-interval-a.patchtext/x-patch; charset=US-ASCII; name=v25-0006-Implement-serialization-functions-for-interval-a.patchDownload
From 2dd11e05c325f191e3a78a1f6bd4eafa916574e3 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Thu, 21 Sep 2023 16:52:30 +0530
Subject: [PATCH v25 6/7] Implement serialization functions for interval
aggregate state
Now that interval aggregates use transition state which of date type Internal,
parallel aggregates require these separate functions.
Jian He and Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 86 +++++++++++++++++++++++++++-
src/include/catalog/pg_aggregate.dat | 4 ++
src/include/catalog/pg_proc.dat | 6 ++
3 files changed, 94 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 8c1b0a774b..bf02ddb92d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -3821,8 +3821,12 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * Prepare state data for an interval aggregate function that needs to compute
- * sum and count.
+ * Prepare state data for an interval aggregate function, that needs to compute
+ * sum and count, in the aggregate's memory context.
+ *
+ * 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)
@@ -4000,6 +4004,84 @@ interval_avg_combine(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(state1);
}
+/*
+ * interval_avg_serialize
+ * Serialize IntervalAggState for interval aggregates.
+ */
+Datum
+interval_avg_serialize(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ StringInfoData buf;
+ bytea *result;
+
+ /* Ensure we disallow calling when not in aggregate context */
+ if (!AggCheckCallContext(fcinfo, NULL))
+ elog(ERROR, "aggregate function called in non-aggregate context");
+
+ /* TODO: Handle NULL inputs? */
+ state = (IntervalAggState *) PG_GETARG_POINTER(0);
+ pq_begintypsend(&buf);
+ /* N */
+ pq_sendint64(&buf, state->N);
+ /* Finite interval value */
+ pq_sendint64(&buf, state->sumX.time);
+ pq_sendint32(&buf, state->sumX.day);
+ pq_sendint32(&buf, state->sumX.month);
+ /* pInfcount */
+ pq_sendint64(&buf, state->pInfcount);
+ /* nInfcount */
+ pq_sendint64(&buf, state->nInfcount);
+ result = pq_endtypsend(&buf);
+ PG_RETURN_BYTEA_P(result);
+}
+
+/*
+ * interval_avg_deserialize
+ * Deserialize bytea into IntervalAggState for interval aggregates.
+ */
+Datum
+interval_avg_deserialize(PG_FUNCTION_ARGS)
+{
+ 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);
+
+ /*
+ * Copy the bytea into a StringInfo so that we can "receive" it using the
+ * standard recv-function infrastructure.
+ */
+ initStringInfo(&buf);
+ appendBinaryStringInfo(&buf,
+ VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
+
+ result = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
+
+ /* N */
+ result->N = pq_getmsgint64(&buf);
+
+ /* Interval struct elements, one by one. */
+ result->sumX.time = pq_getmsgint64(&buf);
+ result->sumX.day = pq_getmsgint(&buf, sizeof(result->sumX.day));
+ result->sumX.month = pq_getmsgint(&buf, sizeof(result->sumX.month));
+
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
+
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
+
+ pq_getmsgend(&buf);
+ pfree(buf.data);
+
+ PG_RETURN_POINTER(result);
+}
+
/*
* Remove the given interval value from the aggregated state.
*/
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index e2087d7be1..0e62c3f7a6 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -45,6 +45,8 @@
aggtranstype => '_float8', agginitval => '{0,0,0}' },
{ 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_accum_inv',
aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
aggmtranstype => 'internal', aggtransspace => '128',
@@ -75,6 +77,8 @@
aggtranstype => 'money', aggmtranstype => 'money' },
{ 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_accum_inv',
aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
aggmtranstype => 'internal', aggtransspace => '128',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 52ef90ba6c..449c1dae82 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4925,6 +4925,12 @@
proname => 'interval_accum_inv', proisstrict => 'f',
prorettype => 'internal', proargtypes => 'internal interval',
prosrc => 'interval_accum_inv' },
+{ oid => '3813', descr => 'aggregate serial function',
+ proname => 'interval_avg_serialize', prorettype => 'bytea',
+ proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
+{ oid => '3814', 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 => 'internal', prosrc => 'interval_avg' },
--
2.35.3
v25-0007-Code-review-of-infinite-interval-support.patchtext/x-patch; charset=US-ASCII; name=v25-0007-Code-review-of-infinite-interval-support.patchDownload
From 3351dac79dbdfa108eafa1aae4c54acd905e6510 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Thu, 28 Sep 2023 14:54:02 +0100
Subject: [PATCH v25 7/7] Code review of infinite interval support.
---
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 27 +-
src/backend/utils/adt/datetime.c | 4 +-
src/backend/utils/adt/selfuncs.c | 16 +-
src/backend/utils/adt/timestamp.c | 366 +++++++++----------
src/include/catalog/pg_aggregate.dat | 11 +-
src/include/catalog/pg_proc.dat | 6 +-
src/include/datatype/timestamp.h | 24 +-
src/include/utils/timestamp.h | 3 -
src/test/regress/expected/interval.out | 375 ++++++++------------
src/test/regress/expected/timestamp.out | 8 +-
src/test/regress/expected/timestamptz.out | 8 +-
src/test/regress/expected/window.out | 406 +++++++++++++++++++---
src/test/regress/sql/interval.sql | 131 +++----
src/test/regress/sql/window.sql | 93 ++++-
15 files changed, 912 insertions(+), 571 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7786853743..2fd9db9afa 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10435,7 +10435,10 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
<literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
- for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
+ 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>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index f2107dd967..ef06474ac3 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -24,6 +24,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
+#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/supportnodes.h"
@@ -2050,8 +2051,6 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
- Assert(!INTERVAL_NOT_FINITE(result));
-
PG_RETURN_INTERVAL_P(result);
}
@@ -2116,7 +2115,8 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* Like time_pl_interval/time_mi_interval, we disregard the month and day
- * fields of the offset. So our test for negative should too.
+ * fields of the offset. So our test for negative should too. This also
+ * catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2126,13 +2126,14 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* We can't use time_pl_interval/time_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum = base - offset->time;
- else
- sum = base + offset->time;
+ else if (pg_add_s64_overflow(base, offset->time, &sum))
+ PG_RETURN_BOOL(less);
if (less)
PG_RETURN_BOOL(val <= sum);
@@ -2666,7 +2667,8 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* Like timetz_pl_interval/timetz_mi_interval, we disregard the month and
- * day fields of the offset. So our test for negative should too.
+ * day fields of the offset. So our test for negative should too. This
+ * also catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2676,13 +2678,14 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* We can't use timetz_pl_interval/timetz_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum.time = base->time - offset->time;
- else
- sum.time = base->time + offset->time;
+ else if (pg_add_s64_overflow(base->time, offset->time, &sum.time))
+ PG_RETURN_BOOL(less);
sum.zone = base->zone;
if (less)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 73dadb747c..fca9a2a6e9 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3613,9 +3613,9 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
return DTERR_BAD_FORMAT;
/*
- * infinity can not be followed by anything else. We
+ * Infinity cannot be followed by anything else. We
* could allow "ago" to reverse the sign of infinity
- * but that signed infinity is more intuitive.
+ * but using signed infinity is more intuitive.
*/
if (i != nf - 1)
return DTERR_BAD_FORMAT;
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 6e87173846..4ea5415f20 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,7 +4795,21 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- return interval2timestamp_no_overflow(DatumGetIntervalP(value));
+ {
+ Interval *interval = DatumGetIntervalP(value);
+
+ /*
+ * Convert the month part of Interval to days using assumed
+ * average month length of 365.25/12.0 days. Not too
+ * accurate, but plenty good enough for our purposes.
+ *
+ * This also works for infinite intervals, which just have all
+ * fields set to INT_MIN/INT_MAX, and so will produce a result
+ * smaller/larger than any finite interval.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index bf02ddb92d..c8e1fb9f9d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,7 +18,6 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
-#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -74,16 +73,16 @@ typedef struct
} generate_series_timestamptz_fctx;
/*
- * The transition datatype for interval aggregates is declared as Internal. It's
- * a pointer to a IntervalAggState allocated in the aggregate context.
+ * The transition datatype for interval aggregates is declared as internal.
+ * It's a pointer to an IntervalAggState allocated in the aggregate context.
*/
typedef struct IntervalAggState
{
- int64 N; /* count of processed intervals */
- Interval sumX; /* sum of processed intervals */
+ int64 N; /* count of finite intervals processed */
+ Interval sumX; /* sum of finite intervals processed */
/* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
- int64 pInfcount; /* count of +Inf values */
- int64 nInfcount; /* count of -Inf values */
+ int64 pInfcount; /* count of +infinity intervals */
+ int64 nInfcount; /* count of -infinity intervals */
} IntervalAggState;
#define IA_TOTAL_COUNT(ia) \
@@ -96,11 +95,12 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
-static void interval_um_internal(Interval *interval, Interval *result);
-static void finite_interval_pl(Interval *result, Interval *span1,
- Interval *span2);
-static void finite_interval_mi(Interval *result, Interval *span1,
- Interval *span2);
+static void EncodeSpecialInterval(const Interval *interval, char *str);
+static void interval_um_internal(const Interval *interval, Interval *result);
+static void finite_interval_pl(const Interval *span1, const Interval *span2,
+ Interval *result);
+static void finite_interval_mi(const Interval *span1, const Interval *span2,
+ Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -1604,8 +1604,8 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
-void
-EncodeSpecialInterval(Interval *interval, char *str)
+static void
+EncodeSpecialInterval(const Interval *interval, char *str)
{
if (INTERVAL_IS_NOBEGIN(interval))
strcpy(str, EARLY);
@@ -2101,6 +2101,13 @@ itm2interval(struct pg_itm *itm, Interval *span)
/* itmin2interval()
* Convert a pg_itm_in structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * Note: if the result is infinite, it is not treated as an overflow. This
+ * avoids any dump/reload hazards from pre-17 databases that do not support
+ * infinite intervals, but do allow finite intervals with all fields set to
+ * INT_MIN/INT_MAX (outside the documented range). Such intervals will be
+ * silently converted to +/-infinity. This may not be ideal, but seems
+ * preferable to failure, and ought to be pretty unlikely in practice.
*/
int
itmin2interval(struct pg_itm_in *itm_in, Interval *span)
@@ -2112,8 +2119,6 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
- if (INTERVAL_NOT_FINITE(span))
- return -1;
return 0;
}
@@ -2280,29 +2285,6 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
-double
-interval2timestamp_no_overflow(Interval *interval)
-{
- double result;
-
- if (INTERVAL_IS_NOBEGIN(interval))
- result = -DBL_MAX;
- else if (INTERVAL_IS_NOEND(interval))
- result = DBL_MAX;
- else
- {
- /*
- * Convert the month part of Interval to days using assumed average
- * month length of 365.25/12.0 days. Not too accurate, but plenty
- * good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
-
- return result;
-}
-
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2526,6 +2508,15 @@ interval_cmp_internal(const Interval *interval1, const Interval *interval2)
return int128_compare(span1, span2);
}
+static int
+interval_sign(const Interval *interval)
+{
+ INT128 span = interval_cmp_value(interval);
+ INT128 zero = int64_to_int128(0);
+
+ return int128_compare(span, zero);
+}
+
Datum
interval_eq(PG_FUNCTION_ARGS)
{
@@ -2801,14 +2792,14 @@ timestamp_mi(PG_FUNCTION_ARGS)
/*
* Subtracting two infinite timestamps with different signs results in an
* infinite interval with the same sign as the left operand. Subtracting
- * two infinte timestamps with the same sign results in an error.
+ * two infinite timestamps with the same sign results in an error.
*/
if (TIMESTAMP_IS_NOBEGIN(dt1))
{
if (TIMESTAMP_IS_NOBEGIN(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOBEGIN(result);
}
@@ -2817,7 +2808,7 @@ timestamp_mi(PG_FUNCTION_ARGS)
if (TIMESTAMP_IS_NOEND(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOEND(result);
}
@@ -3068,16 +3059,16 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
Timestamp result;
/*
- * Adding two infinites with the same sign results in an infinite
- * timestamp with the same sign. Adding two infintes with different signs
- * results in an error.
+ * Adding two infinities with the same sign results in an infinite
+ * timestamp with the same sign. Adding two infinities with different
+ * signs results in an error.
*/
if (INTERVAL_IS_NOBEGIN(span))
{
if (TIMESTAMP_IS_NOEND(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("timestamp out of range")));
else
TIMESTAMP_NOBEGIN(result);
}
@@ -3086,7 +3077,7 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
if (TIMESTAMP_IS_NOBEGIN(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("timestamp out of range")));
else
TIMESTAMP_NOEND(result);
}
@@ -3197,16 +3188,16 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
int tz;
/*
- * Adding two infinites with the same sign results in an infinite
- * timestamp with the same sign. Adding two infintes with different signs
- * results in an error.
+ * Adding two infinities with the same sign results in an infinite
+ * timestamp with the same sign. Adding two infinities with different
+ * signs results in an error.
*/
if (INTERVAL_IS_NOBEGIN(span))
{
if (TIMESTAMP_IS_NOEND(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("timestamp out of range")));
else
TIMESTAMP_NOBEGIN(result);
}
@@ -3215,7 +3206,7 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
if (TIMESTAMP_IS_NOBEGIN(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("timestamp out of range")));
else
TIMESTAMP_NOEND(result);
}
@@ -3362,16 +3353,12 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
/* Negates the given interval */
static void
-interval_um_internal(Interval *interval, Interval *result)
+interval_um_internal(const Interval *interval, Interval *result)
{
if (INTERVAL_IS_NOBEGIN(interval))
INTERVAL_NOEND(result);
else if (INTERVAL_IS_NOEND(interval))
INTERVAL_NOBEGIN(result);
- else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
else
{
result->time = -interval->time;
@@ -3438,8 +3425,8 @@ interval_pl(PG_FUNCTION_ARGS)
/*
* Adding two infinite intervals with the same signs results in an
- * infinite interval with the same sign. Adding two infinte intervals with
- * different signs results in an error.
+ * infinite interval with the same sign. Adding two infinite intervals
+ * with different signs results in an error.
*/
if (INTERVAL_IS_NOBEGIN(span1))
{
@@ -3462,7 +3449,7 @@ interval_pl(PG_FUNCTION_ARGS)
else if (INTERVAL_NOT_FINITE(span2))
memcpy(result, span2, sizeof(Interval));
else
- finite_interval_pl(result, span1, span2);
+ finite_interval_pl(span1, span2, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3479,7 +3466,7 @@ interval_mi(PG_FUNCTION_ARGS)
/*
* Subtracting two infinite intervals with different signs results in an
* infinite interval with the same sign as the left operand. Subtracting
- * two infinte intervals with the same sign results in an error.
+ * two infinite intervals with the same sign results in an error.
*/
if (INTERVAL_IS_NOBEGIN(span1))
{
@@ -3504,7 +3491,7 @@ interval_mi(PG_FUNCTION_ARGS)
else if (INTERVAL_IS_NOEND(span2))
INTERVAL_NOBEGIN(result);
else
- finite_interval_mi(result, span1, span2);
+ finite_interval_mi(span1, span2, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3552,13 +3539,11 @@ interval_mul(PG_FUNCTION_ARGS)
}
else if (isinf(factor))
{
- Interval zero;
int factor_sign,
result_sign;
factor_sign = factor >= 0.0 ? 1 : -1;
- memset(&zero, 0, sizeof(zero));
- result_sign = interval_cmp_internal(span, &zero) * factor_sign;
+ result_sign = interval_sign(span) * factor_sign;
if (result_sign == 0)
ereport(ERROR,
@@ -3740,11 +3725,21 @@ in_range_timestamptz_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
TimestampTz sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = timestamptz_mi_interval_internal(base, offset, NULL);
@@ -3767,11 +3762,21 @@ in_range_timestamp_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Timestamp sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval,
@@ -3798,11 +3803,21 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Interval *sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? INTERVAL_IS_NOEND(base) : INTERVAL_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
@@ -3848,21 +3863,13 @@ makeIntervalAggState(FunctionCallInfo fcinfo)
}
/*
- * Functions to add or subtract two finite intervals.
+ * Functions to add or subtract finite intervals.
*
- * We handle non-finite intervals in different ways when accumulating/discarding
- * intervals and in actual mathematical operations respectively. But the
- * addition or subtraction of finite intervals have same implementation
- * respectively in both these cases.
- *
- * Addition/Subtraction of span1 and span2 is stored in the result. It is fine
- * to pass either of span1 or span2 as a result pointer. In such a case result
- * will overwrite the given value.
- *
- * If the result is not a valid interval, the function throws an error.
+ * These are used for normal arithmetic and aggregation of finite intervals
+ * only. Non-finite intervals require special handling.
*/
static void
-finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
+finite_interval_pl(const Interval *span1, const Interval *span2, Interval *result)
{
Assert(!INTERVAL_NOT_FINITE(span1));
Assert(!INTERVAL_NOT_FINITE(span2));
@@ -3889,7 +3896,7 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
}
static void
-finite_interval_mi(Interval *result, Interval *span1, Interval *span2)
+finite_interval_mi(const Interval *span1, const Interval *span2, Interval *result)
{
Assert(!INTERVAL_NOT_FINITE(span1));
Assert(!INTERVAL_NOT_FINITE(span2));
@@ -3921,26 +3928,56 @@ finite_interval_mi(Interval *result, Interval *span1, Interval *span2)
static void
do_interval_accum(IntervalAggState *state, Interval *newval)
{
- /* Count -infinity inputs separately from all else */
+ /* Infinite inputs are counted separately, and do not affect "N" */
if (INTERVAL_IS_NOBEGIN(newval))
{
state->nInfcount++;
return;
}
- /* Count infinity inputs separately from all else */
if (INTERVAL_IS_NOEND(newval))
{
state->pInfcount++;
return;
}
- finite_interval_pl(&state->sumX, &state->sumX, newval);
+ finite_interval_pl(&state->sumX, newval, &state->sumX);
state->N++;
}
/*
- * Transition function for interval aggregates.
+ * Remove the given interval value from the aggregated state.
+ */
+static void
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+ /* Infinite inputs are counted separately, and do not affect "N" */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount--;
+ return;
+ }
+
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount--;
+ return;
+ }
+
+ /* Handle to be discarded finite value. */
+ state->N--;
+ if (state->N > 0)
+ finite_interval_mi(&state->sumX, newval, &state->sumX);
+ else
+ {
+ /* All values discarded, reset the state */
+ Assert(state->N == 0);
+ memset(&state->sumX, 0, sizeof(state->sumX));
+ }
+}
+
+/*
+ * Transition function for sum() and avg() interval aggregates.
*/
Datum
interval_avg_accum(PG_FUNCTION_ARGS)
@@ -3960,10 +3997,10 @@ interval_avg_accum(PG_FUNCTION_ARGS)
}
/*
- * Combine function for interval aggregates.
+ * Combine function for sum() and avg() interval aggregates.
*
- * Combine the given internal aggregate states and place the combination in the
- * first argument.
+ * Combine the given internal aggregate states and place the combination in
+ * the first argument.
*/
Datum
interval_avg_combine(PG_FUNCTION_ARGS)
@@ -3999,7 +4036,7 @@ interval_avg_combine(PG_FUNCTION_ARGS)
/* Accumulate finite interval values, if any. */
if (state2->N > 0)
- finite_interval_pl(&state1->sumX, &state1->sumX, &state2->sumX);
+ finite_interval_pl(&state1->sumX, &state2->sumX, &state1->sumX);
PG_RETURN_POINTER(state1);
}
@@ -4019,20 +4056,26 @@ interval_avg_serialize(PG_FUNCTION_ARGS)
if (!AggCheckCallContext(fcinfo, NULL))
elog(ERROR, "aggregate function called in non-aggregate context");
- /* TODO: Handle NULL inputs? */
state = (IntervalAggState *) PG_GETARG_POINTER(0);
+
pq_begintypsend(&buf);
+
/* N */
pq_sendint64(&buf, state->N);
- /* Finite interval value */
+
+ /* sumX */
pq_sendint64(&buf, state->sumX.time);
pq_sendint32(&buf, state->sumX.day);
pq_sendint32(&buf, state->sumX.month);
+
/* pInfcount */
pq_sendint64(&buf, state->pInfcount);
+
/* nInfcount */
pq_sendint64(&buf, state->nInfcount);
+
result = pq_endtypsend(&buf);
+
PG_RETURN_BYTEA_P(result);
}
@@ -4065,10 +4108,10 @@ interval_avg_deserialize(PG_FUNCTION_ARGS)
/* N */
result->N = pq_getmsgint64(&buf);
- /* Interval struct elements, one by one. */
+ /* sumX */
result->sumX.time = pq_getmsgint64(&buf);
- result->sumX.day = pq_getmsgint(&buf, sizeof(result->sumX.day));
- result->sumX.month = pq_getmsgint(&buf, sizeof(result->sumX.month));
+ result->sumX.day = pq_getmsgint(&buf, 4);
+ result->sumX.month = pq_getmsgint(&buf, 4);
/* pInfcount */
result->pInfcount = pq_getmsgint64(&buf);
@@ -4083,42 +4126,10 @@ interval_avg_deserialize(PG_FUNCTION_ARGS)
}
/*
- * Remove the given interval value from the aggregated state.
- */
-static void
-do_interval_discard(IntervalAggState *state, Interval *newval)
-{
- /* Count -infinity inputs separately from all else */
- if (INTERVAL_IS_NOBEGIN(newval))
- {
- state->nInfcount--;
- return;
- }
-
- /* Count infinity inputs separately from all else */
- if (INTERVAL_IS_NOEND(newval))
- {
- state->pInfcount--;
- return;
- }
-
- /* Handle to be discarded finite value. */
- state->N--;
- if (state->N > 0)
- finite_interval_mi(&state->sumX, &state->sumX, newval);
- else
- {
- /* All values discarded, reset the state */
- Assert(state->N == 0);
- memset(&state->sumX, 0, sizeof(state->sumX));
- }
-}
-
-/*
- * Generic inverse transition function for interval aggregates
+ * Inverse transition function for sum() and avg() interval aggregates.
*/
Datum
-interval_accum_inv(PG_FUNCTION_ARGS)
+interval_avg_accum_inv(PG_FUNCTION_ARGS)
{
IntervalAggState *state;
@@ -4126,7 +4137,7 @@ interval_accum_inv(PG_FUNCTION_ARGS)
/* Should not get here with no state */
if (state == NULL)
- elog(ERROR, "interval_accum_inv called with NULL state");
+ elog(ERROR, "interval_avg_accum_inv called with NULL state");
if (!PG_ARGISNULL(1))
do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
@@ -4139,38 +4150,39 @@ Datum
interval_avg(PG_FUNCTION_ARGS)
{
IntervalAggState *state;
- double N_datum;
- Interval *sumX;
- sumX = (Interval *) palloc(sizeof(Interval));
state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+ /* If there were no non-null inputs, return NULL */
if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- /* adding plus and minus infinities gives error */
- if (state->pInfcount > 0 && state->nInfcount > 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range.")));
-
- if (state->pInfcount > 0)
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 || state->nInfcount > 0)
{
- INTERVAL_NOEND(sumX);
- PG_RETURN_INTERVAL_P(sumX);
- }
+ Interval *result;
- if (state->nInfcount > 0)
- {
- INTERVAL_NOBEGIN(sumX);
- PG_RETURN_INTERVAL_P(sumX);
- }
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else
+ INTERVAL_NOBEGIN(result);
- N_datum = (double) state->N;
- sumX = &state->sumX;
+ PG_RETURN_INTERVAL_P(result);
+ }
- PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX),
- Float8GetDatum(N_datum)));
+ return DirectFunctionCall2(interval_div,
+ IntervalPGetDatum(&state->sumX),
+ Float8GetDatum((double) state->N));
}
/* sum(interval) aggregate final function */
@@ -4180,33 +4192,31 @@ interval_sum(PG_FUNCTION_ARGS)
IntervalAggState *state;
Interval *result;
- result = (Interval *) palloc(sizeof(Interval));
-
state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
/* If there were no non-null inputs, return NULL */
if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- /* adding plus and minus interval infinities is not possible */
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
if (state->pInfcount > 0 && state->nInfcount > 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range.")));
+ result = (Interval *) palloc(sizeof(Interval));
+
if (state->pInfcount > 0)
- {
INTERVAL_NOEND(result);
- PG_RETURN_INTERVAL_P(result);
- }
-
- if (state->nInfcount > 0)
- {
+ else if (state->nInfcount > 0)
INTERVAL_NOBEGIN(result);
- PG_RETURN_INTERVAL_P(result);
- }
+ else
+ memcpy(result, &state->sumX, sizeof(Interval));
- memcpy(result, &state->sumX, sizeof(Interval));
PG_RETURN_INTERVAL_P(result);
}
@@ -4236,14 +4246,14 @@ timestamp_age(PG_FUNCTION_ARGS)
/*
* Subtracting two infinite timestamps with different signs results in an
* infinite interval with the same sign as the left operand. Subtracting
- * two infinte timestamps with the same sign results in an error.
+ * two infinite timestamps with the same sign results in an error.
*/
if (TIMESTAMP_IS_NOBEGIN(dt1))
{
if (TIMESTAMP_IS_NOBEGIN(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOBEGIN(result);
}
@@ -4252,7 +4262,7 @@ timestamp_age(PG_FUNCTION_ARGS)
if (TIMESTAMP_IS_NOEND(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOEND(result);
}
@@ -4383,14 +4393,14 @@ timestamptz_age(PG_FUNCTION_ARGS)
/*
* Subtracting two infinite timestamps with different signs results in an
* infinite interval with the same sign as the left operand. Subtracting
- * two infinte timestamps with the same sign results in an error.
+ * two infinite timestamps with the same sign results in an error.
*/
if (TIMESTAMP_IS_NOBEGIN(dt1))
{
if (TIMESTAMP_IS_NOBEGIN(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOBEGIN(result);
}
@@ -4399,7 +4409,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
if (TIMESTAMP_IS_NOEND(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOEND(result);
}
@@ -5831,17 +5841,16 @@ extract_timestamptz(PG_FUNCTION_ARGS)
/*
* NonFiniteIntervalPart
*
- * Used by interval_part when extracting from infinite
- * interval. Returns +/-Infinity if that is the appropriate result,
- * otherwise returns zero (which should be taken as meaning to return NULL).
+ * Used by interval_part when extracting from infinite interval. Returns
+ * +/-Infinity if that is the appropriate result, otherwise returns zero
+ * (which should be taken as meaning to return NULL).
*
* Errors thrown here for invalid units should exactly match those that
* would be thrown in the calling functions, else there will be unexpected
* discrepancies between finite- and infinite-input cases.
*/
static float8
-NonFiniteIntervalPart(int type, int unit, char *lowunits,
- bool isNegative, bool isTz)
+NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative)
{
if ((type != UNITS) && (type != RESERV))
ereport(ERROR,
@@ -5908,8 +5917,7 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (INTERVAL_NOT_FINITE(interval))
{
double r = NonFiniteIntervalPart(type, val, lowunits,
- INTERVAL_IS_NOBEGIN(interval),
- false);
+ INTERVAL_IS_NOBEGIN(interval));
if (r != 0.0)
{
@@ -6455,7 +6463,6 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
Timestamp finish = PG_GETARG_TIMESTAMP(1);
Interval *step = PG_GETARG_INTERVAL_P(2);
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -6478,7 +6485,7 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
fctx->step = *step;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
@@ -6541,7 +6548,6 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
Interval *step = PG_GETARG_INTERVAL_P(2);
text *zone = (PG_NARGS() == 4) ? PG_GETARG_TEXT_PP(3) : NULL;
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -6565,7 +6571,7 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
fctx->attimezone = zone ? lookup_timezone(zone) : session_timezone;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 0e62c3f7a6..e1a17cddd8 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -47,11 +47,9 @@
aggfinalfn => 'interval_avg', aggcombinefn => 'interval_avg_combine',
aggserialfn => 'interval_avg_serialize',
aggdeserialfn => 'interval_avg_deserialize',
- aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv',
aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
- aggmtranstype => 'internal', aggtransspace => '128',
- aggmtransspace => '128'
-},
+ aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40' },
# sum
{ aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
@@ -79,10 +77,9 @@
aggfinalfn => 'interval_sum', aggcombinefn => 'interval_avg_combine',
aggserialfn => 'interval_avg_serialize',
aggdeserialfn => 'interval_avg_deserialize',
- aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv',
aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
- aggmtranstype => 'internal', aggtransspace => '128',
- aggmtransspace => '128'},
+ aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40'},
{ aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
aggserialfn => 'numeric_avg_serialize',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 449c1dae82..4cd348b758 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4922,9 +4922,9 @@
prorettype => 'internal', proargtypes => 'internal internal',
prosrc => 'interval_avg_combine' },
{ oid => '3549', descr => 'aggregate transition function',
- proname => 'interval_accum_inv', proisstrict => 'f',
+ proname => 'interval_avg_accum_inv', proisstrict => 'f',
prorettype => 'internal', proargtypes => 'internal interval',
- prosrc => 'interval_accum_inv' },
+ prosrc => 'interval_avg_accum_inv' },
{ oid => '3813', descr => 'aggregate serial function',
proname => 'interval_avg_serialize', prorettype => 'bytea',
proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
@@ -4932,7 +4932,7 @@
proname => 'interval_avg_deserialize', prorettype => 'internal',
proargtypes => 'bytea internal', prosrc => 'interval_avg_deserialize' },
{ oid => '1844', descr => 'aggregate final function',
- proname => 'interval_avg', prorettype => 'interval',
+ proname => 'interval_avg', proisstrict => 'f', prorettype => 'interval',
proargtypes => 'internal', prosrc => 'interval_avg' },
{ oid => '8069', descr => 'aggregate final function',
proname => 'interval_sum', proisstrict => 'f', prorettype => 'interval',
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index f9bd30d225..b63acc0a2f 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -151,7 +151,7 @@ struct pg_itm_in
#define TIMESTAMP_INFINITY PG_INT64_MAX
/*
- * Historically these alias for infinity have been used.
+ * Historically these aliases for infinity have been used.
*/
#define DT_NOBEGIN TIMESTAMP_MINUS_INFINITY
#define DT_NOEND TIMESTAMP_INFINITY
@@ -168,23 +168,29 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+/*
+ * Infinite intervals are represented by setting all fields to the minimum or
+ * maximum integer values.
+ */
#define INTERVAL_NOBEGIN(i) \
do { \
- (i->time) = PG_INT64_MIN; \
- (i->day) = PG_INT32_MIN; \
- (i->month) = PG_INT32_MIN; \
+ (i)->time = PG_INT64_MIN; \
+ (i)->day = PG_INT32_MIN; \
+ (i)->month = PG_INT32_MIN; \
} while (0)
-#define INTERVAL_IS_NOBEGIN(i) ((i->month) == PG_INT32_MIN)
+#define INTERVAL_IS_NOBEGIN(i) \
+ ((i)->month == PG_INT32_MIN && (i)->day == PG_INT32_MIN && (i)->time == PG_INT64_MIN)
#define INTERVAL_NOEND(i) \
do { \
- (i->time) = PG_INT64_MAX; \
- (i->day) = PG_INT32_MAX; \
- (i->month) = PG_INT32_MAX; \
+ (i)->time = PG_INT64_MAX; \
+ (i)->day = PG_INT32_MAX; \
+ (i)->month = PG_INT32_MAX; \
} while (0)
-#define INTERVAL_IS_NOEND(i) ((i->month) == PG_INT32_MAX)
+#define INTERVAL_IS_NOEND(i) \
+ ((i)->month == PG_INT32_MAX && (i)->day == PG_INT32_MAX && (i)->time == PG_INT64_MAX)
#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index fe713fb6dd..c4dd96c8c9 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,7 +118,6 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
-extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -128,8 +127,6 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
-extern double interval2timestamp_no_overflow(Interval *interval);
-
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 8bd72f60b0..00cd3e84f1 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -284,11 +284,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483646 months'),
- ('2147483647 days -2147483647 months'),
+ ('2147483647 days 2147483647 months'),
+ ('2147483647 days -2147483648 months'),
('1 year'),
- ('-2147483648 days 2147483646 months'),
- ('-2147483648 days -2147483647 months');
+ ('-2147483648 days 2147483647 months'),
+ ('-2147483648 days -2147483648 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -315,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
- 1 year | -178956970 years -7 mons -2147483648 days
- 1 year | -178956970 years -7 mons +2147483647 days
- 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
- 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
- 178956970 years 6 mons -2147483648 days | 1 year
- 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
- 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
- 178956970 years 6 mons 2147483647 days | 1 year
- 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
+ -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
+ 1 year | -178956970 years -8 mons -2147483648 days
+ 1 year | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
+ 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons -2147483648 days | 1 year
+ 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
+ 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons 2147483647 days | 1 year
+ 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -339,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -7 mons -2147483648 days
- -178956970 years -7 mons +2147483647 days
+ -178956970 years -8 mons -2147483648 days
+ -178956970 years -8 mons +2147483647 days
1 year
- 178956970 years 6 mons -2147483648 days
- 178956970 years 6 mons 2147483647 days
+ 178956970 years 7 mons -2147483648 days
+ 178956970 years 7 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -485,9 +485,7 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483646 months 30 days');
-ERROR: interval out of range
-SELECT justify_days(interval '2147483646 months 60 days');
+SELECT justify_days(interval '2147483647 months 30 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -508,29 +506,25 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483646 months 30 days');
-ERROR: interval out of range
-SELECT justify_interval(interval '2147483646 months 60 days');
-ERROR: interval out of range
-SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '2147483647 months 30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '-2147483648 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 6 mons 29 days
+ @ 178956970 years 7 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 7 mons 29 days ago
+ @ 178956970 years 8 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -1882,233 +1876,144 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
--- infinite intervals
-SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
-ERROR: interval out of range
-LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
- ^
-SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
-ERROR: interval out of range
-LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
- ^
-CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
-INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
-SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
- i | isfinite
--------------------------+----------
- infinity | f
- -infinity | f
- @ 1 year 2 days 3 hours | t
-(3 rows)
-
-SELECT date '1995-08-06' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT date '1995-08-06' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT date '1995-08-06' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT date '1995-08-06' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT date 'infinity' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT date 'infinity' + interval '-infinity';
-ERROR: interval out of range
-SELECT date '-infinity' + interval 'infinity';
-ERROR: interval out of range
-SELECT date '-infinity' + interval '-infinity';
- ?column?
------------
- -infinity
+--
+-- test infinite intervals
+--
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+ interval
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago
(1 row)
-SELECT date 'infinity' - interval 'infinity';
-ERROR: interval out of range
-SELECT date 'infinity' - interval '-infinity';
- ?column?
-----------
- infinity
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+ interval
+--------------------------------------------------------------------------
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs
(1 row)
-SELECT date '-infinity' - interval 'infinity';
- ?column?
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
-----------
-infinity
(1 row)
-SELECT date '-infinity' - interval '-infinity';
-ERROR: interval out of range
-SELECT interval 'infinity' + interval 'infinity';
- ?column?
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ interval
----------
infinity
(1 row)
-SELECT interval 'infinity' + interval '-infinity';
-ERROR: interval out of range
-SELECT interval '-infinity' + interval 'infinity';
-ERROR: interval out of range
-SELECT interval '-infinity' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
-SELECT interval 'infinity' + interval '10 days';
- ?column?
-----------
- infinity
-(1 row)
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ date | interval | plus | minus
+------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 | -infinity | -infinity | infinity
+ 1995-08-06 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
-SELECT interval '-infinity' + interval '10 days';
- ?column?
------------
- -infinity
-(1 row)
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+ interval1 | interval2 | plus | minus
+-----------+-----------+-----------------------+-----------------------
+ -infinity | -infinity | -infinity | interval out of range
+ -infinity | @ 10 days | -infinity | -infinity
+ -infinity | infinity | interval out of range | -infinity
+ @ 2 mons | -infinity | -infinity | infinity
+ @ 2 mons | @ 10 days | @ 2 mons 10 days | @ 2 mons -10 days
+ @ 2 mons | infinity | infinity | -infinity
+ infinity | -infinity | interval out of range | infinity
+ infinity | @ 10 days | infinity | infinity
+ infinity | infinity | infinity | interval out of range
+(9 rows)
SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
ERROR: interval out of range
SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
ERROR: interval out of range
-SELECT interval 'infinity' - interval 'infinity';
-ERROR: interval out of range
-SELECT interval 'infinity' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT interval '-infinity' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT interval '-infinity' - interval '-infinity';
-ERROR: interval out of range
-SELECT interval 'infinity' - interval '10 days';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT interval '-infinity' - interval '10 days';
- ?column?
------------
- -infinity
-(1 row)
-
SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
ERROR: interval out of range
SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
ERROR: interval out of range
-SELECT timestamp 'infinity' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamp 'infinity' + interval '-infinity';
-ERROR: interval out of range
-SELECT timestamp '-infinity' + interval 'infinity';
-ERROR: interval out of range
-SELECT timestamp '-infinity' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamp 'infinity' - interval 'infinity';
-ERROR: interval out of range
-SELECT timestamp 'infinity' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamp '-infinity' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamp '-infinity' - interval '-infinity';
-ERROR: interval out of range
-SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamptz 'infinity' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamptz 'infinity' + interval '-infinity';
-ERROR: interval out of range
-SELECT timestamptz '-infinity' + interval 'infinity';
-ERROR: interval out of range
-SELECT timestamptz '-infinity' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamptz 'infinity' - interval 'infinity';
-ERROR: interval out of range
-SELECT timestamptz 'infinity' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamp | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
-SELECT timestamptz '-infinity' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamptz | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
-SELECT timestamptz '-infinity' - interval '-infinity';
-ERROR: interval out of range
+-- time +/- infinite interval not supported
SELECT time '11:27:42' + interval 'infinity';
ERROR: cannot add infinite interval to time
SELECT time '11:27:42' + interval '-infinity';
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 53542e076b..835f0e5762 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2131,7 +2131,7 @@ select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12
ERROR: step size cannot be infinite
-- test arithmetic with infinite timestamps
select timestamp 'infinity' - timestamp 'infinity';
-ERROR: timestamp out of range
+ERROR: interval out of range
select timestamp 'infinity' - timestamp '-infinity';
?column?
----------
@@ -2145,7 +2145,7 @@ select timestamp '-infinity' - timestamp 'infinity';
(1 row)
select timestamp '-infinity' - timestamp '-infinity';
-ERROR: timestamp out of range
+ERROR: interval out of range
select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
?column?
----------
@@ -2172,7 +2172,7 @@ select age(timestamp '-infinity');
(1 row)
select age(timestamp 'infinity', timestamp 'infinity');
-ERROR: timestamp out of range
+ERROR: interval out of range
select age(timestamp 'infinity', timestamp '-infinity');
age
----------
@@ -2186,4 +2186,4 @@ select age(timestamp '-infinity', timestamp 'infinity');
(1 row)
select age(timestamp '-infinity', timestamp '-infinity');
-ERROR: timestamp out of range
+ERROR: interval out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 825202d597..1c19613ae3 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -3160,7 +3160,7 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
-- test arithmetic with infinite timestamps
SELECT timestamptz 'infinity' - timestamptz 'infinity';
-ERROR: timestamp out of range
+ERROR: interval out of range
SELECT timestamptz 'infinity' - timestamptz '-infinity';
?column?
----------
@@ -3174,7 +3174,7 @@ SELECT timestamptz '-infinity' - timestamptz 'infinity';
(1 row)
SELECT timestamptz '-infinity' - timestamptz '-infinity';
-ERROR: timestamp out of range
+ERROR: interval out of range
SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
?column?
----------
@@ -3201,7 +3201,7 @@ SELECT age(timestamptz '-infinity');
(1 row)
SELECT age(timestamptz 'infinity', timestamptz 'infinity');
-ERROR: timestamp out of range
+ERROR: interval out of range
SELECT age(timestamptz 'infinity', timestamptz '-infinity');
age
----------
@@ -3215,4 +3215,4 @@ SELECT age(timestamptz '-infinity', timestamptz 'infinity');
(1 row)
SELECT age(timestamptz '-infinity', timestamptz '-infinity');
-ERROR: timestamp out of range
+ERROR: interval out of range
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 63f36e069e..6b8c3c3413 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -2372,6 +2372,7 @@ create temp table datetimes(
f_timestamp timestamp
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -2381,14 +2382,16 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_time range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 1 | 11:00:00 | 1 | 3
+ 0 | 10:00:00 | 0 | 2
+ 1 | 11:00:00 | 0 | 3
2 | 12:00:00 | 1 | 4
3 | 13:00:00 | 2 | 6
4 | 14:00:00 | 3 | 6
@@ -2396,9 +2399,10 @@ window w as (order by f_time range between
6 | 15:00:00 | 4 | 7
7 | 17:00:00 | 7 | 9
8 | 18:00:00 | 7 | 10
- 9 | 19:00:00 | 8 | 10
- 10 | 20:00:00 | 9 | 10
-(10 rows)
+ 9 | 19:00:00 | 8 | 11
+ 10 | 20:00:00 | 9 | 11
+ 11 | 21:00:00 | 10 | 11
+(12 rows)
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2406,7 +2410,8 @@ window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 10 | 20:00:00 | 10 | 8
+ 11 | 21:00:00 | 11 | 9
+ 10 | 20:00:00 | 11 | 8
9 | 19:00:00 | 10 | 7
8 | 18:00:00 | 9 | 7
7 | 17:00:00 | 8 | 5
@@ -2414,9 +2419,70 @@ window w as (order by f_time desc range between
5 | 15:00:00 | 6 | 3
4 | 14:00:00 | 6 | 2
3 | 13:00:00 | 4 | 1
- 2 | 12:00:00 | 3 | 1
- 1 | 11:00:00 | 2 | 1
-(10 rows)
+ 2 | 12:00:00 | 3 | 0
+ 1 | 11:00:00 | 2 | 0
+ 0 | 10:00:00 | 1 | 0
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | 0 | 11
+ 1 | 11:00:00 | 0 | 11
+ 2 | 12:00:00 | 0 | 11
+ 3 | 13:00:00 | 0 | 11
+ 4 | 14:00:00 | 0 | 11
+ 5 | 15:00:00 | 0 | 11
+ 6 | 15:00:00 | 0 | 11
+ 7 | 17:00:00 | 0 | 11
+ 8 | 18:00:00 | 0 | 11
+ 9 | 19:00:00 | 0 | 11
+ 10 | 20:00:00 | 0 | 11
+ 11 | 21:00:00 | 0 | 11
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2424,7 +2490,8 @@ window w as (order by f_timetz range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 1 | 11:00:00+01 | 1 | 3
+ 0 | 10:00:00+01 | 0 | 2
+ 1 | 11:00:00+01 | 0 | 3
2 | 12:00:00+01 | 1 | 4
3 | 13:00:00+01 | 2 | 6
4 | 14:00:00+01 | 3 | 6
@@ -2432,9 +2499,10 @@ window w as (order by f_timetz range between
6 | 15:00:00+01 | 4 | 7
7 | 17:00:00+01 | 7 | 9
8 | 18:00:00+01 | 7 | 10
- 9 | 19:00:00+01 | 8 | 10
- 10 | 20:00:00+01 | 9 | 10
-(10 rows)
+ 9 | 19:00:00+01 | 8 | 11
+ 10 | 20:00:00+01 | 9 | 11
+ 11 | 21:00:00+01 | 10 | 11
+(12 rows)
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2442,7 +2510,8 @@ window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 10 | 20:00:00+01 | 10 | 8
+ 11 | 21:00:00+01 | 11 | 9
+ 10 | 20:00:00+01 | 11 | 8
9 | 19:00:00+01 | 10 | 7
8 | 18:00:00+01 | 9 | 7
7 | 17:00:00+01 | 8 | 5
@@ -2450,9 +2519,70 @@ window w as (order by f_timetz desc range between
5 | 15:00:00+01 | 6 | 3
4 | 14:00:00+01 | 6 | 2
3 | 13:00:00+01 | 4 | 1
- 2 | 12:00:00+01 | 3 | 1
- 1 | 11:00:00+01 | 2 | 1
-(10 rows)
+ 2 | 12:00:00+01 | 3 | 0
+ 1 | 11:00:00+01 | 2 | 0
+ 0 | 10:00:00+01 | 1 | 0
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | 0 | 11
+ 1 | 11:00:00+01 | 0 | 11
+ 2 | 12:00:00+01 | 0 | 11
+ 3 | 13:00:00+01 | 0 | 11
+ 4 | 14:00:00+01 | 0 | 11
+ 5 | 15:00:00+01 | 0 | 11
+ 6 | 15:00:00+01 | 0 | 11
+ 7 | 17:00:00+01 | 0 | 11
+ 8 | 18:00:00+01 | 0 | 11
+ 9 | 19:00:00+01 | 0 | 11
+ 10 | 20:00:00+01 | 0 | 11
+ 11 | 21:00:00+01 | 0 | 11
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2460,6 +2590,7 @@ window w as (order by f_interval range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | @ 1 year | 1 | 2
2 | @ 2 years | 1 | 3
3 | @ 3 years | 2 | 4
@@ -2470,7 +2601,8 @@ window w as (order by f_interval range between
8 | @ 8 years | 7 | 9
9 | @ 9 years | 8 | 10
10 | @ 10 years | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2478,6 +2610,7 @@ window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | @ 10 years | 10 | 9
9 | @ 9 years | 10 | 8
8 | @ 8 years | 9 | 7
@@ -2488,7 +2621,68 @@ window w as (order by f_interval desc range between
3 | @ 3 years | 4 | 2
2 | @ 2 years | 3 | 1
1 | @ 1 year | 2 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 0 | 11
+ 2 | @ 2 years | 0 | 11
+ 3 | @ 3 years | 0 | 11
+ 4 | @ 4 years | 0 | 11
+ 5 | @ 5 years | 0 | 11
+ 6 | @ 5 years | 0 | 11
+ 7 | @ 7 years | 0 | 11
+ 8 | @ 8 years | 0 | 11
+ 9 | @ 9 years | 0 | 11
+ 10 | @ 10 years | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | @ 1 year | 0 | 0
+ 2 | @ 2 years | 0 | 0
+ 3 | @ 3 years | 0 | 0
+ 4 | @ 4 years | 0 | 0
+ 5 | @ 5 years | 0 | 0
+ 6 | @ 5 years | 0 | 0
+ 7 | @ 7 years | 0 | 0
+ 8 | @ 8 years | 0 | 0
+ 9 | @ 9 years | 0 | 0
+ 10 | @ 10 years | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 11 | 11
+ 2 | @ 2 years | 11 | 11
+ 3 | @ 3 years | 11 | 11
+ 4 | @ 4 years | 11 | 11
+ 5 | @ 5 years | 11 | 11
+ 6 | @ 5 years | 11 | 11
+ 7 | @ 7 years | 11 | 11
+ 8 | @ 8 years | 11 | 11
+ 9 | @ 9 years | 11 | 11
+ 10 | @ 10 years | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2496,6 +2690,7 @@ window w as (order by f_timestamptz range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 02:23:54 2000 PDT | 1 | 3
2 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
3 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
@@ -2506,7 +2701,8 @@ window w as (order by f_timestamptz range between
8 | Thu Oct 19 02:23:54 2006 PDT | 7 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 8 | 10
10 | Sun Oct 19 02:23:54 2008 PDT | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2514,6 +2710,7 @@ window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 02:23:54 2008 PDT | 10 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 10 | 8
8 | Thu Oct 19 02:23:54 2006 PDT | 9 | 7
@@ -2524,7 +2721,68 @@ window w as (order by f_timestamptz desc range between
3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 0
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 0
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 0
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 0
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 0
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 0
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 0
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 11 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 11 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 11 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 11 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 11 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 11 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 11 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2532,6 +2790,7 @@ window w as (order by f_timestamp range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 10:23:54 2000 | 1 | 3
2 | Fri Oct 19 10:23:54 2001 | 1 | 4
3 | Fri Oct 19 10:23:54 2001 | 1 | 4
@@ -2542,7 +2801,8 @@ window w as (order by f_timestamp range between
8 | Thu Oct 19 10:23:54 2006 | 7 | 9
9 | Fri Oct 19 10:23:54 2007 | 8 | 10
10 | Sun Oct 19 10:23:54 2008 | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2550,6 +2810,7 @@ window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 10:23:54 2008 | 10 | 9
9 | Fri Oct 19 10:23:54 2007 | 10 | 8
8 | Thu Oct 19 10:23:54 2006 | 9 | 7
@@ -2560,7 +2821,68 @@ window w as (order by f_timestamp desc range between
3 | Fri Oct 19 10:23:54 2001 | 4 | 1
2 | Fri Oct 19 10:23:54 2001 | 4 | 1
1 | Thu Oct 19 10:23:54 2000 | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 0
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 0
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 0
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 0
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 0
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 0
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 0
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 11 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 11 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 11 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 11 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 11 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 11 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 11 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
@@ -4375,36 +4697,34 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
--- window function over interval, infinity and extreme values test the
--- behaviour of accumulation and elimination of these values as the window
--- slides.
+-- moving aggregates over infinite intervals
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
- ('2147483647 days 2147483646 months'), -- extreme interval value
- ('infinity'::timestamptz - now()),
- ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
(NULL::interval),
('-infinity'::interval)) v(x);
- x | curr_next_avg | prev1_curr_avg | curr_next_sum | prev1_curr_sum
-----------------------------------------------+-------------------+-------------------+---------------+----------------
- | infinity | | infinity |
- infinity | infinity | infinity | infinity | infinity
- @ 178956970 years 6 mons 2147483647 days | infinity | infinity | infinity | infinity
- infinity | infinity | infinity | infinity | infinity
- @ 178956970 years 7 mons 2147483648 days ago | infinity | infinity | infinity | infinity
- infinity | infinity | infinity | infinity | infinity
- @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
- @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
- | -infinity | @ 7 days | -infinity | @ 7 days
- -infinity | -infinity | -infinity | -infinity | -infinity
+ x | curr_next_avg | prev_curr_avg | curr_next_sum | prev_curr_sum
+------------------------------------------------------------------------------+-------------------+-------------------+---------------+---------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago | -infinity | infinity | -infinity | infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs | infinity | -infinity | infinity | -infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
(10 rows)
--should fail.
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 414c12be1c..1bffa50fe9 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -73,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483646 months'),
- ('2147483647 days -2147483647 months'),
+ ('2147483647 days 2147483647 months'),
+ ('2147483647 days -2147483648 months'),
('1 year'),
- ('-2147483648 days 2147483646 months'),
- ('-2147483648 days -2147483647 months');
+ ('-2147483648 days 2147483647 months'),
+ ('-2147483648 days -2147483648 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -164,8 +164,7 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483646 months 30 days');
-SELECT justify_days(interval '2147483646 months 60 days');
+SELECT justify_days(interval '2147483647 months 30 days');
-- test justify_interval()
@@ -173,14 +172,12 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483646 months 30 days');
-SELECT justify_interval(interval '2147483646 months 60 days');
-SELECT justify_interval(interval '-2147483647 months -30 days');
-SELECT justify_interval(interval '-2147483647 months -60 days');
-SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -608,6 +605,14 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+--
+-- test infinite intervals
+--
+
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+
-- infinite intervals
SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
@@ -617,54 +622,64 @@ INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2
SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
-SELECT date '1995-08-06' + interval 'infinity';
-SELECT date '1995-08-06' + interval '-infinity';
-SELECT date '1995-08-06' - interval 'infinity';
-SELECT date '1995-08-06' - interval '-infinity';
-SELECT date 'infinity' + interval 'infinity';
-SELECT date 'infinity' + interval '-infinity';
-SELECT date '-infinity' + interval 'infinity';
-SELECT date '-infinity' + interval '-infinity';
-SELECT date 'infinity' - interval 'infinity';
-SELECT date 'infinity' - interval '-infinity';
-SELECT date '-infinity' - interval 'infinity';
-SELECT date '-infinity' - interval '-infinity';
-SELECT interval 'infinity' + interval 'infinity';
-SELECT interval 'infinity' + interval '-infinity';
-SELECT interval '-infinity' + interval 'infinity';
-SELECT interval '-infinity' + interval '-infinity';
-SELECT interval 'infinity' + interval '10 days';
-SELECT interval '-infinity' + interval '10 days';
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+
SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
-SELECT interval 'infinity' - interval 'infinity';
-SELECT interval 'infinity' - interval '-infinity';
-SELECT interval '-infinity' - interval 'infinity';
-SELECT interval '-infinity' - interval '-infinity';
-SELECT interval 'infinity' - interval '10 days';
-SELECT interval '-infinity' - interval '10 days';
SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
-SELECT timestamp 'infinity' + interval 'infinity';
-SELECT timestamp 'infinity' + interval '-infinity';
-SELECT timestamp '-infinity' + interval 'infinity';
-SELECT timestamp '-infinity' + interval '-infinity';
-SELECT timestamp 'infinity' - interval 'infinity';
-SELECT timestamp 'infinity' - interval '-infinity';
-SELECT timestamp '-infinity' - interval 'infinity';
-SELECT timestamp '-infinity' - interval '-infinity';
-SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
-SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
-SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
-SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
-SELECT timestamptz 'infinity' + interval 'infinity';
-SELECT timestamptz 'infinity' + interval '-infinity';
-SELECT timestamptz '-infinity' + interval 'infinity';
-SELECT timestamptz '-infinity' + interval '-infinity';
-SELECT timestamptz 'infinity' - interval 'infinity';
-SELECT timestamptz 'infinity' - interval '-infinity';
-SELECT timestamptz '-infinity' - interval 'infinity';
-SELECT timestamptz '-infinity' - interval '-infinity';
+
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+-- time +/- infinite interval not supported
SELECT time '11:27:42' + interval 'infinity';
SELECT time '11:27:42' + interval '-infinity';
SELECT time '11:27:42' - interval 'infinity';
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 6dd9885949..c2a4cb8d64 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -673,6 +673,7 @@ create temp table datetimes(
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -682,7 +683,8 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -694,6 +696,21 @@ from datetimes
window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
@@ -704,6 +721,21 @@ from datetimes
window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
@@ -714,6 +746,21 @@ from datetimes
window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
@@ -724,6 +771,21 @@ from datetimes
window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
@@ -734,6 +796,21 @@ from datetimes
window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -1591,19 +1668,17 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
--- window function over interval, infinity and extreme values test the
--- behaviour of accumulation and elimination of these values as the window
--- slides.
+-- moving aggregates over infinite intervals
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
- ('2147483647 days 2147483646 months'), -- extreme interval value
- ('infinity'::timestamptz - now()),
- ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
--
2.35.3
On Fri, Sep 29, 2023 at 12:43 PM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
I think that part is now ready to commit, and I plan to push this fix
to make_interval() separately, since it's really a bug-fix, not
related to support for infinite intervals. In line with recent
precedent, I don't think it's worth back-patching though, since such
inputs are pretty unlikely in production.
The changes look good to me. I am not a fan of goto construct. But
this looks nicer.
I think we should introduce interval_out_of_range_error() function on
the lines of float_overflow_error(). Later patches introduce more
places where we raise that error. We can introduce the function as
part of those patches.
2. The various in_range() functions needed adjusting to handle
infinite interval offsets.For timestamp values, I followed the precedent set by the equivalent
float/numeric code. I.e., all (finite and non-finite) timestamps are
regarded as infinitely following -infinity and infinitely preceding
+infinity.For time values, it's a bit different because no time values precede
or follow any other by more than 24 hours, so a window frame between
+inf following and +inf following is empty (whereas in the timestamp
case it contains +inf). Put another way, such a window frame is empty
because a time value can't be infinity.
I will review and test this. I will also take a look at what else we
might be missing in the patch. [5]/messages/by-id/CAAvxfHdzd5JLRBXDAW7OPhsNNACvhsCP3f5R4LNhRVaDuQG0gg@mail.gmail.com did mention that in_range()
functions need to be assessed but I don't see corresponding changes in
the subsequent patches. I will go over that list again.
3. I got rid of interval2timestamp_no_overflow() because I don't think
it really makes much sense to convert an interval to a timestamp, and
it's a bit of a hack anyway (as selfuncs.c itself admits). Actually, I
think it's OK to just leave selfuncs.c as it is. The existing code
will cope just fine with infinite intervals, since they aren't really
infinite, just larger than any others.
This looks odd next to date2timestamp_no_overflow() which returns
-DBL_MIN/DBL_MAX for infinite value. But it's in agreement with what
we do with timestamp i.e. we don't convert infinities to DBL_MIN/MAX.
So I am fine with just adding a comment, the way you have done it.
Don't have much preference here.
4. I tested pg_upgrade on a table with an interval with INT_MAX
months, and it was silently converted to infinity. I think that's
probably the best outcome (better than failing).
[1]: /messages/by-id/CAAvxfHea4+sPybKK7agDYOMo9N-Z3J6ZXf3BOM79pFsFNcRjwA@mail.gmail.com
value but out of documented range of interval [2]Table 8.9 at https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME. The highest value
of Interval = 178000000 (years) * 12 = 2136000000 months which is less
than (2^32 - 1). But we do not prohibit such a value from entering the
database, albeit very less probable.
However, this means
that we really should require all 3 fields of an interval to be
INT_MIN/MAX for it to be considered infinite, otherwise it would be
possible to have multiple internal representations of infinity that do
not compare as equal.Similarly, interval_in() needs to accept such inputs, otherwise things
like pg_dump/restore from pre-17 databases could fail. But since it
now requires all 3 fields of the interval to be INT_MIN/MAX for it to
be infinite, the odds of that happening by accident are vanishingly
small in practice.This approach also means that the range of allowed finite intervals is
only reduced by 1 microsecond at each end of the range, rather than a
whole month.Also, it means that it is no longer necessary to change a number of
the regression tests (such as the justify_interval() tests) for values
near INT_MIN/MAX.
My first patch was comparing all the three fields to determine whether
a given Interval value represents infinity. [3]/messages/by-id/CAAvxfHf0-T99i=Orve_xfonVCvsCuPy7C4avVm=+yu128ujSGg@mail.gmail.com changed that to use
only the month field. I guess that was based on the discussion at [4]/messages/by-id/26022.1545087636@sss.pgh.pa.us.
You may want to review that discussion if not already done. I am fine
either way. We should be able to change the comparison code later if
we see performance getting impacted.
Overall, I think this is now pretty close to being ready for commit.
Thanks.
[1]: /messages/by-id/CAAvxfHea4+sPybKK7agDYOMo9N-Z3J6ZXf3BOM79pFsFNcRjwA@mail.gmail.com
[2]: Table 8.9 at https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME
https://www.postgresql.org/docs/current/datatype-datetime.html#DATATYPE-DATETIME
[3]: /messages/by-id/CAAvxfHf0-T99i=Orve_xfonVCvsCuPy7C4avVm=+yu128ujSGg@mail.gmail.com
[4]: /messages/by-id/26022.1545087636@sss.pgh.pa.us
[5]: /messages/by-id/CAAvxfHdzd5JLRBXDAW7OPhsNNACvhsCP3f5R4LNhRVaDuQG0gg@mail.gmail.com
--
Best Wishes,
Ashutosh Bapat
Hi Dean,
On Wed, Oct 4, 2023 at 6:59 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
I think we should introduce interval_out_of_range_error() function on
the lines of float_overflow_error(). Later patches introduce more
places where we raise that error. We can introduce the function as
part of those patches.
Not done yet. The error is raised from multiple places and we might
find improving error message at some of those places. This refactoring
will make that work difficult. Let me know if you think otherwise.
2. The various in_range() functions needed adjusting to handle
infinite interval offsets.For timestamp values, I followed the precedent set by the equivalent
float/numeric code. I.e., all (finite and non-finite) timestamps are
regarded as infinitely following -infinity and infinitely preceding
+infinity.For time values, it's a bit different because no time values precede
or follow any other by more than 24 hours, so a window frame between
+inf following and +inf following is empty (whereas in the timestamp
case it contains +inf). Put another way, such a window frame is empty
because a time value can't be infinity.
I think this code is reasonable. But users may find it inconsistent
with the other ways to achieve the same outcome. For example, We don't
allow non-finite intervals to be added or subtracted from time or
timetz. So if someone tries to compare the rows using val >/<= base
+/- offset, those queries will fail whereas similar implied conditions
in window specification will not throw an error. If you have
considered this already, I am fine with the code as is.
This code doesn't handle non-finite intervals explicitly. But that's
inline with the interval comparison functions (interval_le/ge etc.)
which rely on infinities being represented by extreme values.
I will review and test this. I will also take a look at what else we
might be missing in the patch. [5] did mention that in_range()
functions need to be assessed but I don't see corresponding changes in
the subsequent patches. I will go over that list again.
Added a separate patch (0009) to fix
brin_minmax_multi_distance_interval(). The fix is inconsistent with
the way infinte timestamp and date is handled in that file. But I
think infinite timestamp and date handling itself is inconsistent with
the way infinite values of float are handled. I have tried to be
consistent with float. May be we should fix date and timestamp
functions as well.
I also changed brin_multi.sql to test infinte interval values in BRIN
index. This required some further changes to existing queries.
I thought about combining these two INSERTs but decided against that
since we would loose NULL interval values.
-- throw in some NULL's and different values
INSERT INTO brintest_multi (inetcol, cidrcol) SELECT
inet 'fe80::6e40:8ff:fea9:8c46' + tenthous,
cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous
FROM tenk1 ORDER BY thousand, tenthous LIMIT 25;
INSERT INTO brintest_multi(intervalcol) VALUES ('-infinity'), ('+infinity');
I took some time understand BRIN before making any changes. That took time.
On your patches.
I like the eval() function you have used and its usage. It's a bit
harder to understand it initially but makes the query and output
crisp. Saves some SQL and output lines too. I tried to use the same
trick for time and timetz
select t as time, i as interval,
eval(format('time %L + interval %L', t, i)) AS time_pl,
eval(format('time %L - interval %L', t, i)) AS time_mi,
eval(format('timetz %L + interval %L', t, i)) AS timetz_pl,
eval(format('timetz %L - interval %L', t, i)) AS timetz_mi
from (values ('11:27:42')) t1(t),
(values ('infinity'),
('-infinity')) as t2(i);
The query and output take the same space. So I decided against using it.
I have added a separate patch (0008) to test negative interval values,
including -infinity, in preceding and following specification.
Patches from 0001 to 0007 are same as what you attached but rebased on
the latest HEAD.
I think we should squash 0002 to 0007.
--
Best Wishes,
Ashutosh Bapat
Attachments:
0009-Handle-infinite-interval-in-brin_minmax_mul-20231010.patchtext/x-patch; charset=US-ASCII; name=0009-Handle-infinite-interval-in-brin_minmax_mul-20231010.patchDownload
From c34c212815b40622b6cd29a314def2eaa7a5dd3e Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Tue, 10 Oct 2023 16:11:21 +0530
Subject: [PATCH 9/9] Handle infinite interval in
brin_minmax_multi_distance_interval()
This also fixes code duplication between brin_minmax_multi_distance_interval() and interval_mi().
Adds testcase for testing infinite interval in BRIN indexes.
---
src/backend/access/brin/brin_minmax_multi.c | 33 ++++-----------------
src/test/regress/expected/brin_multi.out | 9 +++---
src/test/regress/sql/brin_multi.sql | 10 ++++---
3 files changed, 17 insertions(+), 35 deletions(-)
diff --git a/src/backend/access/brin/brin_minmax_multi.c b/src/backend/access/brin/brin_minmax_multi.c
index f8b2a3f9bc..61ebfb4542 100644
--- a/src/backend/access/brin/brin_minmax_multi.c
+++ b/src/backend/access/brin/brin_minmax_multi.c
@@ -2158,37 +2158,16 @@ Datum
brin_minmax_multi_distance_interval(PG_FUNCTION_ARGS)
{
float8 delta = 0;
-
- Interval *ia = PG_GETARG_INTERVAL_P(0);
- Interval *ib = PG_GETARG_INTERVAL_P(1);
Interval *result;
-
int64 dayfraction;
int64 days;
- result = (Interval *) palloc(sizeof(Interval));
-
- result->month = ib->month - ia->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(ib->month, ia->month) &&
- !SAMESIGN(result->month, ib->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->day = ib->day - ia->day;
- if (!SAMESIGN(ib->day, ia->day) &&
- !SAMESIGN(result->day, ib->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->time = ib->time - ia->time;
- if (!SAMESIGN(ib->time, ia->time) &&
- !SAMESIGN(result->time, ib->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ result = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
+ PG_GETARG_DATUM(1),
+ PG_GETARG_DATUM(0)));
+
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_FLOAT8(get_float8_infinity());
/*
* Delta is (fractional) number of days between the intervals. Assume
diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
index 9f46934c9b..2b48069201 100644
--- a/src/test/regress/expected/brin_multi.out
+++ b/src/test/regress/expected/brin_multi.out
@@ -47,6 +47,7 @@ INSERT INTO brintest_multi (inetcol, cidrcol) SELECT
inet 'fe80::6e40:8ff:fea9:8c46' + tenthous,
cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous
FROM tenk1 ORDER BY thousand, tenthous LIMIT 25;
+INSERT INTO brintest_multi(intervalcol) VALUES ('-infinity'), ('+infinity');
-- test minmax-multi specific index options
-- number of values must be >= 16
CREATE INDEX brinidx_multi ON brintest_multi USING brin (
@@ -217,9 +218,9 @@ INSERT INTO brinopers_multi VALUES
'{1972-10-10 03:00:00-04, 1972-10-10 03:00:00-04, 1972-10-19 09:00:00-07, 1972-11-20 19:00:00-03, 1972-11-20 19:00:00-03}',
'{100, 100, 1, 100, 100}'),
('intervalcol', 'interval',
- '{>, >=, =, <=, <}',
- '{00:00:00, 00:00:00, 1 mons 13 days 12:24, 2 mons 23 days 07:48:00, 1 year}',
- '{100, 100, 1, 100, 100}'),
+ '{>, >=, =, =, <=, <}',
+ '{00:00:00, 00:00:00, 1 mons 13 days 12:24, infinity, 2 mons 23 days 07:48:00, 1 year}',
+ '{101, 101, 1, 1, 101, 101}'),
('timetzcol', 'timetz',
'{>, >=, =, <=, <}',
'{01:30:20+02, 01:30:20+02, 01:35:50+02, 23:55:05+02, 23:55:05+02}',
@@ -235,7 +236,7 @@ INSERT INTO brinopers_multi VALUES
('lsncol', 'pg_lsn',
'{>, >=, =, <=, <, IS, IS NOT}',
'{0/1200, 0/1200, 44/455222, 198/1999799, 198/1999799, NULL, NULL}',
- '{100, 100, 1, 100, 100, 25, 100}');
+ '{100, 100, 1, 100, 100, 27, 100}');
DO $x$
DECLARE
r record;
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
index d50dbdee68..b868b5bdb9 100644
--- a/src/test/regress/sql/brin_multi.sql
+++ b/src/test/regress/sql/brin_multi.sql
@@ -50,6 +50,8 @@ INSERT INTO brintest_multi (inetcol, cidrcol) SELECT
cidr 'fe80::6e40:8ff:fea9:8c46' + tenthous
FROM tenk1 ORDER BY thousand, tenthous LIMIT 25;
+INSERT INTO brintest_multi(intervalcol) VALUES ('-infinity'), ('+infinity');
+
-- test minmax-multi specific index options
-- number of values must be >= 16
CREATE INDEX brinidx_multi ON brintest_multi USING brin (
@@ -221,9 +223,9 @@ INSERT INTO brinopers_multi VALUES
'{1972-10-10 03:00:00-04, 1972-10-10 03:00:00-04, 1972-10-19 09:00:00-07, 1972-11-20 19:00:00-03, 1972-11-20 19:00:00-03}',
'{100, 100, 1, 100, 100}'),
('intervalcol', 'interval',
- '{>, >=, =, <=, <}',
- '{00:00:00, 00:00:00, 1 mons 13 days 12:24, 2 mons 23 days 07:48:00, 1 year}',
- '{100, 100, 1, 100, 100}'),
+ '{>, >=, =, =, <=, <}',
+ '{00:00:00, 00:00:00, 1 mons 13 days 12:24, infinity, 2 mons 23 days 07:48:00, 1 year}',
+ '{101, 101, 1, 1, 101, 101}'),
('timetzcol', 'timetz',
'{>, >=, =, <=, <}',
'{01:30:20+02, 01:30:20+02, 01:35:50+02, 23:55:05+02, 23:55:05+02}',
@@ -239,7 +241,7 @@ INSERT INTO brinopers_multi VALUES
('lsncol', 'pg_lsn',
'{>, >=, =, <=, <, IS, IS NOT}',
'{0/1200, 0/1200, 44/455222, 198/1999799, 198/1999799, NULL, NULL}',
- '{100, 100, 1, 100, 100, 25, 100}');
+ '{100, 100, 1, 100, 100, 27, 100}');
DO $x$
DECLARE
--
2.25.1
0005-Use-integer-overflow-checking-routines-to-a-20231010.patchtext/x-patch; charset=US-ASCII; name=0005-Use-integer-overflow-checking-routines-to-a-20231010.patchDownload
From b5b0112984363ee6d6107e297fe883871e50fc04 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Thu, 21 Sep 2023 15:34:33 +0530
Subject: [PATCH 5/9] Use integer overflow checking routines to add and
subtract finite intervals
Use pg_add/sub_s32/64_overflow() routines in finite_interval_pl() and
finite_interval_mi() instead of adding overflow checks in those
functions.
Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 121 +++++++++++--------------
src/test/regress/expected/interval.out | 11 +++
src/test/regress/expected/window.out | 54 ++++-------
src/test/regress/sql/interval.sql | 3 +
src/test/regress/sql/window.sql | 22 ++---
5 files changed, 95 insertions(+), 116 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 50fb793c13..8c1b0a774b 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -99,6 +99,8 @@ static Timestamp timestamptz2timestamp(TimestampTz timestamp);
static void interval_um_internal(Interval *interval, Interval *result);
static void finite_interval_pl(Interval *result, Interval *span1,
Interval *span2);
+static void finite_interval_mi(Interval *result, Interval *span1,
+ Interval *span2);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -3502,34 +3504,7 @@ interval_mi(PG_FUNCTION_ARGS)
else if (INTERVAL_IS_NOEND(span2))
INTERVAL_NOBEGIN(result);
else
- {
- 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")));
-
- if (INTERVAL_NOT_FINITE(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- }
+ finite_interval_mi(result, span1, span2);
PG_RETURN_INTERVAL_P(result);
}
@@ -3869,11 +3844,18 @@ makeIntervalAggState(FunctionCallInfo fcinfo)
}
/*
- * Function to add two finite intervals.
+ * Functions to add or subtract two finite intervals.
+ *
+ * We handle non-finite intervals in different ways when accumulating/discarding
+ * intervals and in actual mathematical operations respectively. But the
+ * addition or subtraction of finite intervals have same implementation
+ * respectively in both these cases.
+ *
+ * Addition/Subtraction of span1 and span2 is stored in the result. It is fine
+ * to pass either of span1 or span2 as a result pointer. In such a case result
+ * will overwrite the given value.
*
- * We handle non-finite intervals in different ways when accumulating intervals
- * and adding two intervals respectively. But the addition of finite interval
- * has same implementation in both these cases.
+ * If the result is not a valid interval, the function throws an error.
*/
static void
finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
@@ -3881,24 +3863,44 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
Assert(!INTERVAL_NOT_FINITE(span1));
Assert(!INTERVAL_NOT_FINITE(span2));
- result->month = span1->month + span2->month;
- /* overflow check copied from int4pl */
- if (SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
+ if (pg_add_s32_overflow(span1->month, span2->month, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+static void
+finite_interval_mi(Interval *result, Interval *span1, Interval *span2)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
+
+ if (pg_sub_s32_overflow(span1->month, span2->month, &result->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))
+ if (pg_sub_s32_overflow(span1->day, span2->day, &result->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))
+ if (pg_sub_s64_overflow(span1->time, span2->time, &result->time))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
@@ -3915,9 +3917,6 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
static void
do_interval_accum(IntervalAggState *state, Interval *newval)
{
-
- Interval temp;
-
/* Count -infinity inputs separately from all else */
if (INTERVAL_IS_NOBEGIN(newval))
{
@@ -3932,8 +3931,7 @@ do_interval_accum(IntervalAggState *state, Interval *newval)
return;
}
- memcpy(&temp, &state->sumX, sizeof(Interval));
- finite_interval_pl(&state->sumX, &temp, newval);
+ finite_interval_pl(&state->sumX, &state->sumX, newval);
state->N++;
}
@@ -3959,6 +3957,9 @@ interval_avg_accum(PG_FUNCTION_ARGS)
/*
* Combine function for interval aggregates.
+ *
+ * Combine the given internal aggregate states and place the combination in the
+ * first argument.
*/
Datum
interval_avg_combine(PG_FUNCTION_ARGS)
@@ -3972,9 +3973,9 @@ interval_avg_combine(PG_FUNCTION_ARGS)
if (state2 == NULL)
PG_RETURN_POINTER(state1);
- /* manually copy all fields from state2 to state1 */
if (state1 == NULL)
{
+ /* manually copy all fields from state2 to state1 */
state1 = makeIntervalAggState(fcinfo);
state1->N = state2->N;
@@ -3992,14 +3993,9 @@ interval_avg_combine(PG_FUNCTION_ARGS)
state1->pInfcount += state2->pInfcount;
state1->nInfcount += state2->nInfcount;
+ /* Accumulate finite interval values, if any. */
if (state2->N > 0)
- {
- Interval temp;
-
- /* Accumulate interval values */
- memcpy(&temp, &state1->sumX, sizeof(Interval));
- finite_interval_pl(&state1->sumX, &temp, &state2->sumX);
- }
+ finite_interval_pl(&state1->sumX, &state1->sumX, &state2->sumX);
PG_RETURN_POINTER(state1);
}
@@ -4025,24 +4021,15 @@ do_interval_discard(IntervalAggState *state, Interval *newval)
}
/* Handle to be discarded finite value. */
+ state->N--;
if (state->N > 0)
- {
- Interval temp;
- Interval neg_val;
-
- neg_val.day = -newval->day;
- neg_val.month = -newval->month;
- neg_val.time = -newval->time;
-
- memcpy(&temp, &state->sumX, sizeof(Interval));
- finite_interval_pl(&state->sumX, &temp, &neg_val);
- }
+ finite_interval_mi(&state->sumX, &state->sumX, newval);
else
{
/* All values discarded, reset the state */
+ Assert(state->N == 0);
memset(&state->sumX, 0, sizeof(state->sumX));
}
- state->N--;
}
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 135d683285..8bd72f60b0 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -347,6 +347,17 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
(5 rows)
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column?
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- Floating point arithmetic rounding errors can lead to unexpected results,
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 45262924cd..63f36e069e 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,51 +4375,37 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
---order by.
-SELECT x
- ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
-FROM (VALUES (NULL::interval),
- ('infinity'::interval),
- ('infinity'::timestamptz - now()),
- ('6 days'::interval),
- (NULL::interval),
- ('-infinity'::interval)) v(x);
- x | curr_next_avg | prev1_curr_sum
------------+---------------+----------------
- -infinity | -infinity | -infinity
- @ 6 days | infinity | -infinity
- infinity | infinity | infinity
- infinity | infinity | infinity
- | | infinity
- | |
-(6 rows)
-
---no order by.
+-- window function over interval, infinity and extreme values test the
+-- behaviour of accumulation and elimination of these values as the window
+-- slides.
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
+ ('2147483647 days 2147483646 months'), -- extreme interval value
('infinity'::timestamptz - now()),
+ ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
(NULL::interval),
('-infinity'::interval)) v(x);
- x | curr_next_avg | curr_next_sum | prev1_curr_avg | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum
------------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
- | infinity | infinity | | | infinity | infinity
- infinity | infinity | infinity | infinity | infinity | infinity | infinity
- infinity | infinity | infinity | infinity | infinity | infinity | infinity
- @ 6 days | @ 6 days 12 hours | @ 13 days | infinity | infinity | infinity | infinity
- @ 7 days | @ 7 days | @ 7 days | @ 6 days 12 hours | @ 13 days | -infinity | -infinity
- | -infinity | -infinity | @ 7 days | @ 7 days | -infinity | -infinity
- -infinity | -infinity | -infinity | -infinity | -infinity | -infinity | -infinity
-(7 rows)
+ x | curr_next_avg | prev1_curr_avg | curr_next_sum | prev1_curr_sum
+----------------------------------------------+-------------------+-------------------+---------------+----------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 6 mons 2147483647 days | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 7 mons 2147483648 days ago | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
+(10 rows)
--should fail.
SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 912803a1b0..414c12be1c 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -99,6 +99,9 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index d00423946d..6dd9885949 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,28 +1591,20 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
---order by.
-SELECT x
- ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
-FROM (VALUES (NULL::interval),
- ('infinity'::interval),
- ('infinity'::timestamptz - now()),
- ('6 days'::interval),
- (NULL::interval),
- ('-infinity'::interval)) v(x);
-
---no order by.
+-- window function over interval, infinity and extreme values test the
+-- behaviour of accumulation and elimination of these values as the window
+-- slides.
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
+ ('2147483647 days 2147483646 months'), -- extreme interval value
('infinity'::timestamptz - now()),
+ ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
(NULL::interval),
--
2.25.1
0006-Implement-serialization-functions-for-inter-20231010.patchtext/x-patch; charset=US-ASCII; name=0006-Implement-serialization-functions-for-inter-20231010.patchDownload
From e85bcba3343ceb9c4a8265449e8b82b701c2b84f Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Thu, 21 Sep 2023 16:52:30 +0530
Subject: [PATCH 6/9] Implement serialization functions for interval aggregate
state
Now that interval aggregates use transition state which of date type Internal,
parallel aggregates require these separate functions.
Jian He and Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 86 +++++++++++++++++++++++++++-
src/include/catalog/pg_aggregate.dat | 4 ++
src/include/catalog/pg_proc.dat | 6 ++
3 files changed, 94 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 8c1b0a774b..bf02ddb92d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -3821,8 +3821,12 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * Prepare state data for an interval aggregate function that needs to compute
- * sum and count.
+ * Prepare state data for an interval aggregate function, that needs to compute
+ * sum and count, in the aggregate's memory context.
+ *
+ * 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)
@@ -4000,6 +4004,84 @@ interval_avg_combine(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(state1);
}
+/*
+ * interval_avg_serialize
+ * Serialize IntervalAggState for interval aggregates.
+ */
+Datum
+interval_avg_serialize(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ StringInfoData buf;
+ bytea *result;
+
+ /* Ensure we disallow calling when not in aggregate context */
+ if (!AggCheckCallContext(fcinfo, NULL))
+ elog(ERROR, "aggregate function called in non-aggregate context");
+
+ /* TODO: Handle NULL inputs? */
+ state = (IntervalAggState *) PG_GETARG_POINTER(0);
+ pq_begintypsend(&buf);
+ /* N */
+ pq_sendint64(&buf, state->N);
+ /* Finite interval value */
+ pq_sendint64(&buf, state->sumX.time);
+ pq_sendint32(&buf, state->sumX.day);
+ pq_sendint32(&buf, state->sumX.month);
+ /* pInfcount */
+ pq_sendint64(&buf, state->pInfcount);
+ /* nInfcount */
+ pq_sendint64(&buf, state->nInfcount);
+ result = pq_endtypsend(&buf);
+ PG_RETURN_BYTEA_P(result);
+}
+
+/*
+ * interval_avg_deserialize
+ * Deserialize bytea into IntervalAggState for interval aggregates.
+ */
+Datum
+interval_avg_deserialize(PG_FUNCTION_ARGS)
+{
+ 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);
+
+ /*
+ * Copy the bytea into a StringInfo so that we can "receive" it using the
+ * standard recv-function infrastructure.
+ */
+ initStringInfo(&buf);
+ appendBinaryStringInfo(&buf,
+ VARDATA_ANY(sstate), VARSIZE_ANY_EXHDR(sstate));
+
+ result = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
+
+ /* N */
+ result->N = pq_getmsgint64(&buf);
+
+ /* Interval struct elements, one by one. */
+ result->sumX.time = pq_getmsgint64(&buf);
+ result->sumX.day = pq_getmsgint(&buf, sizeof(result->sumX.day));
+ result->sumX.month = pq_getmsgint(&buf, sizeof(result->sumX.month));
+
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
+
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
+
+ pq_getmsgend(&buf);
+ pfree(buf.data);
+
+ PG_RETURN_POINTER(result);
+}
+
/*
* Remove the given interval value from the aggregated state.
*/
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index e2087d7be1..0e62c3f7a6 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -45,6 +45,8 @@
aggtranstype => '_float8', agginitval => '{0,0,0}' },
{ 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_accum_inv',
aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
aggmtranstype => 'internal', aggtransspace => '128',
@@ -75,6 +77,8 @@
aggtranstype => 'money', aggmtranstype => 'money' },
{ 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_accum_inv',
aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
aggmtranstype => 'internal', aggtransspace => '128',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f8eca58ddf..34da5f3687 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4925,6 +4925,12 @@
proname => 'interval_accum_inv', proisstrict => 'f',
prorettype => 'internal', proargtypes => 'internal interval',
prosrc => 'interval_accum_inv' },
+{ oid => '3813', descr => 'aggregate serial function',
+ proname => 'interval_avg_serialize', prorettype => 'bytea',
+ proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
+{ oid => '3814', 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 => 'internal', prosrc => 'interval_avg' },
--
2.25.1
0008-Add-tests-for-invalid-preceding-or-followin-20231010.patchtext/x-patch; charset=US-ASCII; name=0008-Add-tests-for-invalid-preceding-or-followin-20231010.patchDownload
From 0e175da667f849e11f510bc24dc7ff6ed1aba288 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Fri, 6 Oct 2023 11:58:21 +0530
Subject: [PATCH 8/9] Add tests for "invalid preceding or following size in
window function"
Ashutosh Bapat
---
src/test/regress/expected/window.out | 50 ++++++++++++++++++++++++++++
src/test/regress/sql/window.sql | 50 ++++++++++++++++++++++++++++
2 files changed, 100 insertions(+)
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 6b8c3c3413..f1eaf4b58b 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -2424,6 +2424,11 @@ window w as (order by f_time desc range between
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: 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
@@ -2484,6 +2489,11 @@ window w as (order by f_time range between
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: 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
@@ -2524,6 +2534,11 @@ window w as (order by f_timetz desc range between
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: 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
@@ -2584,6 +2599,11 @@ window w as (order by f_timetz range between
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: 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
@@ -2624,6 +2644,11 @@ window w as (order by f_interval desc range between
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: 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
@@ -2684,6 +2709,11 @@ window w as (order by f_interval range between
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: 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
@@ -2724,6 +2754,11 @@ window w as (order by f_timestamptz desc range between
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: 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
@@ -2784,6 +2819,11 @@ window w as (order by f_timestamptz range between
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: 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
@@ -2824,6 +2864,11 @@ window w as (order by f_timestamp desc range between
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: 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
@@ -2884,6 +2929,11 @@ window w as (order by f_timestamp range between
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: 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;
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index c2a4cb8d64..586ffb245d 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -696,6 +696,11 @@ 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);
+
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
@@ -711,6 +716,11 @@ 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);
+
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
@@ -721,6 +731,11 @@ from datetimes
window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following);
+
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
@@ -736,6 +751,11 @@ 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);
+
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
@@ -746,6 +766,11 @@ from datetimes
window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following);
+
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
@@ -761,6 +786,11 @@ 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);
+
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
@@ -771,6 +801,11 @@ from datetimes
window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following);
+
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
@@ -786,6 +821,11 @@ 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);
+
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
@@ -796,6 +836,11 @@ from datetimes
window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following);
+
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
@@ -811,6 +856,11 @@ 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);
+
-- 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;
--
2.25.1
0007-Code-review-of-infinite-interval-support-20231010.patchtext/x-patch; charset=US-ASCII; name=0007-Code-review-of-infinite-interval-support-20231010.patchDownload
From 82a7d3ba92e1df0d0ebbda5cfccff25d9450aad6 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Thu, 28 Sep 2023 14:54:02 +0100
Subject: [PATCH 7/9] Code review of infinite interval support.
---
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 27 +-
src/backend/utils/adt/datetime.c | 4 +-
src/backend/utils/adt/selfuncs.c | 16 +-
src/backend/utils/adt/timestamp.c | 366 +++++++++----------
src/include/catalog/pg_aggregate.dat | 11 +-
src/include/catalog/pg_proc.dat | 6 +-
src/include/datatype/timestamp.h | 24 +-
src/include/utils/timestamp.h | 3 -
src/test/regress/expected/interval.out | 375 ++++++++------------
src/test/regress/expected/timestamp.out | 8 +-
src/test/regress/expected/timestamptz.out | 8 +-
src/test/regress/expected/window.out | 406 +++++++++++++++++++---
src/test/regress/sql/interval.sql | 131 +++----
src/test/regress/sql/window.sql | 93 ++++-
15 files changed, 912 insertions(+), 571 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7786853743..2fd9db9afa 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10435,7 +10435,10 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
<literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
- for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
+ 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>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index f2107dd967..ef06474ac3 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -24,6 +24,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
+#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/supportnodes.h"
@@ -2050,8 +2051,6 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
- Assert(!INTERVAL_NOT_FINITE(result));
-
PG_RETURN_INTERVAL_P(result);
}
@@ -2116,7 +2115,8 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* Like time_pl_interval/time_mi_interval, we disregard the month and day
- * fields of the offset. So our test for negative should too.
+ * fields of the offset. So our test for negative should too. This also
+ * catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2126,13 +2126,14 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* We can't use time_pl_interval/time_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum = base - offset->time;
- else
- sum = base + offset->time;
+ else if (pg_add_s64_overflow(base, offset->time, &sum))
+ PG_RETURN_BOOL(less);
if (less)
PG_RETURN_BOOL(val <= sum);
@@ -2666,7 +2667,8 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* Like timetz_pl_interval/timetz_mi_interval, we disregard the month and
- * day fields of the offset. So our test for negative should too.
+ * day fields of the offset. So our test for negative should too. This
+ * also catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2676,13 +2678,14 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* We can't use timetz_pl_interval/timetz_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum.time = base->time - offset->time;
- else
- sum.time = base->time + offset->time;
+ else if (pg_add_s64_overflow(base->time, offset->time, &sum.time))
+ PG_RETURN_BOOL(less);
sum.zone = base->zone;
if (less)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 73dadb747c..fca9a2a6e9 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3613,9 +3613,9 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
return DTERR_BAD_FORMAT;
/*
- * infinity can not be followed by anything else. We
+ * Infinity cannot be followed by anything else. We
* could allow "ago" to reverse the sign of infinity
- * but that signed infinity is more intuitive.
+ * but using signed infinity is more intuitive.
*/
if (i != nf - 1)
return DTERR_BAD_FORMAT;
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 6e87173846..4ea5415f20 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,7 +4795,21 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- return interval2timestamp_no_overflow(DatumGetIntervalP(value));
+ {
+ Interval *interval = DatumGetIntervalP(value);
+
+ /*
+ * Convert the month part of Interval to days using assumed
+ * average month length of 365.25/12.0 days. Not too
+ * accurate, but plenty good enough for our purposes.
+ *
+ * This also works for infinite intervals, which just have all
+ * fields set to INT_MIN/INT_MAX, and so will produce a result
+ * smaller/larger than any finite interval.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index bf02ddb92d..c8e1fb9f9d 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,7 +18,6 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
-#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -74,16 +73,16 @@ typedef struct
} generate_series_timestamptz_fctx;
/*
- * The transition datatype for interval aggregates is declared as Internal. It's
- * a pointer to a IntervalAggState allocated in the aggregate context.
+ * The transition datatype for interval aggregates is declared as internal.
+ * It's a pointer to an IntervalAggState allocated in the aggregate context.
*/
typedef struct IntervalAggState
{
- int64 N; /* count of processed intervals */
- Interval sumX; /* sum of processed intervals */
+ int64 N; /* count of finite intervals processed */
+ Interval sumX; /* sum of finite intervals processed */
/* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
- int64 pInfcount; /* count of +Inf values */
- int64 nInfcount; /* count of -Inf values */
+ int64 pInfcount; /* count of +infinity intervals */
+ int64 nInfcount; /* count of -infinity intervals */
} IntervalAggState;
#define IA_TOTAL_COUNT(ia) \
@@ -96,11 +95,12 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
-static void interval_um_internal(Interval *interval, Interval *result);
-static void finite_interval_pl(Interval *result, Interval *span1,
- Interval *span2);
-static void finite_interval_mi(Interval *result, Interval *span1,
- Interval *span2);
+static void EncodeSpecialInterval(const Interval *interval, char *str);
+static void interval_um_internal(const Interval *interval, Interval *result);
+static void finite_interval_pl(const Interval *span1, const Interval *span2,
+ Interval *result);
+static void finite_interval_mi(const Interval *span1, const Interval *span2,
+ Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -1604,8 +1604,8 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
-void
-EncodeSpecialInterval(Interval *interval, char *str)
+static void
+EncodeSpecialInterval(const Interval *interval, char *str)
{
if (INTERVAL_IS_NOBEGIN(interval))
strcpy(str, EARLY);
@@ -2101,6 +2101,13 @@ itm2interval(struct pg_itm *itm, Interval *span)
/* itmin2interval()
* Convert a pg_itm_in structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * Note: if the result is infinite, it is not treated as an overflow. This
+ * avoids any dump/reload hazards from pre-17 databases that do not support
+ * infinite intervals, but do allow finite intervals with all fields set to
+ * INT_MIN/INT_MAX (outside the documented range). Such intervals will be
+ * silently converted to +/-infinity. This may not be ideal, but seems
+ * preferable to failure, and ought to be pretty unlikely in practice.
*/
int
itmin2interval(struct pg_itm_in *itm_in, Interval *span)
@@ -2112,8 +2119,6 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
- if (INTERVAL_NOT_FINITE(span))
- return -1;
return 0;
}
@@ -2280,29 +2285,6 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
-double
-interval2timestamp_no_overflow(Interval *interval)
-{
- double result;
-
- if (INTERVAL_IS_NOBEGIN(interval))
- result = -DBL_MAX;
- else if (INTERVAL_IS_NOEND(interval))
- result = DBL_MAX;
- else
- {
- /*
- * Convert the month part of Interval to days using assumed average
- * month length of 365.25/12.0 days. Not too accurate, but plenty
- * good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
-
- return result;
-}
-
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2526,6 +2508,15 @@ interval_cmp_internal(const Interval *interval1, const Interval *interval2)
return int128_compare(span1, span2);
}
+static int
+interval_sign(const Interval *interval)
+{
+ INT128 span = interval_cmp_value(interval);
+ INT128 zero = int64_to_int128(0);
+
+ return int128_compare(span, zero);
+}
+
Datum
interval_eq(PG_FUNCTION_ARGS)
{
@@ -2801,14 +2792,14 @@ timestamp_mi(PG_FUNCTION_ARGS)
/*
* Subtracting two infinite timestamps with different signs results in an
* infinite interval with the same sign as the left operand. Subtracting
- * two infinte timestamps with the same sign results in an error.
+ * two infinite timestamps with the same sign results in an error.
*/
if (TIMESTAMP_IS_NOBEGIN(dt1))
{
if (TIMESTAMP_IS_NOBEGIN(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOBEGIN(result);
}
@@ -2817,7 +2808,7 @@ timestamp_mi(PG_FUNCTION_ARGS)
if (TIMESTAMP_IS_NOEND(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOEND(result);
}
@@ -3068,16 +3059,16 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
Timestamp result;
/*
- * Adding two infinites with the same sign results in an infinite
- * timestamp with the same sign. Adding two infintes with different signs
- * results in an error.
+ * Adding two infinities with the same sign results in an infinite
+ * timestamp with the same sign. Adding two infinities with different
+ * signs results in an error.
*/
if (INTERVAL_IS_NOBEGIN(span))
{
if (TIMESTAMP_IS_NOEND(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("timestamp out of range")));
else
TIMESTAMP_NOBEGIN(result);
}
@@ -3086,7 +3077,7 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
if (TIMESTAMP_IS_NOBEGIN(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("timestamp out of range")));
else
TIMESTAMP_NOEND(result);
}
@@ -3197,16 +3188,16 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
int tz;
/*
- * Adding two infinites with the same sign results in an infinite
- * timestamp with the same sign. Adding two infintes with different signs
- * results in an error.
+ * Adding two infinities with the same sign results in an infinite
+ * timestamp with the same sign. Adding two infinities with different
+ * signs results in an error.
*/
if (INTERVAL_IS_NOBEGIN(span))
{
if (TIMESTAMP_IS_NOEND(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("timestamp out of range")));
else
TIMESTAMP_NOBEGIN(result);
}
@@ -3215,7 +3206,7 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
if (TIMESTAMP_IS_NOBEGIN(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("timestamp out of range")));
else
TIMESTAMP_NOEND(result);
}
@@ -3362,16 +3353,12 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
/* Negates the given interval */
static void
-interval_um_internal(Interval *interval, Interval *result)
+interval_um_internal(const Interval *interval, Interval *result)
{
if (INTERVAL_IS_NOBEGIN(interval))
INTERVAL_NOEND(result);
else if (INTERVAL_IS_NOEND(interval))
INTERVAL_NOBEGIN(result);
- else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
else
{
result->time = -interval->time;
@@ -3438,8 +3425,8 @@ interval_pl(PG_FUNCTION_ARGS)
/*
* Adding two infinite intervals with the same signs results in an
- * infinite interval with the same sign. Adding two infinte intervals with
- * different signs results in an error.
+ * infinite interval with the same sign. Adding two infinite intervals
+ * with different signs results in an error.
*/
if (INTERVAL_IS_NOBEGIN(span1))
{
@@ -3462,7 +3449,7 @@ interval_pl(PG_FUNCTION_ARGS)
else if (INTERVAL_NOT_FINITE(span2))
memcpy(result, span2, sizeof(Interval));
else
- finite_interval_pl(result, span1, span2);
+ finite_interval_pl(span1, span2, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3479,7 +3466,7 @@ interval_mi(PG_FUNCTION_ARGS)
/*
* Subtracting two infinite intervals with different signs results in an
* infinite interval with the same sign as the left operand. Subtracting
- * two infinte intervals with the same sign results in an error.
+ * two infinite intervals with the same sign results in an error.
*/
if (INTERVAL_IS_NOBEGIN(span1))
{
@@ -3504,7 +3491,7 @@ interval_mi(PG_FUNCTION_ARGS)
else if (INTERVAL_IS_NOEND(span2))
INTERVAL_NOBEGIN(result);
else
- finite_interval_mi(result, span1, span2);
+ finite_interval_mi(span1, span2, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3552,13 +3539,11 @@ interval_mul(PG_FUNCTION_ARGS)
}
else if (isinf(factor))
{
- Interval zero;
int factor_sign,
result_sign;
factor_sign = factor >= 0.0 ? 1 : -1;
- memset(&zero, 0, sizeof(zero));
- result_sign = interval_cmp_internal(span, &zero) * factor_sign;
+ result_sign = interval_sign(span) * factor_sign;
if (result_sign == 0)
ereport(ERROR,
@@ -3740,11 +3725,21 @@ in_range_timestamptz_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
TimestampTz sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = timestamptz_mi_interval_internal(base, offset, NULL);
@@ -3767,11 +3762,21 @@ in_range_timestamp_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Timestamp sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval,
@@ -3798,11 +3803,21 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Interval *sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? INTERVAL_IS_NOEND(base) : INTERVAL_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
@@ -3848,21 +3863,13 @@ makeIntervalAggState(FunctionCallInfo fcinfo)
}
/*
- * Functions to add or subtract two finite intervals.
+ * Functions to add or subtract finite intervals.
*
- * We handle non-finite intervals in different ways when accumulating/discarding
- * intervals and in actual mathematical operations respectively. But the
- * addition or subtraction of finite intervals have same implementation
- * respectively in both these cases.
- *
- * Addition/Subtraction of span1 and span2 is stored in the result. It is fine
- * to pass either of span1 or span2 as a result pointer. In such a case result
- * will overwrite the given value.
- *
- * If the result is not a valid interval, the function throws an error.
+ * These are used for normal arithmetic and aggregation of finite intervals
+ * only. Non-finite intervals require special handling.
*/
static void
-finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
+finite_interval_pl(const Interval *span1, const Interval *span2, Interval *result)
{
Assert(!INTERVAL_NOT_FINITE(span1));
Assert(!INTERVAL_NOT_FINITE(span2));
@@ -3889,7 +3896,7 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
}
static void
-finite_interval_mi(Interval *result, Interval *span1, Interval *span2)
+finite_interval_mi(const Interval *span1, const Interval *span2, Interval *result)
{
Assert(!INTERVAL_NOT_FINITE(span1));
Assert(!INTERVAL_NOT_FINITE(span2));
@@ -3921,26 +3928,56 @@ finite_interval_mi(Interval *result, Interval *span1, Interval *span2)
static void
do_interval_accum(IntervalAggState *state, Interval *newval)
{
- /* Count -infinity inputs separately from all else */
+ /* Infinite inputs are counted separately, and do not affect "N" */
if (INTERVAL_IS_NOBEGIN(newval))
{
state->nInfcount++;
return;
}
- /* Count infinity inputs separately from all else */
if (INTERVAL_IS_NOEND(newval))
{
state->pInfcount++;
return;
}
- finite_interval_pl(&state->sumX, &state->sumX, newval);
+ finite_interval_pl(&state->sumX, newval, &state->sumX);
state->N++;
}
/*
- * Transition function for interval aggregates.
+ * Remove the given interval value from the aggregated state.
+ */
+static void
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+ /* Infinite inputs are counted separately, and do not affect "N" */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount--;
+ return;
+ }
+
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount--;
+ return;
+ }
+
+ /* Handle to be discarded finite value. */
+ state->N--;
+ if (state->N > 0)
+ finite_interval_mi(&state->sumX, newval, &state->sumX);
+ else
+ {
+ /* All values discarded, reset the state */
+ Assert(state->N == 0);
+ memset(&state->sumX, 0, sizeof(state->sumX));
+ }
+}
+
+/*
+ * Transition function for sum() and avg() interval aggregates.
*/
Datum
interval_avg_accum(PG_FUNCTION_ARGS)
@@ -3960,10 +3997,10 @@ interval_avg_accum(PG_FUNCTION_ARGS)
}
/*
- * Combine function for interval aggregates.
+ * Combine function for sum() and avg() interval aggregates.
*
- * Combine the given internal aggregate states and place the combination in the
- * first argument.
+ * Combine the given internal aggregate states and place the combination in
+ * the first argument.
*/
Datum
interval_avg_combine(PG_FUNCTION_ARGS)
@@ -3999,7 +4036,7 @@ interval_avg_combine(PG_FUNCTION_ARGS)
/* Accumulate finite interval values, if any. */
if (state2->N > 0)
- finite_interval_pl(&state1->sumX, &state1->sumX, &state2->sumX);
+ finite_interval_pl(&state1->sumX, &state2->sumX, &state1->sumX);
PG_RETURN_POINTER(state1);
}
@@ -4019,20 +4056,26 @@ interval_avg_serialize(PG_FUNCTION_ARGS)
if (!AggCheckCallContext(fcinfo, NULL))
elog(ERROR, "aggregate function called in non-aggregate context");
- /* TODO: Handle NULL inputs? */
state = (IntervalAggState *) PG_GETARG_POINTER(0);
+
pq_begintypsend(&buf);
+
/* N */
pq_sendint64(&buf, state->N);
- /* Finite interval value */
+
+ /* sumX */
pq_sendint64(&buf, state->sumX.time);
pq_sendint32(&buf, state->sumX.day);
pq_sendint32(&buf, state->sumX.month);
+
/* pInfcount */
pq_sendint64(&buf, state->pInfcount);
+
/* nInfcount */
pq_sendint64(&buf, state->nInfcount);
+
result = pq_endtypsend(&buf);
+
PG_RETURN_BYTEA_P(result);
}
@@ -4065,10 +4108,10 @@ interval_avg_deserialize(PG_FUNCTION_ARGS)
/* N */
result->N = pq_getmsgint64(&buf);
- /* Interval struct elements, one by one. */
+ /* sumX */
result->sumX.time = pq_getmsgint64(&buf);
- result->sumX.day = pq_getmsgint(&buf, sizeof(result->sumX.day));
- result->sumX.month = pq_getmsgint(&buf, sizeof(result->sumX.month));
+ result->sumX.day = pq_getmsgint(&buf, 4);
+ result->sumX.month = pq_getmsgint(&buf, 4);
/* pInfcount */
result->pInfcount = pq_getmsgint64(&buf);
@@ -4083,42 +4126,10 @@ interval_avg_deserialize(PG_FUNCTION_ARGS)
}
/*
- * Remove the given interval value from the aggregated state.
- */
-static void
-do_interval_discard(IntervalAggState *state, Interval *newval)
-{
- /* Count -infinity inputs separately from all else */
- if (INTERVAL_IS_NOBEGIN(newval))
- {
- state->nInfcount--;
- return;
- }
-
- /* Count infinity inputs separately from all else */
- if (INTERVAL_IS_NOEND(newval))
- {
- state->pInfcount--;
- return;
- }
-
- /* Handle to be discarded finite value. */
- state->N--;
- if (state->N > 0)
- finite_interval_mi(&state->sumX, &state->sumX, newval);
- else
- {
- /* All values discarded, reset the state */
- Assert(state->N == 0);
- memset(&state->sumX, 0, sizeof(state->sumX));
- }
-}
-
-/*
- * Generic inverse transition function for interval aggregates
+ * Inverse transition function for sum() and avg() interval aggregates.
*/
Datum
-interval_accum_inv(PG_FUNCTION_ARGS)
+interval_avg_accum_inv(PG_FUNCTION_ARGS)
{
IntervalAggState *state;
@@ -4126,7 +4137,7 @@ interval_accum_inv(PG_FUNCTION_ARGS)
/* Should not get here with no state */
if (state == NULL)
- elog(ERROR, "interval_accum_inv called with NULL state");
+ elog(ERROR, "interval_avg_accum_inv called with NULL state");
if (!PG_ARGISNULL(1))
do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
@@ -4139,38 +4150,39 @@ Datum
interval_avg(PG_FUNCTION_ARGS)
{
IntervalAggState *state;
- double N_datum;
- Interval *sumX;
- sumX = (Interval *) palloc(sizeof(Interval));
state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+ /* If there were no non-null inputs, return NULL */
if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- /* adding plus and minus infinities gives error */
- if (state->pInfcount > 0 && state->nInfcount > 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range.")));
-
- if (state->pInfcount > 0)
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 || state->nInfcount > 0)
{
- INTERVAL_NOEND(sumX);
- PG_RETURN_INTERVAL_P(sumX);
- }
+ Interval *result;
- if (state->nInfcount > 0)
- {
- INTERVAL_NOBEGIN(sumX);
- PG_RETURN_INTERVAL_P(sumX);
- }
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else
+ INTERVAL_NOBEGIN(result);
- N_datum = (double) state->N;
- sumX = &state->sumX;
+ PG_RETURN_INTERVAL_P(result);
+ }
- PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX),
- Float8GetDatum(N_datum)));
+ return DirectFunctionCall2(interval_div,
+ IntervalPGetDatum(&state->sumX),
+ Float8GetDatum((double) state->N));
}
/* sum(interval) aggregate final function */
@@ -4180,33 +4192,31 @@ interval_sum(PG_FUNCTION_ARGS)
IntervalAggState *state;
Interval *result;
- result = (Interval *) palloc(sizeof(Interval));
-
state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
/* If there were no non-null inputs, return NULL */
if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- /* adding plus and minus interval infinities is not possible */
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
if (state->pInfcount > 0 && state->nInfcount > 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range.")));
+ result = (Interval *) palloc(sizeof(Interval));
+
if (state->pInfcount > 0)
- {
INTERVAL_NOEND(result);
- PG_RETURN_INTERVAL_P(result);
- }
-
- if (state->nInfcount > 0)
- {
+ else if (state->nInfcount > 0)
INTERVAL_NOBEGIN(result);
- PG_RETURN_INTERVAL_P(result);
- }
+ else
+ memcpy(result, &state->sumX, sizeof(Interval));
- memcpy(result, &state->sumX, sizeof(Interval));
PG_RETURN_INTERVAL_P(result);
}
@@ -4236,14 +4246,14 @@ timestamp_age(PG_FUNCTION_ARGS)
/*
* Subtracting two infinite timestamps with different signs results in an
* infinite interval with the same sign as the left operand. Subtracting
- * two infinte timestamps with the same sign results in an error.
+ * two infinite timestamps with the same sign results in an error.
*/
if (TIMESTAMP_IS_NOBEGIN(dt1))
{
if (TIMESTAMP_IS_NOBEGIN(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOBEGIN(result);
}
@@ -4252,7 +4262,7 @@ timestamp_age(PG_FUNCTION_ARGS)
if (TIMESTAMP_IS_NOEND(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOEND(result);
}
@@ -4383,14 +4393,14 @@ timestamptz_age(PG_FUNCTION_ARGS)
/*
* Subtracting two infinite timestamps with different signs results in an
* infinite interval with the same sign as the left operand. Subtracting
- * two infinte timestamps with the same sign results in an error.
+ * two infinite timestamps with the same sign results in an error.
*/
if (TIMESTAMP_IS_NOBEGIN(dt1))
{
if (TIMESTAMP_IS_NOBEGIN(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOBEGIN(result);
}
@@ -4399,7 +4409,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
if (TIMESTAMP_IS_NOEND(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOEND(result);
}
@@ -5831,17 +5841,16 @@ extract_timestamptz(PG_FUNCTION_ARGS)
/*
* NonFiniteIntervalPart
*
- * Used by interval_part when extracting from infinite
- * interval. Returns +/-Infinity if that is the appropriate result,
- * otherwise returns zero (which should be taken as meaning to return NULL).
+ * Used by interval_part when extracting from infinite interval. Returns
+ * +/-Infinity if that is the appropriate result, otherwise returns zero
+ * (which should be taken as meaning to return NULL).
*
* Errors thrown here for invalid units should exactly match those that
* would be thrown in the calling functions, else there will be unexpected
* discrepancies between finite- and infinite-input cases.
*/
static float8
-NonFiniteIntervalPart(int type, int unit, char *lowunits,
- bool isNegative, bool isTz)
+NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative)
{
if ((type != UNITS) && (type != RESERV))
ereport(ERROR,
@@ -5908,8 +5917,7 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (INTERVAL_NOT_FINITE(interval))
{
double r = NonFiniteIntervalPart(type, val, lowunits,
- INTERVAL_IS_NOBEGIN(interval),
- false);
+ INTERVAL_IS_NOBEGIN(interval));
if (r != 0.0)
{
@@ -6455,7 +6463,6 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
Timestamp finish = PG_GETARG_TIMESTAMP(1);
Interval *step = PG_GETARG_INTERVAL_P(2);
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -6478,7 +6485,7 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
fctx->step = *step;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
@@ -6541,7 +6548,6 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
Interval *step = PG_GETARG_INTERVAL_P(2);
text *zone = (PG_NARGS() == 4) ? PG_GETARG_TEXT_PP(3) : NULL;
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -6565,7 +6571,7 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
fctx->attimezone = zone ? lookup_timezone(zone) : session_timezone;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 0e62c3f7a6..e1a17cddd8 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -47,11 +47,9 @@
aggfinalfn => 'interval_avg', aggcombinefn => 'interval_avg_combine',
aggserialfn => 'interval_avg_serialize',
aggdeserialfn => 'interval_avg_deserialize',
- aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv',
aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
- aggmtranstype => 'internal', aggtransspace => '128',
- aggmtransspace => '128'
-},
+ aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40' },
# sum
{ aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
@@ -79,10 +77,9 @@
aggfinalfn => 'interval_sum', aggcombinefn => 'interval_avg_combine',
aggserialfn => 'interval_avg_serialize',
aggdeserialfn => 'interval_avg_deserialize',
- aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv',
aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
- aggmtranstype => 'internal', aggtransspace => '128',
- aggmtransspace => '128'},
+ aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40'},
{ aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
aggserialfn => 'numeric_avg_serialize',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 34da5f3687..d17e916118 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4922,9 +4922,9 @@
prorettype => 'internal', proargtypes => 'internal internal',
prosrc => 'interval_avg_combine' },
{ oid => '3549', descr => 'aggregate transition function',
- proname => 'interval_accum_inv', proisstrict => 'f',
+ proname => 'interval_avg_accum_inv', proisstrict => 'f',
prorettype => 'internal', proargtypes => 'internal interval',
- prosrc => 'interval_accum_inv' },
+ prosrc => 'interval_avg_accum_inv' },
{ oid => '3813', descr => 'aggregate serial function',
proname => 'interval_avg_serialize', prorettype => 'bytea',
proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
@@ -4932,7 +4932,7 @@
proname => 'interval_avg_deserialize', prorettype => 'internal',
proargtypes => 'bytea internal', prosrc => 'interval_avg_deserialize' },
{ oid => '1844', descr => 'aggregate final function',
- proname => 'interval_avg', prorettype => 'interval',
+ proname => 'interval_avg', proisstrict => 'f', prorettype => 'interval',
proargtypes => 'internal', prosrc => 'interval_avg' },
{ oid => '8069', descr => 'aggregate final function',
proname => 'interval_sum', proisstrict => 'f', prorettype => 'interval',
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index f9bd30d225..b63acc0a2f 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -151,7 +151,7 @@ struct pg_itm_in
#define TIMESTAMP_INFINITY PG_INT64_MAX
/*
- * Historically these alias for infinity have been used.
+ * Historically these aliases for infinity have been used.
*/
#define DT_NOBEGIN TIMESTAMP_MINUS_INFINITY
#define DT_NOEND TIMESTAMP_INFINITY
@@ -168,23 +168,29 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+/*
+ * Infinite intervals are represented by setting all fields to the minimum or
+ * maximum integer values.
+ */
#define INTERVAL_NOBEGIN(i) \
do { \
- (i->time) = PG_INT64_MIN; \
- (i->day) = PG_INT32_MIN; \
- (i->month) = PG_INT32_MIN; \
+ (i)->time = PG_INT64_MIN; \
+ (i)->day = PG_INT32_MIN; \
+ (i)->month = PG_INT32_MIN; \
} while (0)
-#define INTERVAL_IS_NOBEGIN(i) ((i->month) == PG_INT32_MIN)
+#define INTERVAL_IS_NOBEGIN(i) \
+ ((i)->month == PG_INT32_MIN && (i)->day == PG_INT32_MIN && (i)->time == PG_INT64_MIN)
#define INTERVAL_NOEND(i) \
do { \
- (i->time) = PG_INT64_MAX; \
- (i->day) = PG_INT32_MAX; \
- (i->month) = PG_INT32_MAX; \
+ (i)->time = PG_INT64_MAX; \
+ (i)->day = PG_INT32_MAX; \
+ (i)->month = PG_INT32_MAX; \
} while (0)
-#define INTERVAL_IS_NOEND(i) ((i->month) == PG_INT32_MAX)
+#define INTERVAL_IS_NOEND(i) \
+ ((i)->month == PG_INT32_MAX && (i)->day == PG_INT32_MAX && (i)->time == PG_INT64_MAX)
#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index fe713fb6dd..c4dd96c8c9 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,7 +118,6 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
-extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -128,8 +127,6 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
-extern double interval2timestamp_no_overflow(Interval *interval);
-
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 8bd72f60b0..00cd3e84f1 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -284,11 +284,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483646 months'),
- ('2147483647 days -2147483647 months'),
+ ('2147483647 days 2147483647 months'),
+ ('2147483647 days -2147483648 months'),
('1 year'),
- ('-2147483648 days 2147483646 months'),
- ('-2147483648 days -2147483647 months');
+ ('-2147483648 days 2147483647 months'),
+ ('-2147483648 days -2147483648 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -315,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
- 1 year | -178956970 years -7 mons -2147483648 days
- 1 year | -178956970 years -7 mons +2147483647 days
- 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
- 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
- 178956970 years 6 mons -2147483648 days | 1 year
- 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
- 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
- 178956970 years 6 mons 2147483647 days | 1 year
- 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
+ -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
+ 1 year | -178956970 years -8 mons -2147483648 days
+ 1 year | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
+ 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons -2147483648 days | 1 year
+ 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
+ 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons 2147483647 days | 1 year
+ 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -339,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -7 mons -2147483648 days
- -178956970 years -7 mons +2147483647 days
+ -178956970 years -8 mons -2147483648 days
+ -178956970 years -8 mons +2147483647 days
1 year
- 178956970 years 6 mons -2147483648 days
- 178956970 years 6 mons 2147483647 days
+ 178956970 years 7 mons -2147483648 days
+ 178956970 years 7 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -485,9 +485,7 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483646 months 30 days');
-ERROR: interval out of range
-SELECT justify_days(interval '2147483646 months 60 days');
+SELECT justify_days(interval '2147483647 months 30 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -508,29 +506,25 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483646 months 30 days');
-ERROR: interval out of range
-SELECT justify_interval(interval '2147483646 months 60 days');
-ERROR: interval out of range
-SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '2147483647 months 30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '-2147483648 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 6 mons 29 days
+ @ 178956970 years 7 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 7 mons 29 days ago
+ @ 178956970 years 8 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -1882,233 +1876,144 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
--- infinite intervals
-SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
-ERROR: interval out of range
-LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
- ^
-SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
-ERROR: interval out of range
-LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
- ^
-CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
-INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
-SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
- i | isfinite
--------------------------+----------
- infinity | f
- -infinity | f
- @ 1 year 2 days 3 hours | t
-(3 rows)
-
-SELECT date '1995-08-06' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT date '1995-08-06' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT date '1995-08-06' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT date '1995-08-06' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT date 'infinity' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT date 'infinity' + interval '-infinity';
-ERROR: interval out of range
-SELECT date '-infinity' + interval 'infinity';
-ERROR: interval out of range
-SELECT date '-infinity' + interval '-infinity';
- ?column?
------------
- -infinity
+--
+-- test infinite intervals
+--
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+ interval
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago
(1 row)
-SELECT date 'infinity' - interval 'infinity';
-ERROR: interval out of range
-SELECT date 'infinity' - interval '-infinity';
- ?column?
-----------
- infinity
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+ interval
+--------------------------------------------------------------------------
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs
(1 row)
-SELECT date '-infinity' - interval 'infinity';
- ?column?
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
-----------
-infinity
(1 row)
-SELECT date '-infinity' - interval '-infinity';
-ERROR: interval out of range
-SELECT interval 'infinity' + interval 'infinity';
- ?column?
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ interval
----------
infinity
(1 row)
-SELECT interval 'infinity' + interval '-infinity';
-ERROR: interval out of range
-SELECT interval '-infinity' + interval 'infinity';
-ERROR: interval out of range
-SELECT interval '-infinity' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
-SELECT interval 'infinity' + interval '10 days';
- ?column?
-----------
- infinity
-(1 row)
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ date | interval | plus | minus
+------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 | -infinity | -infinity | infinity
+ 1995-08-06 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
-SELECT interval '-infinity' + interval '10 days';
- ?column?
------------
- -infinity
-(1 row)
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+ interval1 | interval2 | plus | minus
+-----------+-----------+-----------------------+-----------------------
+ -infinity | -infinity | -infinity | interval out of range
+ -infinity | @ 10 days | -infinity | -infinity
+ -infinity | infinity | interval out of range | -infinity
+ @ 2 mons | -infinity | -infinity | infinity
+ @ 2 mons | @ 10 days | @ 2 mons 10 days | @ 2 mons -10 days
+ @ 2 mons | infinity | infinity | -infinity
+ infinity | -infinity | interval out of range | infinity
+ infinity | @ 10 days | infinity | infinity
+ infinity | infinity | infinity | interval out of range
+(9 rows)
SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
ERROR: interval out of range
SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
ERROR: interval out of range
-SELECT interval 'infinity' - interval 'infinity';
-ERROR: interval out of range
-SELECT interval 'infinity' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT interval '-infinity' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT interval '-infinity' - interval '-infinity';
-ERROR: interval out of range
-SELECT interval 'infinity' - interval '10 days';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT interval '-infinity' - interval '10 days';
- ?column?
------------
- -infinity
-(1 row)
-
SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
ERROR: interval out of range
SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
ERROR: interval out of range
-SELECT timestamp 'infinity' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamp 'infinity' + interval '-infinity';
-ERROR: interval out of range
-SELECT timestamp '-infinity' + interval 'infinity';
-ERROR: interval out of range
-SELECT timestamp '-infinity' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamp 'infinity' - interval 'infinity';
-ERROR: interval out of range
-SELECT timestamp 'infinity' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamp '-infinity' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamp '-infinity' - interval '-infinity';
-ERROR: interval out of range
-SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamptz 'infinity' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamptz 'infinity' + interval '-infinity';
-ERROR: interval out of range
-SELECT timestamptz '-infinity' + interval 'infinity';
-ERROR: interval out of range
-SELECT timestamptz '-infinity' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamptz 'infinity' - interval 'infinity';
-ERROR: interval out of range
-SELECT timestamptz 'infinity' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamp | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
-SELECT timestamptz '-infinity' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamptz | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
-SELECT timestamptz '-infinity' - interval '-infinity';
-ERROR: interval out of range
+-- time +/- infinite interval not supported
SELECT time '11:27:42' + interval 'infinity';
ERROR: cannot add infinite interval to time
SELECT time '11:27:42' + interval '-infinity';
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 53542e076b..835f0e5762 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2131,7 +2131,7 @@ select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12
ERROR: step size cannot be infinite
-- test arithmetic with infinite timestamps
select timestamp 'infinity' - timestamp 'infinity';
-ERROR: timestamp out of range
+ERROR: interval out of range
select timestamp 'infinity' - timestamp '-infinity';
?column?
----------
@@ -2145,7 +2145,7 @@ select timestamp '-infinity' - timestamp 'infinity';
(1 row)
select timestamp '-infinity' - timestamp '-infinity';
-ERROR: timestamp out of range
+ERROR: interval out of range
select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
?column?
----------
@@ -2172,7 +2172,7 @@ select age(timestamp '-infinity');
(1 row)
select age(timestamp 'infinity', timestamp 'infinity');
-ERROR: timestamp out of range
+ERROR: interval out of range
select age(timestamp 'infinity', timestamp '-infinity');
age
----------
@@ -2186,4 +2186,4 @@ select age(timestamp '-infinity', timestamp 'infinity');
(1 row)
select age(timestamp '-infinity', timestamp '-infinity');
-ERROR: timestamp out of range
+ERROR: interval out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 825202d597..1c19613ae3 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -3160,7 +3160,7 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
-- test arithmetic with infinite timestamps
SELECT timestamptz 'infinity' - timestamptz 'infinity';
-ERROR: timestamp out of range
+ERROR: interval out of range
SELECT timestamptz 'infinity' - timestamptz '-infinity';
?column?
----------
@@ -3174,7 +3174,7 @@ SELECT timestamptz '-infinity' - timestamptz 'infinity';
(1 row)
SELECT timestamptz '-infinity' - timestamptz '-infinity';
-ERROR: timestamp out of range
+ERROR: interval out of range
SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
?column?
----------
@@ -3201,7 +3201,7 @@ SELECT age(timestamptz '-infinity');
(1 row)
SELECT age(timestamptz 'infinity', timestamptz 'infinity');
-ERROR: timestamp out of range
+ERROR: interval out of range
SELECT age(timestamptz 'infinity', timestamptz '-infinity');
age
----------
@@ -3215,4 +3215,4 @@ SELECT age(timestamptz '-infinity', timestamptz 'infinity');
(1 row)
SELECT age(timestamptz '-infinity', timestamptz '-infinity');
-ERROR: timestamp out of range
+ERROR: interval out of range
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 63f36e069e..6b8c3c3413 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -2372,6 +2372,7 @@ create temp table datetimes(
f_timestamp timestamp
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -2381,14 +2382,16 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_time range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 1 | 11:00:00 | 1 | 3
+ 0 | 10:00:00 | 0 | 2
+ 1 | 11:00:00 | 0 | 3
2 | 12:00:00 | 1 | 4
3 | 13:00:00 | 2 | 6
4 | 14:00:00 | 3 | 6
@@ -2396,9 +2399,10 @@ window w as (order by f_time range between
6 | 15:00:00 | 4 | 7
7 | 17:00:00 | 7 | 9
8 | 18:00:00 | 7 | 10
- 9 | 19:00:00 | 8 | 10
- 10 | 20:00:00 | 9 | 10
-(10 rows)
+ 9 | 19:00:00 | 8 | 11
+ 10 | 20:00:00 | 9 | 11
+ 11 | 21:00:00 | 10 | 11
+(12 rows)
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2406,7 +2410,8 @@ window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 10 | 20:00:00 | 10 | 8
+ 11 | 21:00:00 | 11 | 9
+ 10 | 20:00:00 | 11 | 8
9 | 19:00:00 | 10 | 7
8 | 18:00:00 | 9 | 7
7 | 17:00:00 | 8 | 5
@@ -2414,9 +2419,70 @@ window w as (order by f_time desc range between
5 | 15:00:00 | 6 | 3
4 | 14:00:00 | 6 | 2
3 | 13:00:00 | 4 | 1
- 2 | 12:00:00 | 3 | 1
- 1 | 11:00:00 | 2 | 1
-(10 rows)
+ 2 | 12:00:00 | 3 | 0
+ 1 | 11:00:00 | 2 | 0
+ 0 | 10:00:00 | 1 | 0
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | 0 | 11
+ 1 | 11:00:00 | 0 | 11
+ 2 | 12:00:00 | 0 | 11
+ 3 | 13:00:00 | 0 | 11
+ 4 | 14:00:00 | 0 | 11
+ 5 | 15:00:00 | 0 | 11
+ 6 | 15:00:00 | 0 | 11
+ 7 | 17:00:00 | 0 | 11
+ 8 | 18:00:00 | 0 | 11
+ 9 | 19:00:00 | 0 | 11
+ 10 | 20:00:00 | 0 | 11
+ 11 | 21:00:00 | 0 | 11
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2424,7 +2490,8 @@ window w as (order by f_timetz range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 1 | 11:00:00+01 | 1 | 3
+ 0 | 10:00:00+01 | 0 | 2
+ 1 | 11:00:00+01 | 0 | 3
2 | 12:00:00+01 | 1 | 4
3 | 13:00:00+01 | 2 | 6
4 | 14:00:00+01 | 3 | 6
@@ -2432,9 +2499,10 @@ window w as (order by f_timetz range between
6 | 15:00:00+01 | 4 | 7
7 | 17:00:00+01 | 7 | 9
8 | 18:00:00+01 | 7 | 10
- 9 | 19:00:00+01 | 8 | 10
- 10 | 20:00:00+01 | 9 | 10
-(10 rows)
+ 9 | 19:00:00+01 | 8 | 11
+ 10 | 20:00:00+01 | 9 | 11
+ 11 | 21:00:00+01 | 10 | 11
+(12 rows)
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2442,7 +2510,8 @@ window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 10 | 20:00:00+01 | 10 | 8
+ 11 | 21:00:00+01 | 11 | 9
+ 10 | 20:00:00+01 | 11 | 8
9 | 19:00:00+01 | 10 | 7
8 | 18:00:00+01 | 9 | 7
7 | 17:00:00+01 | 8 | 5
@@ -2450,9 +2519,70 @@ window w as (order by f_timetz desc range between
5 | 15:00:00+01 | 6 | 3
4 | 14:00:00+01 | 6 | 2
3 | 13:00:00+01 | 4 | 1
- 2 | 12:00:00+01 | 3 | 1
- 1 | 11:00:00+01 | 2 | 1
-(10 rows)
+ 2 | 12:00:00+01 | 3 | 0
+ 1 | 11:00:00+01 | 2 | 0
+ 0 | 10:00:00+01 | 1 | 0
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | 0 | 11
+ 1 | 11:00:00+01 | 0 | 11
+ 2 | 12:00:00+01 | 0 | 11
+ 3 | 13:00:00+01 | 0 | 11
+ 4 | 14:00:00+01 | 0 | 11
+ 5 | 15:00:00+01 | 0 | 11
+ 6 | 15:00:00+01 | 0 | 11
+ 7 | 17:00:00+01 | 0 | 11
+ 8 | 18:00:00+01 | 0 | 11
+ 9 | 19:00:00+01 | 0 | 11
+ 10 | 20:00:00+01 | 0 | 11
+ 11 | 21:00:00+01 | 0 | 11
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2460,6 +2590,7 @@ window w as (order by f_interval range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | @ 1 year | 1 | 2
2 | @ 2 years | 1 | 3
3 | @ 3 years | 2 | 4
@@ -2470,7 +2601,8 @@ window w as (order by f_interval range between
8 | @ 8 years | 7 | 9
9 | @ 9 years | 8 | 10
10 | @ 10 years | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2478,6 +2610,7 @@ window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | @ 10 years | 10 | 9
9 | @ 9 years | 10 | 8
8 | @ 8 years | 9 | 7
@@ -2488,7 +2621,68 @@ window w as (order by f_interval desc range between
3 | @ 3 years | 4 | 2
2 | @ 2 years | 3 | 1
1 | @ 1 year | 2 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 0 | 11
+ 2 | @ 2 years | 0 | 11
+ 3 | @ 3 years | 0 | 11
+ 4 | @ 4 years | 0 | 11
+ 5 | @ 5 years | 0 | 11
+ 6 | @ 5 years | 0 | 11
+ 7 | @ 7 years | 0 | 11
+ 8 | @ 8 years | 0 | 11
+ 9 | @ 9 years | 0 | 11
+ 10 | @ 10 years | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | @ 1 year | 0 | 0
+ 2 | @ 2 years | 0 | 0
+ 3 | @ 3 years | 0 | 0
+ 4 | @ 4 years | 0 | 0
+ 5 | @ 5 years | 0 | 0
+ 6 | @ 5 years | 0 | 0
+ 7 | @ 7 years | 0 | 0
+ 8 | @ 8 years | 0 | 0
+ 9 | @ 9 years | 0 | 0
+ 10 | @ 10 years | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 11 | 11
+ 2 | @ 2 years | 11 | 11
+ 3 | @ 3 years | 11 | 11
+ 4 | @ 4 years | 11 | 11
+ 5 | @ 5 years | 11 | 11
+ 6 | @ 5 years | 11 | 11
+ 7 | @ 7 years | 11 | 11
+ 8 | @ 8 years | 11 | 11
+ 9 | @ 9 years | 11 | 11
+ 10 | @ 10 years | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2496,6 +2690,7 @@ window w as (order by f_timestamptz range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 02:23:54 2000 PDT | 1 | 3
2 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
3 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
@@ -2506,7 +2701,8 @@ window w as (order by f_timestamptz range between
8 | Thu Oct 19 02:23:54 2006 PDT | 7 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 8 | 10
10 | Sun Oct 19 02:23:54 2008 PDT | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2514,6 +2710,7 @@ window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 02:23:54 2008 PDT | 10 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 10 | 8
8 | Thu Oct 19 02:23:54 2006 PDT | 9 | 7
@@ -2524,7 +2721,68 @@ window w as (order by f_timestamptz desc range between
3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 0
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 0
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 0
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 0
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 0
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 0
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 0
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 11 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 11 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 11 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 11 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 11 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 11 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 11 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2532,6 +2790,7 @@ window w as (order by f_timestamp range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 10:23:54 2000 | 1 | 3
2 | Fri Oct 19 10:23:54 2001 | 1 | 4
3 | Fri Oct 19 10:23:54 2001 | 1 | 4
@@ -2542,7 +2801,8 @@ window w as (order by f_timestamp range between
8 | Thu Oct 19 10:23:54 2006 | 7 | 9
9 | Fri Oct 19 10:23:54 2007 | 8 | 10
10 | Sun Oct 19 10:23:54 2008 | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2550,6 +2810,7 @@ window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 10:23:54 2008 | 10 | 9
9 | Fri Oct 19 10:23:54 2007 | 10 | 8
8 | Thu Oct 19 10:23:54 2006 | 9 | 7
@@ -2560,7 +2821,68 @@ window w as (order by f_timestamp desc range between
3 | Fri Oct 19 10:23:54 2001 | 4 | 1
2 | Fri Oct 19 10:23:54 2001 | 4 | 1
1 | Thu Oct 19 10:23:54 2000 | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 0
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 0
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 0
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 0
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 0
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 0
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 0
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 11 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 11 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 11 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 11 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 11 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 11 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 11 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
@@ -4375,36 +4697,34 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
--- window function over interval, infinity and extreme values test the
--- behaviour of accumulation and elimination of these values as the window
--- slides.
+-- moving aggregates over infinite intervals
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
- ('2147483647 days 2147483646 months'), -- extreme interval value
- ('infinity'::timestamptz - now()),
- ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
(NULL::interval),
('-infinity'::interval)) v(x);
- x | curr_next_avg | prev1_curr_avg | curr_next_sum | prev1_curr_sum
-----------------------------------------------+-------------------+-------------------+---------------+----------------
- | infinity | | infinity |
- infinity | infinity | infinity | infinity | infinity
- @ 178956970 years 6 mons 2147483647 days | infinity | infinity | infinity | infinity
- infinity | infinity | infinity | infinity | infinity
- @ 178956970 years 7 mons 2147483648 days ago | infinity | infinity | infinity | infinity
- infinity | infinity | infinity | infinity | infinity
- @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
- @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
- | -infinity | @ 7 days | -infinity | @ 7 days
- -infinity | -infinity | -infinity | -infinity | -infinity
+ x | curr_next_avg | prev_curr_avg | curr_next_sum | prev_curr_sum
+------------------------------------------------------------------------------+-------------------+-------------------+---------------+---------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago | -infinity | infinity | -infinity | infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs | infinity | -infinity | infinity | -infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
(10 rows)
--should fail.
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 414c12be1c..1bffa50fe9 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -73,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483646 months'),
- ('2147483647 days -2147483647 months'),
+ ('2147483647 days 2147483647 months'),
+ ('2147483647 days -2147483648 months'),
('1 year'),
- ('-2147483648 days 2147483646 months'),
- ('-2147483648 days -2147483647 months');
+ ('-2147483648 days 2147483647 months'),
+ ('-2147483648 days -2147483648 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -164,8 +164,7 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483646 months 30 days');
-SELECT justify_days(interval '2147483646 months 60 days');
+SELECT justify_days(interval '2147483647 months 30 days');
-- test justify_interval()
@@ -173,14 +172,12 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483646 months 30 days');
-SELECT justify_interval(interval '2147483646 months 60 days');
-SELECT justify_interval(interval '-2147483647 months -30 days');
-SELECT justify_interval(interval '-2147483647 months -60 days');
-SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -608,6 +605,14 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+--
+-- test infinite intervals
+--
+
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+
-- infinite intervals
SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
@@ -617,54 +622,64 @@ INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2
SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
-SELECT date '1995-08-06' + interval 'infinity';
-SELECT date '1995-08-06' + interval '-infinity';
-SELECT date '1995-08-06' - interval 'infinity';
-SELECT date '1995-08-06' - interval '-infinity';
-SELECT date 'infinity' + interval 'infinity';
-SELECT date 'infinity' + interval '-infinity';
-SELECT date '-infinity' + interval 'infinity';
-SELECT date '-infinity' + interval '-infinity';
-SELECT date 'infinity' - interval 'infinity';
-SELECT date 'infinity' - interval '-infinity';
-SELECT date '-infinity' - interval 'infinity';
-SELECT date '-infinity' - interval '-infinity';
-SELECT interval 'infinity' + interval 'infinity';
-SELECT interval 'infinity' + interval '-infinity';
-SELECT interval '-infinity' + interval 'infinity';
-SELECT interval '-infinity' + interval '-infinity';
-SELECT interval 'infinity' + interval '10 days';
-SELECT interval '-infinity' + interval '10 days';
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+
SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
-SELECT interval 'infinity' - interval 'infinity';
-SELECT interval 'infinity' - interval '-infinity';
-SELECT interval '-infinity' - interval 'infinity';
-SELECT interval '-infinity' - interval '-infinity';
-SELECT interval 'infinity' - interval '10 days';
-SELECT interval '-infinity' - interval '10 days';
SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
-SELECT timestamp 'infinity' + interval 'infinity';
-SELECT timestamp 'infinity' + interval '-infinity';
-SELECT timestamp '-infinity' + interval 'infinity';
-SELECT timestamp '-infinity' + interval '-infinity';
-SELECT timestamp 'infinity' - interval 'infinity';
-SELECT timestamp 'infinity' - interval '-infinity';
-SELECT timestamp '-infinity' - interval 'infinity';
-SELECT timestamp '-infinity' - interval '-infinity';
-SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
-SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
-SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
-SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
-SELECT timestamptz 'infinity' + interval 'infinity';
-SELECT timestamptz 'infinity' + interval '-infinity';
-SELECT timestamptz '-infinity' + interval 'infinity';
-SELECT timestamptz '-infinity' + interval '-infinity';
-SELECT timestamptz 'infinity' - interval 'infinity';
-SELECT timestamptz 'infinity' - interval '-infinity';
-SELECT timestamptz '-infinity' - interval 'infinity';
-SELECT timestamptz '-infinity' - interval '-infinity';
+
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+-- time +/- infinite interval not supported
SELECT time '11:27:42' + interval 'infinity';
SELECT time '11:27:42' + interval '-infinity';
SELECT time '11:27:42' - interval 'infinity';
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 6dd9885949..c2a4cb8d64 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -673,6 +673,7 @@ create temp table datetimes(
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -682,7 +683,8 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -694,6 +696,21 @@ from datetimes
window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
@@ -704,6 +721,21 @@ from datetimes
window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
@@ -714,6 +746,21 @@ from datetimes
window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
@@ -724,6 +771,21 @@ from datetimes
window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
@@ -734,6 +796,21 @@ from datetimes
window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -1591,19 +1668,17 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
--- window function over interval, infinity and extreme values test the
--- behaviour of accumulation and elimination of these values as the window
--- slides.
+-- moving aggregates over infinite intervals
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
- ('2147483647 days 2147483646 months'), -- extreme interval value
- ('infinity'::timestamptz - now()),
- ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
--
2.25.1
0004-Support-infinite-interval-values-in-sum-and-20231010.patchtext/x-patch; charset=US-ASCII; name=0004-Support-infinite-interval-values-in-sum-and-20231010.patchDownload
From 04fe3be000f84762ca37c70d3fabc79903a6bc51 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 20 Sep 2023 15:41:00 +0530
Subject: [PATCH 4/9] Support infinite interval values in sum and avg window
functions
Results of arithmetic operations involing both infinity and -infinity
intervals are undefined. Sliding window functions
accumulate and discard values as the window slides through the data.
Even if the data may have both infinities, none of windows may have both
of them together and thus produce definite results. Hence we can not use
normal operator functions to accumulate and discard values. Instead we
maintain a count of both infinities seen respectively in an aggregate
state and compute the aggregate for each window based on the accumulated
finite value and the counts of infinities, all of which are updated as
the window slides through the data.
Initial patch by Jian He, significantly edited by Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 407 ++++++++++++++++++---------
src/include/catalog/pg_aggregate.dat | 23 +-
src/include/catalog/pg_proc.dat | 20 +-
src/test/regress/expected/window.out | 62 ++++
src/test/regress/sql/window.sql | 43 +++
src/tools/pgindent/typedefs.list | 1 +
6 files changed, 400 insertions(+), 156 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index c52bdb68da..50fb793c13 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -73,6 +73,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+/*
+ * The transition datatype for interval aggregates is declared as Internal. It's
+ * a pointer to a IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+ int64 N; /* count of processed intervals */
+ Interval sumX; /* sum of processed intervals */
+ /* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +Inf values */
+ int64 nInfcount; /* count of -Inf values */
+} 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);
@@ -82,6 +97,8 @@ static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
static void interval_um_internal(Interval *interval, Interval *result);
+static void finite_interval_pl(Interval *result, Interval *span1,
+ Interval *span2);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -3443,34 +3460,7 @@ interval_pl(PG_FUNCTION_ARGS)
else if (INTERVAL_NOT_FINITE(span2))
memcpy(result, span2, sizeof(Interval));
else
- {
- 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")));
-
- 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")));
-
- if (INTERVAL_NOT_FINITE(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- }
+ finite_interval_pl(result, span1, span2);
PG_RETURN_INTERVAL_P(result);
}
@@ -3856,161 +3846,300 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function that needs to compute
+ * sum and count.
+ */
+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;
+}
+
+/*
+ * Function to add two finite intervals.
*
- * 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.
+ * We handle non-finite intervals in different ways when accumulating intervals
+ * and adding two intervals respectively. But the addition of finite interval
+ * has same implementation in both these cases.
*/
+static void
+finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
-Datum
-interval_accum(PG_FUNCTION_ARGS)
+ 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")));
+
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+/*
+ * Accumulate a new input value for interval aggregate functions.
+ */
+static void
+do_interval_accum(IntervalAggState *state, Interval *newval)
{
- 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;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ Interval temp;
+
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount++;
+ return;
+ }
+
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount++;
+ return;
+ }
+
+ memcpy(&temp, &state->sumX, sizeof(Interval));
+ finite_interval_pl(&state->sumX, &temp, newval);
+ state->N++;
+}
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+/*
+ * Transition function for interval aggregates.
+ */
+Datum
+interval_avg_accum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ /* Create the state data on the first call */
+ if (state == NULL)
+ state = makeIntervalAggState(fcinfo);
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ if (!PG_ARGISNULL(1))
+ do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
- PG_RETURN_ARRAYTYPE_P(result);
+ PG_RETURN_POINTER(state);
}
+/*
+ * Combine function for interval aggregates.
+ */
Datum
-interval_combine(PG_FUNCTION_ARGS)
+interval_avg_combine(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 *state1;
+ IntervalAggState *state2;
+
+ state1 = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+ state2 = PG_ARGISNULL(1) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(1);
- Interval *newsum;
- ArrayType *result;
+ if (state2 == NULL)
+ PG_RETURN_POINTER(state1);
+
+ /* manually copy all fields from state2 to state1 */
+ if (state1 == NULL)
+ {
+ state1 = makeIntervalAggState(fcinfo);
- deconstruct_array(transarray1,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums1, NULL, &ndatums1);
- if (ndatums1 != 2)
- elog(ERROR, "expected 2-element interval array");
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
- sum1 = *(DatumGetIntervalP(transdatums1[0]));
- N1 = *(DatumGetIntervalP(transdatums1[1]));
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
- deconstruct_array(transarray2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums2, NULL, &ndatums2);
- if (ndatums2 != 2)
- elog(ERROR, "expected 2-element interval array");
+ PG_RETURN_POINTER(state1);
+ }
+
+ state1->N += state2->N;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
+
+ if (state2->N > 0)
+ {
+ Interval temp;
+
+ /* Accumulate interval values */
+ memcpy(&temp, &state1->sumX, sizeof(Interval));
+ finite_interval_pl(&state1->sumX, &temp, &state2->sumX);
+ }
+
+ PG_RETURN_POINTER(state1);
+}
- sum2 = *(DatumGetIntervalP(transdatums2[0]));
- N2 = *(DatumGetIntervalP(transdatums2[1]));
+/*
+ * Remove the given interval value from the aggregated state.
+ */
+static void
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount--;
+ return;
+ }
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sum1),
- IntervalPGetDatum(&sum2)));
- N1.time += N2.time;
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount--;
+ return;
+ }
- transdatums1[0] = IntervalPGetDatum(newsum);
- transdatums1[1] = IntervalPGetDatum(&N1);
+ /* Handle to be discarded finite value. */
+ if (state->N > 0)
+ {
+ Interval temp;
+ Interval neg_val;
- result = construct_array(transdatums1, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ neg_val.day = -newval->day;
+ neg_val.month = -newval->month;
+ neg_val.time = -newval->time;
- PG_RETURN_ARRAYTYPE_P(result);
+ memcpy(&temp, &state->sumX, sizeof(Interval));
+ finite_interval_pl(&state->sumX, &temp, &neg_val);
+ }
+ else
+ {
+ /* All values discarded, reset the state */
+ memset(&state->sumX, 0, sizeof(state->sumX));
+ }
+ state->N--;
}
+/*
+ * Generic inverse transition function for interval aggregates
+ */
Datum
interval_accum_inv(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Should not get here with no state */
+ if (state == NULL)
+ elog(ERROR, "interval_accum_inv called with NULL state");
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ if (!PG_ARGISNULL(1))
+ do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ PG_RETURN_POINTER(state);
+}
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ double N_datum;
+ Interval *sumX;
+
+ sumX = (Interval *) palloc(sizeof(Interval));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
- PG_RETURN_ARRAYTYPE_P(result);
+ /* adding plus and minus infinities gives error */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ N_datum = (double) state->N;
+ sumX = &state->sumX;
+
+ PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX),
+ Float8GetDatum(N_datum)));
}
+/* sum(interval) aggregate final function */
Datum
-interval_avg(PG_FUNCTION_ARGS)
+interval_sum(PG_FUNCTION_ARGS)
{
- ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
- Datum *transdatums;
- int ndatums;
- Interval sumX,
- N;
+ IntervalAggState *state;
+ Interval *result;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ result = (Interval *) palloc(sizeof(Interval));
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
-}
+ /* adding plus and minus interval infinities is not possible */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ memcpy(result, &state->sumX, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e2087d7be1 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
+ aggmtranstype => 'internal', aggtransspace => '128',
+ aggmtransspace => '128'
+},
# sum
{ aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
@@ -72,10 +73,12 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
+ aggmtranstype => 'internal', aggtransspace => '128',
+ aggmtransspace => '128'},
{ aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
aggserialfn => 'numeric_avg_serialize',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 9805bc6118..f8eca58ddf 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4914,17 +4914,23 @@
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_accum_inv', proisstrict => 'f',
+ prorettype => 'internal', proargtypes => 'internal interval',
+ prosrc => 'interval_accum_inv' },
{ oid => '1844', descr => 'aggregate final function',
proname => 'interval_avg', prorettype => 'interval',
- proargtypes => '_interval', prosrc => 'interval_avg' },
+ proargtypes => 'internal', prosrc => 'interval_avg' },
+{ oid => '8069', 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' },
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..45262924cd 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,6 +4375,68 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev1_curr_sum
+-----------+---------------+----------------
+ -infinity | -infinity | -infinity
+ @ 6 days | infinity | -infinity
+ infinity | infinity | infinity
+ infinity | infinity | infinity
+ | | infinity
+ | |
+(6 rows)
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | curr_next_sum | prev1_curr_avg | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum
+-----------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
+ | infinity | infinity | | | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | @ 13 days | infinity | infinity | infinity | infinity
+ @ 7 days | @ 7 days | @ 7 days | @ 6 days 12 hours | @ 13 days | -infinity | -infinity
+ | -infinity | -infinity | @ 7 days | @ 7 days | -infinity | -infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity | -infinity | -infinity
+(7 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
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..d00423946d 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,6 +1591,49 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('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);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index f3d8a2a855..4df61a69ca 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1204,6 +1204,7 @@ IntegerSet
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup
--
2.25.1
0003-Introduce-infinity-interval-specification-i-20231010.patchtext/x-patch; charset=US-ASCII; name=0003-Introduce-infinity-interval-specification-i-20231010.patchDownload
From 7091b79c8de43126b474cc1d44ffe1436bae1077 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 13 Sep 2023 12:43:49 +0530
Subject: [PATCH 3/9] Introduce 'infinity' interval specification in
DecodeInterval()
This reverts commit d6d1430f404386162831bc32906ad174b2007776 since
RESERVE is required to support infinite intervals. 'infinity' is saved
as a RESERVE in datetktbl[], thus required to parse
'infinity'::interval.
Infinte interval specification i.e. "+infinity", "-infinity" or
"infinity" does not require anything other that one of the three
strings. Fix DecodeInterval() to disallow anything else.
"ago" may be used to revrse the sign of infinity specification like how
ago reverts the sign of finite intervals. But that is not as intuitive
as it is with finite intervals. Hence disallows "infinity ago" as well.
Ashutosh Bapat
---
src/backend/utils/adt/datetime.c | 24 ++++++++++++++++
src/test/regress/expected/interval.out | 39 ++++++++++++++++++++++++++
src/test/regress/sql/interval.sql | 14 +++++++++
3 files changed, 77 insertions(+)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index ea2bc65f50..73dadb747c 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3271,6 +3271,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3599,6 +3602,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+
+ /*
+ * Only reserved words corresponding to infinite
+ * intervals are accepted.
+ */
+ if (uval != DTK_LATE && uval != DTK_EARLY)
+ return DTERR_BAD_FORMAT;
+
+ /*
+ * infinity can not be followed by anything else. We
+ * could allow "ago" to reverse the sign of infinity
+ * but that signed infinity is more intuitive.
+ */
+ if (i != nf - 1)
+ return DTERR_BAD_FORMAT;
+
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 5011fc4815..135d683285 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -2264,3 +2264,42 @@ SELECT INTERVAL '1 year months days 5 hours';
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 590e1000bd..912803a1b0 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -745,3 +745,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
--
2.25.1
0001-Check-for-overflow-in-make_interval-20231010.patchtext/x-patch; charset=US-ASCII; name=0001-Check-for-overflow-in-make_interval-20231010.patchDownload
From 1920e8f29eada764f9206ed99b6565035d7415fd Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH 1/9] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 39 ++++++++++++++++++--------
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 27 ++++++++++++++++++
src/test/regress/sql/interval.sql | 15 ++++++++++
4 files changed, 71 insertions(+), 11 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0e50aaec5a..7ddf64ad2e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1509,24 +1509,41 @@ make_interval(PG_FUNCTION_ARGS)
Interval *result;
/*
- * Reject out-of-range inputs. We really ought to check the integer
- * inputs as well, but it's not entirely clear what limits to apply.
+ * Reject out-of-range inputs. We reject any input values that cause
+ * integer overflow of the corresponding interval fields.
*/
if (isinf(secs) || isnan(secs))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ goto out_of_range;
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ /* years and months -> months */
+ if (pg_mul_s32_overflow(years, MONTHS_PER_YEAR, &result->month) ||
+ pg_add_s32_overflow(result->month, months, &result->month))
+ goto out_of_range;
+
+ /* weeks and days -> days */
+ if (pg_mul_s32_overflow(weeks, DAYS_PER_WEEK, &result->day) ||
+ pg_add_s32_overflow(result->day, days, &result->day))
+ goto out_of_range;
+
+ /* hours and mins -> usecs (cannot overflow 64-bit) */
+ result->time = hours * USECS_PER_HOUR + mins * USECS_PER_MINUTE;
+
+ /* secs -> usecs */
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs) ||
+ pg_add_s64_overflow(result->time, (int64) secs, &result->time))
+ goto out_of_range;
PG_RETURN_INTERVAL_P(result);
+
+out_of_range:
+ ereport(ERROR,
+ errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"));
+
+ PG_RETURN_NULL(); /* keep compiler quiet */
}
/* EncodeSpecialTimestamp()
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index c0ca8e041b..75d19d6594 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,33 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(years := 178956971);
+ERROR: interval out of range
+select make_interval(years := -178956971);
+ERROR: interval out of range
+select make_interval(years := 1, months := 2147483647);
+ERROR: interval out of range
+select make_interval(years := -1, months := -2147483648);
+ERROR: interval out of range
+select make_interval(weeks := 306783379);
+ERROR: interval out of range
+select make_interval(weeks := -306783379);
+ERROR: interval out of range
+select make_interval(weeks := 1, days := 2147483647);
+ERROR: interval out of range
+select make_interval(weeks := -1, days := -2147483648);
+ERROR: interval out of range
+select make_interval(secs := 1e308);
+ERROR: value out of range: overflow
+select make_interval(secs := 1e18);
+ERROR: interval out of range
+select make_interval(secs := -1e18);
+ERROR: interval out of range
+select make_interval(mins := 1, secs := 9223372036800.0);
+ERROR: interval out of range
+select make_interval(mins := -1, secs := -9223372036800.0);
+ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 038fc508d0..a0a373f08b 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,21 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(years := 178956971);
+select make_interval(years := -178956971);
+select make_interval(years := 1, months := 2147483647);
+select make_interval(years := -1, months := -2147483648);
+select make_interval(weeks := 306783379);
+select make_interval(weeks := -306783379);
+select make_interval(weeks := 1, days := 2147483647);
+select make_interval(weeks := -1, days := -2147483648);
+select make_interval(secs := 1e308);
+select make_interval(secs := 1e18);
+select make_interval(secs := -1e18);
+select make_interval(mins := 1, secs := 9223372036800.0);
+select make_interval(mins := -1, secs := -9223372036800.0);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.25.1
0002-Add-infinite-interval-values-20231010.patchtext/x-patch; charset=US-ASCII; name=0002-Add-infinite-interval-values-20231010.patchDownload
From 7a6c9d0ecb7decbd01a92bf3c32203e12a1c6320 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH 2/9] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 4 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 704 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1543 insertions(+), 207 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 8d32a8c9c5..557b81cae8 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2320,12 +2320,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 24ad87f910..7786853743 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9537,7 +9537,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10434,7 +10434,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ae0f24de2c..f2107dd967 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(!INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3104,6 +3131,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..ea2bc65f50 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3574,6 +3574,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index e27ea8ef97..e9e85503f8 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..6e87173846 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 7ddf64ad2e..c52bdb68da 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1536,6 +1558,9 @@ make_interval(PG_FUNCTION_ARGS)
pg_add_s64_overflow(result->time, (int64) secs, &result->time))
goto out_of_range;
+ if (INTERVAL_NOT_FINITE(result))
+ goto out_of_range;
+
PG_RETURN_INTERVAL_P(result);
out_of_range:
@@ -1560,6 +1585,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2038,6 +2074,8 @@ 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;
}
@@ -2055,6 +2093,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2088,7 +2128,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2219,6 +2261,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2714,46 +2779,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2783,6 +2873,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))
@@ -2835,6 +2928,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2858,6 +2956,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,
@@ -2875,6 +2976,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2896,6 +3002,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))
@@ -2914,6 +3023,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2934,7 +3048,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3013,9 +3150,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3042,7 +3177,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3132,9 +3290,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3185,6 +3341,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3192,23 +3373,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3252,27 +3417,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3286,27 +3484,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3331,6 +3564,49 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results
+ * in an infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ Interval zero;
+ int factor_sign,
+ result_sign;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ memset(&zero, 0, sizeof(zero));
+ result_sign = interval_cmp_internal(span, &zero) * factor_sign;
+
+ if (result_sign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_sign < 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ INTERVAL_NOEND(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3391,6 +3667,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3422,6 +3703,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (isinf(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = (int32) (span->month / factor);
result->day = (int32) (span->day / factor);
@@ -3443,6 +3747,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3726,8 +4035,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3846,8 +4182,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3972,6 +4335,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4155,6 +4523,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4393,6 +4766,12 @@ interval_trunc(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ memcpy(result, interval, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
VARSIZE_ANY_EXHDR(units),
false);
@@ -4737,7 +5116,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5011,7 +5390,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5251,6 +5630,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ if ((type != UNITS) && (type != RESERV))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unit \"%s\" not recognized for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_YEAR:
+ case DTK_DECADE:
+ case DTK_CENTURY:
+ case DTK_MILLENNIUM:
+ case DTK_EPOCH:
+ if (isNegative)
+ return -get_float8_infinity();
+ else
+ return get_float8_infinity();
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unit \"%s\" not supported for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+ return 0.0; /* keep compiler quiet */
+ }
+}
/* interval_part() and extract_interval()
* Extract specified field from interval.
@@ -5275,6 +5707,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r != 0.0)
+ {
+ if (retnumeric)
+ {
+ if (r < 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("-Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ else if (r > 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ }
+ else
+ PG_RETURN_FLOAT8(r);
+ }
+ else
+ PG_RETURN_NULL();
+ }
+
if (type == UNITS)
{
interval2itm(*interval, tm);
@@ -5517,6 +5977,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5747,6 +6214,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5813,6 +6287,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5895,6 +6374,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8c9..fe713fb6dd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 75d19d6594..5011fc4815 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,9 @@ SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
6 years | 6 years | 6 years
5 mons | 5 mons | 00:00:00
5 mons 12:00:00 | 5 mons 12:00:00 | 00:00:00
-(10 rows)
+ infinity | infinity | infinity
+ -infinity | -infinity | -infinity
+(12 rows)
-- test inputting and outputting SQL standard interval literals
SET IntervalStyle TO sql_standard;
@@ -1616,31 +1669,31 @@ select make_interval(mins := -1, secs := -9223372036800.0);
ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
--------------------------------------------------------------------
- -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+ -178956970 years -7 mons -2147483648 days -2562047788:00:54.775808
(1 row)
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
---------------------------------------------------
- -178956970-8 -2147483648 -2562047788:00:54.775808
+ -178956970-7 -2147483648 -2562047788:00:54.775808
(1 row)
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
-----------------------------------------------------
- P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+ P-178956970Y-7M-2147483648DT-2562047788H-54.775808S
(1 row)
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
------------------------------------------------------------------------------
- @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+ @ 178956970 years 7 mons 2147483648 days 2562047788 hours 54.775808 secs ago
(1 row)
-- check that '30 days' equals '1 month' according to the hash function
@@ -1719,19 +1772,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1805,7 +1860,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1814,6 +1871,381 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+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"
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 0dd2fe2c82..825202d597 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3154,3 +3158,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index a0a373f08b..590e1000bd 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -528,13 +535,13 @@ select make_interval(mins := -1, secs := -9223372036800.0);
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
-- check that '30 days' equals '1 month' according to the hash function
select '30 days'::interval = '1 month'::interval as t;
@@ -598,6 +605,139 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index 69b36d0420..7876225c25 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -619,3 +621,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.25.1
On Wed, 4 Oct 2023 at 14:29, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
I think we should introduce interval_out_of_range_error() function on
the lines of float_overflow_error(). Later patches introduce more
places where we raise that error. We can introduce the function as
part of those patches.
I'm not convinced that it is really worth it. Note also that even with
this patch, there are still more places that throw "timestamp out of
range" errors than "interval out of range" errors.
4. I tested pg_upgrade on a table with an interval with INT_MAX
months, and it was silently converted to infinity. I think that's
probably the best outcome (better than failing). However, this means
that we really should require all 3 fields of an interval to be
INT_MIN/MAX for it to be considered infinite, otherwise it would be
possible to have multiple internal representations of infinity that do
not compare as equal.My first patch was comparing all the three fields to determine whether
a given Interval value represents infinity. [3] changed that to use
only the month field. I guess that was based on the discussion at [4].
You may want to review that discussion if not already done. I am fine
either way. We should be able to change the comparison code later if
we see performance getting impacted.
Before looking at the details more closely, I might have agreed with
that earlier discussion. However, given that things like pg_upgrade
have the possibility of turning formerly allowed, finite intervals
into infinity, we really need to ensure that there is only one value
equal to infinity, otherwise the results are likely to be very
confusing and counter-intuitive. That means that we have to continue
to regard intervals like INT32_MAX months + 10 days as finite.
While I haven't done any performance testing, I wouldn't expect this
to have much impact. In a 64-bit build, this actually generates 2
comparisons rather than 3 -- one comparing the combined month and day
fields against a 64-bit value containing 2 copies of INT32_MAX, and
one testing the time field. In practice, only the first test will be
executed in the vast majority of cases.
Something that perhaps does need discussing is the fact that
'2147483647 months 2147483647 days 9223372036854775807 usecs' is now
accepted by interval_in() and gives infinity. That's a bit ugly, but I
think it's defensible as a measure to prevent dump/restore errors from
older databases, and in any case, such an interval is outside the
documented range of supported intervals, and is a highly contrived
example, vanishingly improbable in practice.
Alternatively, we could have interval_in() reject this, which would
open up the possibility of dump/restore errors. It could be argued
that that's OK, for similar reasons -- the failing value is highly
unlikely/contrived, and out of the documented range. I don't like that
though. I don't think dump/restore should fail under any
circumstances, however unlikely.
Another alternative is to accept this input, but emit a WARNING. I
don't particularly like that either, since it's forcing a check on
every input value, just to cater for this one specific highly unlikely
input. In fact, both these alternative approaches (rejecting the
value, or emitting a warning), would impose a small performance
penalty on every interval input, which I don't think is really worth
it.
So overall, my preference is to just accept it. Anything else is more
work, for no practical benefit.
Regards,
Dean
On Tue, 10 Oct 2023 at 12:36, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
2. The various in_range() functions needed adjusting to handle
infinite interval offsets.I think this code is reasonable. But users may find it inconsistent
with the other ways to achieve the same outcome. For example, We don't
allow non-finite intervals to be added or subtracted from time or
timetz. So if someone tries to compare the rows using val >/<= base
+/- offset, those queries will fail whereas similar implied conditions
in window specification will not throw an error. If you have
considered this already, I am fine with the code as is.
It's consistent with the documented contract of the in_range support
functions. See https://www.postgresql.org/docs/current/btree-support-funcs.html
In particular, this part:
An additional expectation is that in_range functions should, if
practical, avoid throwing an error if base + offset or base - offset
would overflow. The correct comparison result can be determined even
if that value would be out of the data type's range. Note that if the
data type includes concepts such as "infinity" or "NaN", extra
care may be needed to ensure that in_range's results agree with the
normal sort order of the operator family.
Added a separate patch (0009) to fix
brin_minmax_multi_distance_interval().
I think we can drop this from this thread now, given the discussion
over on the other thread
(/messages/by-id/eef0ea8c-4aaa-8d0d-027f-58b1f35dd170@enterprisedb.com)
I have added a separate patch (0008) to test negative interval values,
including -infinity, in preceding and following specification.Patches from 0001 to 0007 are same as what you attached but rebased on
the latest HEAD.
I'm attaching another update, with a minor change to the aggregate
deserialization function, in line with the recent change to how these
now work elsewhere (see 0c882a298881056176a27ccc44c5c3bb7c8f308c).
0008 seems reasonable. I have added some comments to indicate that
those tests are expected to fail, and why.
I think we should squash 0002 to 0007.
Yes, let's do that with the next update. In fact, we may as well
squash 0002 to 0008.
Regards,
Dean
Attachments:
v27-0003-Introduce-infinity-interval-specification-in-Dec.patchapplication/x-patch; name=v27-0003-Introduce-infinity-interval-specification-in-Dec.patchDownload
From 8d2ddd6e0cd71e5054219d6ad7629ef715ed4ba4 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 13 Sep 2023 12:43:49 +0530
Subject: [PATCH v27 3/8] Introduce 'infinity' interval specification in
DecodeInterval()
This reverts commit d6d1430f404386162831bc32906ad174b2007776 since
RESERVE is required to support infinite intervals. 'infinity' is saved
as a RESERVE in datetktbl[], thus required to parse
'infinity'::interval.
Infinte interval specification i.e. "+infinity", "-infinity" or
"infinity" does not require anything other that one of the three
strings. Fix DecodeInterval() to disallow anything else.
"ago" may be used to revrse the sign of infinity specification like how
ago reverts the sign of finite intervals. But that is not as intuitive
as it is with finite intervals. Hence disallows "infinity ago" as well.
Ashutosh Bapat
---
src/backend/utils/adt/datetime.c | 24 ++++++++++++++++
src/test/regress/expected/interval.out | 39 ++++++++++++++++++++++++++
src/test/regress/sql/interval.sql | 14 +++++++++
3 files changed, 77 insertions(+)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index ea2bc65f50..73dadb747c 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3271,6 +3271,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3599,6 +3602,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+
+ /*
+ * Only reserved words corresponding to infinite
+ * intervals are accepted.
+ */
+ if (uval != DTK_LATE && uval != DTK_EARLY)
+ return DTERR_BAD_FORMAT;
+
+ /*
+ * infinity can not be followed by anything else. We
+ * could allow "ago" to reverse the sign of infinity
+ * but that signed infinity is more intuitive.
+ */
+ if (i != nf - 1)
+ return DTERR_BAD_FORMAT;
+
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 5011fc4815..135d683285 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -2264,3 +2264,42 @@ SELECT INTERVAL '1 year months days 5 hours';
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 590e1000bd..912803a1b0 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -745,3 +745,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
--
2.35.3
v27-0002-Add-infinite-interval-values.patchapplication/x-patch; name=v27-0002-Add-infinite-interval-values.patchDownload
From 8274dfa6009903de9c71b1701740100870f4a3f4 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH v27 2/8] Add infinite interval values
This commit adds positive and negative infinite values to the interval
data type. The entire range of intervals with INT_MAX months or INT_MIN
months are reserved for infinite values. This makes checking finiteness
much simpler.
Ashutosh Bapat and Joe Koshakow and Jian He
---
doc/src/sgml/datatype.sgml | 4 +-
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 34 ++
src/backend/utils/adt/datetime.c | 2 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 12 +-
src/backend/utils/adt/timestamp.c | 704 ++++++++++++++++++----
src/include/datatype/timestamp.h | 19 +
src/include/utils/timestamp.h | 3 +
src/test/regress/expected/horology.out | 73 ++-
src/test/regress/expected/interval.out | 554 +++++++++++++++--
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 172 +++++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
17 files changed, 1543 insertions(+), 207 deletions(-)
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 5d23765705..123d8207f8 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2321,12 +2321,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 7c3e940afe..c4f09df544 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9537,7 +9537,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10434,7 +10434,8 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 56c7746c11..ad97f07601 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2014,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2045,6 +2050,8 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
+ Assert(!INTERVAL_NOT_FINITE(result));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2058,6 +2065,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2088,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2590,6 +2607,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2634,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -3105,6 +3132,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..ea2bc65f50 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3574,6 +3574,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 8131091f79..d176723d95 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..6e87173846 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,17 +4795,7 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- {
- Interval *interval = DatumGetIntervalP(value);
-
- /*
- * Convert the month part of Interval to days using assumed
- * average month length of 365.25/12.0 days. Not too
- * accurate, but plenty good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
+ return interval2timestamp_no_overflow(DatumGetIntervalP(value));
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 647b97aca6..a70639db58 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,6 +18,7 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
+#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -80,6 +81,7 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void interval_um_internal(Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +943,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +973,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1365,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.
@@ -1536,6 +1558,9 @@ make_interval(PG_FUNCTION_ARGS)
pg_add_s64_overflow(result->time, (int64) secs, &result->time))
goto out_of_range;
+ if (INTERVAL_NOT_FINITE(result))
+ goto out_of_range;
+
PG_RETURN_INTERVAL_P(result);
out_of_range:
@@ -1560,6 +1585,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+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)
{
@@ -2038,6 +2074,8 @@ 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;
}
@@ -2055,6 +2093,8 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
@@ -2088,7 +2128,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2219,6 +2261,29 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
+double
+interval2timestamp_no_overflow(Interval *interval)
+{
+ double result;
+
+ if (INTERVAL_IS_NOBEGIN(interval))
+ result = -DBL_MAX;
+ else if (INTERVAL_IS_NOEND(interval))
+ result = DBL_MAX;
+ else
+ {
+ /*
+ * Convert the month part of Interval to days using assumed average
+ * month length of 365.25/12.0 days. Not too accurate, but plenty
+ * good enough for our purposes.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
+
+ return result;
+}
+
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2714,46 +2779,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp 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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2783,6 +2873,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))
@@ -2835,6 +2928,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2858,6 +2956,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,
@@ -2875,6 +2976,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2896,6 +3002,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))
@@ -2914,6 +3023,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2934,7 +3048,30 @@ 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3013,9 +3150,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3042,7 +3177,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- 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.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval 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("interval out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3132,9 +3290,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3185,6 +3341,31 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given interval */
+static void
+interval_um_internal(Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3192,23 +3373,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3252,27 +3417,60 @@ 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")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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")));
- 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3286,27 +3484,62 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * 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))
+ {
+ 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
+ {
+ 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->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")));
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -3331,6 +3564,49 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results
+ * in an infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ Interval zero;
+ int factor_sign,
+ result_sign;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ memset(&zero, 0, sizeof(zero));
+ result_sign = interval_cmp_internal(span, &zero) * factor_sign;
+
+ if (result_sign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_sign < 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ INTERVAL_NOEND(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3391,6 +3667,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3422,6 +3703,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (isinf(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = (int32) (span->month / factor);
result->day = (int32) (span->day / factor);
@@ -3443,6 +3747,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3726,8 +4035,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3846,8 +4182,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinte timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp 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("timestamp out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3972,6 +4335,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4155,6 +4523,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4393,6 +4766,12 @@ interval_trunc(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ memcpy(result, interval, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
VARSIZE_ANY_EXHDR(units),
false);
@@ -4737,7 +5116,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5011,7 +5390,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5251,6 +5630,59 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite
+ * interval. Returns +/-Infinity if that is the appropriate result,
+ * otherwise returns zero (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits,
+ bool isNegative, bool isTz)
+{
+ if ((type != UNITS) && (type != RESERV))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unit \"%s\" not recognized for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_YEAR:
+ case DTK_DECADE:
+ case DTK_CENTURY:
+ case DTK_MILLENNIUM:
+ case DTK_EPOCH:
+ if (isNegative)
+ return -get_float8_infinity();
+ else
+ return get_float8_infinity();
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unit \"%s\" not supported for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+ return 0.0; /* keep compiler quiet */
+ }
+}
/* interval_part() and extract_interval()
* Extract specified field from interval.
@@ -5275,6 +5707,34 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval),
+ false);
+
+ if (r != 0.0)
+ {
+ if (retnumeric)
+ {
+ if (r < 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("-Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ else if (r > 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ }
+ else
+ PG_RETURN_FLOAT8(r);
+ }
+ else
+ PG_RETURN_NULL();
+ }
+
if (type == UNITS)
{
interval2itm(*interval, tm);
@@ -5517,6 +5977,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5747,6 +6214,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5813,6 +6287,11 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
(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);
}
@@ -5895,6 +6374,11 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
(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);
}
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..f9bd30d225 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -168,6 +168,25 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+#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)
+
+#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)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index c4dd96c8c9..fe713fb6dd 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,6 +118,7 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
+extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -127,6 +128,8 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
+extern double interval2timestamp_no_overflow(Interval *interval);
+
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 75d19d6594..5011fc4815 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,16 +268,27 @@ 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);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -272,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons -2147483648 days
- 1 year | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons -2147483648 days | 1 year
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
- 178956970 years 7 mons 2147483647 days | 1 year
- 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons -2147483648 days
+ 1 year | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons -2147483648 days | 1 year
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
+ 178956970 years 6 mons 2147483647 days | 1 year
+ 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -296,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -8 mons -2147483648 days
- -178956970 years -8 mons +2147483647 days
+ -178956970 years -7 mons -2147483648 days
+ -178956970 years -7 mons +2147483647 days
1 year
- 178956970 years 7 mons -2147483648 days
- 178956970 years 7 mons 2147483647 days
+ 178956970 years 6 mons -2147483648 days
+ 178956970 years 6 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -386,12 +429,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -429,7 +474,9 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_days(interval '2147483646 months 60 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -450,25 +497,29 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '2147483646 months 30 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 60 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+ERROR: interval out of range
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 7 mons 29 days
+ @ 178956970 years 6 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 8 mons 29 days ago
+ @ 178956970 years 7 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -820,8 +871,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 +883,9 @@ SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
6 years | 6 years | 6 years
5 mons | 5 mons | 00:00:00
5 mons 12:00:00 | 5 mons 12:00:00 | 00:00:00
-(10 rows)
+ infinity | infinity | infinity
+ -infinity | -infinity | -infinity
+(12 rows)
-- test inputting and outputting SQL standard interval literals
SET IntervalStyle TO sql_standard;
@@ -1616,31 +1669,31 @@ select make_interval(mins := -1, secs := -9223372036800.0);
ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
--------------------------------------------------------------------
- -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+ -178956970 years -7 mons -2147483648 days -2562047788:00:54.775808
(1 row)
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
---------------------------------------------------
- -178956970-8 -2147483648 -2562047788:00:54.775808
+ -178956970-7 -2147483648 -2562047788:00:54.775808
(1 row)
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
-----------------------------------------------------
- P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+ P-178956970Y-7M-2147483648DT-2562047788H-54.775808S
(1 row)
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
------------------------------------------------------------------------------
- @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+ @ 178956970 years 7 mons 2147483648 days 2562047788 hours 54.775808 secs ago
(1 row)
-- check that '30 days' equals '1 month' according to the hash function
@@ -1719,19 +1772,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1805,7 +1860,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1814,6 +1871,381 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
+ ^
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ERROR: interval out of range
+LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
+ ^
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+SELECT date '1995-08-06' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '1995-08-06' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '1995-08-06' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT date '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT date 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT date '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT date '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' + interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' + interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' - interval '10 days';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '-infinity' - interval '10 days';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT timestamp 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamp 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamp '-infinity' - interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval 'infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz 'infinity' + interval '-infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz '-infinity' + interval '-infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz 'infinity' - interval 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - interval '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - interval '-infinity';
+ERROR: interval out of range
+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: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+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"
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..53542e076b 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 2ca2101dd4..e3f0d918b9 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3210,3 +3214,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: timestamp 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: timestamp 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: timestamp 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: timestamp out of range
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index a0a373f08b..590e1000bd 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -69,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483647 months'),
- ('2147483647 days -2147483648 months'),
+ ('2147483647 days 2147483646 months'),
+ ('2147483647 days -2147483647 months'),
('1 year'),
- ('-2147483648 days 2147483647 months'),
- ('-2147483648 days -2147483648 months');
+ ('-2147483648 days 2147483646 months'),
+ ('-2147483648 days -2147483647 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -140,7 +144,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -157,7 +161,8 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483647 months 30 days');
+SELECT justify_days(interval '2147483646 months 30 days');
+SELECT justify_days(interval '2147483646 months 60 days');
-- test justify_interval()
@@ -165,12 +170,14 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483647 months 30 days');
-SELECT justify_interval(interval '-2147483648 months -30 days');
-SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483646 months 30 days');
+SELECT justify_interval(interval '2147483646 months 60 days');
+SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -528,13 +535,13 @@ select make_interval(mins := -1, secs := -9223372036800.0);
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
-- check that '30 days' equals '1 month' according to the hash function
select '30 days'::interval = '1 month'::interval as t;
@@ -598,6 +605,139 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+-- 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;
+
+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 '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval 'infinity' - interval 'infinity';
+SELECT interval 'infinity' - interval '-infinity';
+SELECT interval '-infinity' - interval 'infinity';
+SELECT interval '-infinity' - interval '-infinity';
+SELECT interval 'infinity' - interval '10 days';
+SELECT interval '-infinity' - interval '10 days';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+SELECT timestamp 'infinity' + interval 'infinity';
+SELECT timestamp 'infinity' + interval '-infinity';
+SELECT timestamp '-infinity' + interval 'infinity';
+SELECT timestamp '-infinity' + interval '-infinity';
+SELECT timestamp 'infinity' - interval 'infinity';
+SELECT timestamp 'infinity' - interval '-infinity';
+SELECT timestamp '-infinity' - interval 'infinity';
+SELECT timestamp '-infinity' - interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
+SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
+SELECT timestamptz 'infinity' + interval 'infinity';
+SELECT timestamptz 'infinity' + interval '-infinity';
+SELECT timestamptz '-infinity' + interval 'infinity';
+SELECT timestamptz '-infinity' + interval '-infinity';
+SELECT timestamptz 'infinity' - interval 'infinity';
+SELECT timestamptz 'infinity' - interval '-infinity';
+SELECT timestamptz '-infinity' - interval 'infinity';
+SELECT timestamptz '-infinity' - interval '-infinity';
+SELECT 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';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index cdc57bc160..a2dcd5f5d8 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -642,3 +644,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
--
2.35.3
v27-0004-Support-infinite-interval-values-in-sum-and-avg-.patchapplication/x-patch; name=v27-0004-Support-infinite-interval-values-in-sum-and-avg-.patchDownload
From eeb03c4bddf07385372dda44558874a9f25dbb1b Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Wed, 20 Sep 2023 15:41:00 +0530
Subject: [PATCH v27 4/8] Support infinite interval values in sum and avg
window functions
Results of arithmetic operations involing both infinity and -infinity
intervals are undefined. Sliding window functions
accumulate and discard values as the window slides through the data.
Even if the data may have both infinities, none of windows may have both
of them together and thus produce definite results. Hence we can not use
normal operator functions to accumulate and discard values. Instead we
maintain a count of both infinities seen respectively in an aggregate
state and compute the aggregate for each window based on the accumulated
finite value and the counts of infinities, all of which are updated as
the window slides through the data.
Initial patch by Jian He, significantly edited by Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 407 ++++++++++++++++++---------
src/include/catalog/pg_aggregate.dat | 23 +-
src/include/catalog/pg_proc.dat | 20 +-
src/test/regress/expected/window.out | 62 ++++
src/test/regress/sql/window.sql | 43 +++
src/tools/pgindent/typedefs.list | 1 +
6 files changed, 400 insertions(+), 156 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index a70639db58..450b547036 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -73,6 +73,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+/*
+ * The transition datatype for interval aggregates is declared as Internal. It's
+ * a pointer to a IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+ int64 N; /* count of processed intervals */
+ Interval sumX; /* sum of processed intervals */
+ /* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +Inf values */
+ int64 nInfcount; /* count of -Inf values */
+} 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);
@@ -82,6 +97,8 @@ static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
static void interval_um_internal(Interval *interval, Interval *result);
+static void finite_interval_pl(Interval *result, Interval *span1,
+ Interval *span2);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -3443,34 +3460,7 @@ interval_pl(PG_FUNCTION_ARGS)
else if (INTERVAL_NOT_FINITE(span2))
memcpy(result, span2, sizeof(Interval));
else
- {
- 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")));
-
- 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")));
-
- if (INTERVAL_NOT_FINITE(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- }
+ finite_interval_pl(result, span1, span2);
PG_RETURN_INTERVAL_P(result);
}
@@ -3856,161 +3846,300 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function that needs to compute
+ * sum and count.
+ */
+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;
+}
+
+/*
+ * Function to add two finite intervals.
*
- * 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.
+ * We handle non-finite intervals in different ways when accumulating intervals
+ * and adding two intervals respectively. But the addition of finite interval
+ * has same implementation in both these cases.
*/
+static void
+finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
-Datum
-interval_accum(PG_FUNCTION_ARGS)
+ 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")));
+
+ 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")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+/*
+ * Accumulate a new input value for interval aggregate functions.
+ */
+static void
+do_interval_accum(IntervalAggState *state, Interval *newval)
{
- 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;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ Interval temp;
+
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount++;
+ return;
+ }
+
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount++;
+ return;
+ }
+
+ memcpy(&temp, &state->sumX, sizeof(Interval));
+ finite_interval_pl(&state->sumX, &temp, newval);
+ state->N++;
+}
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+/*
+ * Transition function for interval aggregates.
+ */
+Datum
+interval_avg_accum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ /* Create the state data on the first call */
+ if (state == NULL)
+ state = makeIntervalAggState(fcinfo);
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ if (!PG_ARGISNULL(1))
+ do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
- PG_RETURN_ARRAYTYPE_P(result);
+ PG_RETURN_POINTER(state);
}
+/*
+ * Combine function for interval aggregates.
+ */
Datum
-interval_combine(PG_FUNCTION_ARGS)
+interval_avg_combine(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 *state1;
+ IntervalAggState *state2;
+
+ state1 = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+ state2 = PG_ARGISNULL(1) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(1);
- Interval *newsum;
- ArrayType *result;
+ if (state2 == NULL)
+ PG_RETURN_POINTER(state1);
+
+ /* manually copy all fields from state2 to state1 */
+ if (state1 == NULL)
+ {
+ state1 = makeIntervalAggState(fcinfo);
- deconstruct_array(transarray1,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums1, NULL, &ndatums1);
- if (ndatums1 != 2)
- elog(ERROR, "expected 2-element interval array");
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
- sum1 = *(DatumGetIntervalP(transdatums1[0]));
- N1 = *(DatumGetIntervalP(transdatums1[1]));
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
- deconstruct_array(transarray2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums2, NULL, &ndatums2);
- if (ndatums2 != 2)
- elog(ERROR, "expected 2-element interval array");
+ PG_RETURN_POINTER(state1);
+ }
+
+ state1->N += state2->N;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
+
+ if (state2->N > 0)
+ {
+ Interval temp;
+
+ /* Accumulate interval values */
+ memcpy(&temp, &state1->sumX, sizeof(Interval));
+ finite_interval_pl(&state1->sumX, &temp, &state2->sumX);
+ }
+
+ PG_RETURN_POINTER(state1);
+}
- sum2 = *(DatumGetIntervalP(transdatums2[0]));
- N2 = *(DatumGetIntervalP(transdatums2[1]));
+/*
+ * Remove the given interval value from the aggregated state.
+ */
+static void
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+ /* Count -infinity inputs separately from all else */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount--;
+ return;
+ }
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sum1),
- IntervalPGetDatum(&sum2)));
- N1.time += N2.time;
+ /* Count infinity inputs separately from all else */
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount--;
+ return;
+ }
- transdatums1[0] = IntervalPGetDatum(newsum);
- transdatums1[1] = IntervalPGetDatum(&N1);
+ /* Handle to be discarded finite value. */
+ if (state->N > 0)
+ {
+ Interval temp;
+ Interval neg_val;
- result = construct_array(transdatums1, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ neg_val.day = -newval->day;
+ neg_val.month = -newval->month;
+ neg_val.time = -newval->time;
- PG_RETURN_ARRAYTYPE_P(result);
+ memcpy(&temp, &state->sumX, sizeof(Interval));
+ finite_interval_pl(&state->sumX, &temp, &neg_val);
+ }
+ else
+ {
+ /* All values discarded, reset the state */
+ memset(&state->sumX, 0, sizeof(state->sumX));
+ }
+ state->N--;
}
+/*
+ * Generic inverse transition function for interval aggregates
+ */
Datum
interval_accum_inv(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Should not get here with no state */
+ if (state == NULL)
+ elog(ERROR, "interval_accum_inv called with NULL state");
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ if (!PG_ARGISNULL(1))
+ do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ PG_RETURN_POINTER(state);
+}
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ double N_datum;
+ Interval *sumX;
+
+ sumX = (Interval *) palloc(sizeof(Interval));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
- PG_RETURN_ARRAYTYPE_P(result);
+ /* adding plus and minus infinities gives error */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(sumX);
+ PG_RETURN_INTERVAL_P(sumX);
+ }
+
+ N_datum = (double) state->N;
+ sumX = &state->sumX;
+
+ PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX),
+ Float8GetDatum(N_datum)));
}
+/* sum(interval) aggregate final function */
Datum
-interval_avg(PG_FUNCTION_ARGS)
+interval_sum(PG_FUNCTION_ARGS)
{
- ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
- Datum *transdatums;
- int ndatums;
- Interval sumX,
- N;
+ IntervalAggState *state;
+ Interval *result;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ result = (Interval *) palloc(sizeof(Interval));
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
-}
+ /* adding plus and minus interval infinities is not possible */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ if (state->pInfcount > 0)
+ {
+ INTERVAL_NOEND(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+
+ if (state->nInfcount > 0)
+ {
+ INTERVAL_NOBEGIN(result);
+ PG_RETURN_INTERVAL_P(result);
+ }
+ memcpy(result, &state->sumX, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e2087d7be1 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
+ aggmtranstype => 'internal', aggtransspace => '128',
+ aggmtransspace => '128'
+},
# sum
{ aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
@@ -72,10 +73,12 @@
{ 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',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
+ aggmtranstype => 'internal', aggtransspace => '128',
+ aggmtransspace => '128'},
{ aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
aggserialfn => 'numeric_avg_serialize',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bc41e92677..791359a6e0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4917,17 +4917,23 @@
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_accum_inv', proisstrict => 'f',
+ prorettype => 'internal', proargtypes => 'internal interval',
+ prosrc => 'interval_accum_inv' },
{ oid => '1844', descr => 'aggregate final function',
proname => 'interval_avg', prorettype => 'interval',
- proargtypes => '_interval', prosrc => 'interval_avg' },
+ proargtypes => 'internal', prosrc => 'interval_avg' },
+{ oid => '8069', 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' },
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..45262924cd 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,6 +4375,68 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev1_curr_sum
+-----------+---------------+----------------
+ -infinity | -infinity | -infinity
+ @ 6 days | infinity | -infinity
+ infinity | infinity | infinity
+ infinity | infinity | infinity
+ | | infinity
+ | |
+(6 rows)
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | curr_next_sum | prev1_curr_avg | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum
+-----------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
+ | infinity | infinity | | | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | @ 13 days | infinity | infinity | infinity | infinity
+ @ 7 days | @ 7 days | @ 7 days | @ 6 days 12 hours | @ 13 days | -infinity | -infinity
+ | -infinity | -infinity | @ 7 days | @ 7 days | -infinity | -infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity | -infinity | -infinity
+(7 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
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..d00423946d 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,6 +1591,49 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+--order by.
+SELECT x
+ ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--no order by.
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('infinity'::timestamptz - now()),
+ ('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);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 87c1aee379..236683e5ab 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1205,6 +1205,7 @@ IntegerSet
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup
--
2.35.3
v27-0005-Use-integer-overflow-checking-routines-to-add-an.patchapplication/x-patch; name=v27-0005-Use-integer-overflow-checking-routines-to-add-an.patchDownload
From d6d5149d7395ee454eea3e0f630cb56556ae4681 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Thu, 21 Sep 2023 15:34:33 +0530
Subject: [PATCH v27 5/8] Use integer overflow checking routines to add and
subtract finite intervals
Use pg_add/sub_s32/64_overflow() routines in finite_interval_pl() and
finite_interval_mi() instead of adding overflow checks in those
functions.
Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 121 +++++++++++--------------
src/test/regress/expected/interval.out | 11 +++
src/test/regress/expected/window.out | 54 ++++-------
src/test/regress/sql/interval.sql | 3 +
src/test/regress/sql/window.sql | 22 ++---
5 files changed, 95 insertions(+), 116 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 450b547036..4b35ee0bdf 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -99,6 +99,8 @@ static Timestamp timestamptz2timestamp(TimestampTz timestamp);
static void interval_um_internal(Interval *interval, Interval *result);
static void finite_interval_pl(Interval *result, Interval *span1,
Interval *span2);
+static void finite_interval_mi(Interval *result, Interval *span1,
+ Interval *span2);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -3502,34 +3504,7 @@ interval_mi(PG_FUNCTION_ARGS)
else if (INTERVAL_IS_NOEND(span2))
INTERVAL_NOBEGIN(result);
else
- {
- 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")));
-
- if (INTERVAL_NOT_FINITE(result))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- }
+ finite_interval_mi(result, span1, span2);
PG_RETURN_INTERVAL_P(result);
}
@@ -3869,11 +3844,18 @@ makeIntervalAggState(FunctionCallInfo fcinfo)
}
/*
- * Function to add two finite intervals.
+ * Functions to add or subtract two finite intervals.
+ *
+ * We handle non-finite intervals in different ways when accumulating/discarding
+ * intervals and in actual mathematical operations respectively. But the
+ * addition or subtraction of finite intervals have same implementation
+ * respectively in both these cases.
+ *
+ * Addition/Subtraction of span1 and span2 is stored in the result. It is fine
+ * to pass either of span1 or span2 as a result pointer. In such a case result
+ * will overwrite the given value.
*
- * We handle non-finite intervals in different ways when accumulating intervals
- * and adding two intervals respectively. But the addition of finite interval
- * has same implementation in both these cases.
+ * If the result is not a valid interval, the function throws an error.
*/
static void
finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
@@ -3881,24 +3863,44 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
Assert(!INTERVAL_NOT_FINITE(span1));
Assert(!INTERVAL_NOT_FINITE(span2));
- result->month = span1->month + span2->month;
- /* overflow check copied from int4pl */
- if (SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
+ if (pg_add_s32_overflow(span1->month, span2->month, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+static void
+finite_interval_mi(Interval *result, Interval *span1, Interval *span2)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
+
+ if (pg_sub_s32_overflow(span1->month, span2->month, &result->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))
+ if (pg_sub_s32_overflow(span1->day, span2->day, &result->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))
+ if (pg_sub_s64_overflow(span1->time, span2->time, &result->time))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
@@ -3915,9 +3917,6 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
static void
do_interval_accum(IntervalAggState *state, Interval *newval)
{
-
- Interval temp;
-
/* Count -infinity inputs separately from all else */
if (INTERVAL_IS_NOBEGIN(newval))
{
@@ -3932,8 +3931,7 @@ do_interval_accum(IntervalAggState *state, Interval *newval)
return;
}
- memcpy(&temp, &state->sumX, sizeof(Interval));
- finite_interval_pl(&state->sumX, &temp, newval);
+ finite_interval_pl(&state->sumX, &state->sumX, newval);
state->N++;
}
@@ -3959,6 +3957,9 @@ interval_avg_accum(PG_FUNCTION_ARGS)
/*
* Combine function for interval aggregates.
+ *
+ * Combine the given internal aggregate states and place the combination in the
+ * first argument.
*/
Datum
interval_avg_combine(PG_FUNCTION_ARGS)
@@ -3972,9 +3973,9 @@ interval_avg_combine(PG_FUNCTION_ARGS)
if (state2 == NULL)
PG_RETURN_POINTER(state1);
- /* manually copy all fields from state2 to state1 */
if (state1 == NULL)
{
+ /* manually copy all fields from state2 to state1 */
state1 = makeIntervalAggState(fcinfo);
state1->N = state2->N;
@@ -3992,14 +3993,9 @@ interval_avg_combine(PG_FUNCTION_ARGS)
state1->pInfcount += state2->pInfcount;
state1->nInfcount += state2->nInfcount;
+ /* Accumulate finite interval values, if any. */
if (state2->N > 0)
- {
- Interval temp;
-
- /* Accumulate interval values */
- memcpy(&temp, &state1->sumX, sizeof(Interval));
- finite_interval_pl(&state1->sumX, &temp, &state2->sumX);
- }
+ finite_interval_pl(&state1->sumX, &state1->sumX, &state2->sumX);
PG_RETURN_POINTER(state1);
}
@@ -4025,24 +4021,15 @@ do_interval_discard(IntervalAggState *state, Interval *newval)
}
/* Handle to be discarded finite value. */
+ state->N--;
if (state->N > 0)
- {
- Interval temp;
- Interval neg_val;
-
- neg_val.day = -newval->day;
- neg_val.month = -newval->month;
- neg_val.time = -newval->time;
-
- memcpy(&temp, &state->sumX, sizeof(Interval));
- finite_interval_pl(&state->sumX, &temp, &neg_val);
- }
+ finite_interval_mi(&state->sumX, &state->sumX, newval);
else
{
/* All values discarded, reset the state */
+ Assert(state->N == 0);
memset(&state->sumX, 0, sizeof(state->sumX));
}
- state->N--;
}
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 135d683285..8bd72f60b0 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -347,6 +347,17 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
(5 rows)
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column?
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- Floating point arithmetic rounding errors can lead to unexpected results,
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 45262924cd..63f36e069e 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -4375,51 +4375,37 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
---order by.
-SELECT x
- ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
-FROM (VALUES (NULL::interval),
- ('infinity'::interval),
- ('infinity'::timestamptz - now()),
- ('6 days'::interval),
- (NULL::interval),
- ('-infinity'::interval)) v(x);
- x | curr_next_avg | prev1_curr_sum
------------+---------------+----------------
- -infinity | -infinity | -infinity
- @ 6 days | infinity | -infinity
- infinity | infinity | infinity
- infinity | infinity | infinity
- | | infinity
- | |
-(6 rows)
-
---no order by.
+-- window function over interval, infinity and extreme values test the
+-- behaviour of accumulation and elimination of these values as the window
+-- slides.
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
+ ('2147483647 days 2147483646 months'), -- extreme interval value
('infinity'::timestamptz - now()),
+ ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
(NULL::interval),
('-infinity'::interval)) v(x);
- x | curr_next_avg | curr_next_sum | prev1_curr_avg | prev1_curr_sum | prev1_next2_avg | prev1_next2_sum
------------+-------------------+---------------+-------------------+----------------+-----------------+-----------------
- | infinity | infinity | | | infinity | infinity
- infinity | infinity | infinity | infinity | infinity | infinity | infinity
- infinity | infinity | infinity | infinity | infinity | infinity | infinity
- @ 6 days | @ 6 days 12 hours | @ 13 days | infinity | infinity | infinity | infinity
- @ 7 days | @ 7 days | @ 7 days | @ 6 days 12 hours | @ 13 days | -infinity | -infinity
- | -infinity | -infinity | @ 7 days | @ 7 days | -infinity | -infinity
- -infinity | -infinity | -infinity | -infinity | -infinity | -infinity | -infinity
-(7 rows)
+ x | curr_next_avg | prev1_curr_avg | curr_next_sum | prev1_curr_sum
+----------------------------------------------+-------------------+-------------------+---------------+----------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 6 mons 2147483647 days | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 7 mons 2147483648 days ago | infinity | infinity | infinity | infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
+(10 rows)
--should fail.
SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 912803a1b0..414c12be1c 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -99,6 +99,9 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index d00423946d..6dd9885949 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -1591,28 +1591,20 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
---order by.
-SELECT x
- ,avg(x) OVER(ORDER BY x ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ORDER BY x ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
-FROM (VALUES (NULL::interval),
- ('infinity'::interval),
- ('infinity'::timestamptz - now()),
- ('6 days'::interval),
- (NULL::interval),
- ('-infinity'::interval)) v(x);
-
---no order by.
+-- window function over interval, infinity and extreme values test the
+-- behaviour of accumulation and elimination of these values as the window
+-- slides.
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_avg
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING ) as prev1_next2_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
+ ('2147483647 days 2147483646 months'), -- extreme interval value
('infinity'::timestamptz - now()),
+ ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
(NULL::interval),
--
2.35.3
v27-0001-Check-for-overflow-in-make_interval.patchapplication/x-patch; name=v27-0001-Check-for-overflow-in-make_interval.patchDownload
From 905397e5642dfbdd61c6c795ed6e8ef15ce31be4 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 1 Apr 2023 10:22:53 -0400
Subject: [PATCH v27 1/8] Check for overflow in make_interval
---
src/backend/utils/adt/timestamp.c | 39 ++++++++++++++++++--------
src/include/datatype/timestamp.h | 1 +
src/test/regress/expected/interval.out | 27 ++++++++++++++++++
src/test/regress/sql/interval.sql | 15 ++++++++++
4 files changed, 71 insertions(+), 11 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index e172e90614..647b97aca6 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1509,24 +1509,41 @@ make_interval(PG_FUNCTION_ARGS)
Interval *result;
/*
- * Reject out-of-range inputs. We really ought to check the integer
- * inputs as well, but it's not entirely clear what limits to apply.
+ * Reject out-of-range inputs. We reject any input values that cause
+ * integer overflow of the corresponding interval fields.
*/
if (isinf(secs) || isnan(secs))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ goto out_of_range;
result = (Interval *) palloc(sizeof(Interval));
- result->month = years * MONTHS_PER_YEAR + months;
- result->day = weeks * 7 + days;
- secs = rint(secs * USECS_PER_SEC);
- result->time = hours * ((int64) SECS_PER_HOUR * USECS_PER_SEC) +
- mins * ((int64) SECS_PER_MINUTE * USECS_PER_SEC) +
- (int64) secs;
+ /* years and months -> months */
+ if (pg_mul_s32_overflow(years, MONTHS_PER_YEAR, &result->month) ||
+ pg_add_s32_overflow(result->month, months, &result->month))
+ goto out_of_range;
+
+ /* weeks and days -> days */
+ if (pg_mul_s32_overflow(weeks, DAYS_PER_WEEK, &result->day) ||
+ pg_add_s32_overflow(result->day, days, &result->day))
+ goto out_of_range;
+
+ /* hours and mins -> usecs (cannot overflow 64-bit) */
+ result->time = hours * USECS_PER_HOUR + mins * USECS_PER_MINUTE;
+
+ /* secs -> usecs */
+ secs = rint(float8_mul(secs, USECS_PER_SEC));
+ if (!FLOAT8_FITS_IN_INT64(secs) ||
+ pg_add_s64_overflow(result->time, (int64) secs, &result->time))
+ goto out_of_range;
PG_RETURN_INTERVAL_P(result);
+
+out_of_range:
+ ereport(ERROR,
+ errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range"));
+
+ PG_RETURN_NULL(); /* keep compiler quiet */
}
/* EncodeSpecialTimestamp()
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index ab8ccf89ca..1a6390585c 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -114,6 +114,7 @@ struct pg_itm_in
* 30 days.
*/
#define DAYS_PER_MONTH 30 /* assumes exactly 30 days per month */
+#define DAYS_PER_WEEK 7
#define HOURS_PER_DAY 24 /* assume no daylight savings time changes */
/*
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index c0ca8e041b..75d19d6594 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -1587,6 +1587,33 @@ select interval '-2147483648 months -2147483648 days -9223372036854775808 micros
ERROR: interval field value out of range: "-2147483648 months -2147483648 days -9223372036854775808 microseconds ago"
LINE 1: select interval '-2147483648 months -2147483648 days -922337...
^
+-- overflowing using make_interval
+select make_interval(years := 178956971);
+ERROR: interval out of range
+select make_interval(years := -178956971);
+ERROR: interval out of range
+select make_interval(years := 1, months := 2147483647);
+ERROR: interval out of range
+select make_interval(years := -1, months := -2147483648);
+ERROR: interval out of range
+select make_interval(weeks := 306783379);
+ERROR: interval out of range
+select make_interval(weeks := -306783379);
+ERROR: interval out of range
+select make_interval(weeks := 1, days := 2147483647);
+ERROR: interval out of range
+select make_interval(weeks := -1, days := -2147483648);
+ERROR: interval out of range
+select make_interval(secs := 1e308);
+ERROR: value out of range: overflow
+select make_interval(secs := 1e18);
+ERROR: interval out of range
+select make_interval(secs := -1e18);
+ERROR: interval out of range
+select make_interval(mins := 1, secs := 9223372036800.0);
+ERROR: interval out of range
+select make_interval(mins := -1, secs := -9223372036800.0);
+ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 038fc508d0..a0a373f08b 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -511,6 +511,21 @@ select interval '-2147483648 days ago';
select interval '-9223372036854775808 microseconds ago';
select interval '-2147483648 months -2147483648 days -9223372036854775808 microseconds ago';
+-- overflowing using make_interval
+select make_interval(years := 178956971);
+select make_interval(years := -178956971);
+select make_interval(years := 1, months := 2147483647);
+select make_interval(years := -1, months := -2147483648);
+select make_interval(weeks := 306783379);
+select make_interval(weeks := -306783379);
+select make_interval(weeks := 1, days := 2147483647);
+select make_interval(weeks := -1, days := -2147483648);
+select make_interval(secs := 1e308);
+select make_interval(secs := 1e18);
+select make_interval(secs := -1e18);
+select make_interval(mins := 1, secs := 9223372036800.0);
+select make_interval(mins := -1, secs := -9223372036800.0);
+
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
--
2.35.3
v27-0006-Implement-serialization-functions-for-interval-a.patchapplication/x-patch; name=v27-0006-Implement-serialization-functions-for-interval-a.patchDownload
From be85f1810ccba0988c68a875c5debff630eabf02 Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Thu, 21 Sep 2023 16:52:30 +0530
Subject: [PATCH v27 6/8] Implement serialization functions for interval
aggregate state
Now that interval aggregates use transition state which of date type Internal,
parallel aggregates require these separate functions.
Jian He and Ashutosh Bapat
---
src/backend/utils/adt/timestamp.c | 84 +++++++++++++++++++++++++++-
src/include/catalog/pg_aggregate.dat | 4 ++
src/include/catalog/pg_proc.dat | 6 ++
3 files changed, 92 insertions(+), 2 deletions(-)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 4b35ee0bdf..0b385fd72c 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -3821,8 +3821,12 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * Prepare state data for an interval aggregate function that needs to compute
- * sum and count.
+ * Prepare state data for an interval aggregate function, that needs to compute
+ * sum and count, in the aggregate's memory context.
+ *
+ * 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)
@@ -4000,6 +4004,82 @@ interval_avg_combine(PG_FUNCTION_ARGS)
PG_RETURN_POINTER(state1);
}
+/*
+ * interval_avg_serialize
+ * Serialize IntervalAggState for interval aggregates.
+ */
+Datum
+interval_avg_serialize(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ StringInfoData buf;
+ bytea *result;
+
+ /* Ensure we disallow calling when not in aggregate context */
+ if (!AggCheckCallContext(fcinfo, NULL))
+ elog(ERROR, "aggregate function called in non-aggregate context");
+
+ /* TODO: Handle NULL inputs? */
+ state = (IntervalAggState *) PG_GETARG_POINTER(0);
+ pq_begintypsend(&buf);
+ /* N */
+ pq_sendint64(&buf, state->N);
+ /* Finite interval value */
+ pq_sendint64(&buf, state->sumX.time);
+ pq_sendint32(&buf, state->sumX.day);
+ pq_sendint32(&buf, state->sumX.month);
+ /* pInfcount */
+ pq_sendint64(&buf, state->pInfcount);
+ /* nInfcount */
+ pq_sendint64(&buf, state->nInfcount);
+ result = pq_endtypsend(&buf);
+ PG_RETURN_BYTEA_P(result);
+}
+
+/*
+ * interval_avg_deserialize
+ * Deserialize bytea into IntervalAggState for interval aggregates.
+ */
+Datum
+interval_avg_deserialize(PG_FUNCTION_ARGS)
+{
+ 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));
+
+ /* N */
+ result->N = pq_getmsgint64(&buf);
+
+ /* Interval struct elements, one by one. */
+ result->sumX.time = pq_getmsgint64(&buf);
+ result->sumX.day = pq_getmsgint(&buf, sizeof(result->sumX.day));
+ result->sumX.month = pq_getmsgint(&buf, sizeof(result->sumX.month));
+
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
+
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
+
+ pq_getmsgend(&buf);
+
+ PG_RETURN_POINTER(result);
+}
+
/*
* Remove the given interval value from the aggregated state.
*/
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index e2087d7be1..0e62c3f7a6 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -45,6 +45,8 @@
aggtranstype => '_float8', agginitval => '{0,0,0}' },
{ 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_accum_inv',
aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
aggmtranstype => 'internal', aggtransspace => '128',
@@ -75,6 +77,8 @@
aggtranstype => 'money', aggmtranstype => 'money' },
{ 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_accum_inv',
aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
aggmtranstype => 'internal', aggtransspace => '128',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 791359a6e0..7d66de5043 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4928,6 +4928,12 @@
proname => 'interval_accum_inv', proisstrict => 'f',
prorettype => 'internal', proargtypes => 'internal interval',
prosrc => 'interval_accum_inv' },
+{ oid => '3813', descr => 'aggregate serial function',
+ proname => 'interval_avg_serialize', prorettype => 'bytea',
+ proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
+{ oid => '3814', 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 => 'internal', prosrc => 'interval_avg' },
--
2.35.3
v27-0007-Code-review-of-infinite-interval-support.patchapplication/x-patch; name=v27-0007-Code-review-of-infinite-interval-support.patchDownload
From 784c97837355bedcf84f2aed9d2c608773a81d3a Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Thu, 28 Sep 2023 14:54:02 +0100
Subject: [PATCH v27 7/8] Code review of infinite interval support.
---
doc/src/sgml/func.sgml | 5 +-
src/backend/utils/adt/date.c | 27 +-
src/backend/utils/adt/datetime.c | 4 +-
src/backend/utils/adt/selfuncs.c | 16 +-
src/backend/utils/adt/timestamp.c | 366 +++++++++----------
src/include/catalog/pg_aggregate.dat | 11 +-
src/include/catalog/pg_proc.dat | 6 +-
src/include/datatype/timestamp.h | 24 +-
src/include/utils/timestamp.h | 3 -
src/test/regress/expected/interval.out | 375 ++++++++------------
src/test/regress/expected/timestamp.out | 8 +-
src/test/regress/expected/timestamptz.out | 8 +-
src/test/regress/expected/window.out | 406 +++++++++++++++++++---
src/test/regress/sql/interval.sql | 131 +++----
src/test/regress/sql/window.sql | 93 ++++-
15 files changed, 912 insertions(+), 571 deletions(-)
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c4f09df544..682e3713c6 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -10435,7 +10435,10 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
<literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
- for all types and <literal>hour</literal> and <literal>day</literal> just for <type>interval</type>).
+ 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>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index ad97f07601..cafe3abe21 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -24,6 +24,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
+#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/supportnodes.h"
@@ -2050,8 +2051,6 @@ time_mi_time(PG_FUNCTION_ARGS)
result->day = 0;
result->time = time1 - time2;
- Assert(!INTERVAL_NOT_FINITE(result));
-
PG_RETURN_INTERVAL_P(result);
}
@@ -2116,7 +2115,8 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* Like time_pl_interval/time_mi_interval, we disregard the month and day
- * fields of the offset. So our test for negative should too.
+ * fields of the offset. So our test for negative should too. This also
+ * catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2126,13 +2126,14 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* We can't use time_pl_interval/time_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum = base - offset->time;
- else
- sum = base + offset->time;
+ else if (pg_add_s64_overflow(base, offset->time, &sum))
+ PG_RETURN_BOOL(less);
if (less)
PG_RETURN_BOOL(val <= sum);
@@ -2666,7 +2667,8 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* Like timetz_pl_interval/timetz_mi_interval, we disregard the month and
- * day fields of the offset. So our test for negative should too.
+ * day fields of the offset. So our test for negative should too. This
+ * also catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2676,13 +2678,14 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* We can't use timetz_pl_interval/timetz_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum.time = base->time - offset->time;
- else
- sum.time = base->time + offset->time;
+ else if (pg_add_s64_overflow(base->time, offset->time, &sum.time))
+ PG_RETURN_BOOL(less);
sum.zone = base->zone;
if (less)
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 73dadb747c..fca9a2a6e9 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3613,9 +3613,9 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
return DTERR_BAD_FORMAT;
/*
- * infinity can not be followed by anything else. We
+ * Infinity cannot be followed by anything else. We
* could allow "ago" to reverse the sign of infinity
- * but that signed infinity is more intuitive.
+ * but using signed infinity is more intuitive.
*/
if (i != nf - 1)
return DTERR_BAD_FORMAT;
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 6e87173846..4ea5415f20 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4795,7 +4795,21 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
case DATEOID:
return date2timestamp_no_overflow(DatumGetDateADT(value));
case INTERVALOID:
- return interval2timestamp_no_overflow(DatumGetIntervalP(value));
+ {
+ Interval *interval = DatumGetIntervalP(value);
+
+ /*
+ * Convert the month part of Interval to days using assumed
+ * average month length of 365.25/12.0 days. Not too
+ * accurate, but plenty good enough for our purposes.
+ *
+ * This also works for infinite intervals, which just have all
+ * fields set to INT_MIN/INT_MAX, and so will produce a result
+ * smaller/larger than any finite interval.
+ */
+ return interval->time + interval->day * (double) USECS_PER_DAY +
+ interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
+ }
case TIMEOID:
return DatumGetTimeADT(value);
case TIMETZOID:
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 0b385fd72c..23d43094e8 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -18,7 +18,6 @@
#include <ctype.h>
#include <math.h>
#include <limits.h>
-#include <float.h>
#include <sys/time.h>
#include "access/xact.h"
@@ -74,16 +73,16 @@ typedef struct
} generate_series_timestamptz_fctx;
/*
- * The transition datatype for interval aggregates is declared as Internal. It's
- * a pointer to a IntervalAggState allocated in the aggregate context.
+ * The transition datatype for interval aggregates is declared as internal.
+ * It's a pointer to an IntervalAggState allocated in the aggregate context.
*/
typedef struct IntervalAggState
{
- int64 N; /* count of processed intervals */
- Interval sumX; /* sum of processed intervals */
+ int64 N; /* count of finite intervals processed */
+ Interval sumX; /* sum of finite intervals processed */
/* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
- int64 pInfcount; /* count of +Inf values */
- int64 nInfcount; /* count of -Inf values */
+ int64 pInfcount; /* count of +infinity intervals */
+ int64 nInfcount; /* count of -infinity intervals */
} IntervalAggState;
#define IA_TOTAL_COUNT(ia) \
@@ -96,11 +95,12 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
-static void interval_um_internal(Interval *interval, Interval *result);
-static void finite_interval_pl(Interval *result, Interval *span1,
- Interval *span2);
-static void finite_interval_mi(Interval *result, Interval *span1,
- Interval *span2);
+static void EncodeSpecialInterval(const Interval *interval, char *str);
+static void interval_um_internal(const Interval *interval, Interval *result);
+static void finite_interval_pl(const Interval *span1, const Interval *span2,
+ Interval *result);
+static void finite_interval_mi(const Interval *span1, const Interval *span2,
+ Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -1604,8 +1604,8 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
-void
-EncodeSpecialInterval(Interval *interval, char *str)
+static void
+EncodeSpecialInterval(const Interval *interval, char *str)
{
if (INTERVAL_IS_NOBEGIN(interval))
strcpy(str, EARLY);
@@ -2101,6 +2101,13 @@ itm2interval(struct pg_itm *itm, Interval *span)
/* itmin2interval()
* Convert a pg_itm_in structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * Note: if the result is infinite, it is not treated as an overflow. This
+ * avoids any dump/reload hazards from pre-17 databases that do not support
+ * infinite intervals, but do allow finite intervals with all fields set to
+ * INT_MIN/INT_MAX (outside the documented range). Such intervals will be
+ * silently converted to +/-infinity. This may not be ideal, but seems
+ * preferable to failure, and ought to be pretty unlikely in practice.
*/
int
itmin2interval(struct pg_itm_in *itm_in, Interval *span)
@@ -2112,8 +2119,6 @@ itmin2interval(struct pg_itm_in *itm_in, Interval *span)
span->month = (int32) total_months;
span->day = itm_in->tm_mday;
span->time = itm_in->tm_usec;
- if (INTERVAL_NOT_FINITE(span))
- return -1;
return 0;
}
@@ -2280,29 +2285,6 @@ timestamp_fastcmp(Datum x, Datum y, SortSupport ssup)
}
#endif
-double
-interval2timestamp_no_overflow(Interval *interval)
-{
- double result;
-
- if (INTERVAL_IS_NOBEGIN(interval))
- result = -DBL_MAX;
- else if (INTERVAL_IS_NOEND(interval))
- result = DBL_MAX;
- else
- {
- /*
- * Convert the month part of Interval to days using assumed average
- * month length of 365.25/12.0 days. Not too accurate, but plenty
- * good enough for our purposes.
- */
- return interval->time + interval->day * (double) USECS_PER_DAY +
- interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
- }
-
- return result;
-}
-
Datum
timestamp_sortsupport(PG_FUNCTION_ARGS)
{
@@ -2526,6 +2508,15 @@ interval_cmp_internal(const Interval *interval1, const Interval *interval2)
return int128_compare(span1, span2);
}
+static int
+interval_sign(const Interval *interval)
+{
+ INT128 span = interval_cmp_value(interval);
+ INT128 zero = int64_to_int128(0);
+
+ return int128_compare(span, zero);
+}
+
Datum
interval_eq(PG_FUNCTION_ARGS)
{
@@ -2801,14 +2792,14 @@ timestamp_mi(PG_FUNCTION_ARGS)
/*
* Subtracting two infinite timestamps with different signs results in an
* infinite interval with the same sign as the left operand. Subtracting
- * two infinte timestamps with the same sign results in an error.
+ * two infinite timestamps with the same sign results in an error.
*/
if (TIMESTAMP_IS_NOBEGIN(dt1))
{
if (TIMESTAMP_IS_NOBEGIN(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOBEGIN(result);
}
@@ -2817,7 +2808,7 @@ timestamp_mi(PG_FUNCTION_ARGS)
if (TIMESTAMP_IS_NOEND(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOEND(result);
}
@@ -3068,16 +3059,16 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
Timestamp result;
/*
- * Adding two infinites with the same sign results in an infinite
- * timestamp with the same sign. Adding two infintes with different signs
- * results in an error.
+ * Adding two infinities with the same sign results in an infinite
+ * timestamp with the same sign. Adding two infinities with different
+ * signs results in an error.
*/
if (INTERVAL_IS_NOBEGIN(span))
{
if (TIMESTAMP_IS_NOEND(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("timestamp out of range")));
else
TIMESTAMP_NOBEGIN(result);
}
@@ -3086,7 +3077,7 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
if (TIMESTAMP_IS_NOBEGIN(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("timestamp out of range")));
else
TIMESTAMP_NOEND(result);
}
@@ -3197,16 +3188,16 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
int tz;
/*
- * Adding two infinites with the same sign results in an infinite
- * timestamp with the same sign. Adding two infintes with different signs
- * results in an error.
+ * Adding two infinities with the same sign results in an infinite
+ * timestamp with the same sign. Adding two infinities with different
+ * signs results in an error.
*/
if (INTERVAL_IS_NOBEGIN(span))
{
if (TIMESTAMP_IS_NOEND(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("timestamp out of range")));
else
TIMESTAMP_NOBEGIN(result);
}
@@ -3215,7 +3206,7 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
if (TIMESTAMP_IS_NOBEGIN(timestamp))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ errmsg("timestamp out of range")));
else
TIMESTAMP_NOEND(result);
}
@@ -3362,16 +3353,12 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
/* Negates the given interval */
static void
-interval_um_internal(Interval *interval, Interval *result)
+interval_um_internal(const Interval *interval, Interval *result)
{
if (INTERVAL_IS_NOBEGIN(interval))
INTERVAL_NOEND(result);
else if (INTERVAL_IS_NOEND(interval))
INTERVAL_NOBEGIN(result);
- else if (interval->time == PG_INT64_MIN || interval->day == PG_INT32_MIN || interval->month == PG_INT32_MIN)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
else
{
result->time = -interval->time;
@@ -3438,8 +3425,8 @@ interval_pl(PG_FUNCTION_ARGS)
/*
* Adding two infinite intervals with the same signs results in an
- * infinite interval with the same sign. Adding two infinte intervals with
- * different signs results in an error.
+ * infinite interval with the same sign. Adding two infinite intervals
+ * with different signs results in an error.
*/
if (INTERVAL_IS_NOBEGIN(span1))
{
@@ -3462,7 +3449,7 @@ interval_pl(PG_FUNCTION_ARGS)
else if (INTERVAL_NOT_FINITE(span2))
memcpy(result, span2, sizeof(Interval));
else
- finite_interval_pl(result, span1, span2);
+ finite_interval_pl(span1, span2, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3479,7 +3466,7 @@ interval_mi(PG_FUNCTION_ARGS)
/*
* Subtracting two infinite intervals with different signs results in an
* infinite interval with the same sign as the left operand. Subtracting
- * two infinte intervals with the same sign results in an error.
+ * two infinite intervals with the same sign results in an error.
*/
if (INTERVAL_IS_NOBEGIN(span1))
{
@@ -3504,7 +3491,7 @@ interval_mi(PG_FUNCTION_ARGS)
else if (INTERVAL_IS_NOEND(span2))
INTERVAL_NOBEGIN(result);
else
- finite_interval_mi(result, span1, span2);
+ finite_interval_mi(span1, span2, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3552,13 +3539,11 @@ interval_mul(PG_FUNCTION_ARGS)
}
else if (isinf(factor))
{
- Interval zero;
int factor_sign,
result_sign;
factor_sign = factor >= 0.0 ? 1 : -1;
- memset(&zero, 0, sizeof(zero));
- result_sign = interval_cmp_internal(span, &zero) * factor_sign;
+ result_sign = interval_sign(span) * factor_sign;
if (result_sign == 0)
ereport(ERROR,
@@ -3740,11 +3725,21 @@ in_range_timestamptz_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
TimestampTz sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = timestamptz_mi_interval_internal(base, offset, NULL);
@@ -3767,11 +3762,21 @@ in_range_timestamp_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Timestamp sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval,
@@ -3798,11 +3803,21 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Interval *sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? INTERVAL_IS_NOEND(base) : INTERVAL_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
@@ -3848,21 +3863,13 @@ makeIntervalAggState(FunctionCallInfo fcinfo)
}
/*
- * Functions to add or subtract two finite intervals.
+ * Functions to add or subtract finite intervals.
*
- * We handle non-finite intervals in different ways when accumulating/discarding
- * intervals and in actual mathematical operations respectively. But the
- * addition or subtraction of finite intervals have same implementation
- * respectively in both these cases.
- *
- * Addition/Subtraction of span1 and span2 is stored in the result. It is fine
- * to pass either of span1 or span2 as a result pointer. In such a case result
- * will overwrite the given value.
- *
- * If the result is not a valid interval, the function throws an error.
+ * These are used for normal arithmetic and aggregation of finite intervals
+ * only. Non-finite intervals require special handling.
*/
static void
-finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
+finite_interval_pl(const Interval *span1, const Interval *span2, Interval *result)
{
Assert(!INTERVAL_NOT_FINITE(span1));
Assert(!INTERVAL_NOT_FINITE(span2));
@@ -3889,7 +3896,7 @@ finite_interval_pl(Interval *result, Interval *span1, Interval *span2)
}
static void
-finite_interval_mi(Interval *result, Interval *span1, Interval *span2)
+finite_interval_mi(const Interval *span1, const Interval *span2, Interval *result)
{
Assert(!INTERVAL_NOT_FINITE(span1));
Assert(!INTERVAL_NOT_FINITE(span2));
@@ -3921,26 +3928,56 @@ finite_interval_mi(Interval *result, Interval *span1, Interval *span2)
static void
do_interval_accum(IntervalAggState *state, Interval *newval)
{
- /* Count -infinity inputs separately from all else */
+ /* Infinite inputs are counted separately, and do not affect "N" */
if (INTERVAL_IS_NOBEGIN(newval))
{
state->nInfcount++;
return;
}
- /* Count infinity inputs separately from all else */
if (INTERVAL_IS_NOEND(newval))
{
state->pInfcount++;
return;
}
- finite_interval_pl(&state->sumX, &state->sumX, newval);
+ finite_interval_pl(&state->sumX, newval, &state->sumX);
state->N++;
}
/*
- * Transition function for interval aggregates.
+ * Remove the given interval value from the aggregated state.
+ */
+static void
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+ /* Infinite inputs are counted separately, and do not affect "N" */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount--;
+ return;
+ }
+
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount--;
+ return;
+ }
+
+ /* Handle to be discarded finite value. */
+ state->N--;
+ if (state->N > 0)
+ finite_interval_mi(&state->sumX, newval, &state->sumX);
+ else
+ {
+ /* All values discarded, reset the state */
+ Assert(state->N == 0);
+ memset(&state->sumX, 0, sizeof(state->sumX));
+ }
+}
+
+/*
+ * Transition function for sum() and avg() interval aggregates.
*/
Datum
interval_avg_accum(PG_FUNCTION_ARGS)
@@ -3960,10 +3997,10 @@ interval_avg_accum(PG_FUNCTION_ARGS)
}
/*
- * Combine function for interval aggregates.
+ * Combine function for sum() and avg() interval aggregates.
*
- * Combine the given internal aggregate states and place the combination in the
- * first argument.
+ * Combine the given internal aggregate states and place the combination in
+ * the first argument.
*/
Datum
interval_avg_combine(PG_FUNCTION_ARGS)
@@ -3999,7 +4036,7 @@ interval_avg_combine(PG_FUNCTION_ARGS)
/* Accumulate finite interval values, if any. */
if (state2->N > 0)
- finite_interval_pl(&state1->sumX, &state1->sumX, &state2->sumX);
+ finite_interval_pl(&state1->sumX, &state2->sumX, &state1->sumX);
PG_RETURN_POINTER(state1);
}
@@ -4019,20 +4056,26 @@ interval_avg_serialize(PG_FUNCTION_ARGS)
if (!AggCheckCallContext(fcinfo, NULL))
elog(ERROR, "aggregate function called in non-aggregate context");
- /* TODO: Handle NULL inputs? */
state = (IntervalAggState *) PG_GETARG_POINTER(0);
+
pq_begintypsend(&buf);
+
/* N */
pq_sendint64(&buf, state->N);
- /* Finite interval value */
+
+ /* sumX */
pq_sendint64(&buf, state->sumX.time);
pq_sendint32(&buf, state->sumX.day);
pq_sendint32(&buf, state->sumX.month);
+
/* pInfcount */
pq_sendint64(&buf, state->pInfcount);
+
/* nInfcount */
pq_sendint64(&buf, state->nInfcount);
+
result = pq_endtypsend(&buf);
+
PG_RETURN_BYTEA_P(result);
}
@@ -4064,10 +4107,10 @@ interval_avg_deserialize(PG_FUNCTION_ARGS)
/* N */
result->N = pq_getmsgint64(&buf);
- /* Interval struct elements, one by one. */
+ /* sumX */
result->sumX.time = pq_getmsgint64(&buf);
- result->sumX.day = pq_getmsgint(&buf, sizeof(result->sumX.day));
- result->sumX.month = pq_getmsgint(&buf, sizeof(result->sumX.month));
+ result->sumX.day = pq_getmsgint(&buf, 4);
+ result->sumX.month = pq_getmsgint(&buf, 4);
/* pInfcount */
result->pInfcount = pq_getmsgint64(&buf);
@@ -4081,42 +4124,10 @@ interval_avg_deserialize(PG_FUNCTION_ARGS)
}
/*
- * Remove the given interval value from the aggregated state.
- */
-static void
-do_interval_discard(IntervalAggState *state, Interval *newval)
-{
- /* Count -infinity inputs separately from all else */
- if (INTERVAL_IS_NOBEGIN(newval))
- {
- state->nInfcount--;
- return;
- }
-
- /* Count infinity inputs separately from all else */
- if (INTERVAL_IS_NOEND(newval))
- {
- state->pInfcount--;
- return;
- }
-
- /* Handle to be discarded finite value. */
- state->N--;
- if (state->N > 0)
- finite_interval_mi(&state->sumX, &state->sumX, newval);
- else
- {
- /* All values discarded, reset the state */
- Assert(state->N == 0);
- memset(&state->sumX, 0, sizeof(state->sumX));
- }
-}
-
-/*
- * Generic inverse transition function for interval aggregates
+ * Inverse transition function for sum() and avg() interval aggregates.
*/
Datum
-interval_accum_inv(PG_FUNCTION_ARGS)
+interval_avg_accum_inv(PG_FUNCTION_ARGS)
{
IntervalAggState *state;
@@ -4124,7 +4135,7 @@ interval_accum_inv(PG_FUNCTION_ARGS)
/* Should not get here with no state */
if (state == NULL)
- elog(ERROR, "interval_accum_inv called with NULL state");
+ elog(ERROR, "interval_avg_accum_inv called with NULL state");
if (!PG_ARGISNULL(1))
do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
@@ -4137,38 +4148,39 @@ Datum
interval_avg(PG_FUNCTION_ARGS)
{
IntervalAggState *state;
- double N_datum;
- Interval *sumX;
- sumX = (Interval *) palloc(sizeof(Interval));
state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+ /* If there were no non-null inputs, return NULL */
if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- /* adding plus and minus infinities gives error */
- if (state->pInfcount > 0 && state->nInfcount > 0)
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range.")));
-
- if (state->pInfcount > 0)
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 || state->nInfcount > 0)
{
- INTERVAL_NOEND(sumX);
- PG_RETURN_INTERVAL_P(sumX);
- }
+ Interval *result;
- if (state->nInfcount > 0)
- {
- INTERVAL_NOBEGIN(sumX);
- PG_RETURN_INTERVAL_P(sumX);
- }
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else
+ INTERVAL_NOBEGIN(result);
- N_datum = (double) state->N;
- sumX = &state->sumX;
+ PG_RETURN_INTERVAL_P(result);
+ }
- PG_RETURN_DATUM(DirectFunctionCall2(interval_div, IntervalPGetDatum(sumX),
- Float8GetDatum(N_datum)));
+ return DirectFunctionCall2(interval_div,
+ IntervalPGetDatum(&state->sumX),
+ Float8GetDatum((double) state->N));
}
/* sum(interval) aggregate final function */
@@ -4178,33 +4190,31 @@ interval_sum(PG_FUNCTION_ARGS)
IntervalAggState *state;
Interval *result;
- result = (Interval *) palloc(sizeof(Interval));
-
state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
/* If there were no non-null inputs, return NULL */
if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
- /* adding plus and minus interval infinities is not possible */
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
if (state->pInfcount > 0 && state->nInfcount > 0)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range.")));
+ result = (Interval *) palloc(sizeof(Interval));
+
if (state->pInfcount > 0)
- {
INTERVAL_NOEND(result);
- PG_RETURN_INTERVAL_P(result);
- }
-
- if (state->nInfcount > 0)
- {
+ else if (state->nInfcount > 0)
INTERVAL_NOBEGIN(result);
- PG_RETURN_INTERVAL_P(result);
- }
+ else
+ memcpy(result, &state->sumX, sizeof(Interval));
- memcpy(result, &state->sumX, sizeof(Interval));
PG_RETURN_INTERVAL_P(result);
}
@@ -4234,14 +4244,14 @@ timestamp_age(PG_FUNCTION_ARGS)
/*
* Subtracting two infinite timestamps with different signs results in an
* infinite interval with the same sign as the left operand. Subtracting
- * two infinte timestamps with the same sign results in an error.
+ * two infinite timestamps with the same sign results in an error.
*/
if (TIMESTAMP_IS_NOBEGIN(dt1))
{
if (TIMESTAMP_IS_NOBEGIN(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOBEGIN(result);
}
@@ -4250,7 +4260,7 @@ timestamp_age(PG_FUNCTION_ARGS)
if (TIMESTAMP_IS_NOEND(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOEND(result);
}
@@ -4381,14 +4391,14 @@ timestamptz_age(PG_FUNCTION_ARGS)
/*
* Subtracting two infinite timestamps with different signs results in an
* infinite interval with the same sign as the left operand. Subtracting
- * two infinte timestamps with the same sign results in an error.
+ * two infinite timestamps with the same sign results in an error.
*/
if (TIMESTAMP_IS_NOBEGIN(dt1))
{
if (TIMESTAMP_IS_NOBEGIN(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOBEGIN(result);
}
@@ -4397,7 +4407,7 @@ timestamptz_age(PG_FUNCTION_ARGS)
if (TIMESTAMP_IS_NOEND(dt2))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("timestamp out of range")));
+ errmsg("interval out of range")));
else
INTERVAL_NOEND(result);
}
@@ -5829,17 +5839,16 @@ extract_timestamptz(PG_FUNCTION_ARGS)
/*
* NonFiniteIntervalPart
*
- * Used by interval_part when extracting from infinite
- * interval. Returns +/-Infinity if that is the appropriate result,
- * otherwise returns zero (which should be taken as meaning to return NULL).
+ * Used by interval_part when extracting from infinite interval. Returns
+ * +/-Infinity if that is the appropriate result, otherwise returns zero
+ * (which should be taken as meaning to return NULL).
*
* Errors thrown here for invalid units should exactly match those that
* would be thrown in the calling functions, else there will be unexpected
* discrepancies between finite- and infinite-input cases.
*/
static float8
-NonFiniteIntervalPart(int type, int unit, char *lowunits,
- bool isNegative, bool isTz)
+NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative)
{
if ((type != UNITS) && (type != RESERV))
ereport(ERROR,
@@ -5906,8 +5915,7 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (INTERVAL_NOT_FINITE(interval))
{
double r = NonFiniteIntervalPart(type, val, lowunits,
- INTERVAL_IS_NOBEGIN(interval),
- false);
+ INTERVAL_IS_NOBEGIN(interval));
if (r != 0.0)
{
@@ -6453,7 +6461,6 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
Timestamp finish = PG_GETARG_TIMESTAMP(1);
Interval *step = PG_GETARG_INTERVAL_P(2);
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -6476,7 +6483,7 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
fctx->step = *step;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
@@ -6539,7 +6546,6 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
Interval *step = PG_GETARG_INTERVAL_P(2);
text *zone = (PG_NARGS() == 4) ? PG_GETARG_TEXT_PP(3) : NULL;
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -6563,7 +6569,7 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
fctx->attimezone = zone ? lookup_timezone(zone) : session_timezone;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 0e62c3f7a6..e1a17cddd8 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -47,11 +47,9 @@
aggfinalfn => 'interval_avg', aggcombinefn => 'interval_avg_combine',
aggserialfn => 'interval_avg_serialize',
aggdeserialfn => 'interval_avg_deserialize',
- aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv',
aggmfinalfn => 'interval_avg', aggtranstype => 'internal',
- aggmtranstype => 'internal', aggtransspace => '128',
- aggmtransspace => '128'
-},
+ aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40' },
# sum
{ aggfnoid => 'sum(int8)', aggtransfn => 'int8_avg_accum',
@@ -79,10 +77,9 @@
aggfinalfn => 'interval_sum', aggcombinefn => 'interval_avg_combine',
aggserialfn => 'interval_avg_serialize',
aggdeserialfn => 'interval_avg_deserialize',
- aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_accum_inv',
+ aggmtransfn => 'interval_avg_accum', aggminvtransfn => 'interval_avg_accum_inv',
aggmfinalfn => 'interval_sum', aggtranstype => 'internal',
- aggmtranstype => 'internal', aggtransspace => '128',
- aggmtransspace => '128'},
+ aggtransspace => '40', aggmtranstype => 'internal', aggmtransspace => '40'},
{ aggfnoid => 'sum(numeric)', aggtransfn => 'numeric_avg_accum',
aggfinalfn => 'numeric_sum', aggcombinefn => 'numeric_avg_combine',
aggserialfn => 'numeric_avg_serialize',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7d66de5043..455ed16c79 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4925,9 +4925,9 @@
prorettype => 'internal', proargtypes => 'internal internal',
prosrc => 'interval_avg_combine' },
{ oid => '3549', descr => 'aggregate transition function',
- proname => 'interval_accum_inv', proisstrict => 'f',
+ proname => 'interval_avg_accum_inv', proisstrict => 'f',
prorettype => 'internal', proargtypes => 'internal interval',
- prosrc => 'interval_accum_inv' },
+ prosrc => 'interval_avg_accum_inv' },
{ oid => '3813', descr => 'aggregate serial function',
proname => 'interval_avg_serialize', prorettype => 'bytea',
proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
@@ -4935,7 +4935,7 @@
proname => 'interval_avg_deserialize', prorettype => 'internal',
proargtypes => 'bytea internal', prosrc => 'interval_avg_deserialize' },
{ oid => '1844', descr => 'aggregate final function',
- proname => 'interval_avg', prorettype => 'interval',
+ proname => 'interval_avg', proisstrict => 'f', prorettype => 'interval',
proargtypes => 'internal', prosrc => 'interval_avg' },
{ oid => '8069', descr => 'aggregate final function',
proname => 'interval_sum', proisstrict => 'f', prorettype => 'interval',
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index f9bd30d225..b63acc0a2f 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -151,7 +151,7 @@ struct pg_itm_in
#define TIMESTAMP_INFINITY PG_INT64_MAX
/*
- * Historically these alias for infinity have been used.
+ * Historically these aliases for infinity have been used.
*/
#define DT_NOBEGIN TIMESTAMP_MINUS_INFINITY
#define DT_NOEND TIMESTAMP_INFINITY
@@ -168,23 +168,29 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+/*
+ * Infinite intervals are represented by setting all fields to the minimum or
+ * maximum integer values.
+ */
#define INTERVAL_NOBEGIN(i) \
do { \
- (i->time) = PG_INT64_MIN; \
- (i->day) = PG_INT32_MIN; \
- (i->month) = PG_INT32_MIN; \
+ (i)->time = PG_INT64_MIN; \
+ (i)->day = PG_INT32_MIN; \
+ (i)->month = PG_INT32_MIN; \
} while (0)
-#define INTERVAL_IS_NOBEGIN(i) ((i->month) == PG_INT32_MIN)
+#define INTERVAL_IS_NOBEGIN(i) \
+ ((i)->month == PG_INT32_MIN && (i)->day == PG_INT32_MIN && (i)->time == PG_INT64_MIN)
#define INTERVAL_NOEND(i) \
do { \
- (i->time) = PG_INT64_MAX; \
- (i->day) = PG_INT32_MAX; \
- (i->month) = PG_INT32_MAX; \
+ (i)->time = PG_INT64_MAX; \
+ (i)->day = PG_INT32_MAX; \
+ (i)->month = PG_INT32_MAX; \
} while (0)
-#define INTERVAL_IS_NOEND(i) ((i->month) == PG_INT32_MAX)
+#define INTERVAL_IS_NOEND(i) \
+ ((i)->month == PG_INT32_MAX && (i)->day == PG_INT32_MAX && (i)->time == PG_INT64_MAX)
#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
diff --git a/src/include/utils/timestamp.h b/src/include/utils/timestamp.h
index fe713fb6dd..c4dd96c8c9 100644
--- a/src/include/utils/timestamp.h
+++ b/src/include/utils/timestamp.h
@@ -118,7 +118,6 @@ extern int timestamp2tm(Timestamp dt, int *tzp, struct pg_tm *tm,
fsec_t *fsec, const char **tzn, pg_tz *attimezone);
extern void dt2time(Timestamp jd, int *hour, int *min, int *sec, fsec_t *fsec);
-extern void EncodeSpecialInterval(Interval *interval, char *str);
extern void interval2itm(Interval span, struct pg_itm *itm);
extern int itm2interval(struct pg_itm *itm, Interval *span);
extern int itmin2interval(struct pg_itm_in *itm_in, Interval *span);
@@ -128,8 +127,6 @@ extern void GetEpochTime(struct pg_tm *tm);
extern int timestamp_cmp_internal(Timestamp dt1, Timestamp dt2);
-extern double interval2timestamp_no_overflow(Interval *interval);
-
/* timestamp comparison works for timestamptz also */
#define timestamptz_cmp_internal(dt1,dt2) timestamp_cmp_internal(dt1, dt2)
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 8bd72f60b0..00cd3e84f1 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -284,11 +284,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483646 months'),
- ('2147483647 days -2147483647 months'),
+ ('2147483647 days 2147483647 months'),
+ ('2147483647 days -2147483648 months'),
('1 year'),
- ('-2147483648 days 2147483646 months'),
- ('-2147483648 days -2147483647 months');
+ ('-2147483648 days 2147483647 months'),
+ ('-2147483648 days -2147483648 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
ERROR: interval field value out of range: "2147483648 days"
@@ -315,16 +315,16 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-------------------------------------------+-------------------------------------------
- -178956970 years -7 mons +2147483647 days | -178956970 years -7 mons -2147483648 days
- 1 year | -178956970 years -7 mons -2147483648 days
- 1 year | -178956970 years -7 mons +2147483647 days
- 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons -2147483648 days
- 178956970 years 6 mons -2147483648 days | -178956970 years -7 mons +2147483647 days
- 178956970 years 6 mons -2147483648 days | 1 year
- 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons -2147483648 days
- 178956970 years 6 mons 2147483647 days | -178956970 years -7 mons +2147483647 days
- 178956970 years 6 mons 2147483647 days | 1 year
- 178956970 years 6 mons 2147483647 days | 178956970 years 6 mons -2147483648 days
+ -178956970 years -8 mons +2147483647 days | -178956970 years -8 mons -2147483648 days
+ 1 year | -178956970 years -8 mons -2147483648 days
+ 1 year | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons -2147483648 days
+ 178956970 years 7 mons -2147483648 days | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons -2147483648 days | 1 year
+ 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons -2147483648 days
+ 178956970 years 7 mons 2147483647 days | -178956970 years -8 mons +2147483647 days
+ 178956970 years 7 mons 2147483647 days | 1 year
+ 178956970 years 7 mons 2147483647 days | 178956970 years 7 mons -2147483648 days
(10 rows)
CREATE INDEX ON INTERVAL_TBL_OF USING btree (f1);
@@ -339,11 +339,11 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
f1
-------------------------------------------
- -178956970 years -7 mons -2147483648 days
- -178956970 years -7 mons +2147483647 days
+ -178956970 years -8 mons -2147483648 days
+ -178956970 years -8 mons +2147483647 days
1 year
- 178956970 years 6 mons -2147483648 days
- 178956970 years 6 mons 2147483647 days
+ 178956970 years 7 mons -2147483648 days
+ 178956970 years 7 mons 2147483647 days
(5 rows)
RESET enable_seqscan;
@@ -485,9 +485,7 @@ SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as
SELECT justify_hours(interval '2147483647 days 24 hrs');
ERROR: interval out of range
-SELECT justify_days(interval '2147483646 months 30 days');
-ERROR: interval out of range
-SELECT justify_days(interval '2147483646 months 60 days');
+SELECT justify_days(interval '2147483647 months 30 days');
ERROR: interval out of range
-- test justify_interval()
SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
@@ -508,29 +506,25 @@ SELECT justify_interval(interval '-2147483648 days -24 hrs');
@ 5965232 years 4 mons 9 days ago
(1 row)
-SELECT justify_interval(interval '2147483646 months 30 days');
-ERROR: interval out of range
-SELECT justify_interval(interval '2147483646 months 60 days');
-ERROR: interval out of range
-SELECT justify_interval(interval '-2147483647 months -30 days');
+SELECT justify_interval(interval '2147483647 months 30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483647 months -60 days');
+SELECT justify_interval(interval '-2147483648 months -30 days');
ERROR: interval out of range
-SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
+SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
justify_interval
----------------------------------
- @ 178956970 years 6 mons 29 days
+ @ 178956970 years 7 mons 29 days
(1 row)
-SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
+SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
justify_interval
--------------------------------------
- @ 178956970 years 7 mons 29 days ago
+ @ 178956970 years 8 mons 29 days ago
(1 row)
-SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
+SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
ERROR: interval out of range
-SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
+SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
ERROR: interval out of range
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -1882,233 +1876,144 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
--- infinite intervals
-SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
-ERROR: interval out of range
-LINE 1: SELECT interval '-2147483648 months -2147483648 days -922337...
- ^
-SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
-ERROR: interval out of range
-LINE 1: SELECT interval '2147483647 months 2147483647 days 922337203...
- ^
-CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
-INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
-SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
- i | isfinite
--------------------------+----------
- infinity | f
- -infinity | f
- @ 1 year 2 days 3 hours | t
-(3 rows)
-
-SELECT date '1995-08-06' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT date '1995-08-06' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT date '1995-08-06' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT date '1995-08-06' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT date 'infinity' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT date 'infinity' + interval '-infinity';
-ERROR: interval out of range
-SELECT date '-infinity' + interval 'infinity';
-ERROR: interval out of range
-SELECT date '-infinity' + interval '-infinity';
- ?column?
------------
- -infinity
+--
+-- test infinite intervals
+--
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+ interval
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago
(1 row)
-SELECT date 'infinity' - interval 'infinity';
-ERROR: interval out of range
-SELECT date 'infinity' - interval '-infinity';
- ?column?
-----------
- infinity
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+ interval
+--------------------------------------------------------------------------
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs
(1 row)
-SELECT date '-infinity' - interval 'infinity';
- ?column?
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
-----------
-infinity
(1 row)
-SELECT date '-infinity' - interval '-infinity';
-ERROR: interval out of range
-SELECT interval 'infinity' + interval 'infinity';
- ?column?
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ interval
----------
infinity
(1 row)
-SELECT interval 'infinity' + interval '-infinity';
-ERROR: interval out of range
-SELECT interval '-infinity' + interval 'infinity';
-ERROR: interval out of range
-SELECT interval '-infinity' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
-SELECT interval 'infinity' + interval '10 days';
- ?column?
-----------
- infinity
-(1 row)
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ date | interval | plus | minus
+------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 | -infinity | -infinity | infinity
+ 1995-08-06 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
-SELECT interval '-infinity' + interval '10 days';
- ?column?
------------
- -infinity
-(1 row)
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+ interval1 | interval2 | plus | minus
+-----------+-----------+-----------------------+-----------------------
+ -infinity | -infinity | -infinity | interval out of range
+ -infinity | @ 10 days | -infinity | -infinity
+ -infinity | infinity | interval out of range | -infinity
+ @ 2 mons | -infinity | -infinity | infinity
+ @ 2 mons | @ 10 days | @ 2 mons 10 days | @ 2 mons -10 days
+ @ 2 mons | infinity | infinity | -infinity
+ infinity | -infinity | interval out of range | infinity
+ infinity | @ 10 days | infinity | infinity
+ infinity | infinity | infinity | interval out of range
+(9 rows)
SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
ERROR: interval out of range
SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
ERROR: interval out of range
-SELECT interval 'infinity' - interval 'infinity';
-ERROR: interval out of range
-SELECT interval 'infinity' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT interval '-infinity' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT interval '-infinity' - interval '-infinity';
-ERROR: interval out of range
-SELECT interval 'infinity' - interval '10 days';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT interval '-infinity' - interval '10 days';
- ?column?
------------
- -infinity
-(1 row)
-
SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
ERROR: interval out of range
SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
ERROR: interval out of range
-SELECT timestamp 'infinity' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamp 'infinity' + interval '-infinity';
-ERROR: interval out of range
-SELECT timestamp '-infinity' + interval 'infinity';
-ERROR: interval out of range
-SELECT timestamp '-infinity' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamp 'infinity' - interval 'infinity';
-ERROR: interval out of range
-SELECT timestamp 'infinity' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamp '-infinity' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamp '-infinity' - interval '-infinity';
-ERROR: interval out of range
-SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamptz 'infinity' + interval 'infinity';
- ?column?
-----------
- infinity
-(1 row)
-
-SELECT timestamptz 'infinity' + interval '-infinity';
-ERROR: interval out of range
-SELECT timestamptz '-infinity' + interval 'infinity';
-ERROR: interval out of range
-SELECT timestamptz '-infinity' + interval '-infinity';
- ?column?
------------
- -infinity
-(1 row)
-
-SELECT timestamptz 'infinity' - interval 'infinity';
-ERROR: interval out of range
-SELECT timestamptz 'infinity' - interval '-infinity';
- ?column?
-----------
- infinity
-(1 row)
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamp | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
-SELECT timestamptz '-infinity' - interval 'infinity';
- ?column?
------------
- -infinity
-(1 row)
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamptz | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
-SELECT timestamptz '-infinity' - interval '-infinity';
-ERROR: interval out of range
+-- time +/- infinite interval not supported
SELECT time '11:27:42' + interval 'infinity';
ERROR: cannot add infinite interval to time
SELECT time '11:27:42' + interval '-infinity';
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index 53542e076b..835f0e5762 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2131,7 +2131,7 @@ select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12
ERROR: step size cannot be infinite
-- test arithmetic with infinite timestamps
select timestamp 'infinity' - timestamp 'infinity';
-ERROR: timestamp out of range
+ERROR: interval out of range
select timestamp 'infinity' - timestamp '-infinity';
?column?
----------
@@ -2145,7 +2145,7 @@ select timestamp '-infinity' - timestamp 'infinity';
(1 row)
select timestamp '-infinity' - timestamp '-infinity';
-ERROR: timestamp out of range
+ERROR: interval out of range
select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
?column?
----------
@@ -2172,7 +2172,7 @@ select age(timestamp '-infinity');
(1 row)
select age(timestamp 'infinity', timestamp 'infinity');
-ERROR: timestamp out of range
+ERROR: interval out of range
select age(timestamp 'infinity', timestamp '-infinity');
age
----------
@@ -2186,4 +2186,4 @@ select age(timestamp '-infinity', timestamp 'infinity');
(1 row)
select age(timestamp '-infinity', timestamp '-infinity');
-ERROR: timestamp out of range
+ERROR: interval out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index e3f0d918b9..a084357480 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -3216,7 +3216,7 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
-- test arithmetic with infinite timestamps
SELECT timestamptz 'infinity' - timestamptz 'infinity';
-ERROR: timestamp out of range
+ERROR: interval out of range
SELECT timestamptz 'infinity' - timestamptz '-infinity';
?column?
----------
@@ -3230,7 +3230,7 @@ SELECT timestamptz '-infinity' - timestamptz 'infinity';
(1 row)
SELECT timestamptz '-infinity' - timestamptz '-infinity';
-ERROR: timestamp out of range
+ERROR: interval out of range
SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
?column?
----------
@@ -3257,7 +3257,7 @@ SELECT age(timestamptz '-infinity');
(1 row)
SELECT age(timestamptz 'infinity', timestamptz 'infinity');
-ERROR: timestamp out of range
+ERROR: interval out of range
SELECT age(timestamptz 'infinity', timestamptz '-infinity');
age
----------
@@ -3271,4 +3271,4 @@ SELECT age(timestamptz '-infinity', timestamptz 'infinity');
(1 row)
SELECT age(timestamptz '-infinity', timestamptz '-infinity');
-ERROR: timestamp out of range
+ERROR: interval out of range
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 63f36e069e..6b8c3c3413 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -2372,6 +2372,7 @@ create temp table datetimes(
f_timestamp timestamp
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -2381,14 +2382,16 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_time range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 1 | 11:00:00 | 1 | 3
+ 0 | 10:00:00 | 0 | 2
+ 1 | 11:00:00 | 0 | 3
2 | 12:00:00 | 1 | 4
3 | 13:00:00 | 2 | 6
4 | 14:00:00 | 3 | 6
@@ -2396,9 +2399,10 @@ window w as (order by f_time range between
6 | 15:00:00 | 4 | 7
7 | 17:00:00 | 7 | 9
8 | 18:00:00 | 7 | 10
- 9 | 19:00:00 | 8 | 10
- 10 | 20:00:00 | 9 | 10
-(10 rows)
+ 9 | 19:00:00 | 8 | 11
+ 10 | 20:00:00 | 9 | 11
+ 11 | 21:00:00 | 10 | 11
+(12 rows)
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2406,7 +2410,8 @@ window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 10 | 20:00:00 | 10 | 8
+ 11 | 21:00:00 | 11 | 9
+ 10 | 20:00:00 | 11 | 8
9 | 19:00:00 | 10 | 7
8 | 18:00:00 | 9 | 7
7 | 17:00:00 | 8 | 5
@@ -2414,9 +2419,70 @@ window w as (order by f_time desc range between
5 | 15:00:00 | 6 | 3
4 | 14:00:00 | 6 | 2
3 | 13:00:00 | 4 | 1
- 2 | 12:00:00 | 3 | 1
- 1 | 11:00:00 | 2 | 1
-(10 rows)
+ 2 | 12:00:00 | 3 | 0
+ 1 | 11:00:00 | 2 | 0
+ 0 | 10:00:00 | 1 | 0
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | 0 | 11
+ 1 | 11:00:00 | 0 | 11
+ 2 | 12:00:00 | 0 | 11
+ 3 | 13:00:00 | 0 | 11
+ 4 | 14:00:00 | 0 | 11
+ 5 | 15:00:00 | 0 | 11
+ 6 | 15:00:00 | 0 | 11
+ 7 | 17:00:00 | 0 | 11
+ 8 | 18:00:00 | 0 | 11
+ 9 | 19:00:00 | 0 | 11
+ 10 | 20:00:00 | 0 | 11
+ 11 | 21:00:00 | 0 | 11
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2424,7 +2490,8 @@ window w as (order by f_timetz range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 1 | 11:00:00+01 | 1 | 3
+ 0 | 10:00:00+01 | 0 | 2
+ 1 | 11:00:00+01 | 0 | 3
2 | 12:00:00+01 | 1 | 4
3 | 13:00:00+01 | 2 | 6
4 | 14:00:00+01 | 3 | 6
@@ -2432,9 +2499,10 @@ window w as (order by f_timetz range between
6 | 15:00:00+01 | 4 | 7
7 | 17:00:00+01 | 7 | 9
8 | 18:00:00+01 | 7 | 10
- 9 | 19:00:00+01 | 8 | 10
- 10 | 20:00:00+01 | 9 | 10
-(10 rows)
+ 9 | 19:00:00+01 | 8 | 11
+ 10 | 20:00:00+01 | 9 | 11
+ 11 | 21:00:00+01 | 10 | 11
+(12 rows)
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2442,7 +2510,8 @@ window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 10 | 20:00:00+01 | 10 | 8
+ 11 | 21:00:00+01 | 11 | 9
+ 10 | 20:00:00+01 | 11 | 8
9 | 19:00:00+01 | 10 | 7
8 | 18:00:00+01 | 9 | 7
7 | 17:00:00+01 | 8 | 5
@@ -2450,9 +2519,70 @@ window w as (order by f_timetz desc range between
5 | 15:00:00+01 | 6 | 3
4 | 14:00:00+01 | 6 | 2
3 | 13:00:00+01 | 4 | 1
- 2 | 12:00:00+01 | 3 | 1
- 1 | 11:00:00+01 | 2 | 1
-(10 rows)
+ 2 | 12:00:00+01 | 3 | 0
+ 1 | 11:00:00+01 | 2 | 0
+ 0 | 10:00:00+01 | 1 | 0
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | 0 | 11
+ 1 | 11:00:00+01 | 0 | 11
+ 2 | 12:00:00+01 | 0 | 11
+ 3 | 13:00:00+01 | 0 | 11
+ 4 | 14:00:00+01 | 0 | 11
+ 5 | 15:00:00+01 | 0 | 11
+ 6 | 15:00:00+01 | 0 | 11
+ 7 | 17:00:00+01 | 0 | 11
+ 8 | 18:00:00+01 | 0 | 11
+ 9 | 19:00:00+01 | 0 | 11
+ 10 | 20:00:00+01 | 0 | 11
+ 11 | 21:00:00+01 | 0 | 11
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2460,6 +2590,7 @@ window w as (order by f_interval range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | @ 1 year | 1 | 2
2 | @ 2 years | 1 | 3
3 | @ 3 years | 2 | 4
@@ -2470,7 +2601,8 @@ window w as (order by f_interval range between
8 | @ 8 years | 7 | 9
9 | @ 9 years | 8 | 10
10 | @ 10 years | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2478,6 +2610,7 @@ window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | @ 10 years | 10 | 9
9 | @ 9 years | 10 | 8
8 | @ 8 years | 9 | 7
@@ -2488,7 +2621,68 @@ window w as (order by f_interval desc range between
3 | @ 3 years | 4 | 2
2 | @ 2 years | 3 | 1
1 | @ 1 year | 2 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 0 | 11
+ 2 | @ 2 years | 0 | 11
+ 3 | @ 3 years | 0 | 11
+ 4 | @ 4 years | 0 | 11
+ 5 | @ 5 years | 0 | 11
+ 6 | @ 5 years | 0 | 11
+ 7 | @ 7 years | 0 | 11
+ 8 | @ 8 years | 0 | 11
+ 9 | @ 9 years | 0 | 11
+ 10 | @ 10 years | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | @ 1 year | 0 | 0
+ 2 | @ 2 years | 0 | 0
+ 3 | @ 3 years | 0 | 0
+ 4 | @ 4 years | 0 | 0
+ 5 | @ 5 years | 0 | 0
+ 6 | @ 5 years | 0 | 0
+ 7 | @ 7 years | 0 | 0
+ 8 | @ 8 years | 0 | 0
+ 9 | @ 9 years | 0 | 0
+ 10 | @ 10 years | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 11 | 11
+ 2 | @ 2 years | 11 | 11
+ 3 | @ 3 years | 11 | 11
+ 4 | @ 4 years | 11 | 11
+ 5 | @ 5 years | 11 | 11
+ 6 | @ 5 years | 11 | 11
+ 7 | @ 7 years | 11 | 11
+ 8 | @ 8 years | 11 | 11
+ 9 | @ 9 years | 11 | 11
+ 10 | @ 10 years | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2496,6 +2690,7 @@ window w as (order by f_timestamptz range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 02:23:54 2000 PDT | 1 | 3
2 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
3 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
@@ -2506,7 +2701,8 @@ window w as (order by f_timestamptz range between
8 | Thu Oct 19 02:23:54 2006 PDT | 7 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 8 | 10
10 | Sun Oct 19 02:23:54 2008 PDT | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2514,6 +2710,7 @@ window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 02:23:54 2008 PDT | 10 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 10 | 8
8 | Thu Oct 19 02:23:54 2006 PDT | 9 | 7
@@ -2524,7 +2721,68 @@ window w as (order by f_timestamptz desc range between
3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 0
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 0
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 0
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 0
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 0
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 0
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 0
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 11 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 11 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 11 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 11 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 11 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 11 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 11 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2532,6 +2790,7 @@ window w as (order by f_timestamp range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 10:23:54 2000 | 1 | 3
2 | Fri Oct 19 10:23:54 2001 | 1 | 4
3 | Fri Oct 19 10:23:54 2001 | 1 | 4
@@ -2542,7 +2801,8 @@ window w as (order by f_timestamp range between
8 | Thu Oct 19 10:23:54 2006 | 7 | 9
9 | Fri Oct 19 10:23:54 2007 | 8 | 10
10 | Sun Oct 19 10:23:54 2008 | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2550,6 +2810,7 @@ window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 10:23:54 2008 | 10 | 9
9 | Fri Oct 19 10:23:54 2007 | 10 | 8
8 | Thu Oct 19 10:23:54 2006 | 9 | 7
@@ -2560,7 +2821,68 @@ window w as (order by f_timestamp desc range between
3 | Fri Oct 19 10:23:54 2001 | 4 | 1
2 | Fri Oct 19 10:23:54 2001 | 4 | 1
1 | Thu Oct 19 10:23:54 2000 | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 0
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 0
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 0
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 0
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 0
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 0
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 0
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 11 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 11 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 11 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 11 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 11 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 11 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 11 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
@@ -4375,36 +4697,34 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
--- window function over interval, infinity and extreme values test the
--- behaviour of accumulation and elimination of these values as the window
--- slides.
+-- moving aggregates over infinite intervals
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
- ('2147483647 days 2147483646 months'), -- extreme interval value
- ('infinity'::timestamptz - now()),
- ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
(NULL::interval),
('-infinity'::interval)) v(x);
- x | curr_next_avg | prev1_curr_avg | curr_next_sum | prev1_curr_sum
-----------------------------------------------+-------------------+-------------------+---------------+----------------
- | infinity | | infinity |
- infinity | infinity | infinity | infinity | infinity
- @ 178956970 years 6 mons 2147483647 days | infinity | infinity | infinity | infinity
- infinity | infinity | infinity | infinity | infinity
- @ 178956970 years 7 mons 2147483648 days ago | infinity | infinity | infinity | infinity
- infinity | infinity | infinity | infinity | infinity
- @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
- @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
- | -infinity | @ 7 days | -infinity | @ 7 days
- -infinity | -infinity | -infinity | -infinity | -infinity
+ x | curr_next_avg | prev_curr_avg | curr_next_sum | prev_curr_sum
+------------------------------------------------------------------------------+-------------------+-------------------+---------------+---------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago | -infinity | infinity | -infinity | infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs | infinity | -infinity | infinity | -infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
(10 rows)
--should fail.
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index 414c12be1c..1bffa50fe9 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -73,11 +73,11 @@ SELECT r1.*, r2.*
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
- ('2147483647 days 2147483646 months'),
- ('2147483647 days -2147483647 months'),
+ ('2147483647 days 2147483647 months'),
+ ('2147483647 days -2147483648 months'),
('1 year'),
- ('-2147483648 days 2147483646 months'),
- ('-2147483648 days -2147483647 months');
+ ('-2147483648 days 2147483647 months'),
+ ('-2147483648 days -2147483648 months');
-- these should fail as out-of-range
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('2147483648 days');
INSERT INTO INTERVAL_TBL_OF (f1) VALUES ('-2147483649 days');
@@ -164,8 +164,7 @@ SELECT justify_hours(interval '6 months 3 days 52 hours 3 minutes 2 seconds') as
SELECT justify_days(interval '6 months 36 days 5 hours 4 minutes 3 seconds') as "7 mons 6 days 5 hours 4 mins 3 seconds";
SELECT justify_hours(interval '2147483647 days 24 hrs');
-SELECT justify_days(interval '2147483646 months 30 days');
-SELECT justify_days(interval '2147483646 months 60 days');
+SELECT justify_days(interval '2147483647 months 30 days');
-- test justify_interval()
@@ -173,14 +172,12 @@ SELECT justify_interval(interval '1 month -1 hour') as "1 month -1 hour";
SELECT justify_interval(interval '2147483647 days 24 hrs');
SELECT justify_interval(interval '-2147483648 days -24 hrs');
-SELECT justify_interval(interval '2147483646 months 30 days');
-SELECT justify_interval(interval '2147483646 months 60 days');
-SELECT justify_interval(interval '-2147483647 months -30 days');
-SELECT justify_interval(interval '-2147483647 months -60 days');
-SELECT justify_interval(interval '2147483646 months 30 days -24 hrs');
-SELECT justify_interval(interval '-2147483647 months -30 days 24 hrs');
-SELECT justify_interval(interval '2147483646 months -30 days 1440 hrs');
-SELECT justify_interval(interval '-2147483647 months 30 days -1440 hrs');
+SELECT justify_interval(interval '2147483647 months 30 days');
+SELECT justify_interval(interval '-2147483648 months -30 days');
+SELECT justify_interval(interval '2147483647 months 30 days -24 hrs');
+SELECT justify_interval(interval '-2147483648 months -30 days 24 hrs');
+SELECT justify_interval(interval '2147483647 months -30 days 1440 hrs');
+SELECT justify_interval(interval '-2147483648 months 30 days -1440 hrs');
-- test fractional second input, and detection of duplicate units
SET DATESTYLE = 'ISO';
@@ -608,6 +605,14 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+--
+-- test infinite intervals
+--
+
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+
-- infinite intervals
SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
@@ -617,54 +622,64 @@ INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2
SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
-SELECT date '1995-08-06' + interval 'infinity';
-SELECT date '1995-08-06' + interval '-infinity';
-SELECT date '1995-08-06' - interval 'infinity';
-SELECT date '1995-08-06' - interval '-infinity';
-SELECT date 'infinity' + interval 'infinity';
-SELECT date 'infinity' + interval '-infinity';
-SELECT date '-infinity' + interval 'infinity';
-SELECT date '-infinity' + interval '-infinity';
-SELECT date 'infinity' - interval 'infinity';
-SELECT date 'infinity' - interval '-infinity';
-SELECT date '-infinity' - interval 'infinity';
-SELECT date '-infinity' - interval '-infinity';
-SELECT interval 'infinity' + interval 'infinity';
-SELECT interval 'infinity' + interval '-infinity';
-SELECT interval '-infinity' + interval 'infinity';
-SELECT interval '-infinity' + interval '-infinity';
-SELECT interval 'infinity' + interval '10 days';
-SELECT interval '-infinity' + interval '10 days';
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+
SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
-SELECT interval 'infinity' - interval 'infinity';
-SELECT interval 'infinity' - interval '-infinity';
-SELECT interval '-infinity' - interval 'infinity';
-SELECT interval '-infinity' - interval '-infinity';
-SELECT interval 'infinity' - interval '10 days';
-SELECT interval '-infinity' - interval '10 days';
SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
-SELECT timestamp 'infinity' + interval 'infinity';
-SELECT timestamp 'infinity' + interval '-infinity';
-SELECT timestamp '-infinity' + interval 'infinity';
-SELECT timestamp '-infinity' + interval '-infinity';
-SELECT timestamp 'infinity' - interval 'infinity';
-SELECT timestamp 'infinity' - interval '-infinity';
-SELECT timestamp '-infinity' - interval 'infinity';
-SELECT timestamp '-infinity' - interval '-infinity';
-SELECT timestamptz '1995-08-06 12:30:15' + interval 'infinity';
-SELECT timestamptz '1995-08-06 12:30:15' + interval '-infinity';
-SELECT timestamptz '1995-08-06 12:30:15' - interval 'infinity';
-SELECT timestamptz '1995-08-06 12:30:15' - interval '-infinity';
-SELECT timestamptz 'infinity' + interval 'infinity';
-SELECT timestamptz 'infinity' + interval '-infinity';
-SELECT timestamptz '-infinity' + interval 'infinity';
-SELECT timestamptz '-infinity' + interval '-infinity';
-SELECT timestamptz 'infinity' - interval 'infinity';
-SELECT timestamptz 'infinity' - interval '-infinity';
-SELECT timestamptz '-infinity' - interval 'infinity';
-SELECT timestamptz '-infinity' - interval '-infinity';
+
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+-- time +/- infinite interval not supported
SELECT time '11:27:42' + interval 'infinity';
SELECT time '11:27:42' + interval '-infinity';
SELECT time '11:27:42' - interval 'infinity';
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 6dd9885949..c2a4cb8d64 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -673,6 +673,7 @@ create temp table datetimes(
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -682,7 +683,8 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -694,6 +696,21 @@ from datetimes
window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
@@ -704,6 +721,21 @@ from datetimes
window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
@@ -714,6 +746,21 @@ from datetimes
window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
@@ -724,6 +771,21 @@ from datetimes
window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
@@ -734,6 +796,21 @@ from datetimes
window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -1591,19 +1668,17 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
--- window function over interval, infinity and extreme values test the
--- behaviour of accumulation and elimination of these values as the window
--- slides.
+-- moving aggregates over infinite intervals
SELECT x
,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
- ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
- ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev1_curr_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
FROM (VALUES (NULL::interval),
('infinity'::interval),
- ('2147483647 days 2147483646 months'), -- extreme interval value
- ('infinity'::timestamptz - now()),
- ('-2147483648 days -2147483647 months'), -- extreme interval value
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
('infinity'::interval),
('6 days'::interval),
('7 days'::interval),
--
2.35.3
v27-0008-Add-tests-for-invalid-preceding-or-following-siz.patchapplication/x-patch; name=v27-0008-Add-tests-for-invalid-preceding-or-following-siz.patchDownload
From d17168a500f833ccb4139b9ab4b0ea2a40baa76e Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Fri, 6 Oct 2023 11:58:21 +0530
Subject: [PATCH v27 8/8] Add tests for "invalid preceding or following size in
window function"
Ashutosh Bapat
---
src/test/regress/expected/window.out | 55 ++++++++++++++++++++++++++++
src/test/regress/sql/window.sql | 55 ++++++++++++++++++++++++++++
2 files changed, 110 insertions(+)
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 6b8c3c3413..2201740c18 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -2424,6 +2424,11 @@ window w as (order by f_time desc range between
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
@@ -2484,6 +2489,12 @@ window w as (order by f_time range between
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
@@ -2524,6 +2535,11 @@ window w as (order by f_timetz desc range between
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
@@ -2584,6 +2600,12 @@ window w as (order by f_timetz range between
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
@@ -2624,6 +2646,11 @@ window w as (order by f_interval desc range between
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
@@ -2684,6 +2711,12 @@ window w as (order by f_interval range between
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
@@ -2724,6 +2757,11 @@ window w as (order by f_timestamptz desc range between
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
@@ -2784,6 +2822,12 @@ window w as (order by f_timestamptz range between
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
@@ -2824,6 +2868,11 @@ window w as (order by f_timestamp desc range between
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
@@ -2884,6 +2933,12 @@ window w as (order by f_timestamp range between
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;
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index c2a4cb8d64..437e948d6c 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -696,6 +696,11 @@ 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
@@ -711,6 +716,12 @@ 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
@@ -721,6 +732,11 @@ from datetimes
window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
@@ -736,6 +752,12 @@ 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
@@ -746,6 +768,11 @@ from datetimes
window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
@@ -761,6 +788,12 @@ 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
@@ -771,6 +804,11 @@ from datetimes
window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
@@ -786,6 +824,12 @@ 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
@@ -796,6 +840,11 @@ from datetimes
window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
@@ -811,6 +860,12 @@ 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;
--
2.35.3
On Fri, Oct 27, 2023 at 2:07 PM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Wed, 4 Oct 2023 at 14:29, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:I think we should introduce interval_out_of_range_error() function on
the lines of float_overflow_error(). Later patches introduce more
places where we raise that error. We can introduce the function as
part of those patches.I'm not convinced that it is really worth it. Note also that even with
this patch, there are still more places that throw "timestamp out of
range" errors than "interval out of range" errors.
Fine with me.
4. I tested pg_upgrade on a table with an interval with INT_MAX
months, and it was silently converted to infinity. I think that's
probably the best outcome (better than failing). However, this means
that we really should require all 3 fields of an interval to be
INT_MIN/MAX for it to be considered infinite, otherwise it would be
possible to have multiple internal representations of infinity that do
not compare as equal.My first patch was comparing all the three fields to determine whether
a given Interval value represents infinity. [3] changed that to use
only the month field. I guess that was based on the discussion at [4].
You may want to review that discussion if not already done. I am fine
either way. We should be able to change the comparison code later if
we see performance getting impacted.Before looking at the details more closely, I might have agreed with
that earlier discussion. However, given that things like pg_upgrade
have the possibility of turning formerly allowed, finite intervals
into infinity, we really need to ensure that there is only one value
equal to infinity, otherwise the results are likely to be very
confusing and counter-intuitive. That means that we have to continue
to regard intervals like INT32_MAX months + 10 days as finite.While I haven't done any performance testing, I wouldn't expect this
to have much impact. In a 64-bit build, this actually generates 2
comparisons rather than 3 -- one comparing the combined month and day
fields against a 64-bit value containing 2 copies of INT32_MAX, and
one testing the time field. In practice, only the first test will be
executed in the vast majority of cases.
Thanks for the analysis.
Something that perhaps does need discussing is the fact that
'2147483647 months 2147483647 days 9223372036854775807 usecs' is now
accepted by interval_in() and gives infinity. That's a bit ugly, but I
think it's defensible as a measure to prevent dump/restore errors from
older databases, and in any case, such an interval is outside the
documented range of supported intervals, and is a highly contrived
example, vanishingly improbable in practice.
Agreed.
Alternatively, we could have interval_in() reject this, which would
open up the possibility of dump/restore errors. It could be argued
that that's OK, for similar reasons -- the failing value is highly
unlikely/contrived, and out of the documented range. I don't like that
though. I don't think dump/restore should fail under any
circumstances, however unlikely.
I agree that dump/restore shouldn't fail, especially when restore on
one major version succeeds and fails on another.
Another alternative is to accept this input, but emit a WARNING. I
don't particularly like that either, since it's forcing a check on
every input value, just to cater for this one specific highly unlikely
input. In fact, both these alternative approaches (rejecting the
value, or emitting a warning), would impose a small performance
penalty on every interval input, which I don't think is really worth
it.
Agreed.
So overall, my preference is to just accept it. Anything else is more
work, for no practical benefit.
Ok.
--
Best Wishes,
Ashutosh Bapat
On Fri, 27 Oct 2023 at 09:38, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Tue, 10 Oct 2023 at 12:36, Ashutosh Bapat
I think we should squash 0002 to 0007.
Yes, let's do that with the next update. In fact, we may as well
squash 0002 to 0008.
I have pushed 0001. Here is 0002-0008, squashed down to one commit,
plus the change discussed to use INTERVAL_NOBEGIN() in the btree_gin
code.
It could use another read-through, and then I think it will be ready for commit.
Regards,
Dean
Attachments:
v28-0001-Support-infinite-interval-values.patchtext/x-patch; charset=US-ASCII; name=v28-0001-Support-infinite-interval-values.patchDownload
From e8a12155a8822fd76d81eb250d64e0e6a1b9fdd1 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH v28] Support infinite interval values.
Details ...
Joseph Koshakow, Jian He, and Ashutosh Bapat, reviewed by Dean Rasheed.
Discussion: https://postgr.es/m/CAAvxfHea4%2BsPybKK7agDYOMo9N-Z3J6ZXf3BOM79pFsFNcRjwA%40mail.gmail.com
---
contrib/btree_gin/btree_gin.c | 5 +-
doc/src/sgml/datatype.sgml | 4 +-
doc/src/sgml/func.sgml | 8 +-
src/backend/utils/adt/date.c | 57 +-
src/backend/utils/adt/datetime.c | 26 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 4 +
src/backend/utils/adt/timestamp.c | 1146 ++++++++++++++++-----
src/include/catalog/pg_aggregate.dat | 24 +-
src/include/catalog/pg_proc.dat | 28 +-
src/include/datatype/timestamp.h | 27 +-
src/test/regress/expected/horology.out | 73 +-
src/test/regress/expected/interval.out | 455 +++++++-
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/expected/window.out | 469 ++++++++-
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 182 +++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
src/test/regress/sql/window.sql | 167 ++-
src/tools/pgindent/typedefs.list | 1 +
22 files changed, 2510 insertions(+), 334 deletions(-)
diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c
index b09bb8df96..5e27906f80 100644
--- a/contrib/btree_gin/btree_gin.c
+++ b/contrib/btree_gin/btree_gin.c
@@ -306,9 +306,8 @@ leftmostvalue_interval(void)
{
Interval *v = palloc(sizeof(Interval));
- v->time = PG_INT64_MIN;
- v->day = PG_INT32_MIN;
- v->month = PG_INT32_MIN;
+ INTERVAL_NOBEGIN(v);
+
return IntervalPGetDatum(v);
}
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 5d23765705..123d8207f8 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2321,12 +2321,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c76ec52c55..578079dd89 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9537,7 +9537,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10434,7 +10434,11 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for <type>timestamp</type> inputs; <literal>epoch</literal>, <literal>hour</literal>,
+ <literal>day</literal>, <literal>year</literal>, <literal>decade</literal>,
+ <literal>century</literal>, and <literal>millennium</literal> for
+ <type>interval</type> inputs).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 56c7746c11..cafe3abe21 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -24,6 +24,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
+#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/supportnodes.h"
@@ -2014,6 +2015,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2058,6 +2064,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2087,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2099,7 +2115,8 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* Like time_pl_interval/time_mi_interval, we disregard the month and day
- * fields of the offset. So our test for negative should too.
+ * fields of the offset. So our test for negative should too. This also
+ * catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2109,13 +2126,14 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* We can't use time_pl_interval/time_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum = base - offset->time;
- else
- sum = base + offset->time;
+ else if (pg_add_s64_overflow(base, offset->time, &sum))
+ PG_RETURN_BOOL(less);
if (less)
PG_RETURN_BOOL(val <= sum);
@@ -2590,6 +2608,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2635,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -2639,7 +2667,8 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* Like timetz_pl_interval/timetz_mi_interval, we disregard the month and
- * day fields of the offset. So our test for negative should too.
+ * day fields of the offset. So our test for negative should too. This
+ * also catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2649,13 +2678,14 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* We can't use timetz_pl_interval/timetz_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum.time = base->time - offset->time;
- else
- sum.time = base->time + offset->time;
+ else if (pg_add_s64_overflow(base->time, offset->time, &sum.time))
+ PG_RETURN_BOOL(less);
sum.zone = base->zone;
if (less)
@@ -3105,6 +3135,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..fca9a2a6e9 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3271,6 +3271,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3574,6 +3577,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
@@ -3597,6 +3602,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+
+ /*
+ * Only reserved words corresponding to infinite
+ * intervals are accepted.
+ */
+ if (uval != DTK_LATE && uval != DTK_EARLY)
+ return DTERR_BAD_FORMAT;
+
+ /*
+ * Infinity cannot be followed by anything else. We
+ * could allow "ago" to reverse the sign of infinity
+ * but using signed infinity is more intuitive.
+ */
+ if (i != nf - 1)
+ return DTERR_BAD_FORMAT;
+
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 8131091f79..d176723d95 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..4ea5415f20 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4802,6 +4802,10 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
* Convert the month part of Interval to days using assumed
* average month length of 365.25/12.0 days. Not too
* accurate, but plenty good enough for our purposes.
+ *
+ * This also works for infinite intervals, which just have all
+ * fields set to INT_MIN/INT_MAX, and so will produce a result
+ * smaller/larger than any finite interval.
*/
return interval->time + interval->day * (double) USECS_PER_DAY +
interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 647b97aca6..23d43094e8 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -72,6 +72,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+/*
+ * The transition datatype for interval aggregates is declared as internal.
+ * It's a pointer to an IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+ int64 N; /* count of finite intervals processed */
+ Interval sumX; /* sum of finite intervals processed */
+ /* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +infinity intervals */
+ int64 nInfcount; /* count of -infinity intervals */
+} IntervalAggState;
+
+#define IA_TOTAL_COUNT(ia) \
+ ((ia)->N + (ia)->pInfcount + (ia)->nInfcount)
static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
static Timestamp dt2local(Timestamp dt, int timezone);
@@ -80,6 +95,12 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void EncodeSpecialInterval(const Interval *interval, char *str);
+static void interval_um_internal(const Interval *interval, Interval *result);
+static void finite_interval_pl(const Interval *span1, const Interval *span2,
+ Interval *result);
+static void finite_interval_mi(const Interval *span1, const Interval *span2,
+ Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +962,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +992,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1384,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.
@@ -1536,6 +1577,9 @@ make_interval(PG_FUNCTION_ARGS)
pg_add_s64_overflow(result->time, (int64) secs, &result->time))
goto out_of_range;
+ if (INTERVAL_NOT_FINITE(result))
+ goto out_of_range;
+
PG_RETURN_INTERVAL_P(result);
out_of_range:
@@ -1560,6 +1604,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+static void
+EncodeSpecialInterval(const Interval *interval, char *str)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ strcpy(str, EARLY);
+ else if (INTERVAL_IS_NOEND(interval))
+ strcpy(str, LATE);
+ else /* shouldn't happen */
+ elog(ERROR, "invalid argument for EncodeSpecialInterval");
+}
+
Datum
now(PG_FUNCTION_ARGS)
{
@@ -2038,12 +2093,21 @@ itm2interval(struct pg_itm *itm, Interval *span)
if (pg_add_s64_overflow(span->time, itm->tm_usec,
&span->time))
return -1;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
/* itmin2interval()
* Convert a pg_itm_in structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * Note: if the result is infinite, it is not treated as an overflow. This
+ * avoids any dump/reload hazards from pre-17 databases that do not support
+ * infinite intervals, but do allow finite intervals with all fields set to
+ * INT_MIN/INT_MAX (outside the documented range). Such intervals will be
+ * silently converted to +/-infinity. This may not be ideal, but seems
+ * preferable to failure, and ought to be pretty unlikely in practice.
*/
int
itmin2interval(struct pg_itm_in *itm_in, Interval *span)
@@ -2088,7 +2152,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2442,6 +2508,15 @@ interval_cmp_internal(const Interval *interval1, const Interval *interval2)
return int128_compare(span1, span2);
}
+static int
+interval_sign(const Interval *interval)
+{
+ INT128 span = interval_cmp_value(interval);
+ INT128 zero = int64_to_int128(0);
+
+ return int128_compare(span, zero);
+}
+
Datum
interval_eq(PG_FUNCTION_ARGS)
{
@@ -2714,46 +2789,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinite timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2783,6 +2883,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))
@@ -2835,6 +2938,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2858,6 +2966,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,
@@ -2875,6 +2986,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2896,6 +3012,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))
@@ -2914,6 +3033,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2934,7 +3058,30 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Timestamp result;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Adding two infinities with the same sign results in an infinite
+ * timestamp with the same sign. Adding two infinities with different
+ * signs results in an error.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3013,9 +3160,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3042,7 +3187,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Adding two infinities with the same sign results in an infinite
+ * timestamp with the same sign. Adding two infinities with different
+ * signs results in an error.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3132,9 +3300,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3185,6 +3351,27 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given 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
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3192,23 +3379,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3252,27 +3423,33 @@ 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")));
-
- 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")));
+ /*
+ * Adding two infinite intervals with the same signs results in an
+ * infinite interval with the same sign. Adding two infinite intervals
+ * with different signs results in an error.
+ */
+ 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);
PG_RETURN_INTERVAL_P(result);
}
@@ -3286,27 +3463,35 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->day = span1->day - span2->day;
- if (!SAMESIGN(span1->day, span2->day) &&
- !SAMESIGN(result->day, span1->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->time = span1->time - span2->time;
- if (!SAMESIGN(span1->time, span2->time) &&
- !SAMESIGN(result->time, span1->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite intervals with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinite intervals with the same sign results in an error.
+ */
+ if (INTERVAL_IS_NOBEGIN(span1))
+ {
+ if (INTERVAL_IS_NOBEGIN(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1))
+ {
+ if (INTERVAL_IS_NOEND(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (INTERVAL_IS_NOBEGIN(span2))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(span2))
+ INTERVAL_NOBEGIN(result);
+ else
+ finite_interval_mi(span1, span2, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3331,6 +3516,47 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results
+ * in an infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ int factor_sign,
+ result_sign;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ result_sign = interval_sign(span) * factor_sign;
+
+ if (result_sign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_sign < 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ INTERVAL_NOEND(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3391,6 +3617,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3422,6 +3653,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (isinf(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = (int32) (span->month / factor);
result->day = (int32) (span->day / factor);
@@ -3443,6 +3697,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3466,11 +3725,21 @@ in_range_timestamptz_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
TimestampTz sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = timestamptz_mi_interval_internal(base, offset, NULL);
@@ -3493,11 +3762,21 @@ in_range_timestamp_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Timestamp sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval,
@@ -3524,11 +3803,21 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Interval *sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? INTERVAL_IS_NOEND(base) : INTERVAL_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
@@ -3547,161 +3836,387 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function, that needs to compute
+ * sum and count, in the aggregate's memory context.
*
- * The transition datatype for this aggregate is a 2-element array of
- * intervals, where the first is the running sum and the second contains
- * the number of values so far in its 'time' field. This is a bit ugly
- * but it beats inventing a specialized datatype for the purpose.
+ * The function is used when the state data needs to be allocated in aggregate's
+ * context. When the state data needs to be allocated in the current memory
+ * context, we use palloc0 directly e.g. interval_avg_deserialize().
*/
+static IntervalAggState *
+makeIntervalAggState(FunctionCallInfo fcinfo)
+{
+ IntervalAggState *state;
+ MemoryContext agg_context;
+ MemoryContext old_context;
+
+ if (!AggCheckCallContext(fcinfo, &agg_context))
+ elog(ERROR, "aggregate function called in non-aggregate context");
+
+ old_context = MemoryContextSwitchTo(agg_context);
+ state = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
+
+ MemoryContextSwitchTo(old_context);
+
+ return state;
+}
+
+/*
+ * Functions to add or subtract finite intervals.
+ *
+ * These are used for normal arithmetic and aggregation of finite intervals
+ * only. Non-finite intervals require special handling.
+ */
+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))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+static void
+finite_interval_mi(const Interval *span1, const Interval *span2, Interval *result)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
+
+ if (pg_sub_s32_overflow(span1->month, span2->month, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_sub_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_sub_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+/*
+ * 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 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_accum(PG_FUNCTION_ARGS)
+interval_avg_accum(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Create the state data on the first call */
+ if (state == NULL)
+ state = makeIntervalAggState(fcinfo);
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
+ if (!PG_ARGISNULL(1))
+ do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ 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_avg_combine(PG_FUNCTION_ARGS)
+{
+ 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);
+
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
+
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
+
+ PG_RETURN_POINTER(state1);
+ }
+
+ 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");
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ sstate = PG_GETARG_BYTEA_PP(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /*
+ * 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));
+
+ /* N */
+ result->N = pq_getmsgint64(&buf);
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ /* sumX */
+ result->sumX.time = pq_getmsgint64(&buf);
+ result->sumX.day = pq_getmsgint(&buf, 4);
+ result->sumX.month = pq_getmsgint(&buf, 4);
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
- PG_RETURN_ARRAYTYPE_P(result);
+ pq_getmsgend(&buf);
+
+ 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;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Should not get here with no state */
+ if (state == NULL)
+ elog(ERROR, "interval_avg_accum_inv called with NULL state");
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ if (!PG_ARGISNULL(1))
+ do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
+
+ PG_RETURN_POINTER(state);
+}
+
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 || state->nInfcount > 0)
+ {
+ Interval *result;
+
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else
+ INTERVAL_NOBEGIN(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
+ IntervalPGetDatum(&state->sumX),
+ Float8GetDatum((double) state->N));
}
+/* sum(interval) aggregate final function */
+Datum
+interval_sum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ Interval *result;
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
+
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else if (state->nInfcount > 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ memcpy(result, &state->sumX, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
@@ -3726,8 +4241,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinite timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3846,8 +4388,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinite timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3972,6 +4541,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4155,6 +4729,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4393,6 +4972,12 @@ interval_trunc(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ memcpy(result, interval, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
VARSIZE_ANY_EXHDR(units),
false);
@@ -4737,7 +5322,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5011,7 +5596,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5251,6 +5836,58 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite interval. Returns
+ * +/-Infinity if that is the appropriate result, otherwise returns zero
+ * (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative)
+{
+ if ((type != UNITS) && (type != RESERV))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unit \"%s\" not recognized for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_YEAR:
+ case DTK_DECADE:
+ case DTK_CENTURY:
+ case DTK_MILLENNIUM:
+ case DTK_EPOCH:
+ if (isNegative)
+ return -get_float8_infinity();
+ else
+ return get_float8_infinity();
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unit \"%s\" not supported for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+ return 0.0; /* keep compiler quiet */
+ }
+}
/* interval_part() and extract_interval()
* Extract specified field from interval.
@@ -5275,6 +5912,33 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval));
+
+ if (r != 0.0)
+ {
+ if (retnumeric)
+ {
+ if (r < 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("-Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ else if (r > 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ }
+ else
+ PG_RETURN_FLOAT8(r);
+ }
+ else
+ PG_RETURN_NULL();
+ }
+
if (type == UNITS)
{
interval2itm(*interval, tm);
@@ -5517,6 +6181,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5747,6 +6418,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5783,7 +6461,6 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
Timestamp finish = PG_GETARG_TIMESTAMP(1);
Interval *step = PG_GETARG_INTERVAL_P(2);
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5806,13 +6483,18 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
fctx->step = *step;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
@@ -5864,7 +6546,6 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
Interval *step = PG_GETARG_INTERVAL_P(2);
text *zone = (PG_NARGS() == 4) ? PG_GETARG_TEXT_PP(3) : NULL;
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5888,13 +6569,18 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
fctx->attimezone = zone ? lookup_timezone(zone) : session_timezone;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e1a17cddd8 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
{ 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',
@@ -72,10 +73,13 @@
{ 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',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bc41e92677..455ed16c79 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4917,17 +4917,29 @@
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 => '3813', descr => 'aggregate serial function',
+ proname => 'interval_avg_serialize', prorettype => 'bytea',
+ proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
+{ oid => '3814', 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 => '8069', 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' },
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..b63acc0a2f 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -151,7 +151,7 @@ struct pg_itm_in
#define TIMESTAMP_INFINITY PG_INT64_MAX
/*
- * Historically these alias for infinity have been used.
+ * Historically these aliases for infinity have been used.
*/
#define DT_NOBEGIN TIMESTAMP_MINUS_INFINITY
#define DT_NOEND TIMESTAMP_INFINITY
@@ -168,6 +168,31 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+/*
+ * Infinite intervals are represented by setting all fields to the minimum or
+ * maximum integer values.
+ */
+#define INTERVAL_NOBEGIN(i) \
+ do { \
+ (i)->time = PG_INT64_MIN; \
+ (i)->day = PG_INT32_MIN; \
+ (i)->month = PG_INT32_MIN; \
+ } while (0)
+
+#define INTERVAL_IS_NOBEGIN(i) \
+ ((i)->month == PG_INT32_MIN && (i)->day == PG_INT32_MIN && (i)->time == PG_INT64_MIN)
+
+#define INTERVAL_NOEND(i) \
+ do { \
+ (i)->time = PG_INT64_MAX; \
+ (i)->day = PG_INT32_MAX; \
+ (i)->month = PG_INT32_MAX; \
+ } while (0)
+
+#define INTERVAL_IS_NOEND(i) \
+ ((i)->month == PG_INT32_MAX && (i)->day == PG_INT32_MAX && (i)->time == PG_INT64_MAX)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 75d19d6594..00cd3e84f1 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,7 +268,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);
@@ -304,6 +347,17 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
(5 rows)
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column?
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- Floating point arithmetic rounding errors can lead to unexpected results,
@@ -386,12 +440,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +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;
@@ -1616,31 +1674,31 @@ select make_interval(mins := -1, secs := -9223372036800.0);
ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
--------------------------------------------------------------------
- -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+ -178956970 years -7 mons -2147483648 days -2562047788:00:54.775808
(1 row)
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
---------------------------------------------------
- -178956970-8 -2147483648 -2562047788:00:54.775808
+ -178956970-7 -2147483648 -2562047788:00:54.775808
(1 row)
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
-----------------------------------------------------
- P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+ P-178956970Y-7M-2147483648DT-2562047788H-54.775808S
(1 row)
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
------------------------------------------------------------------------------
- @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+ @ 178956970 years 7 mons 2147483648 days 2562047788 hours 54.775808 secs ago
(1 row)
-- check that '30 days' equals '1 month' according to the hash function
@@ -1719,19 +1777,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1805,7 +1865,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1814,6 +1876,292 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+--
+-- test infinite intervals
+--
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+ interval
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+ interval
+--------------------------------------------------------------------------
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs
+(1 row)
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ interval
+----------
+ infinity
+(1 row)
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ date | interval | plus | minus
+------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 | -infinity | -infinity | infinity
+ 1995-08-06 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+ interval1 | interval2 | plus | minus
+-----------+-----------+-----------------------+-----------------------
+ -infinity | -infinity | -infinity | interval out of range
+ -infinity | @ 10 days | -infinity | -infinity
+ -infinity | infinity | interval out of range | -infinity
+ @ 2 mons | -infinity | -infinity | infinity
+ @ 2 mons | @ 10 days | @ 2 mons 10 days | @ 2 mons -10 days
+ @ 2 mons | infinity | infinity | -infinity
+ infinity | -infinity | interval out of range | infinity
+ infinity | @ 10 days | infinity | infinity
+ infinity | infinity | infinity | interval out of range
+(9 rows)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamp | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamptz | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT time '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+ lhs | rhs | lt | le | eq | gt | ge | ne
+-----------+-------------------------+----+----+----+----+----+----
+ infinity | infinity | f | t | t | f | t | f
+ -infinity | infinity | t | t | f | f | f | t
+ infinity | -infinity | f | f | f | t | t | t
+ -infinity | -infinity | f | t | t | f | t | f
+ infinity | @ 1 year 2 days 3 hours | f | f | f | t | t | t
+ -infinity | @ 1 year 2 days 3 hours | t | t | f | f | f | t
+(6 rows)
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | um | mul | mul_neg | mul_inf | mul_inf_neg | div | div_neg
+-----------+-----------+-----------+-----------+-----------+-------------+-----------+-----------
+ infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity
+ -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity
+(2 rows)
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+ERROR: interval out of range
+SELECT interval 'infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+ERROR: interval out of range
+SELECT interval 'infinity' * 0;
+ERROR: interval out of range
+SELECT interval '-infinity' * 0;
+ERROR: interval out of range
+SELECT interval '0 days' * 'infinity'::float;
+ERROR: interval out of range
+SELECT interval '0 days' * '-infinity'::float;
+ERROR: interval out of range
+SELECT interval '5 days' * 'infinity'::float;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '5 days' * '-infinity'::float;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+ERROR: interval out of range
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | date_trunc
+-----------+------------
+ infinity | infinity
+ -infinity | -infinity
+(2 rows)
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | justify_days | justify_hours | justify_interval
+-----------+--------------+---------------+------------------
+ infinity | infinity | infinity | infinity
+ -infinity | -infinity | -infinity | -infinity
+(2 rows)
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT 'infinity'::interval::time;
+ERROR: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
ERROR: invalid input syntax for type interval: "42 days 2 seconds ago ago"
@@ -1832,3 +2180,42 @@ SELECT INTERVAL '1 year months days 5 hours';
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..835f0e5762 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '-infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp 'infinity', timestamp 'infinity');
+ERROR: interval out of range
+select age(timestamp 'infinity', timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 2ca2101dd4..a084357480 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3210,3 +3214,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+ERROR: interval out of range
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..2201740c18 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -2372,6 +2372,7 @@ create temp table datetimes(
f_timestamp timestamp
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -2381,14 +2382,16 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_time range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 1 | 11:00:00 | 1 | 3
+ 0 | 10:00:00 | 0 | 2
+ 1 | 11:00:00 | 0 | 3
2 | 12:00:00 | 1 | 4
3 | 13:00:00 | 2 | 6
4 | 14:00:00 | 3 | 6
@@ -2396,9 +2399,10 @@ window w as (order by f_time range between
6 | 15:00:00 | 4 | 7
7 | 17:00:00 | 7 | 9
8 | 18:00:00 | 7 | 10
- 9 | 19:00:00 | 8 | 10
- 10 | 20:00:00 | 9 | 10
-(10 rows)
+ 9 | 19:00:00 | 8 | 11
+ 10 | 20:00:00 | 9 | 11
+ 11 | 21:00:00 | 10 | 11
+(12 rows)
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2406,7 +2410,8 @@ window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 10 | 20:00:00 | 10 | 8
+ 11 | 21:00:00 | 11 | 9
+ 10 | 20:00:00 | 11 | 8
9 | 19:00:00 | 10 | 7
8 | 18:00:00 | 9 | 7
7 | 17:00:00 | 8 | 5
@@ -2414,17 +2419,90 @@ window w as (order by f_time desc range between
5 | 15:00:00 | 6 | 3
4 | 14:00:00 | 6 | 2
3 | 13:00:00 | 4 | 1
- 2 | 12:00:00 | 3 | 1
- 1 | 11:00:00 | 2 | 1
-(10 rows)
+ 2 | 12:00:00 | 3 | 0
+ 1 | 11:00:00 | 2 | 0
+ 0 | 10:00:00 | 1 | 0
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | 0 | 11
+ 1 | 11:00:00 | 0 | 11
+ 2 | 12:00:00 | 0 | 11
+ 3 | 13:00:00 | 0 | 11
+ 4 | 14:00:00 | 0 | 11
+ 5 | 15:00:00 | 0 | 11
+ 6 | 15:00:00 | 0 | 11
+ 7 | 17:00:00 | 0 | 11
+ 8 | 18:00:00 | 0 | 11
+ 9 | 19:00:00 | 0 | 11
+ 10 | 20:00:00 | 0 | 11
+ 11 | 21:00:00 | 0 | 11
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 1 | 11:00:00+01 | 1 | 3
+ 0 | 10:00:00+01 | 0 | 2
+ 1 | 11:00:00+01 | 0 | 3
2 | 12:00:00+01 | 1 | 4
3 | 13:00:00+01 | 2 | 6
4 | 14:00:00+01 | 3 | 6
@@ -2432,9 +2510,10 @@ window w as (order by f_timetz range between
6 | 15:00:00+01 | 4 | 7
7 | 17:00:00+01 | 7 | 9
8 | 18:00:00+01 | 7 | 10
- 9 | 19:00:00+01 | 8 | 10
- 10 | 20:00:00+01 | 9 | 10
-(10 rows)
+ 9 | 19:00:00+01 | 8 | 11
+ 10 | 20:00:00+01 | 9 | 11
+ 11 | 21:00:00+01 | 10 | 11
+(12 rows)
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2442,7 +2521,8 @@ window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 10 | 20:00:00+01 | 10 | 8
+ 11 | 21:00:00+01 | 11 | 9
+ 10 | 20:00:00+01 | 11 | 8
9 | 19:00:00+01 | 10 | 7
8 | 18:00:00+01 | 9 | 7
7 | 17:00:00+01 | 8 | 5
@@ -2450,16 +2530,89 @@ window w as (order by f_timetz desc range between
5 | 15:00:00+01 | 6 | 3
4 | 14:00:00+01 | 6 | 2
3 | 13:00:00+01 | 4 | 1
- 2 | 12:00:00+01 | 3 | 1
- 1 | 11:00:00+01 | 2 | 1
-(10 rows)
+ 2 | 12:00:00+01 | 3 | 0
+ 1 | 11:00:00+01 | 2 | 0
+ 0 | 10:00:00+01 | 1 | 0
+(12 rows)
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | 0 | 11
+ 1 | 11:00:00+01 | 0 | 11
+ 2 | 12:00:00+01 | 0 | 11
+ 3 | 13:00:00+01 | 0 | 11
+ 4 | 14:00:00+01 | 0 | 11
+ 5 | 15:00:00+01 | 0 | 11
+ 6 | 15:00:00+01 | 0 | 11
+ 7 | 17:00:00+01 | 0 | 11
+ 8 | 18:00:00+01 | 0 | 11
+ 9 | 19:00:00+01 | 0 | 11
+ 10 | 20:00:00+01 | 0 | 11
+ 11 | 21:00:00+01 | 0 | 11
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | @ 1 year | 1 | 2
2 | @ 2 years | 1 | 3
3 | @ 3 years | 2 | 4
@@ -2470,7 +2623,8 @@ window w as (order by f_interval range between
8 | @ 8 years | 7 | 9
9 | @ 9 years | 8 | 10
10 | @ 10 years | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2478,6 +2632,7 @@ window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | @ 10 years | 10 | 9
9 | @ 9 years | 10 | 8
8 | @ 8 years | 9 | 7
@@ -2488,14 +2643,87 @@ window w as (order by f_interval desc range between
3 | @ 3 years | 4 | 2
2 | @ 2 years | 3 | 1
1 | @ 1 year | 2 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 0 | 11
+ 2 | @ 2 years | 0 | 11
+ 3 | @ 3 years | 0 | 11
+ 4 | @ 4 years | 0 | 11
+ 5 | @ 5 years | 0 | 11
+ 6 | @ 5 years | 0 | 11
+ 7 | @ 7 years | 0 | 11
+ 8 | @ 8 years | 0 | 11
+ 9 | @ 9 years | 0 | 11
+ 10 | @ 10 years | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | @ 1 year | 0 | 0
+ 2 | @ 2 years | 0 | 0
+ 3 | @ 3 years | 0 | 0
+ 4 | @ 4 years | 0 | 0
+ 5 | @ 5 years | 0 | 0
+ 6 | @ 5 years | 0 | 0
+ 7 | @ 7 years | 0 | 0
+ 8 | @ 8 years | 0 | 0
+ 9 | @ 9 years | 0 | 0
+ 10 | @ 10 years | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 11 | 11
+ 2 | @ 2 years | 11 | 11
+ 3 | @ 3 years | 11 | 11
+ 4 | @ 4 years | 11 | 11
+ 5 | @ 5 years | 11 | 11
+ 6 | @ 5 years | 11 | 11
+ 7 | @ 7 years | 11 | 11
+ 8 | @ 8 years | 11 | 11
+ 9 | @ 9 years | 11 | 11
+ 10 | @ 10 years | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 02:23:54 2000 PDT | 1 | 3
2 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
3 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
@@ -2506,7 +2734,8 @@ window w as (order by f_timestamptz range between
8 | Thu Oct 19 02:23:54 2006 PDT | 7 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 8 | 10
10 | Sun Oct 19 02:23:54 2008 PDT | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2514,6 +2743,7 @@ window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 02:23:54 2008 PDT | 10 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 10 | 8
8 | Thu Oct 19 02:23:54 2006 PDT | 9 | 7
@@ -2524,14 +2754,87 @@ window w as (order by f_timestamptz desc range between
3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 0
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 0
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 0
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 0
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 0
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 0
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 0
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 11 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 11 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 11 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 11 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 11 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 11 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 11 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 10:23:54 2000 | 1 | 3
2 | Fri Oct 19 10:23:54 2001 | 1 | 4
3 | Fri Oct 19 10:23:54 2001 | 1 | 4
@@ -2542,7 +2845,8 @@ window w as (order by f_timestamp range between
8 | Thu Oct 19 10:23:54 2006 | 7 | 9
9 | Fri Oct 19 10:23:54 2007 | 8 | 10
10 | Sun Oct 19 10:23:54 2008 | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2550,6 +2854,7 @@ window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 10:23:54 2008 | 10 | 9
9 | Fri Oct 19 10:23:54 2007 | 10 | 8
8 | Thu Oct 19 10:23:54 2006 | 9 | 7
@@ -2560,8 +2865,80 @@ window w as (order by f_timestamp desc range between
3 | Fri Oct 19 10:23:54 2001 | 4 | 1
2 | Fri Oct 19 10:23:54 2001 | 4 | 1
1 | Thu Oct 19 10:23:54 2000 | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 0
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 0
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 0
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 0
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 0
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 0
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 0
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 11 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 11 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 11 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 11 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 11 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 11 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 11 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -4375,6 +4752,52 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev_curr_avg | curr_next_sum | prev_curr_sum
+------------------------------------------------------------------------------+-------------------+-------------------+---------------+---------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago | -infinity | infinity | -infinity | infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs | infinity | -infinity | infinity | -infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
+(10 rows)
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
i | sum
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index a0a373f08b..1bffa50fe9 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -95,6 +99,9 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
@@ -140,7 +147,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -528,13 +535,13 @@ select make_interval(mins := -1, secs := -9223372036800.0);
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
-- check that '30 days' equals '1 month' according to the hash function
select '30 days'::interval = '1 month'::interval as t;
@@ -598,6 +605,157 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+--
+-- test infinite intervals
+--
+
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+SELECT time '11:27:42' + interval '-infinity';
+SELECT time '11:27:42' - interval 'infinity';
+SELECT time '11:27:42' - interval '-infinity';
+SELECT timetz '11:27:42' + interval 'infinity';
+SELECT timetz '11:27:42' + interval '-infinity';
+SELECT timetz '11:27:42' - interval 'infinity';
+SELECT timetz '11:27:42' - interval '-infinity';
+
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+SELECT interval 'infinity' * 'nan';
+SELECT interval '-infinity' * 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+SELECT interval 'infinity' * 0;
+SELECT interval '-infinity' * 0;
+SELECT interval '0 days' * 'infinity'::float;
+SELECT interval '0 days' * '-infinity'::float;
+SELECT interval '5 days' * 'infinity'::float;
+SELECT interval '5 days' * '-infinity'::float;
+
+SELECT interval 'infinity' / 'infinity';
+SELECT interval 'infinity' / '-infinity';
+SELECT interval 'infinity' / 'nan';
+SELECT interval '-infinity' / 'infinity';
+SELECT interval '-infinity' / '-infinity';
+SELECT interval '-infinity' / 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+
+SELECT 'infinity'::interval::time;
+SELECT '-infinity'::interval::time;
+
+SELECT to_char('infinity'::interval, 'YYYY');
+SELECT to_char('-infinity'::interval, 'YYYY');
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
SELECT INTERVAL '2 minutes ago 5 days';
@@ -605,3 +763,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index cdc57bc160..a2dcd5f5d8 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -642,3 +644,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..437e948d6c 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -673,6 +673,7 @@ create temp table datetimes(
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -682,7 +683,8 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -694,6 +696,32 @@ from datetimes
window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
@@ -704,6 +732,32 @@ from datetimes
window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
@@ -714,6 +768,32 @@ from datetimes
window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
@@ -724,6 +804,32 @@ from datetimes
window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
@@ -734,6 +840,32 @@ from datetimes
window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -1591,6 +1723,39 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 87c1aee379..236683e5ab 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1205,6 +1205,7 @@ IntegerSet
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup
--
2.35.3
On Sun, Oct 29, 2023 at 10:09 PM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Fri, 27 Oct 2023 at 09:38, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Tue, 10 Oct 2023 at 12:36, Ashutosh Bapat
I think we should squash 0002 to 0007.
Yes, let's do that with the next update. In fact, we may as well
squash 0002 to 0008.I have pushed 0001. Here is 0002-0008, squashed down to one commit,
plus the change discussed to use INTERVAL_NOBEGIN() in the btree_gin
code.
Thanks. I had to leave this halfway on Friday because of severe tooth ache.
I was actually working on the test part of 0009. Though the code
changes are not required, I think it's better to have a test case
along the same lines as the tests added by Tomas (now committed in
8da86d62a11269e926765c0d6ef6f532b2b8b749). I have attached 0002 for
the same along with your v28.
It could use another read-through, and then I think it will be ready for commit.
Thanks. I went through the whole patch again and am quite fine with it.
Here's my version of commit message
```
Support Infinite interval values
Interval datatype uses the same input and output representation for
infinite intervals as other datatypes representing time that support
infinity. An interval larger than any other interval is represented by
string literal 'infinity' or '+infinity'. An interval which is smaller
than any other interval is represented as '-infinity'. Internally
positive infinity is represented as maximum values supported by all
the member types of Interval datastructure and negative infinity is
represented as minimum values set to all the members. INTERVAL_NOBEGIN
and INTERVAL_NOEND macros can be used to set an Interval structure to
negative and positive infinity respectively. INTERVAL_IS_NOBEGIN and
INTERVAL_IS_NOEND macros are used to test respective values.
INTERVAL_NOT_FINITE macro is used to test whether a given Interval
value is infinite.
Implementation of all known operators now handles infinite interval
values along with operations related to BRIN index, windowing and
selectivity. Regression tests are added to test these implementation.
If a user has stored interval values '-2147483648 months -2147483648
days -9223372036854775807 us' and '2147483647 months 2147483647 days
9223372036854775806 us' in PostgreSQL versions 16 or earlier. Those
values will turn into '-infinity' and 'infinity' respectively after
upgrading to v17. These values are outside the documented range
supported by interval datatype and thus there's almost no possibility
of this occurrence. But it will be good to watch for these values
during upgrade.
```
--
Best Wishes,
Ashutosh Bapat
Attachments:
v28-0001-Support-infinite-interval-values.patchtext/x-patch; charset=US-ASCII; name=v28-0001-Support-infinite-interval-values.patchDownload
From e8a12155a8822fd76d81eb250d64e0e6a1b9fdd1 Mon Sep 17 00:00:00 2001
From: Joseph Koshakow <koshy44@gmail.com>
Date: Sat, 18 Mar 2023 13:59:34 -0400
Subject: [PATCH v28] Support infinite interval values.
Details ...
Joseph Koshakow, Jian He, and Ashutosh Bapat, reviewed by Dean Rasheed.
Discussion: https://postgr.es/m/CAAvxfHea4%2BsPybKK7agDYOMo9N-Z3J6ZXf3BOM79pFsFNcRjwA%40mail.gmail.com
---
contrib/btree_gin/btree_gin.c | 5 +-
doc/src/sgml/datatype.sgml | 4 +-
doc/src/sgml/func.sgml | 8 +-
src/backend/utils/adt/date.c | 57 +-
src/backend/utils/adt/datetime.c | 26 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 4 +
src/backend/utils/adt/timestamp.c | 1146 ++++++++++++++++-----
src/include/catalog/pg_aggregate.dat | 24 +-
src/include/catalog/pg_proc.dat | 28 +-
src/include/datatype/timestamp.h | 27 +-
src/test/regress/expected/horology.out | 73 +-
src/test/regress/expected/interval.out | 455 +++++++-
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/expected/window.out | 469 ++++++++-
src/test/regress/sql/horology.sql | 5 +-
src/test/regress/sql/interval.sql | 182 +++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
src/test/regress/sql/window.sql | 167 ++-
src/tools/pgindent/typedefs.list | 1 +
22 files changed, 2510 insertions(+), 334 deletions(-)
diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c
index b09bb8df96..5e27906f80 100644
--- a/contrib/btree_gin/btree_gin.c
+++ b/contrib/btree_gin/btree_gin.c
@@ -306,9 +306,8 @@ leftmostvalue_interval(void)
{
Interval *v = palloc(sizeof(Interval));
- v->time = PG_INT64_MIN;
- v->day = PG_INT32_MIN;
- v->month = PG_INT32_MIN;
+ INTERVAL_NOBEGIN(v);
+
return IntervalPGetDatum(v);
}
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 5d23765705..123d8207f8 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2321,12 +2321,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index c76ec52c55..578079dd89 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9537,7 +9537,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10434,7 +10434,11 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for <type>timestamp</type> inputs; <literal>epoch</literal>, <literal>hour</literal>,
+ <literal>day</literal>, <literal>year</literal>, <literal>decade</literal>,
+ <literal>century</literal>, and <literal>millennium</literal> for
+ <type>interval</type> inputs).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 56c7746c11..cafe3abe21 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -24,6 +24,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
+#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/supportnodes.h"
@@ -2014,6 +2015,11 @@ interval_time(PG_FUNCTION_ARGS)
TimeADT result;
int64 days;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("time out of range")));
+
result = span->time;
if (result >= USECS_PER_DAY)
{
@@ -2058,6 +2064,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2076,6 +2087,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2099,7 +2115,8 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* Like time_pl_interval/time_mi_interval, we disregard the month and day
- * fields of the offset. So our test for negative should too.
+ * fields of the offset. So our test for negative should too. This also
+ * catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2109,13 +2126,14 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* We can't use time_pl_interval/time_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum = base - offset->time;
- else
- sum = base + offset->time;
+ else if (pg_add_s64_overflow(base, offset->time, &sum))
+ PG_RETURN_BOOL(less);
if (less)
PG_RETURN_BOOL(val <= sum);
@@ -2590,6 +2608,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2612,6 +2635,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -2639,7 +2667,8 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* Like timetz_pl_interval/timetz_mi_interval, we disregard the month and
- * day fields of the offset. So our test for negative should too.
+ * day fields of the offset. So our test for negative should too. This
+ * also catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2649,13 +2678,14 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* We can't use timetz_pl_interval/timetz_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum.time = base->time - offset->time;
- else
- sum.time = base->time + offset->time;
+ else if (pg_add_s64_overflow(base->time, offset->time, &sum.time))
+ PG_RETURN_BOOL(less);
sum.zone = base->zone;
if (less)
@@ -3105,6 +3135,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..fca9a2a6e9 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3271,6 +3271,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3574,6 +3577,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
@@ -3597,6 +3602,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+
+ /*
+ * Only reserved words corresponding to infinite
+ * intervals are accepted.
+ */
+ if (uval != DTK_LATE && uval != DTK_EARLY)
+ return DTERR_BAD_FORMAT;
+
+ /*
+ * Infinity cannot be followed by anything else. We
+ * could allow "ago" to reverse the sign of infinity
+ * but using signed infinity is more intuitive.
+ */
+ if (i != nf - 1)
+ return DTERR_BAD_FORMAT;
+
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 8131091f79..d176723d95 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..4ea5415f20 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4802,6 +4802,10 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
* Convert the month part of Interval to days using assumed
* average month length of 365.25/12.0 days. Not too
* accurate, but plenty good enough for our purposes.
+ *
+ * This also works for infinite intervals, which just have all
+ * fields set to INT_MIN/INT_MAX, and so will produce a result
+ * smaller/larger than any finite interval.
*/
return interval->time + interval->day * (double) USECS_PER_DAY +
interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 647b97aca6..23d43094e8 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -72,6 +72,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+/*
+ * The transition datatype for interval aggregates is declared as internal.
+ * It's a pointer to an IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+ int64 N; /* count of finite intervals processed */
+ Interval sumX; /* sum of finite intervals processed */
+ /* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +infinity intervals */
+ int64 nInfcount; /* count of -infinity intervals */
+} IntervalAggState;
+
+#define IA_TOTAL_COUNT(ia) \
+ ((ia)->N + (ia)->pInfcount + (ia)->nInfcount)
static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
static Timestamp dt2local(Timestamp dt, int timezone);
@@ -80,6 +95,12 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void EncodeSpecialInterval(const Interval *interval, char *str);
+static void interval_um_internal(const Interval *interval, Interval *result);
+static void finite_interval_pl(const Interval *span1, const Interval *span2,
+ Interval *result);
+static void finite_interval_mi(const Interval *span1, const Interval *span2,
+ Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +962,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +992,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1384,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.
@@ -1536,6 +1577,9 @@ make_interval(PG_FUNCTION_ARGS)
pg_add_s64_overflow(result->time, (int64) secs, &result->time))
goto out_of_range;
+ if (INTERVAL_NOT_FINITE(result))
+ goto out_of_range;
+
PG_RETURN_INTERVAL_P(result);
out_of_range:
@@ -1560,6 +1604,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+static void
+EncodeSpecialInterval(const Interval *interval, char *str)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ strcpy(str, EARLY);
+ else if (INTERVAL_IS_NOEND(interval))
+ strcpy(str, LATE);
+ else /* shouldn't happen */
+ elog(ERROR, "invalid argument for EncodeSpecialInterval");
+}
+
Datum
now(PG_FUNCTION_ARGS)
{
@@ -2038,12 +2093,21 @@ itm2interval(struct pg_itm *itm, Interval *span)
if (pg_add_s64_overflow(span->time, itm->tm_usec,
&span->time))
return -1;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
/* itmin2interval()
* Convert a pg_itm_in structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * Note: if the result is infinite, it is not treated as an overflow. This
+ * avoids any dump/reload hazards from pre-17 databases that do not support
+ * infinite intervals, but do allow finite intervals with all fields set to
+ * INT_MIN/INT_MAX (outside the documented range). Such intervals will be
+ * silently converted to +/-infinity. This may not be ideal, but seems
+ * preferable to failure, and ought to be pretty unlikely in practice.
*/
int
itmin2interval(struct pg_itm_in *itm_in, Interval *span)
@@ -2088,7 +2152,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2442,6 +2508,15 @@ interval_cmp_internal(const Interval *interval1, const Interval *interval2)
return int128_compare(span1, span2);
}
+static int
+interval_sign(const Interval *interval)
+{
+ INT128 span = interval_cmp_value(interval);
+ INT128 zero = int64_to_int128(0);
+
+ return int128_compare(span, zero);
+}
+
Datum
interval_eq(PG_FUNCTION_ARGS)
{
@@ -2714,46 +2789,71 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
-
- if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinite timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("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 (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
- result->month = 0;
- result->day = 0;
+ result->month = 0;
+ result->day = 0;
- /*----------
- * This is wrong, but removing it breaks a lot of regression tests.
- * For example:
- *
- * test=> SET timezone = 'EST5EDT';
- * test=> SELECT
- * test-> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz);
- * ?column?
- * ----------------
- * 1 day 01:00:00
- * (1 row)
- *
- * so adding that to the first timestamp gets:
- *
- * test=> SELECT
- * test-> ('2005-10-29 13:22:00-04'::timestamptz +
- * test(> ('2005-10-30 13:22:00-05'::timestamptz -
- * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
- * timezone
- * --------------------
- * 2005-10-30 14:22:00
- * (1 row)
- *----------
- */
- result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
- IntervalPGetDatum(result)));
+ /*----------
+ * This is wrong, but removing it breaks a lot of regression tests.
+ * For example:
+ *
+ * test=> SET timezone = 'EST5EDT';
+ * test=> SELECT
+ * test-> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz);
+ * ?column?
+ * ----------------
+ * 1 day 01:00:00
+ * (1 row)
+ *
+ * so adding that to the first timestamp gets:
+ *
+ * test=> SELECT
+ * test-> ('2005-10-29 13:22:00-04'::timestamptz +
+ * test(> ('2005-10-30 13:22:00-05'::timestamptz -
+ * test(> '2005-10-29 13:22:00-04'::timestamptz)) at time zone 'EST';
+ * timezone
+ * --------------------
+ * 2005-10-30 14:22:00
+ * (1 row)
+ *----------
+ */
+ result = DatumGetIntervalP(DirectFunctionCall1(interval_justify_hours,
+ IntervalPGetDatum(result)));
+ }
PG_RETURN_INTERVAL_P(result);
}
@@ -2783,6 +2883,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))
@@ -2835,6 +2938,11 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2858,6 +2966,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,
@@ -2875,6 +2986,11 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2896,6 +3012,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))
@@ -2914,6 +3033,11 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->month++;
}
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -2934,7 +3058,30 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Timestamp result;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Adding two infinities with the same sign results in an infinite
+ * timestamp with the same sign. Adding two infinities with different
+ * signs results in an error.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3013,9 +3160,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3042,7 +3187,30 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Adding two infinities with the same sign results in an infinite
+ * timestamp with the same sign. Adding two infinities with different
+ * signs results in an error.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3132,9 +3300,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3185,6 +3351,27 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* Negates the given 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
+ {
+ result->time = -interval->time;
+ result->day = -interval->day;
+ result->month = -interval->month;
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3192,23 +3379,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3252,27 +3423,33 @@ 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")));
-
- 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")));
+ /*
+ * Adding two infinite intervals with the same signs results in an
+ * infinite interval with the same sign. Adding two infinite intervals
+ * with different signs results in an error.
+ */
+ 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);
PG_RETURN_INTERVAL_P(result);
}
@@ -3286,27 +3463,35 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->day = span1->day - span2->day;
- if (!SAMESIGN(span1->day, span2->day) &&
- !SAMESIGN(result->day, span1->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->time = span1->time - span2->time;
- if (!SAMESIGN(span1->time, span2->time) &&
- !SAMESIGN(result->time, span1->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Subtracting two infinite intervals with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinite intervals with the same sign results in an error.
+ */
+ if (INTERVAL_IS_NOBEGIN(span1))
+ {
+ if (INTERVAL_IS_NOBEGIN(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1))
+ {
+ if (INTERVAL_IS_NOEND(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (INTERVAL_IS_NOBEGIN(span2))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(span2))
+ INTERVAL_NOBEGIN(result);
+ else
+ finite_interval_mi(span1, span2, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3331,6 +3516,47 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Multiplying an infinite value by another non-zero value always results
+ * in an infinite value but, may change the sign.
+ */
+ 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);
+ }
+ else if (isinf(factor))
+ {
+ int factor_sign,
+ result_sign;
+
+ factor_sign = factor >= 0.0 ? 1 : -1;
+ result_sign = interval_sign(span) * factor_sign;
+
+ if (result_sign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (result_sign < 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ INTERVAL_NOEND(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3391,6 +3617,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3422,6 +3653,29 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ /*
+ * Dividing infinite interval by finite number keeps it infinite but may
+ * change the sign.
+ */
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (isinf(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = (int32) (span->month / factor);
result->day = (int32) (span->day / factor);
@@ -3443,6 +3697,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3466,11 +3725,21 @@ in_range_timestamptz_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
TimestampTz sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = timestamptz_mi_interval_internal(base, offset, NULL);
@@ -3493,11 +3762,21 @@ in_range_timestamp_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Timestamp sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval,
@@ -3524,11 +3803,21 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Interval *sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? INTERVAL_IS_NOEND(base) : INTERVAL_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
@@ -3547,161 +3836,387 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function, that needs to compute
+ * sum and count, in the aggregate's memory context.
*
- * The transition datatype for this aggregate is a 2-element array of
- * intervals, where the first is the running sum and the second contains
- * the number of values so far in its 'time' field. This is a bit ugly
- * but it beats inventing a specialized datatype for the purpose.
+ * The function is used when the state data needs to be allocated in aggregate's
+ * context. When the state data needs to be allocated in the current memory
+ * context, we use palloc0 directly e.g. interval_avg_deserialize().
*/
+static IntervalAggState *
+makeIntervalAggState(FunctionCallInfo fcinfo)
+{
+ IntervalAggState *state;
+ MemoryContext agg_context;
+ MemoryContext old_context;
+
+ if (!AggCheckCallContext(fcinfo, &agg_context))
+ elog(ERROR, "aggregate function called in non-aggregate context");
+
+ old_context = MemoryContextSwitchTo(agg_context);
+ state = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
+
+ MemoryContextSwitchTo(old_context);
+
+ return state;
+}
+
+/*
+ * Functions to add or subtract finite intervals.
+ *
+ * These are used for normal arithmetic and aggregation of finite intervals
+ * only. Non-finite intervals require special handling.
+ */
+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))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+static void
+finite_interval_mi(const Interval *span1, const Interval *span2, Interval *result)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
+
+ if (pg_sub_s32_overflow(span1->month, span2->month, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_sub_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_sub_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+/*
+ * 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 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_accum(PG_FUNCTION_ARGS)
+interval_avg_accum(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Create the state data on the first call */
+ if (state == NULL)
+ state = makeIntervalAggState(fcinfo);
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
+ if (!PG_ARGISNULL(1))
+ do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ 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_avg_combine(PG_FUNCTION_ARGS)
+{
+ 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);
+
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
+
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
+
+ PG_RETURN_POINTER(state1);
+ }
+
+ 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");
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ sstate = PG_GETARG_BYTEA_PP(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /*
+ * 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));
+
+ /* N */
+ result->N = pq_getmsgint64(&buf);
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ /* sumX */
+ result->sumX.time = pq_getmsgint64(&buf);
+ result->sumX.day = pq_getmsgint(&buf, 4);
+ result->sumX.month = pq_getmsgint(&buf, 4);
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
- PG_RETURN_ARRAYTYPE_P(result);
+ pq_getmsgend(&buf);
+
+ 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;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Should not get here with no state */
+ if (state == NULL)
+ elog(ERROR, "interval_avg_accum_inv called with NULL state");
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ if (!PG_ARGISNULL(1))
+ do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
+
+ PG_RETURN_POINTER(state);
+}
+
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 || state->nInfcount > 0)
+ {
+ Interval *result;
+
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else
+ INTERVAL_NOBEGIN(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
+ IntervalPGetDatum(&state->sumX),
+ Float8GetDatum((double) state->N));
}
+/* sum(interval) aggregate final function */
+Datum
+interval_sum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ Interval *result;
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
+
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else if (state->nInfcount > 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ memcpy(result, &state->sumX, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
@@ -3726,8 +4241,35 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinite timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3846,8 +4388,35 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Subtracting two infinite timestamps with different signs results in an
+ * infinite interval with the same sign as the left operand. Subtracting
+ * two infinite timestamps with the same sign results in an error.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3972,6 +4541,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4155,6 +4729,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4393,6 +4972,12 @@ interval_trunc(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ memcpy(result, interval, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
VARSIZE_ANY_EXHDR(units),
false);
@@ -4737,7 +5322,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5011,7 +5596,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5251,6 +5836,58 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite interval. Returns
+ * +/-Infinity if that is the appropriate result, otherwise returns zero
+ * (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative)
+{
+ if ((type != UNITS) && (type != RESERV))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unit \"%s\" not recognized for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_YEAR:
+ case DTK_DECADE:
+ case DTK_CENTURY:
+ case DTK_MILLENNIUM:
+ case DTK_EPOCH:
+ if (isNegative)
+ return -get_float8_infinity();
+ else
+ return get_float8_infinity();
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unit \"%s\" not supported for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+ return 0.0; /* keep compiler quiet */
+ }
+}
/* interval_part() and extract_interval()
* Extract specified field from interval.
@@ -5275,6 +5912,33 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval));
+
+ if (r != 0.0)
+ {
+ if (retnumeric)
+ {
+ if (r < 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("-Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ else if (r > 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ }
+ else
+ PG_RETURN_FLOAT8(r);
+ }
+ else
+ PG_RETURN_NULL();
+ }
+
if (type == UNITS)
{
interval2itm(*interval, tm);
@@ -5517,6 +6181,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5747,6 +6418,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5783,7 +6461,6 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
Timestamp finish = PG_GETARG_TIMESTAMP(1);
Interval *step = PG_GETARG_INTERVAL_P(2);
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5806,13 +6483,18 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
fctx->step = *step;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
@@ -5864,7 +6546,6 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
Interval *step = PG_GETARG_INTERVAL_P(2);
text *zone = (PG_NARGS() == 4) ? PG_GETARG_TEXT_PP(3) : NULL;
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5888,13 +6569,18 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
fctx->attimezone = zone ? lookup_timezone(zone) : session_timezone;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e1a17cddd8 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
{ 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',
@@ -72,10 +73,13 @@
{ 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',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bc41e92677..455ed16c79 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4917,17 +4917,29 @@
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 => '3813', descr => 'aggregate serial function',
+ proname => 'interval_avg_serialize', prorettype => 'bytea',
+ proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
+{ oid => '3814', 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 => '8069', 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' },
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..b63acc0a2f 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -151,7 +151,7 @@ struct pg_itm_in
#define TIMESTAMP_INFINITY PG_INT64_MAX
/*
- * Historically these alias for infinity have been used.
+ * Historically these aliases for infinity have been used.
*/
#define DT_NOBEGIN TIMESTAMP_MINUS_INFINITY
#define DT_NOEND TIMESTAMP_INFINITY
@@ -168,6 +168,31 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+/*
+ * Infinite intervals are represented by setting all fields to the minimum or
+ * maximum integer values.
+ */
+#define INTERVAL_NOBEGIN(i) \
+ do { \
+ (i)->time = PG_INT64_MIN; \
+ (i)->day = PG_INT32_MIN; \
+ (i)->month = PG_INT32_MIN; \
+ } while (0)
+
+#define INTERVAL_IS_NOBEGIN(i) \
+ ((i)->month == PG_INT32_MIN && (i)->day == PG_INT32_MIN && (i)->time == PG_INT64_MIN)
+
+#define INTERVAL_NOEND(i) \
+ do { \
+ (i)->time = PG_INT64_MAX; \
+ (i)->day = PG_INT32_MAX; \
+ (i)->month = PG_INT32_MAX; \
+ } while (0)
+
+#define INTERVAL_IS_NOEND(i) \
+ ((i)->month == PG_INT32_MAX && (i)->day == PG_INT32_MAX && (i)->time == PG_INT64_MAX)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..eaf44b960f 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1159,6 +1159,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1266,6 +1267,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1541,9 +1543,26 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
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
@@ -1704,14 +1723,46 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
+ WHERE isfinite(t.f1) OR 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
@@ -1872,7 +1923,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 75d19d6594..00cd3e84f1 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,7 +268,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);
@@ -304,6 +347,17 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
(5 rows)
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column?
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- Floating point arithmetic rounding errors can lead to unexpected results,
@@ -386,12 +440,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +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;
@@ -1616,31 +1674,31 @@ select make_interval(mins := -1, secs := -9223372036800.0);
ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
--------------------------------------------------------------------
- -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+ -178956970 years -7 mons -2147483648 days -2562047788:00:54.775808
(1 row)
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
---------------------------------------------------
- -178956970-8 -2147483648 -2562047788:00:54.775808
+ -178956970-7 -2147483648 -2562047788:00:54.775808
(1 row)
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
-----------------------------------------------------
- P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+ P-178956970Y-7M-2147483648DT-2562047788H-54.775808S
(1 row)
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
------------------------------------------------------------------------------
- @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+ @ 178956970 years 7 mons 2147483648 days 2562047788 hours 54.775808 secs ago
(1 row)
-- check that '30 days' equals '1 month' according to the hash function
@@ -1719,19 +1777,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1805,7 +1865,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1814,6 +1876,292 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+--
+-- test infinite intervals
+--
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+ interval
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+ interval
+--------------------------------------------------------------------------
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs
+(1 row)
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ interval
+----------
+ infinity
+(1 row)
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ date | interval | plus | minus
+------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 | -infinity | -infinity | infinity
+ 1995-08-06 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+ interval1 | interval2 | plus | minus
+-----------+-----------+-----------------------+-----------------------
+ -infinity | -infinity | -infinity | interval out of range
+ -infinity | @ 10 days | -infinity | -infinity
+ -infinity | infinity | interval out of range | -infinity
+ @ 2 mons | -infinity | -infinity | infinity
+ @ 2 mons | @ 10 days | @ 2 mons 10 days | @ 2 mons -10 days
+ @ 2 mons | infinity | infinity | -infinity
+ infinity | -infinity | interval out of range | infinity
+ infinity | @ 10 days | infinity | infinity
+ infinity | infinity | infinity | interval out of range
+(9 rows)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamp | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamptz | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT time '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+ lhs | rhs | lt | le | eq | gt | ge | ne
+-----------+-------------------------+----+----+----+----+----+----
+ infinity | infinity | f | t | t | f | t | f
+ -infinity | infinity | t | t | f | f | f | t
+ infinity | -infinity | f | f | f | t | t | t
+ -infinity | -infinity | f | t | t | f | t | f
+ infinity | @ 1 year 2 days 3 hours | f | f | f | t | t | t
+ -infinity | @ 1 year 2 days 3 hours | t | t | f | f | f | t
+(6 rows)
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | um | mul | mul_neg | mul_inf | mul_inf_neg | div | div_neg
+-----------+-----------+-----------+-----------+-----------+-------------+-----------+-----------
+ infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity
+ -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity
+(2 rows)
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+ERROR: interval out of range
+SELECT interval 'infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+ERROR: interval out of range
+SELECT interval 'infinity' * 0;
+ERROR: interval out of range
+SELECT interval '-infinity' * 0;
+ERROR: interval out of range
+SELECT interval '0 days' * 'infinity'::float;
+ERROR: interval out of range
+SELECT interval '0 days' * '-infinity'::float;
+ERROR: interval out of range
+SELECT interval '5 days' * 'infinity'::float;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '5 days' * '-infinity'::float;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+ERROR: interval out of range
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | date_trunc
+-----------+------------
+ infinity | infinity
+ -infinity | -infinity
+(2 rows)
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | justify_days | justify_hours | justify_interval
+-----------+--------------+---------------+------------------
+ infinity | infinity | infinity | infinity
+ -infinity | -infinity | -infinity | -infinity
+(2 rows)
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT 'infinity'::interval::time;
+ERROR: time out of range
+SELECT '-infinity'::interval::time;
+ERROR: time out of range
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
ERROR: invalid input syntax for type interval: "42 days 2 seconds ago ago"
@@ -1832,3 +2180,42 @@ SELECT INTERVAL '1 year months days 5 hours';
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..835f0e5762 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '-infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp 'infinity', timestamp 'infinity');
+ERROR: interval out of range
+select age(timestamp 'infinity', timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 2ca2101dd4..a084357480 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3210,3 +3214,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+ERROR: interval out of range
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..2201740c18 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -2372,6 +2372,7 @@ create temp table datetimes(
f_timestamp timestamp
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -2381,14 +2382,16 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_time range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 1 | 11:00:00 | 1 | 3
+ 0 | 10:00:00 | 0 | 2
+ 1 | 11:00:00 | 0 | 3
2 | 12:00:00 | 1 | 4
3 | 13:00:00 | 2 | 6
4 | 14:00:00 | 3 | 6
@@ -2396,9 +2399,10 @@ window w as (order by f_time range between
6 | 15:00:00 | 4 | 7
7 | 17:00:00 | 7 | 9
8 | 18:00:00 | 7 | 10
- 9 | 19:00:00 | 8 | 10
- 10 | 20:00:00 | 9 | 10
-(10 rows)
+ 9 | 19:00:00 | 8 | 11
+ 10 | 20:00:00 | 9 | 11
+ 11 | 21:00:00 | 10 | 11
+(12 rows)
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2406,7 +2410,8 @@ window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 10 | 20:00:00 | 10 | 8
+ 11 | 21:00:00 | 11 | 9
+ 10 | 20:00:00 | 11 | 8
9 | 19:00:00 | 10 | 7
8 | 18:00:00 | 9 | 7
7 | 17:00:00 | 8 | 5
@@ -2414,17 +2419,90 @@ window w as (order by f_time desc range between
5 | 15:00:00 | 6 | 3
4 | 14:00:00 | 6 | 2
3 | 13:00:00 | 4 | 1
- 2 | 12:00:00 | 3 | 1
- 1 | 11:00:00 | 2 | 1
-(10 rows)
+ 2 | 12:00:00 | 3 | 0
+ 1 | 11:00:00 | 2 | 0
+ 0 | 10:00:00 | 1 | 0
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | 0 | 11
+ 1 | 11:00:00 | 0 | 11
+ 2 | 12:00:00 | 0 | 11
+ 3 | 13:00:00 | 0 | 11
+ 4 | 14:00:00 | 0 | 11
+ 5 | 15:00:00 | 0 | 11
+ 6 | 15:00:00 | 0 | 11
+ 7 | 17:00:00 | 0 | 11
+ 8 | 18:00:00 | 0 | 11
+ 9 | 19:00:00 | 0 | 11
+ 10 | 20:00:00 | 0 | 11
+ 11 | 21:00:00 | 0 | 11
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 1 | 11:00:00+01 | 1 | 3
+ 0 | 10:00:00+01 | 0 | 2
+ 1 | 11:00:00+01 | 0 | 3
2 | 12:00:00+01 | 1 | 4
3 | 13:00:00+01 | 2 | 6
4 | 14:00:00+01 | 3 | 6
@@ -2432,9 +2510,10 @@ window w as (order by f_timetz range between
6 | 15:00:00+01 | 4 | 7
7 | 17:00:00+01 | 7 | 9
8 | 18:00:00+01 | 7 | 10
- 9 | 19:00:00+01 | 8 | 10
- 10 | 20:00:00+01 | 9 | 10
-(10 rows)
+ 9 | 19:00:00+01 | 8 | 11
+ 10 | 20:00:00+01 | 9 | 11
+ 11 | 21:00:00+01 | 10 | 11
+(12 rows)
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2442,7 +2521,8 @@ window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 10 | 20:00:00+01 | 10 | 8
+ 11 | 21:00:00+01 | 11 | 9
+ 10 | 20:00:00+01 | 11 | 8
9 | 19:00:00+01 | 10 | 7
8 | 18:00:00+01 | 9 | 7
7 | 17:00:00+01 | 8 | 5
@@ -2450,16 +2530,89 @@ window w as (order by f_timetz desc range between
5 | 15:00:00+01 | 6 | 3
4 | 14:00:00+01 | 6 | 2
3 | 13:00:00+01 | 4 | 1
- 2 | 12:00:00+01 | 3 | 1
- 1 | 11:00:00+01 | 2 | 1
-(10 rows)
+ 2 | 12:00:00+01 | 3 | 0
+ 1 | 11:00:00+01 | 2 | 0
+ 0 | 10:00:00+01 | 1 | 0
+(12 rows)
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | 0 | 11
+ 1 | 11:00:00+01 | 0 | 11
+ 2 | 12:00:00+01 | 0 | 11
+ 3 | 13:00:00+01 | 0 | 11
+ 4 | 14:00:00+01 | 0 | 11
+ 5 | 15:00:00+01 | 0 | 11
+ 6 | 15:00:00+01 | 0 | 11
+ 7 | 17:00:00+01 | 0 | 11
+ 8 | 18:00:00+01 | 0 | 11
+ 9 | 19:00:00+01 | 0 | 11
+ 10 | 20:00:00+01 | 0 | 11
+ 11 | 21:00:00+01 | 0 | 11
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | @ 1 year | 1 | 2
2 | @ 2 years | 1 | 3
3 | @ 3 years | 2 | 4
@@ -2470,7 +2623,8 @@ window w as (order by f_interval range between
8 | @ 8 years | 7 | 9
9 | @ 9 years | 8 | 10
10 | @ 10 years | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2478,6 +2632,7 @@ window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | @ 10 years | 10 | 9
9 | @ 9 years | 10 | 8
8 | @ 8 years | 9 | 7
@@ -2488,14 +2643,87 @@ window w as (order by f_interval desc range between
3 | @ 3 years | 4 | 2
2 | @ 2 years | 3 | 1
1 | @ 1 year | 2 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 0 | 11
+ 2 | @ 2 years | 0 | 11
+ 3 | @ 3 years | 0 | 11
+ 4 | @ 4 years | 0 | 11
+ 5 | @ 5 years | 0 | 11
+ 6 | @ 5 years | 0 | 11
+ 7 | @ 7 years | 0 | 11
+ 8 | @ 8 years | 0 | 11
+ 9 | @ 9 years | 0 | 11
+ 10 | @ 10 years | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | @ 1 year | 0 | 0
+ 2 | @ 2 years | 0 | 0
+ 3 | @ 3 years | 0 | 0
+ 4 | @ 4 years | 0 | 0
+ 5 | @ 5 years | 0 | 0
+ 6 | @ 5 years | 0 | 0
+ 7 | @ 7 years | 0 | 0
+ 8 | @ 8 years | 0 | 0
+ 9 | @ 9 years | 0 | 0
+ 10 | @ 10 years | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 11 | 11
+ 2 | @ 2 years | 11 | 11
+ 3 | @ 3 years | 11 | 11
+ 4 | @ 4 years | 11 | 11
+ 5 | @ 5 years | 11 | 11
+ 6 | @ 5 years | 11 | 11
+ 7 | @ 7 years | 11 | 11
+ 8 | @ 8 years | 11 | 11
+ 9 | @ 9 years | 11 | 11
+ 10 | @ 10 years | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 02:23:54 2000 PDT | 1 | 3
2 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
3 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
@@ -2506,7 +2734,8 @@ window w as (order by f_timestamptz range between
8 | Thu Oct 19 02:23:54 2006 PDT | 7 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 8 | 10
10 | Sun Oct 19 02:23:54 2008 PDT | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2514,6 +2743,7 @@ window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 02:23:54 2008 PDT | 10 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 10 | 8
8 | Thu Oct 19 02:23:54 2006 PDT | 9 | 7
@@ -2524,14 +2754,87 @@ window w as (order by f_timestamptz desc range between
3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 0
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 0
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 0
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 0
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 0
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 0
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 0
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 11 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 11 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 11 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 11 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 11 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 11 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 11 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 10:23:54 2000 | 1 | 3
2 | Fri Oct 19 10:23:54 2001 | 1 | 4
3 | Fri Oct 19 10:23:54 2001 | 1 | 4
@@ -2542,7 +2845,8 @@ window w as (order by f_timestamp range between
8 | Thu Oct 19 10:23:54 2006 | 7 | 9
9 | Fri Oct 19 10:23:54 2007 | 8 | 10
10 | Sun Oct 19 10:23:54 2008 | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2550,6 +2854,7 @@ window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 10:23:54 2008 | 10 | 9
9 | Fri Oct 19 10:23:54 2007 | 10 | 8
8 | Thu Oct 19 10:23:54 2006 | 9 | 7
@@ -2560,8 +2865,80 @@ window w as (order by f_timestamp desc range between
3 | Fri Oct 19 10:23:54 2001 | 4 | 1
2 | Fri Oct 19 10:23:54 2001 | 4 | 1
1 | Thu Oct 19 10:23:54 2000 | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 0
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 0
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 0
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 0
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 0
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 0
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 0
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 11 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 11 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 11 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 11 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 11 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 11 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 11 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -4375,6 +4752,52 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev_curr_avg | curr_next_sum | prev_curr_sum
+------------------------------------------------------------------------------+-------------------+-------------------+---------------+---------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago | -infinity | infinity | -infinity | infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs | infinity | -infinity | infinity | -infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
+(10 rows)
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
i | sum
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..0b4071f316 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -211,10 +211,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -287,11 +289,12 @@ SELECT f1 AS "timestamp"
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY plus, "timestamp", "interval";
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)
+ WHERE isfinite(t.f1) OR isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index a0a373f08b..1bffa50fe9 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -95,6 +99,9 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
@@ -140,7 +147,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -528,13 +535,13 @@ select make_interval(mins := -1, secs := -9223372036800.0);
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
-- check that '30 days' equals '1 month' according to the hash function
select '30 days'::interval = '1 month'::interval as t;
@@ -598,6 +605,157 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+--
+-- test infinite intervals
+--
+
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+SELECT time '11:27:42' + interval '-infinity';
+SELECT time '11:27:42' - interval 'infinity';
+SELECT time '11:27:42' - interval '-infinity';
+SELECT timetz '11:27:42' + interval 'infinity';
+SELECT timetz '11:27:42' + interval '-infinity';
+SELECT timetz '11:27:42' - interval 'infinity';
+SELECT timetz '11:27:42' - interval '-infinity';
+
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+SELECT interval 'infinity' * 'nan';
+SELECT interval '-infinity' * 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+SELECT interval 'infinity' * 0;
+SELECT interval '-infinity' * 0;
+SELECT interval '0 days' * 'infinity'::float;
+SELECT interval '0 days' * '-infinity'::float;
+SELECT interval '5 days' * 'infinity'::float;
+SELECT interval '5 days' * '-infinity'::float;
+
+SELECT interval 'infinity' / 'infinity';
+SELECT interval 'infinity' / '-infinity';
+SELECT interval 'infinity' / 'nan';
+SELECT interval '-infinity' / 'infinity';
+SELECT interval '-infinity' / '-infinity';
+SELECT interval '-infinity' / 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+
+SELECT 'infinity'::interval::time;
+SELECT '-infinity'::interval::time;
+
+SELECT to_char('infinity'::interval, 'YYYY');
+SELECT to_char('-infinity'::interval, 'YYYY');
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
SELECT INTERVAL '2 minutes ago 5 days';
@@ -605,3 +763,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index cdc57bc160..a2dcd5f5d8 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -642,3 +644,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..437e948d6c 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -673,6 +673,7 @@ create temp table datetimes(
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -682,7 +683,8 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -694,6 +696,32 @@ from datetimes
window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
@@ -704,6 +732,32 @@ from datetimes
window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
@@ -714,6 +768,32 @@ from datetimes
window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
@@ -724,6 +804,32 @@ from datetimes
window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
@@ -734,6 +840,32 @@ from datetimes
window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -1591,6 +1723,39 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 87c1aee379..236683e5ab 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1205,6 +1205,7 @@ IntegerSet
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup
--
2.35.3
0002-BRIN-test-for-infinite-interval-values-20231030.patchtext/x-patch; charset=US-ASCII; name=0002-BRIN-test-for-infinite-interval-values-20231030.patchDownload
From ed8279dec49169922c36a1d1696035919611185c Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Mon, 30 Oct 2023 12:37:48 +0530
Subject: [PATCH 2/2] BRIN test for infinite interval values
---
src/test/regress/expected/brin_multi.out | 28 ++++++++++++++++++++++++
src/test/regress/sql/brin_multi.sql | 19 ++++++++++++++++
2 files changed, 47 insertions(+)
diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
index 80801cd4ee..8f4c95b9e6 100644
--- a/src/test/regress/expected/brin_multi.out
+++ b/src/test/regress/expected/brin_multi.out
@@ -941,6 +941,34 @@ SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
Index Cond: (a = '@ 30 years'::interval)
(4 rows)
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+SET enable_seqscan = off;
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years ago'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years ago'::interval)
+(4 rows)
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years'::interval)
+(4 rows)
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
index b666dbad67..116106d30f 100644
--- a/src/test/regress/sql/brin_multi.sql
+++ b/src/test/regress/sql/brin_multi.sql
@@ -682,6 +682,25 @@ SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan = off;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
--
2.25.1
On Mon, Oct 30, 2023 at 6:01 PM Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
Here's my version of commit message
```
Support Infinite interval valuesInterval datatype uses the same input and output representation for
infinite intervals as other datatypes representing time that support
infinity. An interval larger than any other interval is represented by
string literal 'infinity' or '+infinity'. An interval which is smaller
than any other interval is represented as '-infinity'. Internally
positive infinity is represented as maximum values supported by all
the member types of Interval datastructure and negative infinity is
represented as minimum values set to all the members. INTERVAL_NOBEGIN
and INTERVAL_NOEND macros can be used to set an Interval structure to
negative and positive infinity respectively. INTERVAL_IS_NOBEGIN and
INTERVAL_IS_NOEND macros are used to test respective values.
INTERVAL_NOT_FINITE macro is used to test whether a given Interval
value is infinite.Implementation of all known operators now handles infinite interval
values along with operations related to BRIN index, windowing and
selectivity. Regression tests are added to test these implementation.If a user has stored interval values '-2147483648 months -2147483648
days -9223372036854775807 us' and '2147483647 months 2147483647 days
9223372036854775806 us' in PostgreSQL versions 16 or earlier. Those
values will turn into '-infinity' and 'infinity' respectively after
upgrading to v17. These values are outside the documented range
supported by interval datatype and thus there's almost no possibility
of this occurrence. But it will be good to watch for these values
during upgrade.
```
the message is plain enough. I can understand it. thanks!
On Mon, 30 Oct 2023 at 10:01, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
Thanks. I went through the whole patch again and am quite fine with it.
Here's my version of commit message
Going over this again, I noticed a pre-existing integer overflow
problem in interval_time(), which patch 0001 attached fixes.
I squashed the other 2 patches (main patch + new BRIN tests) together
into 0002, and did another copy-editing pass over it. I found one
other issue, which is that overflow checking in interval_um() had gone
missing, and there didn't seem to be any regression test coverage for
that, so I added some.
I also changed the error message in interval_time to "cannot convert
infinite interval to time", which is slightly more informative, and
more consistent with the nearby error messages in time_pl_interval()
and time_mi_interval().
Finally, I rewrote the commit message in slightly higher-level terms,
but that's really up to the committer to decide on.
I'm marking this as ready-for-committer. I'll probably pick it up
myself in a few days, unless another committer claims it first.
Regards,
Dean
Attachments:
v29-0001-Avoid-integer-overflow-hazard-in-interval_time.patchtext/x-patch; charset=US-ASCII; name=v29-0001-Avoid-integer-overflow-hazard-in-interval_time.patchDownload
From aadc2e7306044204a25805e7361539dec2f6a3da Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Sun, 5 Nov 2023 14:45:13 +0000
Subject: [PATCH v29 1/2] Avoid integer overflow hazard in interval_time().
When casting an interval to a time, the original code suffered from
64-bit integer overflow for inputs with a sufficiently large negative
"time" field, leading to bogus results.
Fix by rewriting the algorithm in a simpler form, that more obviously
cannot overflow. While at it, improve the test coverage to test
negative interval inputs.
---
src/backend/utils/adt/date.c | 15 +++------------
src/test/regress/expected/horology.out | 12 ++++++++++++
src/test/regress/sql/horology.sql | 2 ++
3 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 56c7746c11..544e1d32bf 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2012,19 +2012,10 @@ interval_time(PG_FUNCTION_ARGS)
{
Interval *span = PG_GETARG_INTERVAL_P(0);
TimeADT result;
- int64 days;
- result = span->time;
- if (result >= USECS_PER_DAY)
- {
- days = result / USECS_PER_DAY;
- result -= days * USECS_PER_DAY;
- }
- else if (result < 0)
- {
- days = (-result + USECS_PER_DAY - 1) / USECS_PER_DAY;
- result += days * USECS_PER_DAY;
- }
+ result = span->time % USECS_PER_DAY;
+ if (result < 0)
+ result += USECS_PER_DAY;
PG_RETURN_TIMEADT(result);
}
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..8f52661096 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -981,6 +981,18 @@ SELECT CAST(interval '02:03' AS time) AS "02:03:00";
02:03:00
(1 row)
+SELECT CAST(interval '-02:03' AS time) AS "21:57:00";
+ 21:57:00
+----------
+ 21:57:00
+(1 row)
+
+SELECT CAST(interval '-9223372022400000000 us' AS time) AS "00:00:00";
+ 00:00:00
+----------
+ 00:00:00
+(1 row)
+
SELECT time '01:30' + interval '02:01' AS "03:31:00";
03:31:00
----------
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..39a35a6b7c 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -182,6 +182,8 @@ SELECT d1 - interval '1 year' AS one_year FROM TIMESTAMPTZ_TBL;
SELECT CAST(time '01:02' AS interval) AS "+01:02";
SELECT CAST(interval '02:03' AS time) AS "02:03:00";
+SELECT CAST(interval '-02:03' AS time) AS "21:57:00";
+SELECT CAST(interval '-9223372022400000000 us' AS time) AS "00:00:00";
SELECT time '01:30' + interval '02:01' AS "03:31:00";
SELECT time '01:30' - interval '02:01' AS "23:29:00";
SELECT time '02:30' + interval '36:01' AS "14:31:00";
--
2.35.3
v29-0002-Support-infinity-in-the-interval-data-type.patchtext/x-patch; charset=US-ASCII; name=v29-0002-Support-infinity-in-the-interval-data-type.patchDownload
From 9e0c8e67955b5d8890c6fb099b6941dcd12f6c8f Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Mon, 6 Nov 2023 13:52:27 +0000
Subject: [PATCH v29 2/2] Support +/- infinity in the interval data type.
This adds support for infinity to the interval data type, using the
same input/output representation as the other date/time data types
that support infinity. This allows various arithmetic operations on
infinite dates, timestamps and intervals.
The new values are represented by setting all fields of the interval
to INT32/64_MIN for -infinity, and INT32/64_MAX for +infinity. This
ensures that they compare as less/greater than all other interval
values, without the need for any special-case comparison code.
Note that, since those 2 values were formerly accepted as legal finite
intervals, pg_upgrade and dump/restore from an old database will turn
them from finite to infinite intervals. That seems OK, since those
exact values should be extremely rare in practice, and they are
outside the documented range supported by the interval type, which
gives us a certain amount of leeway.
Joseph Koshakow, Jian He, and Ashutosh Bapat, reviewed by Dean Rasheed.
Discussion: https://postgr.es/m/CAAvxfHea4%2BsPybKK7agDYOMo9N-Z3J6ZXf3BOM79pFsFNcRjwA%40mail.gmail.com
---
contrib/btree_gin/btree_gin.c | 5 +-
doc/src/sgml/datatype.sgml | 4 +-
doc/src/sgml/func.sgml | 8 +-
src/backend/utils/adt/date.c | 57 +-
src/backend/utils/adt/datetime.c | 26 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 4 +
src/backend/utils/adt/timestamp.c | 1080 +++++++++++++++++----
src/include/catalog/pg_aggregate.dat | 24 +-
src/include/catalog/pg_proc.dat | 28 +-
src/include/datatype/timestamp.h | 27 +-
src/test/regress/expected/brin_multi.out | 28 +
src/test/regress/expected/horology.out | 71 +-
src/test/regress/expected/interval.out | 499 +++++++++-
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/expected/window.out | 469 ++++++++-
src/test/regress/sql/brin_multi.sql | 19 +
src/test/regress/sql/horology.sql | 3 +-
src/test/regress/sql/interval.sql | 193 +++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
src/test/regress/sql/window.sql | 167 +++-
src/tools/pgindent/typedefs.list | 1 +
24 files changed, 2577 insertions(+), 299 deletions(-)
diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c
index b09bb8df96..5e27906f80 100644
--- a/contrib/btree_gin/btree_gin.c
+++ b/contrib/btree_gin/btree_gin.c
@@ -306,9 +306,8 @@ leftmostvalue_interval(void)
{
Interval *v = palloc(sizeof(Interval));
- v->time = PG_INT64_MIN;
- v->day = PG_INT32_MIN;
- v->month = PG_INT32_MIN;
+ INTERVAL_NOBEGIN(v);
+
return IntervalPGetDatum(v);
}
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 5a6cfbd94d..4aba6d19ee 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2328,12 +2328,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index a6fcac0824..1156eea6ee 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9553,7 +9553,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10450,7 +10450,11 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for <type>timestamp</type> inputs; <literal>epoch</literal>, <literal>hour</literal>,
+ <literal>day</literal>, <literal>year</literal>, <literal>decade</literal>,
+ <literal>century</literal>, and <literal>millennium</literal> for
+ <type>interval</type> inputs).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 544e1d32bf..13745a0adc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -24,6 +24,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
+#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/supportnodes.h"
@@ -2013,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(0);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot convert infinite interval to time")));
+
result = span->time % USECS_PER_DAY;
if (result < 0)
result += USECS_PER_DAY;
@@ -2049,6 +2055,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2067,6 +2078,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2090,7 +2106,8 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* Like time_pl_interval/time_mi_interval, we disregard the month and day
- * fields of the offset. So our test for negative should too.
+ * fields of the offset. So our test for negative should too. This also
+ * catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2100,13 +2117,14 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* We can't use time_pl_interval/time_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum = base - offset->time;
- else
- sum = base + offset->time;
+ else if (pg_add_s64_overflow(base, offset->time, &sum))
+ PG_RETURN_BOOL(less);
if (less)
PG_RETURN_BOOL(val <= sum);
@@ -2581,6 +2599,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2603,6 +2626,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -2630,7 +2658,8 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* Like timetz_pl_interval/timetz_mi_interval, we disregard the month and
- * day fields of the offset. So our test for negative should too.
+ * day fields of the offset. So our test for negative should too. This
+ * also catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2640,13 +2669,14 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* We can't use timetz_pl_interval/timetz_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum.time = base->time - offset->time;
- else
- sum.time = base->time + offset->time;
+ else if (pg_add_s64_overflow(base->time, offset->time, &sum.time))
+ PG_RETURN_BOOL(less);
sum.zone = base->zone;
if (less)
@@ -3096,6 +3126,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..fca9a2a6e9 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3271,6 +3271,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3574,6 +3577,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
@@ -3597,6 +3602,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+
+ /*
+ * Only reserved words corresponding to infinite
+ * intervals are accepted.
+ */
+ if (uval != DTK_LATE && uval != DTK_EARLY)
+ return DTERR_BAD_FORMAT;
+
+ /*
+ * Infinity cannot be followed by anything else. We
+ * could allow "ago" to reverse the sign of infinity
+ * but using signed infinity is more intuitive.
+ */
+ if (i != nf - 1)
+ return DTERR_BAD_FORMAT;
+
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 8131091f79..d176723d95 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..4ea5415f20 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4802,6 +4802,10 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
* Convert the month part of Interval to days using assumed
* average month length of 365.25/12.0 days. Not too
* accurate, but plenty good enough for our purposes.
+ *
+ * This also works for infinite intervals, which just have all
+ * fields set to INT_MIN/INT_MAX, and so will produce a result
+ * smaller/larger than any finite interval.
*/
return interval->time + interval->day * (double) USECS_PER_DAY +
interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 647b97aca6..0aee842752 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -72,6 +72,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+/*
+ * The transition datatype for interval aggregates is declared as internal.
+ * It's a pointer to an IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+ int64 N; /* count of finite intervals processed */
+ Interval sumX; /* sum of finite intervals processed */
+ /* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +infinity intervals */
+ int64 nInfcount; /* count of -infinity intervals */
+} IntervalAggState;
+
+#define IA_TOTAL_COUNT(ia) \
+ ((ia)->N + (ia)->pInfcount + (ia)->nInfcount)
static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
static Timestamp dt2local(Timestamp dt, int timezone);
@@ -80,6 +95,12 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void EncodeSpecialInterval(const Interval *interval, char *str);
+static void interval_um_internal(const Interval *interval, Interval *result);
+static void finite_interval_pl(const Interval *span1, const Interval *span2,
+ Interval *result);
+static void finite_interval_mi(const Interval *span1, const Interval *span2,
+ Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +962,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +992,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1384,10 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod,
INT64CONST(0)
};
+ /* Typmod has no effect on infinite intervals */
+ if (INTERVAL_NOT_FINITE(interval))
+ return true;
+
/*
* Unspecified range and precision? Then not necessary to adjust. Setting
* typmod to -1 is the convention for all data types.
@@ -1536,6 +1574,10 @@ make_interval(PG_FUNCTION_ARGS)
pg_add_s64_overflow(result->time, (int64) secs, &result->time))
goto out_of_range;
+ /* make sure that the result is finite */
+ if (INTERVAL_NOT_FINITE(result))
+ goto out_of_range;
+
PG_RETURN_INTERVAL_P(result);
out_of_range:
@@ -1560,6 +1602,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+static void
+EncodeSpecialInterval(const Interval *interval, char *str)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ strcpy(str, EARLY);
+ else if (INTERVAL_IS_NOEND(interval))
+ strcpy(str, LATE);
+ else /* shouldn't happen */
+ elog(ERROR, "invalid argument for EncodeSpecialInterval");
+}
+
Datum
now(PG_FUNCTION_ARGS)
{
@@ -2015,6 +2068,9 @@ interval2itm(Interval span, struct pg_itm *itm)
/* itm2interval()
* Convert a pg_itm structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * This is for use in computations expected to produce finite results. Any
+ * inputs that lead to infinite results are treated as overflows.
*/
int
itm2interval(struct pg_itm *itm, Interval *span)
@@ -2038,12 +2094,21 @@ itm2interval(struct pg_itm *itm, Interval *span)
if (pg_add_s64_overflow(span->time, itm->tm_usec,
&span->time))
return -1;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
/* itmin2interval()
* Convert a pg_itm_in structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * Note: if the result is infinite, it is not treated as an overflow. This
+ * avoids any dump/reload hazards from pre-17 databases that do not support
+ * infinite intervals, but do allow finite intervals with all fields set to
+ * INT_MIN/INT_MAX (outside the documented range). Such intervals will be
+ * silently converted to +/-infinity. This may not be ideal, but seems
+ * preferable to failure, and ought to be pretty unlikely in practice.
*/
int
itmin2interval(struct pg_itm_in *itm_in, Interval *span)
@@ -2088,7 +2153,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2442,6 +2509,15 @@ interval_cmp_internal(const Interval *interval1, const Interval *interval2)
return int128_compare(span1, span2);
}
+static int
+interval_sign(const Interval *interval)
+{
+ INT128 span = interval_cmp_value(interval);
+ INT128 zero = int64_to_int128(0);
+
+ return int128_compare(span, zero);
+}
+
Datum
interval_eq(PG_FUNCTION_ARGS)
{
@@ -2714,10 +2790,39 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else /* TIMESTAMP_IS_NOEND(dt2) */
+ INTERVAL_NOBEGIN(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
ereport(ERROR,
@@ -2783,6 +2888,10 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
/* pre-justify days if it might prevent overflow */
if ((result->day > 0 && result->time > 0) ||
(result->day < 0 && result->time < 0))
@@ -2858,6 +2967,10 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
TMODULO(result->time, wholeday, USECS_PER_DAY);
if (pg_add_s32_overflow(result->day, wholeday, &result->day))
ereport(ERROR,
@@ -2896,6 +3009,10 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
wholemonth = result->day / DAYS_PER_MONTH;
result->day -= wholemonth * DAYS_PER_MONTH;
if (pg_add_s32_overflow(result->month, wholemonth, &result->month))
@@ -2934,7 +3051,31 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Timestamp result;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the timestamp type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3013,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3042,7 +3181,31 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the timestamptz type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3132,9 +3295,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3185,6 +3346,29 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* interval_um_internal()
+ * Negate an interval.
+ */
+static void
+interval_um_internal(const Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else
+ {
+ /* Negate each field, guarding against overflow */
+ if (pg_sub_s64_overflow(0, interval->time, &result->time) ||
+ pg_sub_s32_overflow(0, interval->day, &result->day) ||
+ pg_sub_s32_overflow(0, interval->month, &result->month) ||
+ INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3192,23 +3376,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3252,27 +3420,34 @@ 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")));
-
- 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_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);
PG_RETURN_INTERVAL_P(result);
}
@@ -3286,27 +3461,36 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->day = span1->day - span2->day;
- if (!SAMESIGN(span1->day, span2->day) &&
- !SAMESIGN(result->day, span1->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->time = span1->time - span2->time;
- if (!SAMESIGN(span1->time, span2->time) &&
- !SAMESIGN(result->time, span1->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span1))
+ {
+ if (INTERVAL_IS_NOBEGIN(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1))
+ {
+ if (INTERVAL_IS_NOEND(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (INTERVAL_IS_NOBEGIN(span2))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(span2))
+ INTERVAL_NOBEGIN(result);
+ else
+ finite_interval_mi(span1, span2, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3331,6 +3515,46 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ /*
+ * Handle NaN and infinities.
+ *
+ * We treat "0 * infinity" and "infinity * 0" as errors, since the
+ * interval type has nothing equivalent to NaN.
+ */
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor == 0.0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+ if (isinf(factor))
+ {
+ int isign = interval_sign(span);
+
+ if (isign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (factor * isign < 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ INTERVAL_NOEND(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3391,6 +3615,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3422,6 +3651,33 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ /*
+ * Handle NaN and infinities.
+ *
+ * We treat "infinity / infinity" as an error, since the interval type has
+ * nothing equivalent to NaN. Otherwise, dividing by infinity is handled
+ * by the regular division code, causing all fields to be set to zero.
+ */
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (isinf(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = (int32) (span->month / factor);
result->day = (int32) (span->day / factor);
@@ -3443,6 +3699,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3466,11 +3727,21 @@ in_range_timestamptz_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
TimestampTz sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = timestamptz_mi_interval_internal(base, offset, NULL);
@@ -3493,11 +3764,21 @@ in_range_timestamp_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Timestamp sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval,
@@ -3524,11 +3805,21 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Interval *sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? INTERVAL_IS_NOEND(base) : INTERVAL_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
@@ -3547,161 +3838,387 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function, that needs to compute
+ * sum and count, in the aggregate's memory context.
+ *
+ * The 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;
+}
+
+/*
+ * Functions to add or subtract finite intervals.
*
- * 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.
+ * These are used for normal arithmetic and aggregation of finite intervals
+ * only. Non-finite intervals require special handling.
*/
+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))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+static void
+finite_interval_mi(const Interval *span1, const Interval *span2, Interval *result)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
+
+ if (pg_sub_s32_overflow(span1->month, span2->month, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_sub_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_sub_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+/*
+ * 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_accum(PG_FUNCTION_ARGS)
+interval_avg_accum(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Create the state data on the first call */
+ if (state == NULL)
+ state = makeIntervalAggState(fcinfo);
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
+ if (!PG_ARGISNULL(1))
+ do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ PG_RETURN_POINTER(state);
+}
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+/*
+ * Combine function for sum() and avg() interval aggregates.
+ *
+ * Combine the given internal aggregate states and place the combination in
+ * the first argument.
+ */
+Datum
+interval_avg_combine(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state1;
+ IntervalAggState *state2;
- PG_RETURN_ARRAYTYPE_P(result);
+ 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);
+
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
+
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
+
+ PG_RETURN_POINTER(state1);
+ }
+
+ state1->N += state2->N;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
+
+ /* Accumulate finite interval values, if any. */
+ if (state2->N > 0)
+ finite_interval_pl(&state1->sumX, &state2->sumX, &state1->sumX);
+
+ 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;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ if (!AggCheckCallContext(fcinfo, NULL))
+ elog(ERROR, "aggregate function called in non-aggregate context");
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ sstate = PG_GETARG_BYTEA_PP(0);
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ /*
+ * Initialize a StringInfo so that we can "receive" it using the standard
+ * recv-function infrastructure.
+ */
+ initReadOnlyStringInfo(&buf, VARDATA_ANY(sstate),
+ VARSIZE_ANY_EXHDR(sstate));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ result = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ /* N */
+ result->N = pq_getmsgint64(&buf);
- PG_RETURN_ARRAYTYPE_P(result);
+ /* sumX */
+ result->sumX.time = pq_getmsgint64(&buf);
+ result->sumX.day = pq_getmsgint(&buf, 4);
+ result->sumX.month = pq_getmsgint(&buf, 4);
+
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
+
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
+
+ pq_getmsgend(&buf);
+
+ 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");
+
+ if (!PG_ARGISNULL(1))
+ do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
+
+ PG_RETURN_POINTER(state);
+}
+
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 || state->nInfcount > 0)
+ {
+ Interval *result;
+
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else
+ INTERVAL_NOBEGIN(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
+ IntervalPGetDatum(&state->sumX),
+ Float8GetDatum((double) state->N));
}
+/* sum(interval) aggregate final function */
+Datum
+interval_sum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ Interval *result;
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
+
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else if (state->nInfcount > 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ memcpy(result, &state->sumX, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
@@ -3726,8 +4243,36 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3846,8 +4391,36 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3972,6 +4545,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4155,6 +4733,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4393,6 +4976,12 @@ interval_trunc(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ memcpy(result, interval, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
VARSIZE_ANY_EXHDR(units),
false);
@@ -4737,7 +5326,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5011,7 +5600,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5251,6 +5840,58 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite interval. Returns
+ * +/-Infinity if that is the appropriate result, otherwise returns zero
+ * (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative)
+{
+ if ((type != UNITS) && (type != RESERV))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unit \"%s\" not recognized for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_YEAR:
+ case DTK_DECADE:
+ case DTK_CENTURY:
+ case DTK_MILLENNIUM:
+ case DTK_EPOCH:
+ if (isNegative)
+ return -get_float8_infinity();
+ else
+ return get_float8_infinity();
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unit \"%s\" not supported for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+ return 0.0; /* keep compiler quiet */
+ }
+}
/* interval_part() and extract_interval()
* Extract specified field from interval.
@@ -5275,6 +5916,33 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval));
+
+ if (r != 0.0)
+ {
+ if (retnumeric)
+ {
+ if (r < 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("-Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ else if (r > 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ }
+ else
+ PG_RETURN_FLOAT8(r);
+ }
+ else
+ PG_RETURN_NULL();
+ }
+
if (type == UNITS)
{
interval2itm(*interval, tm);
@@ -5517,6 +6185,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5747,6 +6422,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5783,7 +6465,6 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
Timestamp finish = PG_GETARG_TIMESTAMP(1);
Interval *step = PG_GETARG_INTERVAL_P(2);
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5806,13 +6487,18 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
fctx->step = *step;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
@@ -5864,7 +6550,6 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
Interval *step = PG_GETARG_INTERVAL_P(2);
text *zone = (PG_NARGS() == 4) ? PG_GETARG_TEXT_PP(3) : NULL;
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5888,13 +6573,18 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
fctx->attimezone = zone ? lookup_timezone(zone) : session_timezone;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e1a17cddd8 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
{ 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',
@@ -72,10 +73,13 @@
{ 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',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 091f7e343c..868add0224 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4917,17 +4917,29 @@
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 => '3813', descr => 'aggregate serial function',
+ proname => 'interval_avg_serialize', prorettype => 'bytea',
+ proargtypes => 'internal', prosrc => 'interval_avg_serialize' },
+{ oid => '3814', 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 => '8069', 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' },
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..b63acc0a2f 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -151,7 +151,7 @@ struct pg_itm_in
#define TIMESTAMP_INFINITY PG_INT64_MAX
/*
- * Historically these alias for infinity have been used.
+ * Historically these aliases for infinity have been used.
*/
#define DT_NOBEGIN TIMESTAMP_MINUS_INFINITY
#define DT_NOEND TIMESTAMP_INFINITY
@@ -168,6 +168,31 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+/*
+ * Infinite intervals are represented by setting all fields to the minimum or
+ * maximum integer values.
+ */
+#define INTERVAL_NOBEGIN(i) \
+ do { \
+ (i)->time = PG_INT64_MIN; \
+ (i)->day = PG_INT32_MIN; \
+ (i)->month = PG_INT32_MIN; \
+ } while (0)
+
+#define INTERVAL_IS_NOBEGIN(i) \
+ ((i)->month == PG_INT32_MIN && (i)->day == PG_INT32_MIN && (i)->time == PG_INT64_MIN)
+
+#define INTERVAL_NOEND(i) \
+ do { \
+ (i)->time = PG_INT64_MAX; \
+ (i)->day = PG_INT32_MAX; \
+ (i)->month = PG_INT32_MAX; \
+ } while (0)
+
+#define INTERVAL_IS_NOEND(i) \
+ ((i)->month == PG_INT32_MAX && (i)->day == PG_INT32_MAX && (i)->time == PG_INT64_MAX)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
index 80801cd4ee..8f4c95b9e6 100644
--- a/src/test/regress/expected/brin_multi.out
+++ b/src/test/regress/expected/brin_multi.out
@@ -941,6 +941,34 @@ SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
Index Cond: (a = '@ 30 years'::interval)
(4 rows)
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+SET enable_seqscan = off;
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years ago'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years ago'::interval)
+(4 rows)
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years'::interval)
+(4 rows)
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 8f52661096..cfb4b205e4 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1171,6 +1171,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1278,6 +1279,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1556,6 +1558,22 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | -infinity | -infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | -infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | -infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | -infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | -infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | -infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | -infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | -infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | -infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | -infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | -infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | -infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | -infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | -infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | -infinity
Thu Jan 01 00:00:00 1970 PST | @ 14 secs ago | Wed Dec 31 23:59:46 1969 PST
Thu Jan 01 00:00:00 1970 PST | @ 1 min | Thu Jan 01 00:01:00 1970 PST
Thu Jan 01 00:00:00 1970 PST | @ 5 hours | Thu Jan 01 05:00:00 1970 PST
@@ -1716,14 +1734,45 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | infinity | -infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | -infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | -infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | -infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | -infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | -infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | -infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | -infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | -infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | -infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | -infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | -infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | -infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | -infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | -infinity
Thu Jan 01 00:00:00 1970 PST | @ 34 years | Wed Jan 01 00:00:00 1936 PST
Wed Feb 28 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
Thu Feb 29 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
@@ -1884,7 +1933,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 75d19d6594..a481781475 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,8 +268,63 @@ SELECT r1.*, r2.*
34 years | 5 mons
34 years | 5 mons 12:00:00
34 years | 6 years
-(45 rows)
+ infinity | -infinity
+ infinity | -00:00:14
+ infinity | 00:01:00
+ infinity | 05:00:00
+ infinity | 1 day 02:03:04
+ infinity | 10 days
+ infinity | 3 mons
+ infinity | 5 mons
+ infinity | 5 mons 12:00:00
+ infinity | 6 years
+ infinity | 34 years
+(66 rows)
+
+-- test unary minus
+SELECT f1, -f1 FROM INTERVAL_TBL;
+ f1 | ?column?
+-----------------+-------------------
+ 00:01:00 | -00:01:00
+ 05:00:00 | -05:00:00
+ 10 days | -10 days
+ 34 years | -34 years
+ 3 mons | -3 mons
+ -00:00:14 | 00:00:14
+ 1 day 02:03:04 | -1 days -02:03:04
+ 6 years | -6 years
+ 5 mons | -5 mons
+ 5 mons 12:00:00 | -5 mons -12:00:00
+ infinity | -infinity
+ -infinity | infinity
+(12 rows)
+
+SELECT -('-2147483648 months'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-2147483647 months'::interval); -- ok
+ ?column?
+------------------------
+ 178956970 years 7 mons
+(1 row)
+
+SELECT -('-2147483648 days'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-2147483647 days'::interval); -- ok
+ ?column?
+-----------------
+ 2147483647 days
+(1 row)
+
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-9223372036854775807 us'::interval); -- ok
+ ?column?
+-------------------------
+ 2562047788:00:54.775807
+(1 row)
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+ERROR: interval out of range
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
@@ -304,6 +391,17 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
(5 rows)
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column?
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- Floating point arithmetic rounding errors can lead to unexpected results,
@@ -386,12 +484,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +920,8 @@ SELECT interval '1 2:03:04.5678' minute to second(2);
SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
(f1 + INTERVAL '1 month')::INTERVAL MONTH::INTERVAL YEAR AS "years"
FROM interval_tbl;
- f1 | minutes | years
------------------+-----------------+----------
+ f1 | minutes | years
+-----------------+-----------------+-----------
00:01:00 | 00:01:00 | 00:00:00
05:00:00 | 05:00:00 | 00:00:00
10 days | 10 days | 00:00:00
@@ -832,7 +932,9 @@ SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
6 years | 6 years | 6 years
5 mons | 5 mons | 00:00:00
5 mons 12:00:00 | 5 mons 12:00:00 | 00:00:00
-(10 rows)
+ infinity | infinity | infinity
+ -infinity | -infinity | -infinity
+(12 rows)
-- test inputting and outputting SQL standard interval literals
SET IntervalStyle TO sql_standard;
@@ -1616,31 +1718,31 @@ select make_interval(mins := -1, secs := -9223372036800.0);
ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
--------------------------------------------------------------------
- -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+ -178956970 years -7 mons -2147483648 days -2562047788:00:54.775808
(1 row)
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
---------------------------------------------------
- -178956970-8 -2147483648 -2562047788:00:54.775808
+ -178956970-7 -2147483648 -2562047788:00:54.775808
(1 row)
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
-----------------------------------------------------
- P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+ P-178956970Y-7M-2147483648DT-2562047788H-54.775808S
(1 row)
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
------------------------------------------------------------------------------
- @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+ @ 178956970 years 7 mons 2147483648 days 2562047788 hours 54.775808 secs ago
(1 row)
-- check that '30 days' equals '1 month' according to the hash function
@@ -1719,19 +1821,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1805,7 +1909,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1814,6 +1920,292 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+--
+-- test infinite intervals
+--
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+ interval
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+ interval
+--------------------------------------------------------------------------
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs
+(1 row)
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ interval
+----------
+ infinity
+(1 row)
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ date | interval | plus | minus
+------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 | -infinity | -infinity | infinity
+ 1995-08-06 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+ interval1 | interval2 | plus | minus
+-----------+-----------+-----------------------+-----------------------
+ -infinity | -infinity | -infinity | interval out of range
+ -infinity | @ 10 days | -infinity | -infinity
+ -infinity | infinity | interval out of range | -infinity
+ @ 2 mons | -infinity | -infinity | infinity
+ @ 2 mons | @ 10 days | @ 2 mons 10 days | @ 2 mons -10 days
+ @ 2 mons | infinity | infinity | -infinity
+ infinity | -infinity | interval out of range | infinity
+ infinity | @ 10 days | infinity | infinity
+ infinity | infinity | infinity | interval out of range
+(9 rows)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamp | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamptz | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT time '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+ lhs | rhs | lt | le | eq | gt | ge | ne
+-----------+-------------------------+----+----+----+----+----+----
+ infinity | infinity | f | t | t | f | t | f
+ -infinity | infinity | t | t | f | f | f | t
+ infinity | -infinity | f | f | f | t | t | t
+ -infinity | -infinity | f | t | t | f | t | f
+ infinity | @ 1 year 2 days 3 hours | f | f | f | t | t | t
+ -infinity | @ 1 year 2 days 3 hours | t | t | f | f | f | t
+(6 rows)
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | um | mul | mul_neg | mul_inf | mul_inf_neg | div | div_neg
+-----------+-----------+-----------+-----------+-----------+-------------+-----------+-----------
+ infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity
+ -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity
+(2 rows)
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+ERROR: interval out of range
+SELECT interval 'infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+ERROR: interval out of range
+SELECT interval 'infinity' * 0;
+ERROR: interval out of range
+SELECT interval '-infinity' * 0;
+ERROR: interval out of range
+SELECT interval '0 days' * 'infinity'::float;
+ERROR: interval out of range
+SELECT interval '0 days' * '-infinity'::float;
+ERROR: interval out of range
+SELECT interval '5 days' * 'infinity'::float;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '5 days' * '-infinity'::float;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+ERROR: interval out of range
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | date_trunc
+-----------+------------
+ infinity | infinity
+ -infinity | -infinity
+(2 rows)
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | justify_days | justify_hours | justify_interval
+-----------+--------------+---------------+------------------
+ infinity | infinity | infinity | infinity
+ -infinity | -infinity | -infinity | -infinity
+(2 rows)
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT 'infinity'::interval::time;
+ERROR: cannot convert infinite interval to time
+SELECT '-infinity'::interval::time;
+ERROR: cannot convert infinite interval to time
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
ERROR: invalid input syntax for type interval: "42 days 2 seconds ago ago"
@@ -1832,3 +2224,42 @@ SELECT INTERVAL '1 year months days 5 hours';
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..835f0e5762 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '-infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp 'infinity', timestamp 'infinity');
+ERROR: interval out of range
+select age(timestamp 'infinity', timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 2ca2101dd4..a084357480 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3210,3 +3214,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+ERROR: interval out of range
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..2201740c18 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -2372,6 +2372,7 @@ create temp table datetimes(
f_timestamp timestamp
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -2381,14 +2382,16 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_time range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 1 | 11:00:00 | 1 | 3
+ 0 | 10:00:00 | 0 | 2
+ 1 | 11:00:00 | 0 | 3
2 | 12:00:00 | 1 | 4
3 | 13:00:00 | 2 | 6
4 | 14:00:00 | 3 | 6
@@ -2396,9 +2399,10 @@ window w as (order by f_time range between
6 | 15:00:00 | 4 | 7
7 | 17:00:00 | 7 | 9
8 | 18:00:00 | 7 | 10
- 9 | 19:00:00 | 8 | 10
- 10 | 20:00:00 | 9 | 10
-(10 rows)
+ 9 | 19:00:00 | 8 | 11
+ 10 | 20:00:00 | 9 | 11
+ 11 | 21:00:00 | 10 | 11
+(12 rows)
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2406,7 +2410,8 @@ window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 10 | 20:00:00 | 10 | 8
+ 11 | 21:00:00 | 11 | 9
+ 10 | 20:00:00 | 11 | 8
9 | 19:00:00 | 10 | 7
8 | 18:00:00 | 9 | 7
7 | 17:00:00 | 8 | 5
@@ -2414,17 +2419,90 @@ window w as (order by f_time desc range between
5 | 15:00:00 | 6 | 3
4 | 14:00:00 | 6 | 2
3 | 13:00:00 | 4 | 1
- 2 | 12:00:00 | 3 | 1
- 1 | 11:00:00 | 2 | 1
-(10 rows)
+ 2 | 12:00:00 | 3 | 0
+ 1 | 11:00:00 | 2 | 0
+ 0 | 10:00:00 | 1 | 0
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | 0 | 11
+ 1 | 11:00:00 | 0 | 11
+ 2 | 12:00:00 | 0 | 11
+ 3 | 13:00:00 | 0 | 11
+ 4 | 14:00:00 | 0 | 11
+ 5 | 15:00:00 | 0 | 11
+ 6 | 15:00:00 | 0 | 11
+ 7 | 17:00:00 | 0 | 11
+ 8 | 18:00:00 | 0 | 11
+ 9 | 19:00:00 | 0 | 11
+ 10 | 20:00:00 | 0 | 11
+ 11 | 21:00:00 | 0 | 11
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 1 | 11:00:00+01 | 1 | 3
+ 0 | 10:00:00+01 | 0 | 2
+ 1 | 11:00:00+01 | 0 | 3
2 | 12:00:00+01 | 1 | 4
3 | 13:00:00+01 | 2 | 6
4 | 14:00:00+01 | 3 | 6
@@ -2432,9 +2510,10 @@ window w as (order by f_timetz range between
6 | 15:00:00+01 | 4 | 7
7 | 17:00:00+01 | 7 | 9
8 | 18:00:00+01 | 7 | 10
- 9 | 19:00:00+01 | 8 | 10
- 10 | 20:00:00+01 | 9 | 10
-(10 rows)
+ 9 | 19:00:00+01 | 8 | 11
+ 10 | 20:00:00+01 | 9 | 11
+ 11 | 21:00:00+01 | 10 | 11
+(12 rows)
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2442,7 +2521,8 @@ window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 10 | 20:00:00+01 | 10 | 8
+ 11 | 21:00:00+01 | 11 | 9
+ 10 | 20:00:00+01 | 11 | 8
9 | 19:00:00+01 | 10 | 7
8 | 18:00:00+01 | 9 | 7
7 | 17:00:00+01 | 8 | 5
@@ -2450,16 +2530,89 @@ window w as (order by f_timetz desc range between
5 | 15:00:00+01 | 6 | 3
4 | 14:00:00+01 | 6 | 2
3 | 13:00:00+01 | 4 | 1
- 2 | 12:00:00+01 | 3 | 1
- 1 | 11:00:00+01 | 2 | 1
-(10 rows)
+ 2 | 12:00:00+01 | 3 | 0
+ 1 | 11:00:00+01 | 2 | 0
+ 0 | 10:00:00+01 | 1 | 0
+(12 rows)
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | 0 | 11
+ 1 | 11:00:00+01 | 0 | 11
+ 2 | 12:00:00+01 | 0 | 11
+ 3 | 13:00:00+01 | 0 | 11
+ 4 | 14:00:00+01 | 0 | 11
+ 5 | 15:00:00+01 | 0 | 11
+ 6 | 15:00:00+01 | 0 | 11
+ 7 | 17:00:00+01 | 0 | 11
+ 8 | 18:00:00+01 | 0 | 11
+ 9 | 19:00:00+01 | 0 | 11
+ 10 | 20:00:00+01 | 0 | 11
+ 11 | 21:00:00+01 | 0 | 11
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | @ 1 year | 1 | 2
2 | @ 2 years | 1 | 3
3 | @ 3 years | 2 | 4
@@ -2470,7 +2623,8 @@ window w as (order by f_interval range between
8 | @ 8 years | 7 | 9
9 | @ 9 years | 8 | 10
10 | @ 10 years | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2478,6 +2632,7 @@ window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | @ 10 years | 10 | 9
9 | @ 9 years | 10 | 8
8 | @ 8 years | 9 | 7
@@ -2488,14 +2643,87 @@ window w as (order by f_interval desc range between
3 | @ 3 years | 4 | 2
2 | @ 2 years | 3 | 1
1 | @ 1 year | 2 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 0 | 11
+ 2 | @ 2 years | 0 | 11
+ 3 | @ 3 years | 0 | 11
+ 4 | @ 4 years | 0 | 11
+ 5 | @ 5 years | 0 | 11
+ 6 | @ 5 years | 0 | 11
+ 7 | @ 7 years | 0 | 11
+ 8 | @ 8 years | 0 | 11
+ 9 | @ 9 years | 0 | 11
+ 10 | @ 10 years | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | @ 1 year | 0 | 0
+ 2 | @ 2 years | 0 | 0
+ 3 | @ 3 years | 0 | 0
+ 4 | @ 4 years | 0 | 0
+ 5 | @ 5 years | 0 | 0
+ 6 | @ 5 years | 0 | 0
+ 7 | @ 7 years | 0 | 0
+ 8 | @ 8 years | 0 | 0
+ 9 | @ 9 years | 0 | 0
+ 10 | @ 10 years | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 11 | 11
+ 2 | @ 2 years | 11 | 11
+ 3 | @ 3 years | 11 | 11
+ 4 | @ 4 years | 11 | 11
+ 5 | @ 5 years | 11 | 11
+ 6 | @ 5 years | 11 | 11
+ 7 | @ 7 years | 11 | 11
+ 8 | @ 8 years | 11 | 11
+ 9 | @ 9 years | 11 | 11
+ 10 | @ 10 years | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 02:23:54 2000 PDT | 1 | 3
2 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
3 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
@@ -2506,7 +2734,8 @@ window w as (order by f_timestamptz range between
8 | Thu Oct 19 02:23:54 2006 PDT | 7 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 8 | 10
10 | Sun Oct 19 02:23:54 2008 PDT | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2514,6 +2743,7 @@ window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 02:23:54 2008 PDT | 10 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 10 | 8
8 | Thu Oct 19 02:23:54 2006 PDT | 9 | 7
@@ -2524,14 +2754,87 @@ window w as (order by f_timestamptz desc range between
3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 0
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 0
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 0
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 0
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 0
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 0
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 0
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 11 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 11 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 11 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 11 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 11 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 11 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 11 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 10:23:54 2000 | 1 | 3
2 | Fri Oct 19 10:23:54 2001 | 1 | 4
3 | Fri Oct 19 10:23:54 2001 | 1 | 4
@@ -2542,7 +2845,8 @@ window w as (order by f_timestamp range between
8 | Thu Oct 19 10:23:54 2006 | 7 | 9
9 | Fri Oct 19 10:23:54 2007 | 8 | 10
10 | Sun Oct 19 10:23:54 2008 | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2550,6 +2854,7 @@ window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 10:23:54 2008 | 10 | 9
9 | Fri Oct 19 10:23:54 2007 | 10 | 8
8 | Thu Oct 19 10:23:54 2006 | 9 | 7
@@ -2560,8 +2865,80 @@ window w as (order by f_timestamp desc range between
3 | Fri Oct 19 10:23:54 2001 | 4 | 1
2 | Fri Oct 19 10:23:54 2001 | 4 | 1
1 | Thu Oct 19 10:23:54 2000 | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 0
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 0
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 0
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 0
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 0
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 0
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 0
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 11 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 11 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 11 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 11 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 11 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 11 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 11 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -4375,6 +4752,52 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev_curr_avg | curr_next_sum | prev_curr_sum
+------------------------------------------------------------------------------+-------------------+-------------------+---------------+---------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago | -infinity | infinity | -infinity | infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs | infinity | -infinity | infinity | -infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
+(10 rows)
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
i | sum
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
index b666dbad67..116106d30f 100644
--- a/src/test/regress/sql/brin_multi.sql
+++ b/src/test/regress/sql/brin_multi.sql
@@ -682,6 +682,25 @@ SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan = off;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 39a35a6b7c..252bce4b1c 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -213,10 +213,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -293,7 +295,6 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index a0a373f08b..caad291890 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -66,6 +70,17 @@ SELECT r1.*, r2.*
WHERE r1.f1 > r2.f1
ORDER BY r1.f1, r2.f1;
+-- test unary minus
+
+SELECT f1, -f1 FROM INTERVAL_TBL;
+SELECT -('-2147483648 months'::interval); -- should fail
+SELECT -('-2147483647 months'::interval); -- ok
+SELECT -('-2147483648 days'::interval); -- should fail
+SELECT -('-2147483647 days'::interval); -- ok
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+SELECT -('-9223372036854775807 us'::interval); -- ok
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
@@ -95,6 +110,9 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
@@ -140,7 +158,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -528,13 +546,13 @@ select make_interval(mins := -1, secs := -9223372036800.0);
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
-- check that '30 days' equals '1 month' according to the hash function
select '30 days'::interval = '1 month'::interval as t;
@@ -598,6 +616,157 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+--
+-- test infinite intervals
+--
+
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+SELECT time '11:27:42' + interval '-infinity';
+SELECT time '11:27:42' - interval 'infinity';
+SELECT time '11:27:42' - interval '-infinity';
+SELECT timetz '11:27:42' + interval 'infinity';
+SELECT timetz '11:27:42' + interval '-infinity';
+SELECT timetz '11:27:42' - interval 'infinity';
+SELECT timetz '11:27:42' - interval '-infinity';
+
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+SELECT interval 'infinity' * 'nan';
+SELECT interval '-infinity' * 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+SELECT interval 'infinity' * 0;
+SELECT interval '-infinity' * 0;
+SELECT interval '0 days' * 'infinity'::float;
+SELECT interval '0 days' * '-infinity'::float;
+SELECT interval '5 days' * 'infinity'::float;
+SELECT interval '5 days' * '-infinity'::float;
+
+SELECT interval 'infinity' / 'infinity';
+SELECT interval 'infinity' / '-infinity';
+SELECT interval 'infinity' / 'nan';
+SELECT interval '-infinity' / 'infinity';
+SELECT interval '-infinity' / '-infinity';
+SELECT interval '-infinity' / 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+
+SELECT 'infinity'::interval::time;
+SELECT '-infinity'::interval::time;
+
+SELECT to_char('infinity'::interval, 'YYYY');
+SELECT to_char('-infinity'::interval, 'YYYY');
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
SELECT INTERVAL '2 minutes ago 5 days';
@@ -605,3 +774,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index cdc57bc160..a2dcd5f5d8 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -642,3 +644,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..437e948d6c 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -673,6 +673,7 @@ create temp table datetimes(
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -682,7 +683,8 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -694,6 +696,32 @@ from datetimes
window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
@@ -704,6 +732,32 @@ from datetimes
window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
@@ -714,6 +768,32 @@ from datetimes
window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
@@ -724,6 +804,32 @@ from datetimes
window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
@@ -734,6 +840,32 @@ from datetimes
window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -1591,6 +1723,39 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 87c1aee379..236683e5ab 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1205,6 +1205,7 @@ IntegerSet
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup
--
2.35.3
On Mon, 6 Nov 2023 at 17:08, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
I'm marking this as ready-for-committer. I'll probably pick it up
myself in a few days, unless another committer claims it first.
Ah, it seems that one of this patch's new OIDs conflicts with a recent
commit. The best way to avoid that (or at least make it much less
likely) is by using the suggestion at the end of the unused_oids
script output, which is a random value in the 8000-9999 range.
New version attached doing that, to run it past the cfbot again.
Regards,
Dean
Attachments:
v30-0001-Avoid-integer-overflow-hazard-in-interval_time.patchtext/x-patch; charset=US-ASCII; name=v30-0001-Avoid-integer-overflow-hazard-in-interval_time.patchDownload
From dbc3e01bbe79786c345e6c454bcfe583370098f2 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Sun, 5 Nov 2023 14:45:13 +0000
Subject: [PATCH v30 1/2] Avoid integer overflow hazard in interval_time().
When casting an interval to a time, the original code suffered from
64-bit integer overflow for inputs with a sufficiently large negative
"time" field, leading to bogus results.
Fix by rewriting the algorithm in a simpler form, that more obviously
cannot overflow. While at it, improve the test coverage to test
negative interval inputs.
---
src/backend/utils/adt/date.c | 15 +++------------
src/test/regress/expected/horology.out | 12 ++++++++++++
src/test/regress/sql/horology.sql | 2 ++
3 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 56c7746c11..544e1d32bf 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2012,19 +2012,10 @@ interval_time(PG_FUNCTION_ARGS)
{
Interval *span = PG_GETARG_INTERVAL_P(0);
TimeADT result;
- int64 days;
- result = span->time;
- if (result >= USECS_PER_DAY)
- {
- days = result / USECS_PER_DAY;
- result -= days * USECS_PER_DAY;
- }
- else if (result < 0)
- {
- days = (-result + USECS_PER_DAY - 1) / USECS_PER_DAY;
- result += days * USECS_PER_DAY;
- }
+ result = span->time % USECS_PER_DAY;
+ if (result < 0)
+ result += USECS_PER_DAY;
PG_RETURN_TIMEADT(result);
}
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..8f52661096 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -981,6 +981,18 @@ SELECT CAST(interval '02:03' AS time) AS "02:03:00";
02:03:00
(1 row)
+SELECT CAST(interval '-02:03' AS time) AS "21:57:00";
+ 21:57:00
+----------
+ 21:57:00
+(1 row)
+
+SELECT CAST(interval '-9223372022400000000 us' AS time) AS "00:00:00";
+ 00:00:00
+----------
+ 00:00:00
+(1 row)
+
SELECT time '01:30' + interval '02:01' AS "03:31:00";
03:31:00
----------
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..39a35a6b7c 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -182,6 +182,8 @@ SELECT d1 - interval '1 year' AS one_year FROM TIMESTAMPTZ_TBL;
SELECT CAST(time '01:02' AS interval) AS "+01:02";
SELECT CAST(interval '02:03' AS time) AS "02:03:00";
+SELECT CAST(interval '-02:03' AS time) AS "21:57:00";
+SELECT CAST(interval '-9223372022400000000 us' AS time) AS "00:00:00";
SELECT time '01:30' + interval '02:01' AS "03:31:00";
SELECT time '01:30' - interval '02:01' AS "23:29:00";
SELECT time '02:30' + interval '36:01' AS "14:31:00";
--
2.35.3
v30-0002-Support-infinity-in-the-interval-data-type.patchtext/x-patch; charset=US-ASCII; name=v30-0002-Support-infinity-in-the-interval-data-type.patchDownload
From 1a8df3660270f6d462ae5eee1ad3264542d0ea61 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Mon, 6 Nov 2023 13:52:27 +0000
Subject: [PATCH v30 2/2] Support +/- infinity in the interval data type.
This adds support for infinity to the interval data type, using the
same input/output representation as the other date/time data types
that support infinity. This allows various arithmetic operations on
infinite dates, timestamps and intervals.
The new values are represented by setting all fields of the interval
to INT32/64_MIN for -infinity, and INT32/64_MAX for +infinity. This
ensures that they compare as less/greater than all other interval
values, without the need for any special-case comparison code.
Note that, since those 2 values were formerly accepted as legal finite
intervals, pg_upgrade and dump/restore from an old database will turn
them from finite to infinite intervals. That seems OK, since those
exact values should be extremely rare in practice, and they are
outside the documented range supported by the interval type, which
gives us a certain amount of leeway.
Joseph Koshakow, Jian He, and Ashutosh Bapat, reviewed by Dean Rasheed.
Discussion: https://postgr.es/m/CAAvxfHea4%2BsPybKK7agDYOMo9N-Z3J6ZXf3BOM79pFsFNcRjwA%40mail.gmail.com
---
contrib/btree_gin/btree_gin.c | 5 +-
doc/src/sgml/datatype.sgml | 4 +-
doc/src/sgml/func.sgml | 8 +-
src/backend/utils/adt/date.c | 57 +-
src/backend/utils/adt/datetime.c | 26 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 4 +
src/backend/utils/adt/timestamp.c | 1080 +++++++++++++++++----
src/include/catalog/pg_aggregate.dat | 24 +-
src/include/catalog/pg_proc.dat | 28 +-
src/include/datatype/timestamp.h | 27 +-
src/test/regress/expected/brin_multi.out | 28 +
src/test/regress/expected/horology.out | 71 +-
src/test/regress/expected/interval.out | 499 +++++++++-
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/expected/window.out | 469 ++++++++-
src/test/regress/sql/brin_multi.sql | 19 +
src/test/regress/sql/horology.sql | 3 +-
src/test/regress/sql/interval.sql | 193 +++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
src/test/regress/sql/window.sql | 167 +++-
src/tools/pgindent/typedefs.list | 1 +
24 files changed, 2577 insertions(+), 299 deletions(-)
diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c
index b09bb8df96..5e27906f80 100644
--- a/contrib/btree_gin/btree_gin.c
+++ b/contrib/btree_gin/btree_gin.c
@@ -306,9 +306,8 @@ leftmostvalue_interval(void)
{
Interval *v = palloc(sizeof(Interval));
- v->time = PG_INT64_MIN;
- v->day = PG_INT32_MIN;
- v->month = PG_INT32_MIN;
+ INTERVAL_NOBEGIN(v);
+
return IntervalPGetDatum(v);
}
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 5a6cfbd94d..4aba6d19ee 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2328,12 +2328,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d963f0a0a0..146d71dd9f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9553,7 +9553,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10450,7 +10450,11 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for <type>timestamp</type> inputs; <literal>epoch</literal>, <literal>hour</literal>,
+ <literal>day</literal>, <literal>year</literal>, <literal>decade</literal>,
+ <literal>century</literal>, and <literal>millennium</literal> for
+ <type>interval</type> inputs).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 544e1d32bf..13745a0adc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -24,6 +24,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
+#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/supportnodes.h"
@@ -2013,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(0);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot convert infinite interval to time")));
+
result = span->time % USECS_PER_DAY;
if (result < 0)
result += USECS_PER_DAY;
@@ -2049,6 +2055,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2067,6 +2078,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2090,7 +2106,8 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* Like time_pl_interval/time_mi_interval, we disregard the month and day
- * fields of the offset. So our test for negative should too.
+ * fields of the offset. So our test for negative should too. This also
+ * catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2100,13 +2117,14 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* We can't use time_pl_interval/time_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum = base - offset->time;
- else
- sum = base + offset->time;
+ else if (pg_add_s64_overflow(base, offset->time, &sum))
+ PG_RETURN_BOOL(less);
if (less)
PG_RETURN_BOOL(val <= sum);
@@ -2581,6 +2599,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2603,6 +2626,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -2630,7 +2658,8 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* Like timetz_pl_interval/timetz_mi_interval, we disregard the month and
- * day fields of the offset. So our test for negative should too.
+ * day fields of the offset. So our test for negative should too. This
+ * also catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2640,13 +2669,14 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* We can't use timetz_pl_interval/timetz_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum.time = base->time - offset->time;
- else
- sum.time = base->time + offset->time;
+ else if (pg_add_s64_overflow(base->time, offset->time, &sum.time))
+ PG_RETURN_BOOL(less);
sum.zone = base->zone;
if (less)
@@ -3096,6 +3126,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..fca9a2a6e9 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3271,6 +3271,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3574,6 +3577,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
@@ -3597,6 +3602,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+
+ /*
+ * Only reserved words corresponding to infinite
+ * intervals are accepted.
+ */
+ if (uval != DTK_LATE && uval != DTK_EARLY)
+ return DTERR_BAD_FORMAT;
+
+ /*
+ * Infinity cannot be followed by anything else. We
+ * could allow "ago" to reverse the sign of infinity
+ * but using signed infinity is more intuitive.
+ */
+ if (i != nf - 1)
+ return DTERR_BAD_FORMAT;
+
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 8131091f79..d176723d95 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..4ea5415f20 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4802,6 +4802,10 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
* Convert the month part of Interval to days using assumed
* average month length of 365.25/12.0 days. Not too
* accurate, but plenty good enough for our purposes.
+ *
+ * This also works for infinite intervals, which just have all
+ * fields set to INT_MIN/INT_MAX, and so will produce a result
+ * smaller/larger than any finite interval.
*/
return interval->time + interval->day * (double) USECS_PER_DAY +
interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 647b97aca6..0aee842752 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -72,6 +72,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+/*
+ * The transition datatype for interval aggregates is declared as internal.
+ * It's a pointer to an IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+ int64 N; /* count of finite intervals processed */
+ Interval sumX; /* sum of finite intervals processed */
+ /* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +infinity intervals */
+ int64 nInfcount; /* count of -infinity intervals */
+} IntervalAggState;
+
+#define IA_TOTAL_COUNT(ia) \
+ ((ia)->N + (ia)->pInfcount + (ia)->nInfcount)
static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
static Timestamp dt2local(Timestamp dt, int timezone);
@@ -80,6 +95,12 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void EncodeSpecialInterval(const Interval *interval, char *str);
+static void interval_um_internal(const Interval *interval, Interval *result);
+static void finite_interval_pl(const Interval *span1, const Interval *span2,
+ Interval *result);
+static void finite_interval_mi(const Interval *span1, const Interval *span2,
+ Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +962,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +992,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1384,10 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod,
INT64CONST(0)
};
+ /* Typmod has no effect on infinite intervals */
+ if (INTERVAL_NOT_FINITE(interval))
+ return true;
+
/*
* Unspecified range and precision? Then not necessary to adjust. Setting
* typmod to -1 is the convention for all data types.
@@ -1536,6 +1574,10 @@ make_interval(PG_FUNCTION_ARGS)
pg_add_s64_overflow(result->time, (int64) secs, &result->time))
goto out_of_range;
+ /* make sure that the result is finite */
+ if (INTERVAL_NOT_FINITE(result))
+ goto out_of_range;
+
PG_RETURN_INTERVAL_P(result);
out_of_range:
@@ -1560,6 +1602,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+static void
+EncodeSpecialInterval(const Interval *interval, char *str)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ strcpy(str, EARLY);
+ else if (INTERVAL_IS_NOEND(interval))
+ strcpy(str, LATE);
+ else /* shouldn't happen */
+ elog(ERROR, "invalid argument for EncodeSpecialInterval");
+}
+
Datum
now(PG_FUNCTION_ARGS)
{
@@ -2015,6 +2068,9 @@ interval2itm(Interval span, struct pg_itm *itm)
/* itm2interval()
* Convert a pg_itm structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * This is for use in computations expected to produce finite results. Any
+ * inputs that lead to infinite results are treated as overflows.
*/
int
itm2interval(struct pg_itm *itm, Interval *span)
@@ -2038,12 +2094,21 @@ itm2interval(struct pg_itm *itm, Interval *span)
if (pg_add_s64_overflow(span->time, itm->tm_usec,
&span->time))
return -1;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
/* itmin2interval()
* Convert a pg_itm_in structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * Note: if the result is infinite, it is not treated as an overflow. This
+ * avoids any dump/reload hazards from pre-17 databases that do not support
+ * infinite intervals, but do allow finite intervals with all fields set to
+ * INT_MIN/INT_MAX (outside the documented range). Such intervals will be
+ * silently converted to +/-infinity. This may not be ideal, but seems
+ * preferable to failure, and ought to be pretty unlikely in practice.
*/
int
itmin2interval(struct pg_itm_in *itm_in, Interval *span)
@@ -2088,7 +2153,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2442,6 +2509,15 @@ interval_cmp_internal(const Interval *interval1, const Interval *interval2)
return int128_compare(span1, span2);
}
+static int
+interval_sign(const Interval *interval)
+{
+ INT128 span = interval_cmp_value(interval);
+ INT128 zero = int64_to_int128(0);
+
+ return int128_compare(span, zero);
+}
+
Datum
interval_eq(PG_FUNCTION_ARGS)
{
@@ -2714,10 +2790,39 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else /* TIMESTAMP_IS_NOEND(dt2) */
+ INTERVAL_NOBEGIN(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
ereport(ERROR,
@@ -2783,6 +2888,10 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
/* pre-justify days if it might prevent overflow */
if ((result->day > 0 && result->time > 0) ||
(result->day < 0 && result->time < 0))
@@ -2858,6 +2967,10 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
TMODULO(result->time, wholeday, USECS_PER_DAY);
if (pg_add_s32_overflow(result->day, wholeday, &result->day))
ereport(ERROR,
@@ -2896,6 +3009,10 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
wholemonth = result->day / DAYS_PER_MONTH;
result->day -= wholemonth * DAYS_PER_MONTH;
if (pg_add_s32_overflow(result->month, wholemonth, &result->month))
@@ -2934,7 +3051,31 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Timestamp result;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the timestamp type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3013,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3042,7 +3181,31 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the timestamptz type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3132,9 +3295,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3185,6 +3346,29 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* interval_um_internal()
+ * Negate an interval.
+ */
+static void
+interval_um_internal(const Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else
+ {
+ /* Negate each field, guarding against overflow */
+ if (pg_sub_s64_overflow(0, interval->time, &result->time) ||
+ pg_sub_s32_overflow(0, interval->day, &result->day) ||
+ pg_sub_s32_overflow(0, interval->month, &result->month) ||
+ INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3192,23 +3376,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3252,27 +3420,34 @@ 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")));
-
- 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_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);
PG_RETURN_INTERVAL_P(result);
}
@@ -3286,27 +3461,36 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->day = span1->day - span2->day;
- if (!SAMESIGN(span1->day, span2->day) &&
- !SAMESIGN(result->day, span1->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->time = span1->time - span2->time;
- if (!SAMESIGN(span1->time, span2->time) &&
- !SAMESIGN(result->time, span1->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span1))
+ {
+ if (INTERVAL_IS_NOBEGIN(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1))
+ {
+ if (INTERVAL_IS_NOEND(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (INTERVAL_IS_NOBEGIN(span2))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(span2))
+ INTERVAL_NOBEGIN(result);
+ else
+ finite_interval_mi(span1, span2, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3331,6 +3515,46 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ /*
+ * Handle NaN and infinities.
+ *
+ * We treat "0 * infinity" and "infinity * 0" as errors, since the
+ * interval type has nothing equivalent to NaN.
+ */
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor == 0.0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+ if (isinf(factor))
+ {
+ int isign = interval_sign(span);
+
+ if (isign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (factor * isign < 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ INTERVAL_NOEND(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3391,6 +3615,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3422,6 +3651,33 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ /*
+ * Handle NaN and infinities.
+ *
+ * We treat "infinity / infinity" as an error, since the interval type has
+ * nothing equivalent to NaN. Otherwise, dividing by infinity is handled
+ * by the regular division code, causing all fields to be set to zero.
+ */
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (isinf(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = (int32) (span->month / factor);
result->day = (int32) (span->day / factor);
@@ -3443,6 +3699,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3466,11 +3727,21 @@ in_range_timestamptz_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
TimestampTz sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = timestamptz_mi_interval_internal(base, offset, NULL);
@@ -3493,11 +3764,21 @@ in_range_timestamp_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Timestamp sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval,
@@ -3524,11 +3805,21 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Interval *sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? INTERVAL_IS_NOEND(base) : INTERVAL_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
@@ -3547,161 +3838,387 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function, that needs to compute
+ * sum and count, in the aggregate's memory context.
+ *
+ * The 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;
+}
+
+/*
+ * Functions to add or subtract finite intervals.
*
- * 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.
+ * These are used for normal arithmetic and aggregation of finite intervals
+ * only. Non-finite intervals require special handling.
*/
+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))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+static void
+finite_interval_mi(const Interval *span1, const Interval *span2, Interval *result)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
+
+ if (pg_sub_s32_overflow(span1->month, span2->month, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_sub_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_sub_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+/*
+ * 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_accum(PG_FUNCTION_ARGS)
+interval_avg_accum(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Create the state data on the first call */
+ if (state == NULL)
+ state = makeIntervalAggState(fcinfo);
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
+ if (!PG_ARGISNULL(1))
+ do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ PG_RETURN_POINTER(state);
+}
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+/*
+ * Combine function for sum() and avg() interval aggregates.
+ *
+ * Combine the given internal aggregate states and place the combination in
+ * the first argument.
+ */
+Datum
+interval_avg_combine(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state1;
+ IntervalAggState *state2;
- PG_RETURN_ARRAYTYPE_P(result);
+ 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);
+
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
+
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
+
+ PG_RETURN_POINTER(state1);
+ }
+
+ state1->N += state2->N;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
+
+ /* Accumulate finite interval values, if any. */
+ if (state2->N > 0)
+ finite_interval_pl(&state1->sumX, &state2->sumX, &state1->sumX);
+
+ 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;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ if (!AggCheckCallContext(fcinfo, NULL))
+ elog(ERROR, "aggregate function called in non-aggregate context");
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ sstate = PG_GETARG_BYTEA_PP(0);
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ /*
+ * Initialize a StringInfo so that we can "receive" it using the standard
+ * recv-function infrastructure.
+ */
+ initReadOnlyStringInfo(&buf, VARDATA_ANY(sstate),
+ VARSIZE_ANY_EXHDR(sstate));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ result = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ /* N */
+ result->N = pq_getmsgint64(&buf);
- PG_RETURN_ARRAYTYPE_P(result);
+ /* sumX */
+ result->sumX.time = pq_getmsgint64(&buf);
+ result->sumX.day = pq_getmsgint(&buf, 4);
+ result->sumX.month = pq_getmsgint(&buf, 4);
+
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
+
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
+
+ pq_getmsgend(&buf);
+
+ 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");
+
+ if (!PG_ARGISNULL(1))
+ do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
+
+ PG_RETURN_POINTER(state);
+}
+
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 || state->nInfcount > 0)
+ {
+ Interval *result;
+
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else
+ INTERVAL_NOBEGIN(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
+ IntervalPGetDatum(&state->sumX),
+ Float8GetDatum((double) state->N));
}
+/* sum(interval) aggregate final function */
+Datum
+interval_sum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ Interval *result;
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
+
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else if (state->nInfcount > 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ memcpy(result, &state->sumX, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
@@ -3726,8 +4243,36 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3846,8 +4391,36 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3972,6 +4545,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4155,6 +4733,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4393,6 +4976,12 @@ interval_trunc(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ memcpy(result, interval, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
VARSIZE_ANY_EXHDR(units),
false);
@@ -4737,7 +5326,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5011,7 +5600,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5251,6 +5840,58 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite interval. Returns
+ * +/-Infinity if that is the appropriate result, otherwise returns zero
+ * (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative)
+{
+ if ((type != UNITS) && (type != RESERV))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unit \"%s\" not recognized for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_YEAR:
+ case DTK_DECADE:
+ case DTK_CENTURY:
+ case DTK_MILLENNIUM:
+ case DTK_EPOCH:
+ if (isNegative)
+ return -get_float8_infinity();
+ else
+ return get_float8_infinity();
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unit \"%s\" not supported for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+ return 0.0; /* keep compiler quiet */
+ }
+}
/* interval_part() and extract_interval()
* Extract specified field from interval.
@@ -5275,6 +5916,33 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval));
+
+ if (r != 0.0)
+ {
+ if (retnumeric)
+ {
+ if (r < 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("-Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ else if (r > 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ }
+ else
+ PG_RETURN_FLOAT8(r);
+ }
+ else
+ PG_RETURN_NULL();
+ }
+
if (type == UNITS)
{
interval2itm(*interval, tm);
@@ -5517,6 +6185,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5747,6 +6422,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5783,7 +6465,6 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
Timestamp finish = PG_GETARG_TIMESTAMP(1);
Interval *step = PG_GETARG_INTERVAL_P(2);
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5806,13 +6487,18 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
fctx->step = *step;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
@@ -5864,7 +6550,6 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
Interval *step = PG_GETARG_INTERVAL_P(2);
text *zone = (PG_NARGS() == 4) ? PG_GETARG_TEXT_PP(3) : NULL;
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5888,13 +6573,18 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
fctx->attimezone = zone ? lookup_timezone(zone) : session_timezone;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e1a17cddd8 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
{ 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',
@@ -72,10 +73,13 @@
{ 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',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f14aed422a..d6a990e531 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4917,17 +4917,29 @@
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' },
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..b63acc0a2f 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -151,7 +151,7 @@ struct pg_itm_in
#define TIMESTAMP_INFINITY PG_INT64_MAX
/*
- * Historically these alias for infinity have been used.
+ * Historically these aliases for infinity have been used.
*/
#define DT_NOBEGIN TIMESTAMP_MINUS_INFINITY
#define DT_NOEND TIMESTAMP_INFINITY
@@ -168,6 +168,31 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+/*
+ * Infinite intervals are represented by setting all fields to the minimum or
+ * maximum integer values.
+ */
+#define INTERVAL_NOBEGIN(i) \
+ do { \
+ (i)->time = PG_INT64_MIN; \
+ (i)->day = PG_INT32_MIN; \
+ (i)->month = PG_INT32_MIN; \
+ } while (0)
+
+#define INTERVAL_IS_NOBEGIN(i) \
+ ((i)->month == PG_INT32_MIN && (i)->day == PG_INT32_MIN && (i)->time == PG_INT64_MIN)
+
+#define INTERVAL_NOEND(i) \
+ do { \
+ (i)->time = PG_INT64_MAX; \
+ (i)->day = PG_INT32_MAX; \
+ (i)->month = PG_INT32_MAX; \
+ } while (0)
+
+#define INTERVAL_IS_NOEND(i) \
+ ((i)->month == PG_INT32_MAX && (i)->day == PG_INT32_MAX && (i)->time == PG_INT64_MAX)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
index 80801cd4ee..8f4c95b9e6 100644
--- a/src/test/regress/expected/brin_multi.out
+++ b/src/test/regress/expected/brin_multi.out
@@ -941,6 +941,34 @@ SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
Index Cond: (a = '@ 30 years'::interval)
(4 rows)
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+SET enable_seqscan = off;
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years ago'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years ago'::interval)
+(4 rows)
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years'::interval)
+(4 rows)
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 8f52661096..cfb4b205e4 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1171,6 +1171,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1278,6 +1279,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1556,6 +1558,22 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | -infinity | -infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | -infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | -infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | -infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | -infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | -infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | -infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | -infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | -infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | -infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | -infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | -infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | -infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | -infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | -infinity
Thu Jan 01 00:00:00 1970 PST | @ 14 secs ago | Wed Dec 31 23:59:46 1969 PST
Thu Jan 01 00:00:00 1970 PST | @ 1 min | Thu Jan 01 00:01:00 1970 PST
Thu Jan 01 00:00:00 1970 PST | @ 5 hours | Thu Jan 01 05:00:00 1970 PST
@@ -1716,14 +1734,45 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | infinity | -infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | -infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | -infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | -infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | -infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | -infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | -infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | -infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | -infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | -infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | -infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | -infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | -infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | -infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | -infinity
Thu Jan 01 00:00:00 1970 PST | @ 34 years | Wed Jan 01 00:00:00 1936 PST
Wed Feb 28 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
Thu Feb 29 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
@@ -1884,7 +1933,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 75d19d6594..a481781475 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,8 +268,63 @@ SELECT r1.*, r2.*
34 years | 5 mons
34 years | 5 mons 12:00:00
34 years | 6 years
-(45 rows)
+ infinity | -infinity
+ infinity | -00:00:14
+ infinity | 00:01:00
+ infinity | 05:00:00
+ infinity | 1 day 02:03:04
+ infinity | 10 days
+ infinity | 3 mons
+ infinity | 5 mons
+ infinity | 5 mons 12:00:00
+ infinity | 6 years
+ infinity | 34 years
+(66 rows)
+
+-- test unary minus
+SELECT f1, -f1 FROM INTERVAL_TBL;
+ f1 | ?column?
+-----------------+-------------------
+ 00:01:00 | -00:01:00
+ 05:00:00 | -05:00:00
+ 10 days | -10 days
+ 34 years | -34 years
+ 3 mons | -3 mons
+ -00:00:14 | 00:00:14
+ 1 day 02:03:04 | -1 days -02:03:04
+ 6 years | -6 years
+ 5 mons | -5 mons
+ 5 mons 12:00:00 | -5 mons -12:00:00
+ infinity | -infinity
+ -infinity | infinity
+(12 rows)
+
+SELECT -('-2147483648 months'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-2147483647 months'::interval); -- ok
+ ?column?
+------------------------
+ 178956970 years 7 mons
+(1 row)
+
+SELECT -('-2147483648 days'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-2147483647 days'::interval); -- ok
+ ?column?
+-----------------
+ 2147483647 days
+(1 row)
+
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-9223372036854775807 us'::interval); -- ok
+ ?column?
+-------------------------
+ 2562047788:00:54.775807
+(1 row)
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+ERROR: interval out of range
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
@@ -304,6 +391,17 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
(5 rows)
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column?
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- Floating point arithmetic rounding errors can lead to unexpected results,
@@ -386,12 +484,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +920,8 @@ SELECT interval '1 2:03:04.5678' minute to second(2);
SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
(f1 + INTERVAL '1 month')::INTERVAL MONTH::INTERVAL YEAR AS "years"
FROM interval_tbl;
- f1 | minutes | years
------------------+-----------------+----------
+ f1 | minutes | years
+-----------------+-----------------+-----------
00:01:00 | 00:01:00 | 00:00:00
05:00:00 | 05:00:00 | 00:00:00
10 days | 10 days | 00:00:00
@@ -832,7 +932,9 @@ SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
6 years | 6 years | 6 years
5 mons | 5 mons | 00:00:00
5 mons 12:00:00 | 5 mons 12:00:00 | 00:00:00
-(10 rows)
+ infinity | infinity | infinity
+ -infinity | -infinity | -infinity
+(12 rows)
-- test inputting and outputting SQL standard interval literals
SET IntervalStyle TO sql_standard;
@@ -1616,31 +1718,31 @@ select make_interval(mins := -1, secs := -9223372036800.0);
ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
--------------------------------------------------------------------
- -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+ -178956970 years -7 mons -2147483648 days -2562047788:00:54.775808
(1 row)
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
---------------------------------------------------
- -178956970-8 -2147483648 -2562047788:00:54.775808
+ -178956970-7 -2147483648 -2562047788:00:54.775808
(1 row)
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
-----------------------------------------------------
- P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+ P-178956970Y-7M-2147483648DT-2562047788H-54.775808S
(1 row)
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
------------------------------------------------------------------------------
- @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+ @ 178956970 years 7 mons 2147483648 days 2562047788 hours 54.775808 secs ago
(1 row)
-- check that '30 days' equals '1 month' according to the hash function
@@ -1719,19 +1821,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1805,7 +1909,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1814,6 +1920,292 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+--
+-- test infinite intervals
+--
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+ interval
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+ interval
+--------------------------------------------------------------------------
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs
+(1 row)
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ interval
+----------
+ infinity
+(1 row)
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ date | interval | plus | minus
+------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 | -infinity | -infinity | infinity
+ 1995-08-06 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+ interval1 | interval2 | plus | minus
+-----------+-----------+-----------------------+-----------------------
+ -infinity | -infinity | -infinity | interval out of range
+ -infinity | @ 10 days | -infinity | -infinity
+ -infinity | infinity | interval out of range | -infinity
+ @ 2 mons | -infinity | -infinity | infinity
+ @ 2 mons | @ 10 days | @ 2 mons 10 days | @ 2 mons -10 days
+ @ 2 mons | infinity | infinity | -infinity
+ infinity | -infinity | interval out of range | infinity
+ infinity | @ 10 days | infinity | infinity
+ infinity | infinity | infinity | interval out of range
+(9 rows)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamp | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamptz | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT time '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+ lhs | rhs | lt | le | eq | gt | ge | ne
+-----------+-------------------------+----+----+----+----+----+----
+ infinity | infinity | f | t | t | f | t | f
+ -infinity | infinity | t | t | f | f | f | t
+ infinity | -infinity | f | f | f | t | t | t
+ -infinity | -infinity | f | t | t | f | t | f
+ infinity | @ 1 year 2 days 3 hours | f | f | f | t | t | t
+ -infinity | @ 1 year 2 days 3 hours | t | t | f | f | f | t
+(6 rows)
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | um | mul | mul_neg | mul_inf | mul_inf_neg | div | div_neg
+-----------+-----------+-----------+-----------+-----------+-------------+-----------+-----------
+ infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity
+ -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity
+(2 rows)
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+ERROR: interval out of range
+SELECT interval 'infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+ERROR: interval out of range
+SELECT interval 'infinity' * 0;
+ERROR: interval out of range
+SELECT interval '-infinity' * 0;
+ERROR: interval out of range
+SELECT interval '0 days' * 'infinity'::float;
+ERROR: interval out of range
+SELECT interval '0 days' * '-infinity'::float;
+ERROR: interval out of range
+SELECT interval '5 days' * 'infinity'::float;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '5 days' * '-infinity'::float;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+ERROR: interval out of range
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | date_trunc
+-----------+------------
+ infinity | infinity
+ -infinity | -infinity
+(2 rows)
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | justify_days | justify_hours | justify_interval
+-----------+--------------+---------------+------------------
+ infinity | infinity | infinity | infinity
+ -infinity | -infinity | -infinity | -infinity
+(2 rows)
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT 'infinity'::interval::time;
+ERROR: cannot convert infinite interval to time
+SELECT '-infinity'::interval::time;
+ERROR: cannot convert infinite interval to time
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
ERROR: invalid input syntax for type interval: "42 days 2 seconds ago ago"
@@ -1832,3 +2224,42 @@ SELECT INTERVAL '1 year months days 5 hours';
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..835f0e5762 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '-infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp 'infinity', timestamp 'infinity');
+ERROR: interval out of range
+select age(timestamp 'infinity', timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 2ca2101dd4..a084357480 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3210,3 +3214,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+ERROR: interval out of range
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..2201740c18 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -2372,6 +2372,7 @@ create temp table datetimes(
f_timestamp timestamp
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -2381,14 +2382,16 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_time range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 1 | 11:00:00 | 1 | 3
+ 0 | 10:00:00 | 0 | 2
+ 1 | 11:00:00 | 0 | 3
2 | 12:00:00 | 1 | 4
3 | 13:00:00 | 2 | 6
4 | 14:00:00 | 3 | 6
@@ -2396,9 +2399,10 @@ window w as (order by f_time range between
6 | 15:00:00 | 4 | 7
7 | 17:00:00 | 7 | 9
8 | 18:00:00 | 7 | 10
- 9 | 19:00:00 | 8 | 10
- 10 | 20:00:00 | 9 | 10
-(10 rows)
+ 9 | 19:00:00 | 8 | 11
+ 10 | 20:00:00 | 9 | 11
+ 11 | 21:00:00 | 10 | 11
+(12 rows)
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2406,7 +2410,8 @@ window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 10 | 20:00:00 | 10 | 8
+ 11 | 21:00:00 | 11 | 9
+ 10 | 20:00:00 | 11 | 8
9 | 19:00:00 | 10 | 7
8 | 18:00:00 | 9 | 7
7 | 17:00:00 | 8 | 5
@@ -2414,17 +2419,90 @@ window w as (order by f_time desc range between
5 | 15:00:00 | 6 | 3
4 | 14:00:00 | 6 | 2
3 | 13:00:00 | 4 | 1
- 2 | 12:00:00 | 3 | 1
- 1 | 11:00:00 | 2 | 1
-(10 rows)
+ 2 | 12:00:00 | 3 | 0
+ 1 | 11:00:00 | 2 | 0
+ 0 | 10:00:00 | 1 | 0
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | 0 | 11
+ 1 | 11:00:00 | 0 | 11
+ 2 | 12:00:00 | 0 | 11
+ 3 | 13:00:00 | 0 | 11
+ 4 | 14:00:00 | 0 | 11
+ 5 | 15:00:00 | 0 | 11
+ 6 | 15:00:00 | 0 | 11
+ 7 | 17:00:00 | 0 | 11
+ 8 | 18:00:00 | 0 | 11
+ 9 | 19:00:00 | 0 | 11
+ 10 | 20:00:00 | 0 | 11
+ 11 | 21:00:00 | 0 | 11
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 1 | 11:00:00+01 | 1 | 3
+ 0 | 10:00:00+01 | 0 | 2
+ 1 | 11:00:00+01 | 0 | 3
2 | 12:00:00+01 | 1 | 4
3 | 13:00:00+01 | 2 | 6
4 | 14:00:00+01 | 3 | 6
@@ -2432,9 +2510,10 @@ window w as (order by f_timetz range between
6 | 15:00:00+01 | 4 | 7
7 | 17:00:00+01 | 7 | 9
8 | 18:00:00+01 | 7 | 10
- 9 | 19:00:00+01 | 8 | 10
- 10 | 20:00:00+01 | 9 | 10
-(10 rows)
+ 9 | 19:00:00+01 | 8 | 11
+ 10 | 20:00:00+01 | 9 | 11
+ 11 | 21:00:00+01 | 10 | 11
+(12 rows)
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2442,7 +2521,8 @@ window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 10 | 20:00:00+01 | 10 | 8
+ 11 | 21:00:00+01 | 11 | 9
+ 10 | 20:00:00+01 | 11 | 8
9 | 19:00:00+01 | 10 | 7
8 | 18:00:00+01 | 9 | 7
7 | 17:00:00+01 | 8 | 5
@@ -2450,16 +2530,89 @@ window w as (order by f_timetz desc range between
5 | 15:00:00+01 | 6 | 3
4 | 14:00:00+01 | 6 | 2
3 | 13:00:00+01 | 4 | 1
- 2 | 12:00:00+01 | 3 | 1
- 1 | 11:00:00+01 | 2 | 1
-(10 rows)
+ 2 | 12:00:00+01 | 3 | 0
+ 1 | 11:00:00+01 | 2 | 0
+ 0 | 10:00:00+01 | 1 | 0
+(12 rows)
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | 0 | 11
+ 1 | 11:00:00+01 | 0 | 11
+ 2 | 12:00:00+01 | 0 | 11
+ 3 | 13:00:00+01 | 0 | 11
+ 4 | 14:00:00+01 | 0 | 11
+ 5 | 15:00:00+01 | 0 | 11
+ 6 | 15:00:00+01 | 0 | 11
+ 7 | 17:00:00+01 | 0 | 11
+ 8 | 18:00:00+01 | 0 | 11
+ 9 | 19:00:00+01 | 0 | 11
+ 10 | 20:00:00+01 | 0 | 11
+ 11 | 21:00:00+01 | 0 | 11
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | @ 1 year | 1 | 2
2 | @ 2 years | 1 | 3
3 | @ 3 years | 2 | 4
@@ -2470,7 +2623,8 @@ window w as (order by f_interval range between
8 | @ 8 years | 7 | 9
9 | @ 9 years | 8 | 10
10 | @ 10 years | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2478,6 +2632,7 @@ window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | @ 10 years | 10 | 9
9 | @ 9 years | 10 | 8
8 | @ 8 years | 9 | 7
@@ -2488,14 +2643,87 @@ window w as (order by f_interval desc range between
3 | @ 3 years | 4 | 2
2 | @ 2 years | 3 | 1
1 | @ 1 year | 2 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 0 | 11
+ 2 | @ 2 years | 0 | 11
+ 3 | @ 3 years | 0 | 11
+ 4 | @ 4 years | 0 | 11
+ 5 | @ 5 years | 0 | 11
+ 6 | @ 5 years | 0 | 11
+ 7 | @ 7 years | 0 | 11
+ 8 | @ 8 years | 0 | 11
+ 9 | @ 9 years | 0 | 11
+ 10 | @ 10 years | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | @ 1 year | 0 | 0
+ 2 | @ 2 years | 0 | 0
+ 3 | @ 3 years | 0 | 0
+ 4 | @ 4 years | 0 | 0
+ 5 | @ 5 years | 0 | 0
+ 6 | @ 5 years | 0 | 0
+ 7 | @ 7 years | 0 | 0
+ 8 | @ 8 years | 0 | 0
+ 9 | @ 9 years | 0 | 0
+ 10 | @ 10 years | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 11 | 11
+ 2 | @ 2 years | 11 | 11
+ 3 | @ 3 years | 11 | 11
+ 4 | @ 4 years | 11 | 11
+ 5 | @ 5 years | 11 | 11
+ 6 | @ 5 years | 11 | 11
+ 7 | @ 7 years | 11 | 11
+ 8 | @ 8 years | 11 | 11
+ 9 | @ 9 years | 11 | 11
+ 10 | @ 10 years | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 02:23:54 2000 PDT | 1 | 3
2 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
3 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
@@ -2506,7 +2734,8 @@ window w as (order by f_timestamptz range between
8 | Thu Oct 19 02:23:54 2006 PDT | 7 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 8 | 10
10 | Sun Oct 19 02:23:54 2008 PDT | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2514,6 +2743,7 @@ window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 02:23:54 2008 PDT | 10 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 10 | 8
8 | Thu Oct 19 02:23:54 2006 PDT | 9 | 7
@@ -2524,14 +2754,87 @@ window w as (order by f_timestamptz desc range between
3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 0
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 0
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 0
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 0
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 0
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 0
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 0
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 11 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 11 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 11 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 11 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 11 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 11 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 11 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 10:23:54 2000 | 1 | 3
2 | Fri Oct 19 10:23:54 2001 | 1 | 4
3 | Fri Oct 19 10:23:54 2001 | 1 | 4
@@ -2542,7 +2845,8 @@ window w as (order by f_timestamp range between
8 | Thu Oct 19 10:23:54 2006 | 7 | 9
9 | Fri Oct 19 10:23:54 2007 | 8 | 10
10 | Sun Oct 19 10:23:54 2008 | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2550,6 +2854,7 @@ window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 10:23:54 2008 | 10 | 9
9 | Fri Oct 19 10:23:54 2007 | 10 | 8
8 | Thu Oct 19 10:23:54 2006 | 9 | 7
@@ -2560,8 +2865,80 @@ window w as (order by f_timestamp desc range between
3 | Fri Oct 19 10:23:54 2001 | 4 | 1
2 | Fri Oct 19 10:23:54 2001 | 4 | 1
1 | Thu Oct 19 10:23:54 2000 | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 0
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 0
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 0
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 0
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 0
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 0
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 0
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 11 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 11 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 11 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 11 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 11 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 11 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 11 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -4375,6 +4752,52 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev_curr_avg | curr_next_sum | prev_curr_sum
+------------------------------------------------------------------------------+-------------------+-------------------+---------------+---------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago | -infinity | infinity | -infinity | infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs | infinity | -infinity | infinity | -infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
+(10 rows)
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
i | sum
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
index b666dbad67..116106d30f 100644
--- a/src/test/regress/sql/brin_multi.sql
+++ b/src/test/regress/sql/brin_multi.sql
@@ -682,6 +682,25 @@ SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan = off;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 39a35a6b7c..252bce4b1c 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -213,10 +213,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -293,7 +295,6 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index a0a373f08b..caad291890 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -66,6 +70,17 @@ SELECT r1.*, r2.*
WHERE r1.f1 > r2.f1
ORDER BY r1.f1, r2.f1;
+-- test unary minus
+
+SELECT f1, -f1 FROM INTERVAL_TBL;
+SELECT -('-2147483648 months'::interval); -- should fail
+SELECT -('-2147483647 months'::interval); -- ok
+SELECT -('-2147483648 days'::interval); -- should fail
+SELECT -('-2147483647 days'::interval); -- ok
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+SELECT -('-9223372036854775807 us'::interval); -- ok
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
@@ -95,6 +110,9 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
@@ -140,7 +158,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -528,13 +546,13 @@ select make_interval(mins := -1, secs := -9223372036800.0);
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
-- check that '30 days' equals '1 month' according to the hash function
select '30 days'::interval = '1 month'::interval as t;
@@ -598,6 +616,157 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+--
+-- test infinite intervals
+--
+
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+SELECT time '11:27:42' + interval '-infinity';
+SELECT time '11:27:42' - interval 'infinity';
+SELECT time '11:27:42' - interval '-infinity';
+SELECT timetz '11:27:42' + interval 'infinity';
+SELECT timetz '11:27:42' + interval '-infinity';
+SELECT timetz '11:27:42' - interval 'infinity';
+SELECT timetz '11:27:42' - interval '-infinity';
+
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+SELECT interval 'infinity' * 'nan';
+SELECT interval '-infinity' * 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+SELECT interval 'infinity' * 0;
+SELECT interval '-infinity' * 0;
+SELECT interval '0 days' * 'infinity'::float;
+SELECT interval '0 days' * '-infinity'::float;
+SELECT interval '5 days' * 'infinity'::float;
+SELECT interval '5 days' * '-infinity'::float;
+
+SELECT interval 'infinity' / 'infinity';
+SELECT interval 'infinity' / '-infinity';
+SELECT interval 'infinity' / 'nan';
+SELECT interval '-infinity' / 'infinity';
+SELECT interval '-infinity' / '-infinity';
+SELECT interval '-infinity' / 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+
+SELECT 'infinity'::interval::time;
+SELECT '-infinity'::interval::time;
+
+SELECT to_char('infinity'::interval, 'YYYY');
+SELECT to_char('-infinity'::interval, 'YYYY');
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
SELECT INTERVAL '2 minutes ago 5 days';
@@ -605,3 +774,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index cdc57bc160..a2dcd5f5d8 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -642,3 +644,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..437e948d6c 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -673,6 +673,7 @@ create temp table datetimes(
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -682,7 +683,8 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -694,6 +696,32 @@ from datetimes
window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
@@ -704,6 +732,32 @@ from datetimes
window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
@@ -714,6 +768,32 @@ from datetimes
window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
@@ -724,6 +804,32 @@ from datetimes
window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
@@ -734,6 +840,32 @@ from datetimes
window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -1591,6 +1723,39 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 87c1aee379..236683e5ab 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1205,6 +1205,7 @@ IntegerSet
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup
--
2.35.3
On Tue, 7 Nov 2023 at 14:33, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
New version attached doing that, to run it past the cfbot again.
Ah, Windows Server didn't like that. Trying again with "INT64CONST(0)"
instead of just "0" in interval_um().
Regards,
Dean
Attachments:
v31-0001-Avoid-integer-overflow-hazard-in-interval_time.patchtext/x-patch; charset=US-ASCII; name=v31-0001-Avoid-integer-overflow-hazard-in-interval_time.patchDownload
From dbc3e01bbe79786c345e6c454bcfe583370098f2 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Sun, 5 Nov 2023 14:45:13 +0000
Subject: [PATCH v31 1/2] Avoid integer overflow hazard in interval_time().
When casting an interval to a time, the original code suffered from
64-bit integer overflow for inputs with a sufficiently large negative
"time" field, leading to bogus results.
Fix by rewriting the algorithm in a simpler form, that more obviously
cannot overflow. While at it, improve the test coverage to test
negative interval inputs.
---
src/backend/utils/adt/date.c | 15 +++------------
src/test/regress/expected/horology.out | 12 ++++++++++++
src/test/regress/sql/horology.sql | 2 ++
3 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 56c7746c11..544e1d32bf 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2012,19 +2012,10 @@ interval_time(PG_FUNCTION_ARGS)
{
Interval *span = PG_GETARG_INTERVAL_P(0);
TimeADT result;
- int64 days;
- result = span->time;
- if (result >= USECS_PER_DAY)
- {
- days = result / USECS_PER_DAY;
- result -= days * USECS_PER_DAY;
- }
- else if (result < 0)
- {
- days = (-result + USECS_PER_DAY - 1) / USECS_PER_DAY;
- result += days * USECS_PER_DAY;
- }
+ result = span->time % USECS_PER_DAY;
+ if (result < 0)
+ result += USECS_PER_DAY;
PG_RETURN_TIMEADT(result);
}
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..8f52661096 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -981,6 +981,18 @@ SELECT CAST(interval '02:03' AS time) AS "02:03:00";
02:03:00
(1 row)
+SELECT CAST(interval '-02:03' AS time) AS "21:57:00";
+ 21:57:00
+----------
+ 21:57:00
+(1 row)
+
+SELECT CAST(interval '-9223372022400000000 us' AS time) AS "00:00:00";
+ 00:00:00
+----------
+ 00:00:00
+(1 row)
+
SELECT time '01:30' + interval '02:01' AS "03:31:00";
03:31:00
----------
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..39a35a6b7c 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -182,6 +182,8 @@ SELECT d1 - interval '1 year' AS one_year FROM TIMESTAMPTZ_TBL;
SELECT CAST(time '01:02' AS interval) AS "+01:02";
SELECT CAST(interval '02:03' AS time) AS "02:03:00";
+SELECT CAST(interval '-02:03' AS time) AS "21:57:00";
+SELECT CAST(interval '-9223372022400000000 us' AS time) AS "00:00:00";
SELECT time '01:30' + interval '02:01' AS "03:31:00";
SELECT time '01:30' - interval '02:01' AS "23:29:00";
SELECT time '02:30' + interval '36:01' AS "14:31:00";
--
2.35.3
v31-0002-Support-infinity-in-the-interval-data-type.patchtext/x-patch; charset=US-ASCII; name=v31-0002-Support-infinity-in-the-interval-data-type.patchDownload
From 970fb377562a7bb1f2ca22c4e660312ec4f8cd45 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Mon, 6 Nov 2023 13:52:27 +0000
Subject: [PATCH v31 2/2] Support +/- infinity in the interval data type.
This adds support for infinity to the interval data type, using the
same input/output representation as the other date/time data types
that support infinity. This allows various arithmetic operations on
infinite dates, timestamps and intervals.
The new values are represented by setting all fields of the interval
to INT32/64_MIN for -infinity, and INT32/64_MAX for +infinity. This
ensures that they compare as less/greater than all other interval
values, without the need for any special-case comparison code.
Note that, since those 2 values were formerly accepted as legal finite
intervals, pg_upgrade and dump/restore from an old database will turn
them from finite to infinite intervals. That seems OK, since those
exact values should be extremely rare in practice, and they are
outside the documented range supported by the interval type, which
gives us a certain amount of leeway.
Joseph Koshakow, Jian He, and Ashutosh Bapat, reviewed by Dean Rasheed.
Discussion: https://postgr.es/m/CAAvxfHea4%2BsPybKK7agDYOMo9N-Z3J6ZXf3BOM79pFsFNcRjwA%40mail.gmail.com
---
contrib/btree_gin/btree_gin.c | 5 +-
doc/src/sgml/datatype.sgml | 4 +-
doc/src/sgml/func.sgml | 8 +-
src/backend/utils/adt/date.c | 57 +-
src/backend/utils/adt/datetime.c | 26 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 4 +
src/backend/utils/adt/timestamp.c | 1080 +++++++++++++++++----
src/include/catalog/pg_aggregate.dat | 24 +-
src/include/catalog/pg_proc.dat | 28 +-
src/include/datatype/timestamp.h | 27 +-
src/test/regress/expected/brin_multi.out | 28 +
src/test/regress/expected/horology.out | 71 +-
src/test/regress/expected/interval.out | 499 +++++++++-
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/expected/window.out | 469 ++++++++-
src/test/regress/sql/brin_multi.sql | 19 +
src/test/regress/sql/horology.sql | 3 +-
src/test/regress/sql/interval.sql | 193 +++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
src/test/regress/sql/window.sql | 167 +++-
src/tools/pgindent/typedefs.list | 1 +
24 files changed, 2577 insertions(+), 299 deletions(-)
diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c
index b09bb8df96..5e27906f80 100644
--- a/contrib/btree_gin/btree_gin.c
+++ b/contrib/btree_gin/btree_gin.c
@@ -306,9 +306,8 @@ leftmostvalue_interval(void)
{
Interval *v = palloc(sizeof(Interval));
- v->time = PG_INT64_MIN;
- v->day = PG_INT32_MIN;
- v->month = PG_INT32_MIN;
+ INTERVAL_NOBEGIN(v);
+
return IntervalPGetDatum(v);
}
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 5a6cfbd94d..4aba6d19ee 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2328,12 +2328,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d963f0a0a0..146d71dd9f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9553,7 +9553,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10450,7 +10450,11 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for <type>timestamp</type> inputs; <literal>epoch</literal>, <literal>hour</literal>,
+ <literal>day</literal>, <literal>year</literal>, <literal>decade</literal>,
+ <literal>century</literal>, and <literal>millennium</literal> for
+ <type>interval</type> inputs).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 544e1d32bf..13745a0adc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -24,6 +24,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
+#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/supportnodes.h"
@@ -2013,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(0);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot convert infinite interval to time")));
+
result = span->time % USECS_PER_DAY;
if (result < 0)
result += USECS_PER_DAY;
@@ -2049,6 +2055,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2067,6 +2078,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2090,7 +2106,8 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* Like time_pl_interval/time_mi_interval, we disregard the month and day
- * fields of the offset. So our test for negative should too.
+ * fields of the offset. So our test for negative should too. This also
+ * catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2100,13 +2117,14 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* We can't use time_pl_interval/time_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum = base - offset->time;
- else
- sum = base + offset->time;
+ else if (pg_add_s64_overflow(base, offset->time, &sum))
+ PG_RETURN_BOOL(less);
if (less)
PG_RETURN_BOOL(val <= sum);
@@ -2581,6 +2599,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2603,6 +2626,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -2630,7 +2658,8 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* Like timetz_pl_interval/timetz_mi_interval, we disregard the month and
- * day fields of the offset. So our test for negative should too.
+ * day fields of the offset. So our test for negative should too. This
+ * also catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2640,13 +2669,14 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* We can't use timetz_pl_interval/timetz_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum.time = base->time - offset->time;
- else
- sum.time = base->time + offset->time;
+ else if (pg_add_s64_overflow(base->time, offset->time, &sum.time))
+ PG_RETURN_BOOL(less);
sum.zone = base->zone;
if (less)
@@ -3096,6 +3126,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..fca9a2a6e9 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3271,6 +3271,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3574,6 +3577,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
@@ -3597,6 +3602,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+
+ /*
+ * Only reserved words corresponding to infinite
+ * intervals are accepted.
+ */
+ if (uval != DTK_LATE && uval != DTK_EARLY)
+ return DTERR_BAD_FORMAT;
+
+ /*
+ * Infinity cannot be followed by anything else. We
+ * could allow "ago" to reverse the sign of infinity
+ * but using signed infinity is more intuitive.
+ */
+ if (i != nf - 1)
+ return DTERR_BAD_FORMAT;
+
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 8131091f79..d176723d95 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..4ea5415f20 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4802,6 +4802,10 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
* Convert the month part of Interval to days using assumed
* average month length of 365.25/12.0 days. Not too
* accurate, but plenty good enough for our purposes.
+ *
+ * This also works for infinite intervals, which just have all
+ * fields set to INT_MIN/INT_MAX, and so will produce a result
+ * smaller/larger than any finite interval.
*/
return interval->time + interval->day * (double) USECS_PER_DAY +
interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 647b97aca6..54080e76b4 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -72,6 +72,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+/*
+ * The transition datatype for interval aggregates is declared as internal.
+ * It's a pointer to an IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+ int64 N; /* count of finite intervals processed */
+ Interval sumX; /* sum of finite intervals processed */
+ /* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +infinity intervals */
+ int64 nInfcount; /* count of -infinity intervals */
+} IntervalAggState;
+
+#define IA_TOTAL_COUNT(ia) \
+ ((ia)->N + (ia)->pInfcount + (ia)->nInfcount)
static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
static Timestamp dt2local(Timestamp dt, int timezone);
@@ -80,6 +95,12 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void EncodeSpecialInterval(const Interval *interval, char *str);
+static void interval_um_internal(const Interval *interval, Interval *result);
+static void finite_interval_pl(const Interval *span1, const Interval *span2,
+ Interval *result);
+static void finite_interval_mi(const Interval *span1, const Interval *span2,
+ Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +962,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +992,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1384,10 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod,
INT64CONST(0)
};
+ /* Typmod has no effect on infinite intervals */
+ if (INTERVAL_NOT_FINITE(interval))
+ return true;
+
/*
* Unspecified range and precision? Then not necessary to adjust. Setting
* typmod to -1 is the convention for all data types.
@@ -1536,6 +1574,10 @@ make_interval(PG_FUNCTION_ARGS)
pg_add_s64_overflow(result->time, (int64) secs, &result->time))
goto out_of_range;
+ /* make sure that the result is finite */
+ if (INTERVAL_NOT_FINITE(result))
+ goto out_of_range;
+
PG_RETURN_INTERVAL_P(result);
out_of_range:
@@ -1560,6 +1602,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+static void
+EncodeSpecialInterval(const Interval *interval, char *str)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ strcpy(str, EARLY);
+ else if (INTERVAL_IS_NOEND(interval))
+ strcpy(str, LATE);
+ else /* shouldn't happen */
+ elog(ERROR, "invalid argument for EncodeSpecialInterval");
+}
+
Datum
now(PG_FUNCTION_ARGS)
{
@@ -2015,6 +2068,9 @@ interval2itm(Interval span, struct pg_itm *itm)
/* itm2interval()
* Convert a pg_itm structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * This is for use in computations expected to produce finite results. Any
+ * inputs that lead to infinite results are treated as overflows.
*/
int
itm2interval(struct pg_itm *itm, Interval *span)
@@ -2038,12 +2094,21 @@ itm2interval(struct pg_itm *itm, Interval *span)
if (pg_add_s64_overflow(span->time, itm->tm_usec,
&span->time))
return -1;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
/* itmin2interval()
* Convert a pg_itm_in structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * Note: if the result is infinite, it is not treated as an overflow. This
+ * avoids any dump/reload hazards from pre-17 databases that do not support
+ * infinite intervals, but do allow finite intervals with all fields set to
+ * INT_MIN/INT_MAX (outside the documented range). Such intervals will be
+ * silently converted to +/-infinity. This may not be ideal, but seems
+ * preferable to failure, and ought to be pretty unlikely in practice.
*/
int
itmin2interval(struct pg_itm_in *itm_in, Interval *span)
@@ -2088,7 +2153,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2442,6 +2509,15 @@ interval_cmp_internal(const Interval *interval1, const Interval *interval2)
return int128_compare(span1, span2);
}
+static int
+interval_sign(const Interval *interval)
+{
+ INT128 span = interval_cmp_value(interval);
+ INT128 zero = int64_to_int128(0);
+
+ return int128_compare(span, zero);
+}
+
Datum
interval_eq(PG_FUNCTION_ARGS)
{
@@ -2714,10 +2790,39 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else /* TIMESTAMP_IS_NOEND(dt2) */
+ INTERVAL_NOBEGIN(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
ereport(ERROR,
@@ -2783,6 +2888,10 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
/* pre-justify days if it might prevent overflow */
if ((result->day > 0 && result->time > 0) ||
(result->day < 0 && result->time < 0))
@@ -2858,6 +2967,10 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
TMODULO(result->time, wholeday, USECS_PER_DAY);
if (pg_add_s32_overflow(result->day, wholeday, &result->day))
ereport(ERROR,
@@ -2896,6 +3009,10 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
wholemonth = result->day / DAYS_PER_MONTH;
result->day -= wholemonth * DAYS_PER_MONTH;
if (pg_add_s32_overflow(result->month, wholemonth, &result->month))
@@ -2934,7 +3051,31 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Timestamp result;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the timestamp type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3013,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3042,7 +3181,31 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the timestamptz type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3132,9 +3295,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3185,6 +3346,29 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* interval_um_internal()
+ * Negate an interval.
+ */
+static void
+interval_um_internal(const Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else
+ {
+ /* Negate each field, guarding against overflow */
+ if (pg_sub_s64_overflow(INT64CONST(0), interval->time, &result->time) ||
+ pg_sub_s32_overflow(0, interval->day, &result->day) ||
+ pg_sub_s32_overflow(0, interval->month, &result->month) ||
+ INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3192,23 +3376,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3252,27 +3420,34 @@ 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")));
-
- 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_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);
PG_RETURN_INTERVAL_P(result);
}
@@ -3286,27 +3461,36 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->day = span1->day - span2->day;
- if (!SAMESIGN(span1->day, span2->day) &&
- !SAMESIGN(result->day, span1->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->time = span1->time - span2->time;
- if (!SAMESIGN(span1->time, span2->time) &&
- !SAMESIGN(result->time, span1->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span1))
+ {
+ if (INTERVAL_IS_NOBEGIN(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1))
+ {
+ if (INTERVAL_IS_NOEND(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (INTERVAL_IS_NOBEGIN(span2))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(span2))
+ INTERVAL_NOBEGIN(result);
+ else
+ finite_interval_mi(span1, span2, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3331,6 +3515,46 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ /*
+ * Handle NaN and infinities.
+ *
+ * We treat "0 * infinity" and "infinity * 0" as errors, since the
+ * interval type has nothing equivalent to NaN.
+ */
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor == 0.0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+ if (isinf(factor))
+ {
+ int isign = interval_sign(span);
+
+ if (isign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (factor * isign < 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ INTERVAL_NOEND(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3391,6 +3615,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3422,6 +3651,33 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ /*
+ * Handle NaN and infinities.
+ *
+ * We treat "infinity / infinity" as an error, since the interval type has
+ * nothing equivalent to NaN. Otherwise, dividing by infinity is handled
+ * by the regular division code, causing all fields to be set to zero.
+ */
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (isinf(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = (int32) (span->month / factor);
result->day = (int32) (span->day / factor);
@@ -3443,6 +3699,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3466,11 +3727,21 @@ in_range_timestamptz_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
TimestampTz sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = timestamptz_mi_interval_internal(base, offset, NULL);
@@ -3493,11 +3764,21 @@ in_range_timestamp_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Timestamp sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval,
@@ -3524,11 +3805,21 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Interval *sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? INTERVAL_IS_NOEND(base) : INTERVAL_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
@@ -3547,161 +3838,387 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function, that needs to compute
+ * sum and count, in the aggregate's memory context.
+ *
+ * The 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;
+}
+
+/*
+ * Functions to add or subtract finite intervals.
*
- * 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.
+ * These are used for normal arithmetic and aggregation of finite intervals
+ * only. Non-finite intervals require special handling.
*/
+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))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+static void
+finite_interval_mi(const Interval *span1, const Interval *span2, Interval *result)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
+
+ if (pg_sub_s32_overflow(span1->month, span2->month, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_sub_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_sub_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+/*
+ * 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_accum(PG_FUNCTION_ARGS)
+interval_avg_accum(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Create the state data on the first call */
+ if (state == NULL)
+ state = makeIntervalAggState(fcinfo);
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
+ if (!PG_ARGISNULL(1))
+ do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ PG_RETURN_POINTER(state);
+}
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+/*
+ * Combine function for sum() and avg() interval aggregates.
+ *
+ * Combine the given internal aggregate states and place the combination in
+ * the first argument.
+ */
+Datum
+interval_avg_combine(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state1;
+ IntervalAggState *state2;
- PG_RETURN_ARRAYTYPE_P(result);
+ 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);
+
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
+
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
+
+ PG_RETURN_POINTER(state1);
+ }
+
+ state1->N += state2->N;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
+
+ /* Accumulate finite interval values, if any. */
+ if (state2->N > 0)
+ finite_interval_pl(&state1->sumX, &state2->sumX, &state1->sumX);
+
+ 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;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ if (!AggCheckCallContext(fcinfo, NULL))
+ elog(ERROR, "aggregate function called in non-aggregate context");
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ sstate = PG_GETARG_BYTEA_PP(0);
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ /*
+ * Initialize a StringInfo so that we can "receive" it using the standard
+ * recv-function infrastructure.
+ */
+ initReadOnlyStringInfo(&buf, VARDATA_ANY(sstate),
+ VARSIZE_ANY_EXHDR(sstate));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ result = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ /* N */
+ result->N = pq_getmsgint64(&buf);
- PG_RETURN_ARRAYTYPE_P(result);
+ /* sumX */
+ result->sumX.time = pq_getmsgint64(&buf);
+ result->sumX.day = pq_getmsgint(&buf, 4);
+ result->sumX.month = pq_getmsgint(&buf, 4);
+
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
+
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
+
+ pq_getmsgend(&buf);
+
+ 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");
+
+ if (!PG_ARGISNULL(1))
+ do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
+
+ PG_RETURN_POINTER(state);
+}
+
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 || state->nInfcount > 0)
+ {
+ Interval *result;
+
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else
+ INTERVAL_NOBEGIN(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
+ IntervalPGetDatum(&state->sumX),
+ Float8GetDatum((double) state->N));
}
+/* sum(interval) aggregate final function */
+Datum
+interval_sum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ Interval *result;
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
+
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else if (state->nInfcount > 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ memcpy(result, &state->sumX, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
@@ -3726,8 +4243,36 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3846,8 +4391,36 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3972,6 +4545,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4155,6 +4733,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4393,6 +4976,12 @@ interval_trunc(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ memcpy(result, interval, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
VARSIZE_ANY_EXHDR(units),
false);
@@ -4737,7 +5326,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5011,7 +5600,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5251,6 +5840,58 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite interval. Returns
+ * +/-Infinity if that is the appropriate result, otherwise returns zero
+ * (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative)
+{
+ if ((type != UNITS) && (type != RESERV))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unit \"%s\" not recognized for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_YEAR:
+ case DTK_DECADE:
+ case DTK_CENTURY:
+ case DTK_MILLENNIUM:
+ case DTK_EPOCH:
+ if (isNegative)
+ return -get_float8_infinity();
+ else
+ return get_float8_infinity();
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unit \"%s\" not supported for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+ return 0.0; /* keep compiler quiet */
+ }
+}
/* interval_part() and extract_interval()
* Extract specified field from interval.
@@ -5275,6 +5916,33 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval));
+
+ if (r != 0.0)
+ {
+ if (retnumeric)
+ {
+ if (r < 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("-Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ else if (r > 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ }
+ else
+ PG_RETURN_FLOAT8(r);
+ }
+ else
+ PG_RETURN_NULL();
+ }
+
if (type == UNITS)
{
interval2itm(*interval, tm);
@@ -5517,6 +6185,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5747,6 +6422,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5783,7 +6465,6 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
Timestamp finish = PG_GETARG_TIMESTAMP(1);
Interval *step = PG_GETARG_INTERVAL_P(2);
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5806,13 +6487,18 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
fctx->step = *step;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
@@ -5864,7 +6550,6 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
Interval *step = PG_GETARG_INTERVAL_P(2);
text *zone = (PG_NARGS() == 4) ? PG_GETARG_TEXT_PP(3) : NULL;
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5888,13 +6573,18 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
fctx->attimezone = zone ? lookup_timezone(zone) : session_timezone;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e1a17cddd8 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
{ 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',
@@ -72,10 +73,13 @@
{ 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',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f14aed422a..d6a990e531 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4917,17 +4917,29 @@
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' },
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..b63acc0a2f 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -151,7 +151,7 @@ struct pg_itm_in
#define TIMESTAMP_INFINITY PG_INT64_MAX
/*
- * Historically these alias for infinity have been used.
+ * Historically these aliases for infinity have been used.
*/
#define DT_NOBEGIN TIMESTAMP_MINUS_INFINITY
#define DT_NOEND TIMESTAMP_INFINITY
@@ -168,6 +168,31 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+/*
+ * Infinite intervals are represented by setting all fields to the minimum or
+ * maximum integer values.
+ */
+#define INTERVAL_NOBEGIN(i) \
+ do { \
+ (i)->time = PG_INT64_MIN; \
+ (i)->day = PG_INT32_MIN; \
+ (i)->month = PG_INT32_MIN; \
+ } while (0)
+
+#define INTERVAL_IS_NOBEGIN(i) \
+ ((i)->month == PG_INT32_MIN && (i)->day == PG_INT32_MIN && (i)->time == PG_INT64_MIN)
+
+#define INTERVAL_NOEND(i) \
+ do { \
+ (i)->time = PG_INT64_MAX; \
+ (i)->day = PG_INT32_MAX; \
+ (i)->month = PG_INT32_MAX; \
+ } while (0)
+
+#define INTERVAL_IS_NOEND(i) \
+ ((i)->month == PG_INT32_MAX && (i)->day == PG_INT32_MAX && (i)->time == PG_INT64_MAX)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
index 80801cd4ee..8f4c95b9e6 100644
--- a/src/test/regress/expected/brin_multi.out
+++ b/src/test/regress/expected/brin_multi.out
@@ -941,6 +941,34 @@ SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
Index Cond: (a = '@ 30 years'::interval)
(4 rows)
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+SET enable_seqscan = off;
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years ago'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years ago'::interval)
+(4 rows)
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years'::interval)
+(4 rows)
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 8f52661096..cfb4b205e4 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1171,6 +1171,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1278,6 +1279,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1556,6 +1558,22 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | -infinity | -infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | -infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | -infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | -infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | -infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | -infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | -infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | -infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | -infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | -infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | -infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | -infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | -infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | -infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | -infinity
Thu Jan 01 00:00:00 1970 PST | @ 14 secs ago | Wed Dec 31 23:59:46 1969 PST
Thu Jan 01 00:00:00 1970 PST | @ 1 min | Thu Jan 01 00:01:00 1970 PST
Thu Jan 01 00:00:00 1970 PST | @ 5 hours | Thu Jan 01 05:00:00 1970 PST
@@ -1716,14 +1734,45 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | infinity | -infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | -infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | -infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | -infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | -infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | -infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | -infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | -infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | -infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | -infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | -infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | -infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | -infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | -infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | -infinity
Thu Jan 01 00:00:00 1970 PST | @ 34 years | Wed Jan 01 00:00:00 1936 PST
Wed Feb 28 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
Thu Feb 29 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
@@ -1884,7 +1933,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 75d19d6594..a481781475 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,8 +268,63 @@ SELECT r1.*, r2.*
34 years | 5 mons
34 years | 5 mons 12:00:00
34 years | 6 years
-(45 rows)
+ infinity | -infinity
+ infinity | -00:00:14
+ infinity | 00:01:00
+ infinity | 05:00:00
+ infinity | 1 day 02:03:04
+ infinity | 10 days
+ infinity | 3 mons
+ infinity | 5 mons
+ infinity | 5 mons 12:00:00
+ infinity | 6 years
+ infinity | 34 years
+(66 rows)
+
+-- test unary minus
+SELECT f1, -f1 FROM INTERVAL_TBL;
+ f1 | ?column?
+-----------------+-------------------
+ 00:01:00 | -00:01:00
+ 05:00:00 | -05:00:00
+ 10 days | -10 days
+ 34 years | -34 years
+ 3 mons | -3 mons
+ -00:00:14 | 00:00:14
+ 1 day 02:03:04 | -1 days -02:03:04
+ 6 years | -6 years
+ 5 mons | -5 mons
+ 5 mons 12:00:00 | -5 mons -12:00:00
+ infinity | -infinity
+ -infinity | infinity
+(12 rows)
+
+SELECT -('-2147483648 months'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-2147483647 months'::interval); -- ok
+ ?column?
+------------------------
+ 178956970 years 7 mons
+(1 row)
+
+SELECT -('-2147483648 days'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-2147483647 days'::interval); -- ok
+ ?column?
+-----------------
+ 2147483647 days
+(1 row)
+
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-9223372036854775807 us'::interval); -- ok
+ ?column?
+-------------------------
+ 2562047788:00:54.775807
+(1 row)
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+ERROR: interval out of range
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
@@ -304,6 +391,17 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
(5 rows)
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column?
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- Floating point arithmetic rounding errors can lead to unexpected results,
@@ -386,12 +484,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +920,8 @@ SELECT interval '1 2:03:04.5678' minute to second(2);
SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
(f1 + INTERVAL '1 month')::INTERVAL MONTH::INTERVAL YEAR AS "years"
FROM interval_tbl;
- f1 | minutes | years
------------------+-----------------+----------
+ f1 | minutes | years
+-----------------+-----------------+-----------
00:01:00 | 00:01:00 | 00:00:00
05:00:00 | 05:00:00 | 00:00:00
10 days | 10 days | 00:00:00
@@ -832,7 +932,9 @@ SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
6 years | 6 years | 6 years
5 mons | 5 mons | 00:00:00
5 mons 12:00:00 | 5 mons 12:00:00 | 00:00:00
-(10 rows)
+ infinity | infinity | infinity
+ -infinity | -infinity | -infinity
+(12 rows)
-- test inputting and outputting SQL standard interval literals
SET IntervalStyle TO sql_standard;
@@ -1616,31 +1718,31 @@ select make_interval(mins := -1, secs := -9223372036800.0);
ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
--------------------------------------------------------------------
- -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+ -178956970 years -7 mons -2147483648 days -2562047788:00:54.775808
(1 row)
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
---------------------------------------------------
- -178956970-8 -2147483648 -2562047788:00:54.775808
+ -178956970-7 -2147483648 -2562047788:00:54.775808
(1 row)
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
-----------------------------------------------------
- P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+ P-178956970Y-7M-2147483648DT-2562047788H-54.775808S
(1 row)
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
------------------------------------------------------------------------------
- @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+ @ 178956970 years 7 mons 2147483648 days 2562047788 hours 54.775808 secs ago
(1 row)
-- check that '30 days' equals '1 month' according to the hash function
@@ -1719,19 +1821,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1805,7 +1909,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1814,6 +1920,292 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+--
+-- test infinite intervals
+--
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+ interval
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+ interval
+--------------------------------------------------------------------------
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs
+(1 row)
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ interval
+----------
+ infinity
+(1 row)
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ date | interval | plus | minus
+------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 | -infinity | -infinity | infinity
+ 1995-08-06 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+ interval1 | interval2 | plus | minus
+-----------+-----------+-----------------------+-----------------------
+ -infinity | -infinity | -infinity | interval out of range
+ -infinity | @ 10 days | -infinity | -infinity
+ -infinity | infinity | interval out of range | -infinity
+ @ 2 mons | -infinity | -infinity | infinity
+ @ 2 mons | @ 10 days | @ 2 mons 10 days | @ 2 mons -10 days
+ @ 2 mons | infinity | infinity | -infinity
+ infinity | -infinity | interval out of range | infinity
+ infinity | @ 10 days | infinity | infinity
+ infinity | infinity | infinity | interval out of range
+(9 rows)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamp | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamptz | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT time '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+ lhs | rhs | lt | le | eq | gt | ge | ne
+-----------+-------------------------+----+----+----+----+----+----
+ infinity | infinity | f | t | t | f | t | f
+ -infinity | infinity | t | t | f | f | f | t
+ infinity | -infinity | f | f | f | t | t | t
+ -infinity | -infinity | f | t | t | f | t | f
+ infinity | @ 1 year 2 days 3 hours | f | f | f | t | t | t
+ -infinity | @ 1 year 2 days 3 hours | t | t | f | f | f | t
+(6 rows)
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | um | mul | mul_neg | mul_inf | mul_inf_neg | div | div_neg
+-----------+-----------+-----------+-----------+-----------+-------------+-----------+-----------
+ infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity
+ -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity
+(2 rows)
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+ERROR: interval out of range
+SELECT interval 'infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+ERROR: interval out of range
+SELECT interval 'infinity' * 0;
+ERROR: interval out of range
+SELECT interval '-infinity' * 0;
+ERROR: interval out of range
+SELECT interval '0 days' * 'infinity'::float;
+ERROR: interval out of range
+SELECT interval '0 days' * '-infinity'::float;
+ERROR: interval out of range
+SELECT interval '5 days' * 'infinity'::float;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '5 days' * '-infinity'::float;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+ERROR: interval out of range
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | date_trunc
+-----------+------------
+ infinity | infinity
+ -infinity | -infinity
+(2 rows)
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | justify_days | justify_hours | justify_interval
+-----------+--------------+---------------+------------------
+ infinity | infinity | infinity | infinity
+ -infinity | -infinity | -infinity | -infinity
+(2 rows)
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT 'infinity'::interval::time;
+ERROR: cannot convert infinite interval to time
+SELECT '-infinity'::interval::time;
+ERROR: cannot convert infinite interval to time
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
ERROR: invalid input syntax for type interval: "42 days 2 seconds ago ago"
@@ -1832,3 +2224,42 @@ SELECT INTERVAL '1 year months days 5 hours';
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..835f0e5762 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '-infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp 'infinity', timestamp 'infinity');
+ERROR: interval out of range
+select age(timestamp 'infinity', timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 2ca2101dd4..a084357480 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3210,3 +3214,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+ERROR: interval out of range
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..2201740c18 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -2372,6 +2372,7 @@ create temp table datetimes(
f_timestamp timestamp
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -2381,14 +2382,16 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_time range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 1 | 11:00:00 | 1 | 3
+ 0 | 10:00:00 | 0 | 2
+ 1 | 11:00:00 | 0 | 3
2 | 12:00:00 | 1 | 4
3 | 13:00:00 | 2 | 6
4 | 14:00:00 | 3 | 6
@@ -2396,9 +2399,10 @@ window w as (order by f_time range between
6 | 15:00:00 | 4 | 7
7 | 17:00:00 | 7 | 9
8 | 18:00:00 | 7 | 10
- 9 | 19:00:00 | 8 | 10
- 10 | 20:00:00 | 9 | 10
-(10 rows)
+ 9 | 19:00:00 | 8 | 11
+ 10 | 20:00:00 | 9 | 11
+ 11 | 21:00:00 | 10 | 11
+(12 rows)
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2406,7 +2410,8 @@ window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 10 | 20:00:00 | 10 | 8
+ 11 | 21:00:00 | 11 | 9
+ 10 | 20:00:00 | 11 | 8
9 | 19:00:00 | 10 | 7
8 | 18:00:00 | 9 | 7
7 | 17:00:00 | 8 | 5
@@ -2414,17 +2419,90 @@ window w as (order by f_time desc range between
5 | 15:00:00 | 6 | 3
4 | 14:00:00 | 6 | 2
3 | 13:00:00 | 4 | 1
- 2 | 12:00:00 | 3 | 1
- 1 | 11:00:00 | 2 | 1
-(10 rows)
+ 2 | 12:00:00 | 3 | 0
+ 1 | 11:00:00 | 2 | 0
+ 0 | 10:00:00 | 1 | 0
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | 0 | 11
+ 1 | 11:00:00 | 0 | 11
+ 2 | 12:00:00 | 0 | 11
+ 3 | 13:00:00 | 0 | 11
+ 4 | 14:00:00 | 0 | 11
+ 5 | 15:00:00 | 0 | 11
+ 6 | 15:00:00 | 0 | 11
+ 7 | 17:00:00 | 0 | 11
+ 8 | 18:00:00 | 0 | 11
+ 9 | 19:00:00 | 0 | 11
+ 10 | 20:00:00 | 0 | 11
+ 11 | 21:00:00 | 0 | 11
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 1 | 11:00:00+01 | 1 | 3
+ 0 | 10:00:00+01 | 0 | 2
+ 1 | 11:00:00+01 | 0 | 3
2 | 12:00:00+01 | 1 | 4
3 | 13:00:00+01 | 2 | 6
4 | 14:00:00+01 | 3 | 6
@@ -2432,9 +2510,10 @@ window w as (order by f_timetz range between
6 | 15:00:00+01 | 4 | 7
7 | 17:00:00+01 | 7 | 9
8 | 18:00:00+01 | 7 | 10
- 9 | 19:00:00+01 | 8 | 10
- 10 | 20:00:00+01 | 9 | 10
-(10 rows)
+ 9 | 19:00:00+01 | 8 | 11
+ 10 | 20:00:00+01 | 9 | 11
+ 11 | 21:00:00+01 | 10 | 11
+(12 rows)
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2442,7 +2521,8 @@ window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 10 | 20:00:00+01 | 10 | 8
+ 11 | 21:00:00+01 | 11 | 9
+ 10 | 20:00:00+01 | 11 | 8
9 | 19:00:00+01 | 10 | 7
8 | 18:00:00+01 | 9 | 7
7 | 17:00:00+01 | 8 | 5
@@ -2450,16 +2530,89 @@ window w as (order by f_timetz desc range between
5 | 15:00:00+01 | 6 | 3
4 | 14:00:00+01 | 6 | 2
3 | 13:00:00+01 | 4 | 1
- 2 | 12:00:00+01 | 3 | 1
- 1 | 11:00:00+01 | 2 | 1
-(10 rows)
+ 2 | 12:00:00+01 | 3 | 0
+ 1 | 11:00:00+01 | 2 | 0
+ 0 | 10:00:00+01 | 1 | 0
+(12 rows)
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | 0 | 11
+ 1 | 11:00:00+01 | 0 | 11
+ 2 | 12:00:00+01 | 0 | 11
+ 3 | 13:00:00+01 | 0 | 11
+ 4 | 14:00:00+01 | 0 | 11
+ 5 | 15:00:00+01 | 0 | 11
+ 6 | 15:00:00+01 | 0 | 11
+ 7 | 17:00:00+01 | 0 | 11
+ 8 | 18:00:00+01 | 0 | 11
+ 9 | 19:00:00+01 | 0 | 11
+ 10 | 20:00:00+01 | 0 | 11
+ 11 | 21:00:00+01 | 0 | 11
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | @ 1 year | 1 | 2
2 | @ 2 years | 1 | 3
3 | @ 3 years | 2 | 4
@@ -2470,7 +2623,8 @@ window w as (order by f_interval range between
8 | @ 8 years | 7 | 9
9 | @ 9 years | 8 | 10
10 | @ 10 years | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2478,6 +2632,7 @@ window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | @ 10 years | 10 | 9
9 | @ 9 years | 10 | 8
8 | @ 8 years | 9 | 7
@@ -2488,14 +2643,87 @@ window w as (order by f_interval desc range between
3 | @ 3 years | 4 | 2
2 | @ 2 years | 3 | 1
1 | @ 1 year | 2 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 0 | 11
+ 2 | @ 2 years | 0 | 11
+ 3 | @ 3 years | 0 | 11
+ 4 | @ 4 years | 0 | 11
+ 5 | @ 5 years | 0 | 11
+ 6 | @ 5 years | 0 | 11
+ 7 | @ 7 years | 0 | 11
+ 8 | @ 8 years | 0 | 11
+ 9 | @ 9 years | 0 | 11
+ 10 | @ 10 years | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | @ 1 year | 0 | 0
+ 2 | @ 2 years | 0 | 0
+ 3 | @ 3 years | 0 | 0
+ 4 | @ 4 years | 0 | 0
+ 5 | @ 5 years | 0 | 0
+ 6 | @ 5 years | 0 | 0
+ 7 | @ 7 years | 0 | 0
+ 8 | @ 8 years | 0 | 0
+ 9 | @ 9 years | 0 | 0
+ 10 | @ 10 years | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 11 | 11
+ 2 | @ 2 years | 11 | 11
+ 3 | @ 3 years | 11 | 11
+ 4 | @ 4 years | 11 | 11
+ 5 | @ 5 years | 11 | 11
+ 6 | @ 5 years | 11 | 11
+ 7 | @ 7 years | 11 | 11
+ 8 | @ 8 years | 11 | 11
+ 9 | @ 9 years | 11 | 11
+ 10 | @ 10 years | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 02:23:54 2000 PDT | 1 | 3
2 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
3 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
@@ -2506,7 +2734,8 @@ window w as (order by f_timestamptz range between
8 | Thu Oct 19 02:23:54 2006 PDT | 7 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 8 | 10
10 | Sun Oct 19 02:23:54 2008 PDT | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2514,6 +2743,7 @@ window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 02:23:54 2008 PDT | 10 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 10 | 8
8 | Thu Oct 19 02:23:54 2006 PDT | 9 | 7
@@ -2524,14 +2754,87 @@ window w as (order by f_timestamptz desc range between
3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 0
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 0
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 0
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 0
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 0
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 0
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 0
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 11 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 11 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 11 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 11 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 11 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 11 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 11 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 10:23:54 2000 | 1 | 3
2 | Fri Oct 19 10:23:54 2001 | 1 | 4
3 | Fri Oct 19 10:23:54 2001 | 1 | 4
@@ -2542,7 +2845,8 @@ window w as (order by f_timestamp range between
8 | Thu Oct 19 10:23:54 2006 | 7 | 9
9 | Fri Oct 19 10:23:54 2007 | 8 | 10
10 | Sun Oct 19 10:23:54 2008 | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2550,6 +2854,7 @@ window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 10:23:54 2008 | 10 | 9
9 | Fri Oct 19 10:23:54 2007 | 10 | 8
8 | Thu Oct 19 10:23:54 2006 | 9 | 7
@@ -2560,8 +2865,80 @@ window w as (order by f_timestamp desc range between
3 | Fri Oct 19 10:23:54 2001 | 4 | 1
2 | Fri Oct 19 10:23:54 2001 | 4 | 1
1 | Thu Oct 19 10:23:54 2000 | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 0
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 0
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 0
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 0
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 0
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 0
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 0
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 11 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 11 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 11 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 11 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 11 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 11 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 11 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -4375,6 +4752,52 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev_curr_avg | curr_next_sum | prev_curr_sum
+------------------------------------------------------------------------------+-------------------+-------------------+---------------+---------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago | -infinity | infinity | -infinity | infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs | infinity | -infinity | infinity | -infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
+(10 rows)
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
i | sum
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
index b666dbad67..116106d30f 100644
--- a/src/test/regress/sql/brin_multi.sql
+++ b/src/test/regress/sql/brin_multi.sql
@@ -682,6 +682,25 @@ SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan = off;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 39a35a6b7c..252bce4b1c 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -213,10 +213,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -293,7 +295,6 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index a0a373f08b..caad291890 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -66,6 +70,17 @@ SELECT r1.*, r2.*
WHERE r1.f1 > r2.f1
ORDER BY r1.f1, r2.f1;
+-- test unary minus
+
+SELECT f1, -f1 FROM INTERVAL_TBL;
+SELECT -('-2147483648 months'::interval); -- should fail
+SELECT -('-2147483647 months'::interval); -- ok
+SELECT -('-2147483648 days'::interval); -- should fail
+SELECT -('-2147483647 days'::interval); -- ok
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+SELECT -('-9223372036854775807 us'::interval); -- ok
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
@@ -95,6 +110,9 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
@@ -140,7 +158,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -528,13 +546,13 @@ select make_interval(mins := -1, secs := -9223372036800.0);
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
-- check that '30 days' equals '1 month' according to the hash function
select '30 days'::interval = '1 month'::interval as t;
@@ -598,6 +616,157 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+--
+-- test infinite intervals
+--
+
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+SELECT time '11:27:42' + interval '-infinity';
+SELECT time '11:27:42' - interval 'infinity';
+SELECT time '11:27:42' - interval '-infinity';
+SELECT timetz '11:27:42' + interval 'infinity';
+SELECT timetz '11:27:42' + interval '-infinity';
+SELECT timetz '11:27:42' - interval 'infinity';
+SELECT timetz '11:27:42' - interval '-infinity';
+
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+SELECT interval 'infinity' * 'nan';
+SELECT interval '-infinity' * 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+SELECT interval 'infinity' * 0;
+SELECT interval '-infinity' * 0;
+SELECT interval '0 days' * 'infinity'::float;
+SELECT interval '0 days' * '-infinity'::float;
+SELECT interval '5 days' * 'infinity'::float;
+SELECT interval '5 days' * '-infinity'::float;
+
+SELECT interval 'infinity' / 'infinity';
+SELECT interval 'infinity' / '-infinity';
+SELECT interval 'infinity' / 'nan';
+SELECT interval '-infinity' / 'infinity';
+SELECT interval '-infinity' / '-infinity';
+SELECT interval '-infinity' / 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+
+SELECT 'infinity'::interval::time;
+SELECT '-infinity'::interval::time;
+
+SELECT to_char('infinity'::interval, 'YYYY');
+SELECT to_char('-infinity'::interval, 'YYYY');
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
SELECT INTERVAL '2 minutes ago 5 days';
@@ -605,3 +774,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index cdc57bc160..a2dcd5f5d8 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -642,3 +644,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..437e948d6c 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -673,6 +673,7 @@ create temp table datetimes(
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -682,7 +683,8 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -694,6 +696,32 @@ from datetimes
window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
@@ -704,6 +732,32 @@ from datetimes
window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
@@ -714,6 +768,32 @@ from datetimes
window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
@@ -724,6 +804,32 @@ from datetimes
window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
@@ -734,6 +840,32 @@ from datetimes
window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -1591,6 +1723,39 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 87c1aee379..236683e5ab 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1205,6 +1205,7 @@ IntegerSet
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup
--
2.35.3
On Wed, Nov 8, 2023 at 7:42 AM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Tue, 7 Nov 2023 at 14:33, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
New version attached doing that, to run it past the cfbot again.
Ah, Windows Server didn't like that. Trying again with "INT64CONST(0)"
instead of just "0" in interval_um().Regards,
Dean
I found this:
https://developercommunity.visualstudio.com/t/please-implement-integer-overflow-detection/409051
maybe related.
On Wed, 8 Nov 2023 at 06:56, jian he <jian.universality@gmail.com> wrote:
On Tue, 7 Nov 2023 at 14:33, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
Ah, Windows Server didn't like that. Trying again with "INT64CONST(0)"
instead of just "0" in interval_um().I found this:
https://developercommunity.visualstudio.com/t/please-implement-integer-overflow-detection/409051
maybe related.
Hmm, actually, this has revealed a bug in our 64-bit integer
subtraction code, on platforms that don't have builtins or 128-bit
integer support. I have created a new thread for that, since it's
nothing to do with this patch.
Regards,
Dean
On Wed, Nov 8, 2023 at 5:32 PM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Wed, 8 Nov 2023 at 06:56, jian he <jian.universality@gmail.com> wrote:
On Tue, 7 Nov 2023 at 14:33, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
Ah, Windows Server didn't like that. Trying again with "INT64CONST(0)"
instead of just "0" in interval_um().I found this:
https://developercommunity.visualstudio.com/t/please-implement-integer-overflow-detection/409051
maybe related.Hmm, actually, this has revealed a bug in our 64-bit integer
subtraction code, on platforms that don't have builtins or 128-bit
integer support. I have created a new thread for that, since it's
nothing to do with this patch.
Just to test whether that bug fix also fixes the failure seen with
this patchset, I am attaching the patchset including the patch with
the fix.
0001 - fix in other thread
0002 and 0003 are 0001 and 0002 in the previous patch set.
--
Best Wishes,
Ashutosh Bapat
Attachments:
0002-Avoid-integer-overflow-hazard-in-interval_t-20231109.patchtext/x-patch; charset=US-ASCII; name=0002-Avoid-integer-overflow-hazard-in-interval_t-20231109.patchDownload
From 3117b2d53261ae0414b4dcdfad1d960d4f5a0256 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Sun, 5 Nov 2023 14:45:13 +0000
Subject: [PATCH 2/3] Avoid integer overflow hazard in interval_time().
When casting an interval to a time, the original code suffered from
64-bit integer overflow for inputs with a sufficiently large negative
"time" field, leading to bogus results.
Fix by rewriting the algorithm in a simpler form, that more obviously
cannot overflow. While at it, improve the test coverage to test
negative interval inputs.
---
src/backend/utils/adt/date.c | 15 +++------------
src/test/regress/expected/horology.out | 12 ++++++++++++
src/test/regress/sql/horology.sql | 2 ++
3 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 56c7746c11..544e1d32bf 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -2012,19 +2012,10 @@ interval_time(PG_FUNCTION_ARGS)
{
Interval *span = PG_GETARG_INTERVAL_P(0);
TimeADT result;
- int64 days;
- result = span->time;
- if (result >= USECS_PER_DAY)
- {
- days = result / USECS_PER_DAY;
- result -= days * USECS_PER_DAY;
- }
- else if (result < 0)
- {
- days = (-result + USECS_PER_DAY - 1) / USECS_PER_DAY;
- result += days * USECS_PER_DAY;
- }
+ result = span->time % USECS_PER_DAY;
+ if (result < 0)
+ result += USECS_PER_DAY;
PG_RETURN_TIMEADT(result);
}
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index e63e5b30fe..8f52661096 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -981,6 +981,18 @@ SELECT CAST(interval '02:03' AS time) AS "02:03:00";
02:03:00
(1 row)
+SELECT CAST(interval '-02:03' AS time) AS "21:57:00";
+ 21:57:00
+----------
+ 21:57:00
+(1 row)
+
+SELECT CAST(interval '-9223372022400000000 us' AS time) AS "00:00:00";
+ 00:00:00
+----------
+ 00:00:00
+(1 row)
+
SELECT time '01:30' + interval '02:01' AS "03:31:00";
03:31:00
----------
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index f7f8c8d2dd..39a35a6b7c 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -182,6 +182,8 @@ SELECT d1 - interval '1 year' AS one_year FROM TIMESTAMPTZ_TBL;
SELECT CAST(time '01:02' AS interval) AS "+01:02";
SELECT CAST(interval '02:03' AS time) AS "02:03:00";
+SELECT CAST(interval '-02:03' AS time) AS "21:57:00";
+SELECT CAST(interval '-9223372022400000000 us' AS time) AS "00:00:00";
SELECT time '01:30' + interval '02:01' AS "03:31:00";
SELECT time '01:30' - interval '02:01' AS "23:29:00";
SELECT time '02:30' + interval '36:01' AS "14:31:00";
--
2.25.1
0001-Fix-integer-overflow-in-pg_sub_s64_overflow-20231109.patchtext/x-patch; charset=US-ASCII; name=0001-Fix-integer-overflow-in-pg_sub_s64_overflow-20231109.patchDownload
From e921eb52650d59847af81cc2f1b8a197ad3811cd Mon Sep 17 00:00:00 2001
From: Ashutosh Bapat <ashutosh.bapat@enterprisedb.com>
Date: Thu, 9 Nov 2023 12:27:20 +0530
Subject: [PATCH 1/3] Fix integer overflow in pg_sub_s64_overflow()
Dean Rasheed's patch as is
---
src/include/common/int.h | 6 +++++-
src/test/regress/expected/int8.out | 2 ++
src/test/regress/sql/int8.sql | 1 +
3 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/src/include/common/int.h b/src/include/common/int.h
index 450800894e..487124473d 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -200,8 +200,12 @@ pg_sub_s64_overflow(int64 a, int64 b, int64 *result)
*result = (int64) res;
return false;
#else
+ /*
+ * Note: overflow is also possible when a == 0 and b < 0 (specifically,
+ * when b == PG_INT64_MIN).
+ */
if ((a < 0 && b > 0 && a < PG_INT64_MIN + b) ||
- (a > 0 && b < 0 && a > PG_INT64_MAX + b))
+ (a >= 0 && b < 0 && a > PG_INT64_MAX + b))
{
*result = 0x5EED; /* to avoid spurious warnings */
return true;
diff --git a/src/test/regress/expected/int8.out b/src/test/regress/expected/int8.out
index 9542d622ba..fddc09f630 100644
--- a/src/test/regress/expected/int8.out
+++ b/src/test/regress/expected/int8.out
@@ -679,6 +679,8 @@ select -('-9223372036854775807'::int8);
select -('-9223372036854775808'::int8);
ERROR: bigint out of range
+select 0::int8 - '-9223372036854775808'::int8;
+ERROR: bigint out of range
select '9223372036854775800'::int8 + '9223372036854775800'::int8;
ERROR: bigint out of range
select '-9223372036854775800'::int8 + '-9223372036854775800'::int8;
diff --git a/src/test/regress/sql/int8.sql b/src/test/regress/sql/int8.sql
index 33f664dd02..fffb28906a 100644
--- a/src/test/regress/sql/int8.sql
+++ b/src/test/regress/sql/int8.sql
@@ -132,6 +132,7 @@ select '9223372036854775808'::int8;
select -('-9223372036854775807'::int8);
select -('-9223372036854775808'::int8);
+select 0::int8 - '-9223372036854775808'::int8;
select '9223372036854775800'::int8 + '9223372036854775800'::int8;
select '-9223372036854775800'::int8 + '-9223372036854775800'::int8;
--
2.25.1
0003-Support-infinity-in-the-interval-data-type-20231109.patchtext/x-patch; charset=US-ASCII; name=0003-Support-infinity-in-the-interval-data-type-20231109.patchDownload
From 837d2f6f1af7a38f189c875428140b1174d77783 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Mon, 6 Nov 2023 13:52:27 +0000
Subject: [PATCH 3/3] Support +/- infinity in the interval data type.
This adds support for infinity to the interval data type, using the
same input/output representation as the other date/time data types
that support infinity. This allows various arithmetic operations on
infinite dates, timestamps and intervals.
The new values are represented by setting all fields of the interval
to INT32/64_MIN for -infinity, and INT32/64_MAX for +infinity. This
ensures that they compare as less/greater than all other interval
values, without the need for any special-case comparison code.
Note that, since those 2 values were formerly accepted as legal finite
intervals, pg_upgrade and dump/restore from an old database will turn
them from finite to infinite intervals. That seems OK, since those
exact values should be extremely rare in practice, and they are
outside the documented range supported by the interval type, which
gives us a certain amount of leeway.
Joseph Koshakow, Jian He, and Ashutosh Bapat, reviewed by Dean Rasheed.
Discussion: https://postgr.es/m/CAAvxfHea4%2BsPybKK7agDYOMo9N-Z3J6ZXf3BOM79pFsFNcRjwA%40mail.gmail.com
---
contrib/btree_gin/btree_gin.c | 5 +-
doc/src/sgml/datatype.sgml | 4 +-
doc/src/sgml/func.sgml | 8 +-
src/backend/utils/adt/date.c | 57 +-
src/backend/utils/adt/datetime.c | 26 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 4 +
src/backend/utils/adt/timestamp.c | 1080 +++++++++++++++++----
src/include/catalog/pg_aggregate.dat | 24 +-
src/include/catalog/pg_proc.dat | 28 +-
src/include/datatype/timestamp.h | 27 +-
src/test/regress/expected/brin_multi.out | 28 +
src/test/regress/expected/horology.out | 71 +-
src/test/regress/expected/interval.out | 499 +++++++++-
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/expected/window.out | 469 ++++++++-
src/test/regress/sql/brin_multi.sql | 19 +
src/test/regress/sql/horology.sql | 3 +-
src/test/regress/sql/interval.sql | 193 +++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
src/test/regress/sql/window.sql | 167 +++-
src/tools/pgindent/typedefs.list | 1 +
24 files changed, 2577 insertions(+), 299 deletions(-)
diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c
index b09bb8df96..5e27906f80 100644
--- a/contrib/btree_gin/btree_gin.c
+++ b/contrib/btree_gin/btree_gin.c
@@ -306,9 +306,8 @@ leftmostvalue_interval(void)
{
Interval *v = palloc(sizeof(Interval));
- v->time = PG_INT64_MIN;
- v->day = PG_INT32_MIN;
- v->month = PG_INT32_MIN;
+ INTERVAL_NOBEGIN(v);
+
return IntervalPGetDatum(v);
}
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 5a6cfbd94d..4aba6d19ee 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2328,12 +2328,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d963f0a0a0..146d71dd9f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9553,7 +9553,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10450,7 +10450,11 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for <type>timestamp</type> inputs; <literal>epoch</literal>, <literal>hour</literal>,
+ <literal>day</literal>, <literal>year</literal>, <literal>decade</literal>,
+ <literal>century</literal>, and <literal>millennium</literal> for
+ <type>interval</type> inputs).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 544e1d32bf..13745a0adc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -24,6 +24,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
+#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/supportnodes.h"
@@ -2013,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(0);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot convert infinite interval to time")));
+
result = span->time % USECS_PER_DAY;
if (result < 0)
result += USECS_PER_DAY;
@@ -2049,6 +2055,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2067,6 +2078,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2090,7 +2106,8 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* Like time_pl_interval/time_mi_interval, we disregard the month and day
- * fields of the offset. So our test for negative should too.
+ * fields of the offset. So our test for negative should too. This also
+ * catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2100,13 +2117,14 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* We can't use time_pl_interval/time_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum = base - offset->time;
- else
- sum = base + offset->time;
+ else if (pg_add_s64_overflow(base, offset->time, &sum))
+ PG_RETURN_BOOL(less);
if (less)
PG_RETURN_BOOL(val <= sum);
@@ -2581,6 +2599,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2603,6 +2626,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -2630,7 +2658,8 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* Like timetz_pl_interval/timetz_mi_interval, we disregard the month and
- * day fields of the offset. So our test for negative should too.
+ * day fields of the offset. So our test for negative should too. This
+ * also catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2640,13 +2669,14 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* We can't use timetz_pl_interval/timetz_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum.time = base->time - offset->time;
- else
- sum.time = base->time + offset->time;
+ else if (pg_add_s64_overflow(base->time, offset->time, &sum.time))
+ PG_RETURN_BOOL(less);
sum.zone = base->zone;
if (less)
@@ -3096,6 +3126,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..fca9a2a6e9 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3271,6 +3271,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3574,6 +3577,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
@@ -3597,6 +3602,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+
+ /*
+ * Only reserved words corresponding to infinite
+ * intervals are accepted.
+ */
+ if (uval != DTK_LATE && uval != DTK_EARLY)
+ return DTERR_BAD_FORMAT;
+
+ /*
+ * Infinity cannot be followed by anything else. We
+ * could allow "ago" to reverse the sign of infinity
+ * but using signed infinity is more intuitive.
+ */
+ if (i != nf - 1)
+ return DTERR_BAD_FORMAT;
+
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 8131091f79..d176723d95 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..4ea5415f20 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4802,6 +4802,10 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
* Convert the month part of Interval to days using assumed
* average month length of 365.25/12.0 days. Not too
* accurate, but plenty good enough for our purposes.
+ *
+ * This also works for infinite intervals, which just have all
+ * fields set to INT_MIN/INT_MAX, and so will produce a result
+ * smaller/larger than any finite interval.
*/
return interval->time + interval->day * (double) USECS_PER_DAY +
interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 647b97aca6..54080e76b4 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -72,6 +72,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+/*
+ * The transition datatype for interval aggregates is declared as internal.
+ * It's a pointer to an IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+ int64 N; /* count of finite intervals processed */
+ Interval sumX; /* sum of finite intervals processed */
+ /* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +infinity intervals */
+ int64 nInfcount; /* count of -infinity intervals */
+} IntervalAggState;
+
+#define IA_TOTAL_COUNT(ia) \
+ ((ia)->N + (ia)->pInfcount + (ia)->nInfcount)
static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
static Timestamp dt2local(Timestamp dt, int timezone);
@@ -80,6 +95,12 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void EncodeSpecialInterval(const Interval *interval, char *str);
+static void interval_um_internal(const Interval *interval, Interval *result);
+static void finite_interval_pl(const Interval *span1, const Interval *span2,
+ Interval *result);
+static void finite_interval_mi(const Interval *span1, const Interval *span2,
+ Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +962,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +992,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1384,10 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod,
INT64CONST(0)
};
+ /* Typmod has no effect on infinite intervals */
+ if (INTERVAL_NOT_FINITE(interval))
+ return true;
+
/*
* Unspecified range and precision? Then not necessary to adjust. Setting
* typmod to -1 is the convention for all data types.
@@ -1536,6 +1574,10 @@ make_interval(PG_FUNCTION_ARGS)
pg_add_s64_overflow(result->time, (int64) secs, &result->time))
goto out_of_range;
+ /* make sure that the result is finite */
+ if (INTERVAL_NOT_FINITE(result))
+ goto out_of_range;
+
PG_RETURN_INTERVAL_P(result);
out_of_range:
@@ -1560,6 +1602,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+static void
+EncodeSpecialInterval(const Interval *interval, char *str)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ strcpy(str, EARLY);
+ else if (INTERVAL_IS_NOEND(interval))
+ strcpy(str, LATE);
+ else /* shouldn't happen */
+ elog(ERROR, "invalid argument for EncodeSpecialInterval");
+}
+
Datum
now(PG_FUNCTION_ARGS)
{
@@ -2015,6 +2068,9 @@ interval2itm(Interval span, struct pg_itm *itm)
/* itm2interval()
* Convert a pg_itm structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * This is for use in computations expected to produce finite results. Any
+ * inputs that lead to infinite results are treated as overflows.
*/
int
itm2interval(struct pg_itm *itm, Interval *span)
@@ -2038,12 +2094,21 @@ itm2interval(struct pg_itm *itm, Interval *span)
if (pg_add_s64_overflow(span->time, itm->tm_usec,
&span->time))
return -1;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
/* itmin2interval()
* Convert a pg_itm_in structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * Note: if the result is infinite, it is not treated as an overflow. This
+ * avoids any dump/reload hazards from pre-17 databases that do not support
+ * infinite intervals, but do allow finite intervals with all fields set to
+ * INT_MIN/INT_MAX (outside the documented range). Such intervals will be
+ * silently converted to +/-infinity. This may not be ideal, but seems
+ * preferable to failure, and ought to be pretty unlikely in practice.
*/
int
itmin2interval(struct pg_itm_in *itm_in, Interval *span)
@@ -2088,7 +2153,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2442,6 +2509,15 @@ interval_cmp_internal(const Interval *interval1, const Interval *interval2)
return int128_compare(span1, span2);
}
+static int
+interval_sign(const Interval *interval)
+{
+ INT128 span = interval_cmp_value(interval);
+ INT128 zero = int64_to_int128(0);
+
+ return int128_compare(span, zero);
+}
+
Datum
interval_eq(PG_FUNCTION_ARGS)
{
@@ -2714,10 +2790,39 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else /* TIMESTAMP_IS_NOEND(dt2) */
+ INTERVAL_NOBEGIN(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
ereport(ERROR,
@@ -2783,6 +2888,10 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
/* pre-justify days if it might prevent overflow */
if ((result->day > 0 && result->time > 0) ||
(result->day < 0 && result->time < 0))
@@ -2858,6 +2967,10 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
TMODULO(result->time, wholeday, USECS_PER_DAY);
if (pg_add_s32_overflow(result->day, wholeday, &result->day))
ereport(ERROR,
@@ -2896,6 +3009,10 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
wholemonth = result->day / DAYS_PER_MONTH;
result->day -= wholemonth * DAYS_PER_MONTH;
if (pg_add_s32_overflow(result->month, wholemonth, &result->month))
@@ -2934,7 +3051,31 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Timestamp result;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the timestamp type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3013,9 +3154,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3042,7 +3181,31 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the timestamptz type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3132,9 +3295,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3185,6 +3346,29 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* interval_um_internal()
+ * Negate an interval.
+ */
+static void
+interval_um_internal(const Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else
+ {
+ /* Negate each field, guarding against overflow */
+ if (pg_sub_s64_overflow(INT64CONST(0), interval->time, &result->time) ||
+ pg_sub_s32_overflow(0, interval->day, &result->day) ||
+ pg_sub_s32_overflow(0, interval->month, &result->month) ||
+ INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3192,23 +3376,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3252,27 +3420,34 @@ 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")));
-
- 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_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);
PG_RETURN_INTERVAL_P(result);
}
@@ -3286,27 +3461,36 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->day = span1->day - span2->day;
- if (!SAMESIGN(span1->day, span2->day) &&
- !SAMESIGN(result->day, span1->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
-
- result->time = span1->time - span2->time;
- if (!SAMESIGN(span1->time, span2->time) &&
- !SAMESIGN(result->time, span1->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span1))
+ {
+ if (INTERVAL_IS_NOBEGIN(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1))
+ {
+ if (INTERVAL_IS_NOEND(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (INTERVAL_IS_NOBEGIN(span2))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(span2))
+ INTERVAL_NOBEGIN(result);
+ else
+ finite_interval_mi(span1, span2, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3331,6 +3515,46 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ /*
+ * Handle NaN and infinities.
+ *
+ * We treat "0 * infinity" and "infinity * 0" as errors, since the
+ * interval type has nothing equivalent to NaN.
+ */
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor == 0.0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+ if (isinf(factor))
+ {
+ int isign = interval_sign(span);
+
+ if (isign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (factor * isign < 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ INTERVAL_NOEND(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3391,6 +3615,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3422,6 +3651,33 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ /*
+ * Handle NaN and infinities.
+ *
+ * We treat "infinity / infinity" as an error, since the interval type has
+ * nothing equivalent to NaN. Otherwise, dividing by infinity is handled
+ * by the regular division code, causing all fields to be set to zero.
+ */
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (isinf(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = (int32) (span->month / factor);
result->day = (int32) (span->day / factor);
@@ -3443,6 +3699,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3466,11 +3727,21 @@ in_range_timestamptz_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
TimestampTz sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = timestamptz_mi_interval_internal(base, offset, NULL);
@@ -3493,11 +3764,21 @@ in_range_timestamp_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Timestamp sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval,
@@ -3524,11 +3805,21 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Interval *sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? INTERVAL_IS_NOEND(base) : INTERVAL_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
@@ -3547,161 +3838,387 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function, that needs to compute
+ * sum and count, in the aggregate's memory context.
+ *
+ * The 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;
+}
+
+/*
+ * Functions to add or subtract finite intervals.
*
- * 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.
+ * These are used for normal arithmetic and aggregation of finite intervals
+ * only. Non-finite intervals require special handling.
*/
+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))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_add_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+static void
+finite_interval_mi(const Interval *span1, const Interval *span2, Interval *result)
+{
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
+
+ if (pg_sub_s32_overflow(span1->month, span2->month, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_sub_s32_overflow(span1->day, span2->day, &result->day))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_sub_s64_overflow(span1->time, span2->time, &result->time))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+}
+
+/*
+ * 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_accum(PG_FUNCTION_ARGS)
+interval_avg_accum(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 *state;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ /* Create the state data on the first call */
+ if (state == NULL)
+ state = makeIntervalAggState(fcinfo);
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
+ if (!PG_ARGISNULL(1))
+ do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ PG_RETURN_POINTER(state);
+}
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+/*
+ * Combine function for sum() and avg() interval aggregates.
+ *
+ * Combine the given internal aggregate states and place the combination in
+ * the first argument.
+ */
+Datum
+interval_avg_combine(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state1;
+ IntervalAggState *state2;
- PG_RETURN_ARRAYTYPE_P(result);
+ 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);
+
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
+
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
+
+ PG_RETURN_POINTER(state1);
+ }
+
+ state1->N += state2->N;
+ state1->pInfcount += state2->pInfcount;
+ state1->nInfcount += state2->nInfcount;
+
+ /* Accumulate finite interval values, if any. */
+ if (state2->N > 0)
+ finite_interval_pl(&state1->sumX, &state2->sumX, &state1->sumX);
+
+ 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;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ if (!AggCheckCallContext(fcinfo, NULL))
+ elog(ERROR, "aggregate function called in non-aggregate context");
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ sstate = PG_GETARG_BYTEA_PP(0);
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ /*
+ * Initialize a StringInfo so that we can "receive" it using the standard
+ * recv-function infrastructure.
+ */
+ initReadOnlyStringInfo(&buf, VARDATA_ANY(sstate),
+ VARSIZE_ANY_EXHDR(sstate));
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ result = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ /* N */
+ result->N = pq_getmsgint64(&buf);
- PG_RETURN_ARRAYTYPE_P(result);
+ /* sumX */
+ result->sumX.time = pq_getmsgint64(&buf);
+ result->sumX.day = pq_getmsgint(&buf, 4);
+ result->sumX.month = pq_getmsgint(&buf, 4);
+
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
+
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
+
+ pq_getmsgend(&buf);
+
+ 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");
+
+ if (!PG_ARGISNULL(1))
+ do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
+
+ PG_RETURN_POINTER(state);
+}
+
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 || state->nInfcount > 0)
+ {
+ Interval *result;
+
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else
+ INTERVAL_NOBEGIN(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
+ IntervalPGetDatum(&state->sumX),
+ Float8GetDatum((double) state->N));
}
+/* sum(interval) aggregate final function */
+Datum
+interval_sum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ Interval *result;
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
+
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else if (state->nInfcount > 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ memcpy(result, &state->sumX, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
@@ -3726,8 +4243,36 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3846,8 +4391,36 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3972,6 +4545,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4155,6 +4733,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4393,6 +4976,12 @@ interval_trunc(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ memcpy(result, interval, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
VARSIZE_ANY_EXHDR(units),
false);
@@ -4737,7 +5326,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5011,7 +5600,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5251,6 +5840,58 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite interval. Returns
+ * +/-Infinity if that is the appropriate result, otherwise returns zero
+ * (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative)
+{
+ if ((type != UNITS) && (type != RESERV))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unit \"%s\" not recognized for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_YEAR:
+ case DTK_DECADE:
+ case DTK_CENTURY:
+ case DTK_MILLENNIUM:
+ case DTK_EPOCH:
+ if (isNegative)
+ return -get_float8_infinity();
+ else
+ return get_float8_infinity();
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unit \"%s\" not supported for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+ return 0.0; /* keep compiler quiet */
+ }
+}
/* interval_part() and extract_interval()
* Extract specified field from interval.
@@ -5275,6 +5916,33 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval));
+
+ if (r != 0.0)
+ {
+ if (retnumeric)
+ {
+ if (r < 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("-Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ else if (r > 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ }
+ else
+ PG_RETURN_FLOAT8(r);
+ }
+ else
+ PG_RETURN_NULL();
+ }
+
if (type == UNITS)
{
interval2itm(*interval, tm);
@@ -5517,6 +6185,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5747,6 +6422,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5783,7 +6465,6 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
Timestamp finish = PG_GETARG_TIMESTAMP(1);
Interval *step = PG_GETARG_INTERVAL_P(2);
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5806,13 +6487,18 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
fctx->step = *step;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
@@ -5864,7 +6550,6 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
Interval *step = PG_GETARG_INTERVAL_P(2);
text *zone = (PG_NARGS() == 4) ? PG_GETARG_TEXT_PP(3) : NULL;
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5888,13 +6573,18 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
fctx->attimezone = zone ? lookup_timezone(zone) : session_timezone;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e1a17cddd8 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
{ 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',
@@ -72,10 +73,13 @@
{ 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',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f14aed422a..d6a990e531 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4917,17 +4917,29 @@
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' },
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..b63acc0a2f 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -151,7 +151,7 @@ struct pg_itm_in
#define TIMESTAMP_INFINITY PG_INT64_MAX
/*
- * Historically these alias for infinity have been used.
+ * Historically these aliases for infinity have been used.
*/
#define DT_NOBEGIN TIMESTAMP_MINUS_INFINITY
#define DT_NOEND TIMESTAMP_INFINITY
@@ -168,6 +168,31 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+/*
+ * Infinite intervals are represented by setting all fields to the minimum or
+ * maximum integer values.
+ */
+#define INTERVAL_NOBEGIN(i) \
+ do { \
+ (i)->time = PG_INT64_MIN; \
+ (i)->day = PG_INT32_MIN; \
+ (i)->month = PG_INT32_MIN; \
+ } while (0)
+
+#define INTERVAL_IS_NOBEGIN(i) \
+ ((i)->month == PG_INT32_MIN && (i)->day == PG_INT32_MIN && (i)->time == PG_INT64_MIN)
+
+#define INTERVAL_NOEND(i) \
+ do { \
+ (i)->time = PG_INT64_MAX; \
+ (i)->day = PG_INT32_MAX; \
+ (i)->month = PG_INT32_MAX; \
+ } while (0)
+
+#define INTERVAL_IS_NOEND(i) \
+ ((i)->month == PG_INT32_MAX && (i)->day == PG_INT32_MAX && (i)->time == PG_INT64_MAX)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
index 80801cd4ee..8f4c95b9e6 100644
--- a/src/test/regress/expected/brin_multi.out
+++ b/src/test/regress/expected/brin_multi.out
@@ -941,6 +941,34 @@ SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
Index Cond: (a = '@ 30 years'::interval)
(4 rows)
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+SET enable_seqscan = off;
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years ago'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years ago'::interval)
+(4 rows)
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years'::interval)
+(4 rows)
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 8f52661096..cfb4b205e4 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1171,6 +1171,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1278,6 +1279,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1556,6 +1558,22 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | -infinity | -infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | -infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | -infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | -infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | -infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | -infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | -infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | -infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | -infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | -infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | -infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | -infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | -infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | -infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | -infinity
Thu Jan 01 00:00:00 1970 PST | @ 14 secs ago | Wed Dec 31 23:59:46 1969 PST
Thu Jan 01 00:00:00 1970 PST | @ 1 min | Thu Jan 01 00:01:00 1970 PST
Thu Jan 01 00:00:00 1970 PST | @ 5 hours | Thu Jan 01 05:00:00 1970 PST
@@ -1716,14 +1734,45 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | infinity | -infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | -infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | -infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | -infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | -infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | -infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | -infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | -infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | -infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | -infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | -infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | -infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | -infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | -infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | -infinity
Thu Jan 01 00:00:00 1970 PST | @ 34 years | Wed Jan 01 00:00:00 1936 PST
Wed Feb 28 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
Thu Feb 29 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
@@ -1884,7 +1933,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 75d19d6594..a481781475 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,8 +268,63 @@ SELECT r1.*, r2.*
34 years | 5 mons
34 years | 5 mons 12:00:00
34 years | 6 years
-(45 rows)
+ infinity | -infinity
+ infinity | -00:00:14
+ infinity | 00:01:00
+ infinity | 05:00:00
+ infinity | 1 day 02:03:04
+ infinity | 10 days
+ infinity | 3 mons
+ infinity | 5 mons
+ infinity | 5 mons 12:00:00
+ infinity | 6 years
+ infinity | 34 years
+(66 rows)
+
+-- test unary minus
+SELECT f1, -f1 FROM INTERVAL_TBL;
+ f1 | ?column?
+-----------------+-------------------
+ 00:01:00 | -00:01:00
+ 05:00:00 | -05:00:00
+ 10 days | -10 days
+ 34 years | -34 years
+ 3 mons | -3 mons
+ -00:00:14 | 00:00:14
+ 1 day 02:03:04 | -1 days -02:03:04
+ 6 years | -6 years
+ 5 mons | -5 mons
+ 5 mons 12:00:00 | -5 mons -12:00:00
+ infinity | -infinity
+ -infinity | infinity
+(12 rows)
+
+SELECT -('-2147483648 months'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-2147483647 months'::interval); -- ok
+ ?column?
+------------------------
+ 178956970 years 7 mons
+(1 row)
+
+SELECT -('-2147483648 days'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-2147483647 days'::interval); -- ok
+ ?column?
+-----------------
+ 2147483647 days
+(1 row)
+
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-9223372036854775807 us'::interval); -- ok
+ ?column?
+-------------------------
+ 2562047788:00:54.775807
+(1 row)
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+ERROR: interval out of range
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
@@ -304,6 +391,17 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
(5 rows)
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column?
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- Floating point arithmetic rounding errors can lead to unexpected results,
@@ -386,12 +484,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +920,8 @@ SELECT interval '1 2:03:04.5678' minute to second(2);
SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
(f1 + INTERVAL '1 month')::INTERVAL MONTH::INTERVAL YEAR AS "years"
FROM interval_tbl;
- f1 | minutes | years
------------------+-----------------+----------
+ f1 | minutes | years
+-----------------+-----------------+-----------
00:01:00 | 00:01:00 | 00:00:00
05:00:00 | 05:00:00 | 00:00:00
10 days | 10 days | 00:00:00
@@ -832,7 +932,9 @@ SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
6 years | 6 years | 6 years
5 mons | 5 mons | 00:00:00
5 mons 12:00:00 | 5 mons 12:00:00 | 00:00:00
-(10 rows)
+ infinity | infinity | infinity
+ -infinity | -infinity | -infinity
+(12 rows)
-- test inputting and outputting SQL standard interval literals
SET IntervalStyle TO sql_standard;
@@ -1616,31 +1718,31 @@ select make_interval(mins := -1, secs := -9223372036800.0);
ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
--------------------------------------------------------------------
- -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+ -178956970 years -7 mons -2147483648 days -2562047788:00:54.775808
(1 row)
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
---------------------------------------------------
- -178956970-8 -2147483648 -2562047788:00:54.775808
+ -178956970-7 -2147483648 -2562047788:00:54.775808
(1 row)
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
-----------------------------------------------------
- P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+ P-178956970Y-7M-2147483648DT-2562047788H-54.775808S
(1 row)
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
------------------------------------------------------------------------------
- @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+ @ 178956970 years 7 mons 2147483648 days 2562047788 hours 54.775808 secs ago
(1 row)
-- check that '30 days' equals '1 month' according to the hash function
@@ -1719,19 +1821,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1805,7 +1909,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1814,6 +1920,292 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+--
+-- test infinite intervals
+--
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+ interval
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+ interval
+--------------------------------------------------------------------------
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs
+(1 row)
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ interval
+----------
+ infinity
+(1 row)
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ date | interval | plus | minus
+------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 | -infinity | -infinity | infinity
+ 1995-08-06 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+ interval1 | interval2 | plus | minus
+-----------+-----------+-----------------------+-----------------------
+ -infinity | -infinity | -infinity | interval out of range
+ -infinity | @ 10 days | -infinity | -infinity
+ -infinity | infinity | interval out of range | -infinity
+ @ 2 mons | -infinity | -infinity | infinity
+ @ 2 mons | @ 10 days | @ 2 mons 10 days | @ 2 mons -10 days
+ @ 2 mons | infinity | infinity | -infinity
+ infinity | -infinity | interval out of range | infinity
+ infinity | @ 10 days | infinity | infinity
+ infinity | infinity | infinity | interval out of range
+(9 rows)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamp | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamptz | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT time '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+ lhs | rhs | lt | le | eq | gt | ge | ne
+-----------+-------------------------+----+----+----+----+----+----
+ infinity | infinity | f | t | t | f | t | f
+ -infinity | infinity | t | t | f | f | f | t
+ infinity | -infinity | f | f | f | t | t | t
+ -infinity | -infinity | f | t | t | f | t | f
+ infinity | @ 1 year 2 days 3 hours | f | f | f | t | t | t
+ -infinity | @ 1 year 2 days 3 hours | t | t | f | f | f | t
+(6 rows)
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | um | mul | mul_neg | mul_inf | mul_inf_neg | div | div_neg
+-----------+-----------+-----------+-----------+-----------+-------------+-----------+-----------
+ infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity
+ -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity
+(2 rows)
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+ERROR: interval out of range
+SELECT interval 'infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+ERROR: interval out of range
+SELECT interval 'infinity' * 0;
+ERROR: interval out of range
+SELECT interval '-infinity' * 0;
+ERROR: interval out of range
+SELECT interval '0 days' * 'infinity'::float;
+ERROR: interval out of range
+SELECT interval '0 days' * '-infinity'::float;
+ERROR: interval out of range
+SELECT interval '5 days' * 'infinity'::float;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '5 days' * '-infinity'::float;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+ERROR: interval out of range
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | date_trunc
+-----------+------------
+ infinity | infinity
+ -infinity | -infinity
+(2 rows)
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | justify_days | justify_hours | justify_interval
+-----------+--------------+---------------+------------------
+ infinity | infinity | infinity | infinity
+ -infinity | -infinity | -infinity | -infinity
+(2 rows)
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT 'infinity'::interval::time;
+ERROR: cannot convert infinite interval to time
+SELECT '-infinity'::interval::time;
+ERROR: cannot convert infinite interval to time
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
ERROR: invalid input syntax for type interval: "42 days 2 seconds ago ago"
@@ -1832,3 +2224,42 @@ SELECT INTERVAL '1 year months days 5 hours';
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..835f0e5762 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '-infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp 'infinity', timestamp 'infinity');
+ERROR: interval out of range
+select age(timestamp 'infinity', timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 2ca2101dd4..a084357480 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3210,3 +3214,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+ERROR: interval out of range
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..2201740c18 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -2372,6 +2372,7 @@ create temp table datetimes(
f_timestamp timestamp
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -2381,14 +2382,16 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_time range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 1 | 11:00:00 | 1 | 3
+ 0 | 10:00:00 | 0 | 2
+ 1 | 11:00:00 | 0 | 3
2 | 12:00:00 | 1 | 4
3 | 13:00:00 | 2 | 6
4 | 14:00:00 | 3 | 6
@@ -2396,9 +2399,10 @@ window w as (order by f_time range between
6 | 15:00:00 | 4 | 7
7 | 17:00:00 | 7 | 9
8 | 18:00:00 | 7 | 10
- 9 | 19:00:00 | 8 | 10
- 10 | 20:00:00 | 9 | 10
-(10 rows)
+ 9 | 19:00:00 | 8 | 11
+ 10 | 20:00:00 | 9 | 11
+ 11 | 21:00:00 | 10 | 11
+(12 rows)
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2406,7 +2410,8 @@ window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 10 | 20:00:00 | 10 | 8
+ 11 | 21:00:00 | 11 | 9
+ 10 | 20:00:00 | 11 | 8
9 | 19:00:00 | 10 | 7
8 | 18:00:00 | 9 | 7
7 | 17:00:00 | 8 | 5
@@ -2414,17 +2419,90 @@ window w as (order by f_time desc range between
5 | 15:00:00 | 6 | 3
4 | 14:00:00 | 6 | 2
3 | 13:00:00 | 4 | 1
- 2 | 12:00:00 | 3 | 1
- 1 | 11:00:00 | 2 | 1
-(10 rows)
+ 2 | 12:00:00 | 3 | 0
+ 1 | 11:00:00 | 2 | 0
+ 0 | 10:00:00 | 1 | 0
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | 0 | 11
+ 1 | 11:00:00 | 0 | 11
+ 2 | 12:00:00 | 0 | 11
+ 3 | 13:00:00 | 0 | 11
+ 4 | 14:00:00 | 0 | 11
+ 5 | 15:00:00 | 0 | 11
+ 6 | 15:00:00 | 0 | 11
+ 7 | 17:00:00 | 0 | 11
+ 8 | 18:00:00 | 0 | 11
+ 9 | 19:00:00 | 0 | 11
+ 10 | 20:00:00 | 0 | 11
+ 11 | 21:00:00 | 0 | 11
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 1 | 11:00:00+01 | 1 | 3
+ 0 | 10:00:00+01 | 0 | 2
+ 1 | 11:00:00+01 | 0 | 3
2 | 12:00:00+01 | 1 | 4
3 | 13:00:00+01 | 2 | 6
4 | 14:00:00+01 | 3 | 6
@@ -2432,9 +2510,10 @@ window w as (order by f_timetz range between
6 | 15:00:00+01 | 4 | 7
7 | 17:00:00+01 | 7 | 9
8 | 18:00:00+01 | 7 | 10
- 9 | 19:00:00+01 | 8 | 10
- 10 | 20:00:00+01 | 9 | 10
-(10 rows)
+ 9 | 19:00:00+01 | 8 | 11
+ 10 | 20:00:00+01 | 9 | 11
+ 11 | 21:00:00+01 | 10 | 11
+(12 rows)
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2442,7 +2521,8 @@ window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 10 | 20:00:00+01 | 10 | 8
+ 11 | 21:00:00+01 | 11 | 9
+ 10 | 20:00:00+01 | 11 | 8
9 | 19:00:00+01 | 10 | 7
8 | 18:00:00+01 | 9 | 7
7 | 17:00:00+01 | 8 | 5
@@ -2450,16 +2530,89 @@ window w as (order by f_timetz desc range between
5 | 15:00:00+01 | 6 | 3
4 | 14:00:00+01 | 6 | 2
3 | 13:00:00+01 | 4 | 1
- 2 | 12:00:00+01 | 3 | 1
- 1 | 11:00:00+01 | 2 | 1
-(10 rows)
+ 2 | 12:00:00+01 | 3 | 0
+ 1 | 11:00:00+01 | 2 | 0
+ 0 | 10:00:00+01 | 1 | 0
+(12 rows)
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | 0 | 11
+ 1 | 11:00:00+01 | 0 | 11
+ 2 | 12:00:00+01 | 0 | 11
+ 3 | 13:00:00+01 | 0 | 11
+ 4 | 14:00:00+01 | 0 | 11
+ 5 | 15:00:00+01 | 0 | 11
+ 6 | 15:00:00+01 | 0 | 11
+ 7 | 17:00:00+01 | 0 | 11
+ 8 | 18:00:00+01 | 0 | 11
+ 9 | 19:00:00+01 | 0 | 11
+ 10 | 20:00:00+01 | 0 | 11
+ 11 | 21:00:00+01 | 0 | 11
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | @ 1 year | 1 | 2
2 | @ 2 years | 1 | 3
3 | @ 3 years | 2 | 4
@@ -2470,7 +2623,8 @@ window w as (order by f_interval range between
8 | @ 8 years | 7 | 9
9 | @ 9 years | 8 | 10
10 | @ 10 years | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2478,6 +2632,7 @@ window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | @ 10 years | 10 | 9
9 | @ 9 years | 10 | 8
8 | @ 8 years | 9 | 7
@@ -2488,14 +2643,87 @@ window w as (order by f_interval desc range between
3 | @ 3 years | 4 | 2
2 | @ 2 years | 3 | 1
1 | @ 1 year | 2 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 0 | 11
+ 2 | @ 2 years | 0 | 11
+ 3 | @ 3 years | 0 | 11
+ 4 | @ 4 years | 0 | 11
+ 5 | @ 5 years | 0 | 11
+ 6 | @ 5 years | 0 | 11
+ 7 | @ 7 years | 0 | 11
+ 8 | @ 8 years | 0 | 11
+ 9 | @ 9 years | 0 | 11
+ 10 | @ 10 years | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | @ 1 year | 0 | 0
+ 2 | @ 2 years | 0 | 0
+ 3 | @ 3 years | 0 | 0
+ 4 | @ 4 years | 0 | 0
+ 5 | @ 5 years | 0 | 0
+ 6 | @ 5 years | 0 | 0
+ 7 | @ 7 years | 0 | 0
+ 8 | @ 8 years | 0 | 0
+ 9 | @ 9 years | 0 | 0
+ 10 | @ 10 years | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 11 | 11
+ 2 | @ 2 years | 11 | 11
+ 3 | @ 3 years | 11 | 11
+ 4 | @ 4 years | 11 | 11
+ 5 | @ 5 years | 11 | 11
+ 6 | @ 5 years | 11 | 11
+ 7 | @ 7 years | 11 | 11
+ 8 | @ 8 years | 11 | 11
+ 9 | @ 9 years | 11 | 11
+ 10 | @ 10 years | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 02:23:54 2000 PDT | 1 | 3
2 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
3 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
@@ -2506,7 +2734,8 @@ window w as (order by f_timestamptz range between
8 | Thu Oct 19 02:23:54 2006 PDT | 7 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 8 | 10
10 | Sun Oct 19 02:23:54 2008 PDT | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2514,6 +2743,7 @@ window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 02:23:54 2008 PDT | 10 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 10 | 8
8 | Thu Oct 19 02:23:54 2006 PDT | 9 | 7
@@ -2524,14 +2754,87 @@ window w as (order by f_timestamptz desc range between
3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 0
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 0
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 0
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 0
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 0
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 0
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 0
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 11 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 11 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 11 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 11 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 11 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 11 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 11 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 10:23:54 2000 | 1 | 3
2 | Fri Oct 19 10:23:54 2001 | 1 | 4
3 | Fri Oct 19 10:23:54 2001 | 1 | 4
@@ -2542,7 +2845,8 @@ window w as (order by f_timestamp range between
8 | Thu Oct 19 10:23:54 2006 | 7 | 9
9 | Fri Oct 19 10:23:54 2007 | 8 | 10
10 | Sun Oct 19 10:23:54 2008 | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2550,6 +2854,7 @@ window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 10:23:54 2008 | 10 | 9
9 | Fri Oct 19 10:23:54 2007 | 10 | 8
8 | Thu Oct 19 10:23:54 2006 | 9 | 7
@@ -2560,8 +2865,80 @@ window w as (order by f_timestamp desc range between
3 | Fri Oct 19 10:23:54 2001 | 4 | 1
2 | Fri Oct 19 10:23:54 2001 | 4 | 1
1 | Thu Oct 19 10:23:54 2000 | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 0
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 0
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 0
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 0
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 0
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 0
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 0
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 11 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 11 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 11 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 11 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 11 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 11 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 11 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -4375,6 +4752,52 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev_curr_avg | curr_next_sum | prev_curr_sum
+------------------------------------------------------------------------------+-------------------+-------------------+---------------+---------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago | -infinity | infinity | -infinity | infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs | infinity | -infinity | infinity | -infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
+(10 rows)
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
i | sum
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
index b666dbad67..116106d30f 100644
--- a/src/test/regress/sql/brin_multi.sql
+++ b/src/test/regress/sql/brin_multi.sql
@@ -682,6 +682,25 @@ SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan = off;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 39a35a6b7c..252bce4b1c 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -213,10 +213,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -293,7 +295,6 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index a0a373f08b..caad291890 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -66,6 +70,17 @@ SELECT r1.*, r2.*
WHERE r1.f1 > r2.f1
ORDER BY r1.f1, r2.f1;
+-- test unary minus
+
+SELECT f1, -f1 FROM INTERVAL_TBL;
+SELECT -('-2147483648 months'::interval); -- should fail
+SELECT -('-2147483647 months'::interval); -- ok
+SELECT -('-2147483648 days'::interval); -- should fail
+SELECT -('-2147483647 days'::interval); -- ok
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+SELECT -('-9223372036854775807 us'::interval); -- ok
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
@@ -95,6 +110,9 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
@@ -140,7 +158,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -528,13 +546,13 @@ select make_interval(mins := -1, secs := -9223372036800.0);
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
-- check that '30 days' equals '1 month' according to the hash function
select '30 days'::interval = '1 month'::interval as t;
@@ -598,6 +616,157 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+--
+-- test infinite intervals
+--
+
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+SELECT time '11:27:42' + interval '-infinity';
+SELECT time '11:27:42' - interval 'infinity';
+SELECT time '11:27:42' - interval '-infinity';
+SELECT timetz '11:27:42' + interval 'infinity';
+SELECT timetz '11:27:42' + interval '-infinity';
+SELECT timetz '11:27:42' - interval 'infinity';
+SELECT timetz '11:27:42' - interval '-infinity';
+
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+SELECT interval 'infinity' * 'nan';
+SELECT interval '-infinity' * 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+SELECT interval 'infinity' * 0;
+SELECT interval '-infinity' * 0;
+SELECT interval '0 days' * 'infinity'::float;
+SELECT interval '0 days' * '-infinity'::float;
+SELECT interval '5 days' * 'infinity'::float;
+SELECT interval '5 days' * '-infinity'::float;
+
+SELECT interval 'infinity' / 'infinity';
+SELECT interval 'infinity' / '-infinity';
+SELECT interval 'infinity' / 'nan';
+SELECT interval '-infinity' / 'infinity';
+SELECT interval '-infinity' / '-infinity';
+SELECT interval '-infinity' / 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+
+SELECT 'infinity'::interval::time;
+SELECT '-infinity'::interval::time;
+
+SELECT to_char('infinity'::interval, 'YYYY');
+SELECT to_char('-infinity'::interval, 'YYYY');
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
SELECT INTERVAL '2 minutes ago 5 days';
@@ -605,3 +774,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index cdc57bc160..a2dcd5f5d8 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -642,3 +644,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..437e948d6c 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -673,6 +673,7 @@ create temp table datetimes(
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -682,7 +683,8 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -694,6 +696,32 @@ from datetimes
window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
@@ -704,6 +732,32 @@ from datetimes
window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
@@ -714,6 +768,32 @@ from datetimes
window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
@@ -724,6 +804,32 @@ from datetimes
window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
@@ -734,6 +840,32 @@ from datetimes
window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -1591,6 +1723,39 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bf50a32119..efc47bed10 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1205,6 +1205,7 @@ IntegerSet
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup
--
2.25.1
On Thu, 9 Nov 2023 at 07:15, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:
Just to test whether that bug fix also fixes the failure seen with
this patchset, I am attaching the patchset including the patch with
the fix.0001 - fix in other thread
0002 and 0003 are 0001 and 0002 in the previous patch set.
Thanks. That's confirmed, it has indeed turned green!
Regards,
Dean
On Thu, 9 Nov 2023 at 08:37, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Thu, 9 Nov 2023 at 07:15, Ashutosh Bapat
<ashutosh.bapat.oss@gmail.com> wrote:Just to test whether that bug fix also fixes the failure seen with
this patchset, I am attaching the patchset including the patch with
the fix.0001 - fix in other thread
0002 and 0003 are 0001 and 0002 in the previous patch set.Thanks. That's confirmed, it has indeed turned green!
OK, I have pushed 0001 and 0002. Here's the remaining (main) patch.
I couldn't resist making one more cosmetic change -- I moved
finite_interval_pl() and finite_interval_mi() up to where they're
first used, which is where that code was originally, making it
slightly easier to compare old-vs-new code side-by-side, and because I
think that's the more natural place for them.
Regards,
Dean
Attachments:
v32-0001-Support-infinity-in-the-interval-data-type.patchtext/x-patch; charset=US-ASCII; name=v32-0001-Support-infinity-in-the-interval-data-type.patchDownload
From a8b210f01d31337b6bf074741af7c221df35fc67 Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Mon, 6 Nov 2023 13:52:27 +0000
Subject: [PATCH v32] Support +/- infinity in the interval data type.
This adds support for infinity to the interval data type, using the
same input/output representation as the other date/time data types
that support infinity. This allows various arithmetic operations on
infinite dates, timestamps and intervals.
The new values are represented by setting all fields of the interval
to INT32/64_MIN for -infinity, and INT32/64_MAX for +infinity. This
ensures that they compare as less/greater than all other interval
values, without the need for any special-case comparison code.
Note that, since those 2 values were formerly accepted as legal finite
intervals, pg_upgrade and dump/restore from an old database will turn
them from finite to infinite intervals. That seems OK, since those
exact values should be extremely rare in practice, and they are
outside the documented range supported by the interval type, which
gives us a certain amount of leeway.
Joseph Koshakow, Jian He, and Ashutosh Bapat, reviewed by Dean Rasheed.
Discussion: https://postgr.es/m/CAAvxfHea4%2BsPybKK7agDYOMo9N-Z3J6ZXf3BOM79pFsFNcRjwA%40mail.gmail.com
---
contrib/btree_gin/btree_gin.c | 5 +-
doc/src/sgml/datatype.sgml | 4 +-
doc/src/sgml/func.sgml | 8 +-
src/backend/utils/adt/date.c | 57 +-
src/backend/utils/adt/datetime.c | 26 +
src/backend/utils/adt/formatting.c | 2 +-
src/backend/utils/adt/selfuncs.c | 4 +
src/backend/utils/adt/timestamp.c | 1044 +++++++++++++++++----
src/include/catalog/pg_aggregate.dat | 24 +-
src/include/catalog/pg_proc.dat | 28 +-
src/include/datatype/timestamp.h | 27 +-
src/test/regress/expected/brin_multi.out | 28 +
src/test/regress/expected/horology.out | 71 +-
src/test/regress/expected/interval.out | 499 +++++++++-
src/test/regress/expected/timestamp.out | 62 ++
src/test/regress/expected/timestamptz.out | 62 ++
src/test/regress/expected/window.out | 469 ++++++++-
src/test/regress/sql/brin_multi.sql | 19 +
src/test/regress/sql/horology.sql | 3 +-
src/test/regress/sql/interval.sql | 193 +++-
src/test/regress/sql/timestamp.sql | 19 +
src/test/regress/sql/timestamptz.sql | 18 +
src/test/regress/sql/window.sql | 167 +++-
src/tools/pgindent/typedefs.list | 1 +
24 files changed, 2554 insertions(+), 286 deletions(-)
diff --git a/contrib/btree_gin/btree_gin.c b/contrib/btree_gin/btree_gin.c
index b09bb8df96..5e27906f80 100644
--- a/contrib/btree_gin/btree_gin.c
+++ b/contrib/btree_gin/btree_gin.c
@@ -306,9 +306,8 @@ leftmostvalue_interval(void)
{
Interval *v = palloc(sizeof(Interval));
- v->time = PG_INT64_MIN;
- v->day = PG_INT32_MIN;
- v->month = PG_INT32_MIN;
+ INTERVAL_NOBEGIN(v);
+
return IntervalPGetDatum(v);
}
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
index 5a6cfbd94d..4aba6d19ee 100644
--- a/doc/src/sgml/datatype.sgml
+++ b/doc/src/sgml/datatype.sgml
@@ -2328,12 +2328,12 @@ TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'
</row>
<row>
<entry><literal>infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>later than all other time stamps</entry>
</row>
<row>
<entry><literal>-infinity</literal></entry>
- <entry><type>date</type>, <type>timestamp</type></entry>
+ <entry><type>date</type>, <type>timestamp</type>, <type>interval</type></entry>
<entry>earlier than all other time stamps</entry>
</row>
<row>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d963f0a0a0..146d71dd9f 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -9553,7 +9553,7 @@ SELECT regexp_match('abc01234xyz', '(?:(.*?)(\d+)(.*)){1,1}');
<returnvalue>boolean</returnvalue>
</para>
<para>
- Test for finite interval (currently always true)
+ Test for finite interval (not +/-infinity)
</para>
<para>
<literal>isfinite(interval '4 hours')</literal>
@@ -10450,7 +10450,11 @@ SELECT EXTRACT(YEAR FROM TIMESTAMP '2001-02-16 20:38:40');
When the input value is +/-Infinity, <function>extract</function> returns
+/-Infinity for monotonically-increasing fields (<literal>epoch</literal>,
<literal>julian</literal>, <literal>year</literal>, <literal>isoyear</literal>,
- <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>).
+ <literal>decade</literal>, <literal>century</literal>, and <literal>millennium</literal>
+ for <type>timestamp</type> inputs; <literal>epoch</literal>, <literal>hour</literal>,
+ <literal>day</literal>, <literal>year</literal>, <literal>decade</literal>,
+ <literal>century</literal>, and <literal>millennium</literal> for
+ <type>interval</type> inputs).
For other fields, NULL is returned. <productname>PostgreSQL</productname>
versions before 9.6 returned zero for all cases of infinite input.
</para>
diff --git a/src/backend/utils/adt/date.c b/src/backend/utils/adt/date.c
index 544e1d32bf..13745a0adc 100644
--- a/src/backend/utils/adt/date.c
+++ b/src/backend/utils/adt/date.c
@@ -24,6 +24,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
+#include "common/int.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/supportnodes.h"
@@ -2013,6 +2014,11 @@ interval_time(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(0);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot convert infinite interval to time")));
+
result = span->time % USECS_PER_DAY;
if (result < 0)
result += USECS_PER_DAY;
@@ -2049,6 +2055,11 @@ time_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = time + span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2067,6 +2078,11 @@ time_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeADT result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = time - span->time;
result -= result / USECS_PER_DAY * USECS_PER_DAY;
if (result < INT64CONST(0))
@@ -2090,7 +2106,8 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* Like time_pl_interval/time_mi_interval, we disregard the month and day
- * fields of the offset. So our test for negative should too.
+ * fields of the offset. So our test for negative should too. This also
+ * catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2100,13 +2117,14 @@ in_range_time_interval(PG_FUNCTION_ARGS)
/*
* We can't use time_pl_interval/time_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum = base - offset->time;
- else
- sum = base + offset->time;
+ else if (pg_add_s64_overflow(base, offset->time, &sum))
+ PG_RETURN_BOOL(less);
if (less)
PG_RETURN_BOOL(val <= sum);
@@ -2581,6 +2599,11 @@ timetz_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot add infinite interval to time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time + span->time;
@@ -2603,6 +2626,11 @@ timetz_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
TimeTzADT *result;
+ if (INTERVAL_NOT_FINITE(span))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("cannot subtract infinite interval from time")));
+
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
result->time = time->time - span->time;
@@ -2630,7 +2658,8 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* Like timetz_pl_interval/timetz_mi_interval, we disregard the month and
- * day fields of the offset. So our test for negative should too.
+ * day fields of the offset. So our test for negative should too. This
+ * also catches -infinity, so we only need worry about +infinity below.
*/
if (offset->time < 0)
ereport(ERROR,
@@ -2640,13 +2669,14 @@ in_range_timetz_interval(PG_FUNCTION_ARGS)
/*
* We can't use timetz_pl_interval/timetz_mi_interval here, because their
* wraparound behavior would give wrong (or at least undesirable) answers.
- * Fortunately the equivalent non-wrapping behavior is trivial, especially
- * since we don't worry about integer overflow.
+ * Fortunately the equivalent non-wrapping behavior is trivial, except
+ * that adding an infinite (or very large) interval might cause integer
+ * overflow. Subtraction cannot overflow here.
*/
if (sub)
sum.time = base->time - offset->time;
- else
- sum.time = base->time + offset->time;
+ else if (pg_add_s64_overflow(base->time, offset->time, &sum.time))
+ PG_RETURN_BOOL(less);
sum.zone = base->zone;
if (less)
@@ -3096,6 +3126,13 @@ timetz_izone(PG_FUNCTION_ARGS)
TimeTzADT *result;
int tz;
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
index 267dfd37b2..fca9a2a6e9 100644
--- a/src/backend/utils/adt/datetime.c
+++ b/src/backend/utils/adt/datetime.c
@@ -3271,6 +3271,9 @@ ClearPgItmIn(struct pg_itm_in *itm_in)
*
* Allow ISO-style time span, with implicit units on number of days
* preceding an hh:mm:ss field. - thomas 1998-04-30
+ *
+ * itm_in remains undefined for infinite interval values for which dtype alone
+ * suffices.
*/
int
DecodeInterval(char **field, int *ftype, int nf, int range,
@@ -3574,6 +3577,8 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
if (parsing_unit_val)
return DTERR_BAD_FORMAT;
type = DecodeUnits(i, field[i], &uval);
+ if (type == UNKNOWN_FIELD)
+ type = DecodeSpecial(i, field[i], &uval);
if (type == IGNORE_DTF)
continue;
@@ -3597,6 +3602,27 @@ DecodeInterval(char **field, int *ftype, int nf, int range,
type = uval;
break;
+ case RESERV:
+ tmask = (DTK_DATE_M | DTK_TIME_M);
+
+ /*
+ * Only reserved words corresponding to infinite
+ * intervals are accepted.
+ */
+ if (uval != DTK_LATE && uval != DTK_EARLY)
+ return DTERR_BAD_FORMAT;
+
+ /*
+ * Infinity cannot be followed by anything else. We
+ * could allow "ago" to reverse the sign of infinity
+ * but using signed infinity is more intuitive.
+ */
+ if (i != nf - 1)
+ return DTERR_BAD_FORMAT;
+
+ *dtype = uval;
+ break;
+
default:
return DTERR_BAD_FORMAT;
}
diff --git a/src/backend/utils/adt/formatting.c b/src/backend/utils/adt/formatting.c
index 8131091f79..d176723d95 100644
--- a/src/backend/utils/adt/formatting.c
+++ b/src/backend/utils/adt/formatting.c
@@ -4127,7 +4127,7 @@ interval_to_char(PG_FUNCTION_ARGS)
struct pg_itm tt,
*itm = &tt;
- if (VARSIZE_ANY_EXHDR(fmt) <= 0)
+ if (VARSIZE_ANY_EXHDR(fmt) <= 0 || INTERVAL_NOT_FINITE(it))
PG_RETURN_NULL();
ZERO_tmtc(&tmtc);
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index c4fcd0076e..4ea5415f20 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -4802,6 +4802,10 @@ convert_timevalue_to_scalar(Datum value, Oid typid, bool *failure)
* Convert the month part of Interval to days using assumed
* average month length of 365.25/12.0 days. Not too
* accurate, but plenty good enough for our purposes.
+ *
+ * This also works for infinite intervals, which just have all
+ * fields set to INT_MIN/INT_MAX, and so will produce a result
+ * smaller/larger than any finite interval.
*/
return interval->time + interval->day * (double) USECS_PER_DAY +
interval->month * ((DAYS_PER_YEAR / (double) MONTHS_PER_YEAR) * USECS_PER_DAY);
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 647b97aca6..b53bf47786 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -72,6 +72,21 @@ typedef struct
pg_tz *attimezone;
} generate_series_timestamptz_fctx;
+/*
+ * The transition datatype for interval aggregates is declared as internal.
+ * It's a pointer to an IntervalAggState allocated in the aggregate context.
+ */
+typedef struct IntervalAggState
+{
+ int64 N; /* count of finite intervals processed */
+ Interval sumX; /* sum of finite intervals processed */
+ /* These counts are *not* included in N! Use IA_TOTAL_COUNT() as needed */
+ int64 pInfcount; /* count of +infinity intervals */
+ int64 nInfcount; /* count of -infinity intervals */
+} IntervalAggState;
+
+#define IA_TOTAL_COUNT(ia) \
+ ((ia)->N + (ia)->pInfcount + (ia)->nInfcount)
static TimeOffset time2t(const int hour, const int min, const int sec, const fsec_t fsec);
static Timestamp dt2local(Timestamp dt, int timezone);
@@ -80,6 +95,8 @@ static bool AdjustIntervalForTypmod(Interval *interval, int32 typmod,
static TimestampTz timestamp2timestamptz(Timestamp timestamp);
static Timestamp timestamptz2timestamp(TimestampTz timestamp);
+static void EncodeSpecialInterval(const Interval *interval, char *str);
+static void interval_um_internal(const Interval *interval, Interval *result);
/* common code for timestamptypmodin and timestamptztypmodin */
static int32
@@ -941,6 +958,14 @@ interval_in(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
break;
+ case DTK_LATE:
+ INTERVAL_NOEND(result);
+ break;
+
+ case DTK_EARLY:
+ INTERVAL_NOBEGIN(result);
+ break;
+
default:
elog(ERROR, "unexpected dtype %d while parsing interval \"%s\"",
dtype, str);
@@ -963,8 +988,13 @@ interval_out(PG_FUNCTION_ARGS)
*itm = &tt;
char buf[MAXDATELEN + 1];
- interval2itm(*span, itm);
- EncodeInterval(itm, IntervalStyle, buf);
+ if (INTERVAL_NOT_FINITE(span))
+ EncodeSpecialInterval(span, buf);
+ else
+ {
+ interval2itm(*span, itm);
+ EncodeInterval(itm, IntervalStyle, buf);
+ }
result = pstrdup(buf);
PG_RETURN_CSTRING(result);
@@ -1350,6 +1380,10 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod,
INT64CONST(0)
};
+ /* Typmod has no effect on infinite intervals */
+ if (INTERVAL_NOT_FINITE(interval))
+ return true;
+
/*
* Unspecified range and precision? Then not necessary to adjust. Setting
* typmod to -1 is the convention for all data types.
@@ -1536,6 +1570,10 @@ make_interval(PG_FUNCTION_ARGS)
pg_add_s64_overflow(result->time, (int64) secs, &result->time))
goto out_of_range;
+ /* make sure that the result is finite */
+ if (INTERVAL_NOT_FINITE(result))
+ goto out_of_range;
+
PG_RETURN_INTERVAL_P(result);
out_of_range:
@@ -1560,6 +1598,17 @@ EncodeSpecialTimestamp(Timestamp dt, char *str)
elog(ERROR, "invalid argument for EncodeSpecialTimestamp");
}
+static void
+EncodeSpecialInterval(const Interval *interval, char *str)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ strcpy(str, EARLY);
+ else if (INTERVAL_IS_NOEND(interval))
+ strcpy(str, LATE);
+ else /* shouldn't happen */
+ elog(ERROR, "invalid argument for EncodeSpecialInterval");
+}
+
Datum
now(PG_FUNCTION_ARGS)
{
@@ -2015,6 +2064,9 @@ interval2itm(Interval span, struct pg_itm *itm)
/* itm2interval()
* Convert a pg_itm structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * This is for use in computations expected to produce finite results. Any
+ * inputs that lead to infinite results are treated as overflows.
*/
int
itm2interval(struct pg_itm *itm, Interval *span)
@@ -2038,12 +2090,21 @@ itm2interval(struct pg_itm *itm, Interval *span)
if (pg_add_s64_overflow(span->time, itm->tm_usec,
&span->time))
return -1;
+ if (INTERVAL_NOT_FINITE(span))
+ return -1;
return 0;
}
/* itmin2interval()
* Convert a pg_itm_in structure to an Interval.
* Returns 0 if OK, -1 on overflow.
+ *
+ * Note: if the result is infinite, it is not treated as an overflow. This
+ * avoids any dump/reload hazards from pre-17 databases that do not support
+ * infinite intervals, but do allow finite intervals with all fields set to
+ * INT_MIN/INT_MAX (outside the documented range). Such intervals will be
+ * silently converted to +/-infinity. This may not be ideal, but seems
+ * preferable to failure, and ought to be pretty unlikely in practice.
*/
int
itmin2interval(struct pg_itm_in *itm_in, Interval *span)
@@ -2088,7 +2149,9 @@ timestamp_finite(PG_FUNCTION_ARGS)
Datum
interval_finite(PG_FUNCTION_ARGS)
{
- PG_RETURN_BOOL(true);
+ Interval *interval = PG_GETARG_INTERVAL_P(0);
+
+ PG_RETURN_BOOL(!INTERVAL_NOT_FINITE(interval));
}
@@ -2442,6 +2505,15 @@ interval_cmp_internal(const Interval *interval1, const Interval *interval2)
return int128_compare(span1, span2);
}
+static int
+interval_sign(const Interval *interval)
+{
+ INT128 span = interval_cmp_value(interval);
+ INT128 zero = int64_to_int128(0);
+
+ return int128_compare(span, zero);
+}
+
Datum
interval_eq(PG_FUNCTION_ARGS)
{
@@ -2714,10 +2786,39 @@ timestamp_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
if (TIMESTAMP_NOT_FINITE(dt1) || TIMESTAMP_NOT_FINITE(dt2))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("cannot subtract infinite timestamps")));
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else /* TIMESTAMP_IS_NOEND(dt2) */
+ INTERVAL_NOBEGIN(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
if (unlikely(pg_sub_s64_overflow(dt1, dt2, &result->time)))
ereport(ERROR,
@@ -2783,6 +2884,10 @@ interval_justify_interval(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
/* pre-justify days if it might prevent overflow */
if ((result->day > 0 && result->time > 0) ||
(result->day < 0 && result->time < 0))
@@ -2858,6 +2963,10 @@ interval_justify_hours(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
TMODULO(result->time, wholeday, USECS_PER_DAY);
if (pg_add_s32_overflow(result->day, wholeday, &result->day))
ereport(ERROR,
@@ -2896,6 +3005,10 @@ interval_justify_days(PG_FUNCTION_ARGS)
result->day = span->day;
result->time = span->time;
+ /* do nothing for infinite intervals */
+ if (INTERVAL_NOT_FINITE(result))
+ PG_RETURN_INTERVAL_P(result);
+
wholemonth = result->day / DAYS_PER_MONTH;
result->day -= wholemonth * DAYS_PER_MONTH;
if (pg_add_s32_overflow(result->month, wholemonth, &result->month))
@@ -2934,7 +3047,31 @@ timestamp_pl_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Timestamp result;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the timestamp type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3013,9 +3150,7 @@ timestamp_mi_interval(PG_FUNCTION_ARGS)
Interval *span = PG_GETARG_INTERVAL_P(1);
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return DirectFunctionCall2(timestamp_pl_interval,
TimestampGetDatum(timestamp),
@@ -3042,7 +3177,31 @@ timestamptz_pl_interval_internal(TimestampTz timestamp,
TimestampTz result;
int tz;
- if (TIMESTAMP_NOT_FINITE(timestamp))
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the timestamptz type has nothing equivalent to NaN.
+ */
+ if (INTERVAL_IS_NOBEGIN(span))
+ {
+ if (TIMESTAMP_IS_NOEND(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(timestamp))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+ else
+ TIMESTAMP_NOEND(result);
+ }
+ else if (TIMESTAMP_NOT_FINITE(timestamp))
result = timestamp;
else
{
@@ -3132,9 +3291,7 @@ timestamptz_mi_interval_internal(TimestampTz timestamp,
{
Interval tspan;
- tspan.month = -span->month;
- tspan.day = -span->day;
- tspan.time = -span->time;
+ interval_um_internal(span, &tspan);
return timestamptz_pl_interval_internal(timestamp, &tspan, attimezone);
}
@@ -3185,6 +3342,29 @@ timestamptz_mi_interval_at_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMP(timestamptz_mi_interval_internal(timestamp, span, attimezone));
}
+/* interval_um_internal()
+ * Negate an interval.
+ */
+static void
+interval_um_internal(const Interval *interval, Interval *result)
+{
+ if (INTERVAL_IS_NOBEGIN(interval))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(interval))
+ INTERVAL_NOBEGIN(result);
+ else
+ {
+ /* Negate each field, guarding against overflow */
+ if (pg_sub_s64_overflow(INT64CONST(0), interval->time, &result->time) ||
+ pg_sub_s32_overflow(0, interval->day, &result->day) ||
+ pg_sub_s32_overflow(0, interval->month, &result->month) ||
+ INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ }
+}
+
Datum
interval_um(PG_FUNCTION_ARGS)
{
@@ -3192,23 +3372,7 @@ interval_um(PG_FUNCTION_ARGS)
Interval *result;
result = (Interval *) palloc(sizeof(Interval));
-
- result->time = -interval->time;
- /* overflow check copied from int4um */
- if (interval->time != 0 && SAMESIGN(result->time, interval->time))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->day = -interval->day;
- if (interval->day != 0 && SAMESIGN(result->day, interval->day))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
- result->month = -interval->month;
- if (interval->month != 0 && SAMESIGN(result->month, interval->month))
- ereport(ERROR,
- (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
- errmsg("interval out of range")));
+ interval_um_internal(interval, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3243,42 +3407,35 @@ interval_larger(PG_FUNCTION_ARGS)
PG_RETURN_INTERVAL_P(result);
}
-Datum
-interval_pl(PG_FUNCTION_ARGS)
+static void
+finite_interval_pl(const Interval *span1, const Interval *span2, Interval *result)
{
- Interval *span1 = PG_GETARG_INTERVAL_P(0);
- Interval *span2 = PG_GETARG_INTERVAL_P(1);
- Interval *result;
-
- result = (Interval *) palloc(sizeof(Interval));
+ Assert(!INTERVAL_NOT_FINITE(span1));
+ Assert(!INTERVAL_NOT_FINITE(span2));
- result->month = span1->month + span2->month;
- /* overflow check copied from int4pl */
- if (SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
+ if (pg_add_s32_overflow(span1->month, span2->month, &result->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))
+ if (pg_add_s32_overflow(span1->day, span2->day, &result->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))
+ if (pg_add_s64_overflow(span1->time, span2->time, &result->time))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
- PG_RETURN_INTERVAL_P(result);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
}
Datum
-interval_mi(PG_FUNCTION_ARGS)
+interval_pl(PG_FUNCTION_ARGS)
{
Interval *span1 = PG_GETARG_INTERVAL_P(0);
Interval *span2 = PG_GETARG_INTERVAL_P(1);
@@ -3286,27 +3443,104 @@ interval_mi(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- result->month = span1->month - span2->month;
- /* overflow check copied from int4mi */
- if (!SAMESIGN(span1->month, span2->month) &&
- !SAMESIGN(result->month, span1->month))
+ /*
+ * 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);
+
+ 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));
+
+ if (pg_sub_s32_overflow(span1->month, span2->month, &result->month))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (pg_sub_s32_overflow(span1->day, span2->day, &result->day))
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))
+ if (pg_sub_s64_overflow(span1->time, span2->time, &result->time))
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))
+ if (INTERVAL_NOT_FINITE(result))
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("interval out of range")));
+}
+
+Datum
+interval_mi(PG_FUNCTION_ARGS)
+{
+ Interval *span1 = PG_GETARG_INTERVAL_P(0);
+ Interval *span2 = PG_GETARG_INTERVAL_P(1);
+ Interval *result;
+
+ 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 (INTERVAL_IS_NOBEGIN(span1))
+ {
+ if (INTERVAL_IS_NOBEGIN(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (INTERVAL_IS_NOEND(span1))
+ {
+ if (INTERVAL_IS_NOEND(span2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (INTERVAL_IS_NOBEGIN(span2))
+ INTERVAL_NOEND(result);
+ else if (INTERVAL_IS_NOEND(span2))
+ INTERVAL_NOBEGIN(result);
+ else
+ finite_interval_mi(span1, span2, result);
PG_RETURN_INTERVAL_P(result);
}
@@ -3331,6 +3565,46 @@ interval_mul(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ /*
+ * Handle NaN and infinities.
+ *
+ * We treat "0 * infinity" and "infinity * 0" as errors, since the
+ * interval type has nothing equivalent to NaN.
+ */
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (factor == 0.0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+ if (isinf(factor))
+ {
+ int isign = interval_sign(span);
+
+ if (isign == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else if (factor * isign < 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ INTERVAL_NOEND(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result_double = span->month * factor;
if (isnan(result_double) ||
result_double > INT_MAX || result_double < INT_MIN)
@@ -3391,6 +3665,11 @@ interval_mul(PG_FUNCTION_ARGS)
errmsg("interval out of range")));
result->time = (int64) result_double;
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3422,6 +3701,33 @@ interval_div(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DIVISION_BY_ZERO),
errmsg("division by zero")));
+ /*
+ * Handle NaN and infinities.
+ *
+ * We treat "infinity / infinity" as an error, since the interval type has
+ * nothing equivalent to NaN. Otherwise, dividing by infinity is handled
+ * by the regular division code, causing all fields to be set to zero.
+ */
+ if (isnan(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (INTERVAL_NOT_FINITE(span))
+ {
+ if (isinf(factor))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
+ if (factor < 0.0)
+ interval_um_internal(span, result);
+ else
+ memcpy(result, span, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
result->month = (int32) (span->month / factor);
result->day = (int32) (span->day / factor);
@@ -3443,6 +3749,11 @@ interval_div(PG_FUNCTION_ARGS)
result->day += (int32) month_remainder_days;
result->time = rint(span->time / factor + sec_remainder * USECS_PER_SEC);
+ if (INTERVAL_NOT_FINITE(result))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+
PG_RETURN_INTERVAL_P(result);
}
@@ -3466,11 +3777,21 @@ in_range_timestamptz_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
TimestampTz sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = timestamptz_mi_interval_internal(base, offset, NULL);
@@ -3493,11 +3814,21 @@ in_range_timestamp_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Timestamp sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? TIMESTAMP_IS_NOEND(base) : TIMESTAMP_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetTimestamp(DirectFunctionCall2(timestamp_mi_interval,
@@ -3524,11 +3855,21 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
bool less = PG_GETARG_BOOL(4);
Interval *sum;
- if (int128_compare(interval_cmp_value(offset), int64_to_int128(0)) < 0)
+ if (interval_sign(offset) < 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE),
errmsg("invalid preceding or following size in window function")));
+ /*
+ * Deal with cases where both base and offset are infinite, and computing
+ * base +/- offset would cause an error. As for float and numeric types,
+ * we assume that all values infinitely precede +infinity and infinitely
+ * follow -infinity. See in_range_float8_float8() for reasoning.
+ */
+ if (INTERVAL_IS_NOEND(offset) &&
+ (sub ? INTERVAL_IS_NOEND(base) : INTERVAL_IS_NOBEGIN(base)))
+ PG_RETURN_BOOL(true);
+
/* We don't currently bother to avoid overflow hazards here */
if (sub)
sum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
@@ -3547,161 +3888,327 @@ in_range_interval_interval(PG_FUNCTION_ARGS)
/*
- * interval_accum, interval_accum_inv, and interval_avg implement the
- * AVG(interval) aggregate.
+ * Prepare state data for an interval aggregate function, that needs to compute
+ * sum and count, in the aggregate's memory context.
*
- * The transition datatype for this aggregate is a 2-element array of
- * intervals, where the first is the running sum and the second contains
- * the number of values so far in its 'time' field. This is a bit ugly
- * but it beats inventing a specialized datatype for the purpose.
+ * The function is used when the state data needs to be allocated in aggregate's
+ * context. When the state data needs to be allocated in the current memory
+ * context, we use palloc0 directly e.g. interval_avg_deserialize().
+ */
+static IntervalAggState *
+makeIntervalAggState(FunctionCallInfo fcinfo)
+{
+ IntervalAggState *state;
+ MemoryContext agg_context;
+ MemoryContext old_context;
+
+ if (!AggCheckCallContext(fcinfo, &agg_context))
+ elog(ERROR, "aggregate function called in non-aggregate context");
+
+ old_context = MemoryContextSwitchTo(agg_context);
+
+ state = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
+
+ MemoryContextSwitchTo(old_context);
+
+ return state;
+}
+
+/*
+ * Accumulate a new input value for interval aggregate functions.
+ */
+static void
+do_interval_accum(IntervalAggState *state, Interval *newval)
+{
+ /* Infinite inputs are counted separately, and do not affect "N" */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount++;
+ return;
+ }
+
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount++;
+ return;
+ }
+
+ finite_interval_pl(&state->sumX, newval, &state->sumX);
+ state->N++;
+}
+
+/*
+ * Remove the given interval value from the aggregated state.
+ */
+static void
+do_interval_discard(IntervalAggState *state, Interval *newval)
+{
+ /* Infinite inputs are counted separately, and do not affect "N" */
+ if (INTERVAL_IS_NOBEGIN(newval))
+ {
+ state->nInfcount--;
+ return;
+ }
+
+ if (INTERVAL_IS_NOEND(newval))
+ {
+ state->pInfcount--;
+ return;
+ }
+
+ /* Handle the to-be-discarded finite value. */
+ state->N--;
+ if (state->N > 0)
+ finite_interval_mi(&state->sumX, newval, &state->sumX);
+ else
+ {
+ /* All values discarded, reset the state */
+ Assert(state->N == 0);
+ memset(&state->sumX, 0, sizeof(state->sumX));
+ }
+}
+
+/*
+ * Transition function for sum() and avg() interval aggregates.
*/
+Datum
+interval_avg_accum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ /* Create the state data on the first call */
+ if (state == NULL)
+ state = makeIntervalAggState(fcinfo);
+ if (!PG_ARGISNULL(1))
+ do_interval_accum(state, PG_GETARG_INTERVAL_P(1));
+
+ PG_RETURN_POINTER(state);
+}
+
+/*
+ * Combine function for sum() and avg() interval aggregates.
+ *
+ * Combine the given internal aggregate states and place the combination in
+ * the first argument.
+ */
Datum
-interval_accum(PG_FUNCTION_ARGS)
+interval_avg_combine(PG_FUNCTION_ARGS)
{
- ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
- Interval *newval = PG_GETARG_INTERVAL_P(1);
- Datum *transdatums;
- int ndatums;
- Interval sumX,
- N;
- Interval *newsum;
- ArrayType *result;
+ IntervalAggState *state1;
+ IntervalAggState *state2;
+
+ state1 = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+ state2 = PG_ARGISNULL(1) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(1);
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ if (state2 == NULL)
+ PG_RETURN_POINTER(state1);
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ if (state1 == NULL)
+ {
+ /* manually copy all fields from state2 to state1 */
+ state1 = makeIntervalAggState(fcinfo);
+
+ state1->N = state2->N;
+ state1->pInfcount = state2->pInfcount;
+ state1->nInfcount = state2->nInfcount;
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_pl,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time += 1;
+ state1->sumX.day = state2->sumX.day;
+ state1->sumX.month = state2->sumX.month;
+ state1->sumX.time = state2->sumX.time;
+
+ 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;
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ 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));
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ result = (IntervalAggState *) palloc0(sizeof(IntervalAggState));
- newsum = DatumGetIntervalP(DirectFunctionCall2(interval_mi,
- IntervalPGetDatum(&sumX),
- IntervalPGetDatum(newval)));
- N.time -= 1;
+ /* N */
+ result->N = pq_getmsgint64(&buf);
- transdatums[0] = IntervalPGetDatum(newsum);
- transdatums[1] = IntervalPGetDatum(&N);
+ /* sumX */
+ result->sumX.time = pq_getmsgint64(&buf);
+ result->sumX.day = pq_getmsgint(&buf, 4);
+ result->sumX.month = pq_getmsgint(&buf, 4);
- result = construct_array(transdatums, 2,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE);
+ /* pInfcount */
+ result->pInfcount = pq_getmsgint64(&buf);
- PG_RETURN_ARRAYTYPE_P(result);
+ /* nInfcount */
+ result->nInfcount = pq_getmsgint64(&buf);
+
+ pq_getmsgend(&buf);
+
+ PG_RETURN_POINTER(result);
}
+/*
+ * Inverse transition function for sum() and avg() interval aggregates.
+ */
Datum
-interval_avg(PG_FUNCTION_ARGS)
+interval_avg_accum_inv(PG_FUNCTION_ARGS)
{
- ArrayType *transarray = PG_GETARG_ARRAYTYPE_P(0);
- Datum *transdatums;
- int ndatums;
- Interval sumX,
- N;
+ IntervalAggState *state;
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
- deconstruct_array(transarray,
- INTERVALOID, sizeof(Interval), false, TYPALIGN_DOUBLE,
- &transdatums, NULL, &ndatums);
- if (ndatums != 2)
- elog(ERROR, "expected 2-element interval array");
+ /* Should not get here with no state */
+ if (state == NULL)
+ elog(ERROR, "interval_avg_accum_inv called with NULL state");
- sumX = *(DatumGetIntervalP(transdatums[0]));
- N = *(DatumGetIntervalP(transdatums[1]));
+ if (!PG_ARGISNULL(1))
+ do_interval_discard(state, PG_GETARG_INTERVAL_P(1));
- /* SQL defines AVG of no values to be NULL */
- if (N.time == 0)
+ PG_RETURN_POINTER(state);
+}
+
+/* avg(interval) aggregate final function */
+Datum
+interval_avg(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
PG_RETURN_NULL();
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 || state->nInfcount > 0)
+ {
+ Interval *result;
+
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else
+ INTERVAL_NOBEGIN(result);
+
+ PG_RETURN_INTERVAL_P(result);
+ }
+
return DirectFunctionCall2(interval_div,
- IntervalPGetDatum(&sumX),
- Float8GetDatum((double) N.time));
+ IntervalPGetDatum(&state->sumX),
+ Float8GetDatum((double) state->N));
}
+/* sum(interval) aggregate final function */
+Datum
+interval_sum(PG_FUNCTION_ARGS)
+{
+ IntervalAggState *state;
+ Interval *result;
+
+ state = PG_ARGISNULL(0) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(0);
+
+ /* If there were no non-null inputs, return NULL */
+ if (state == NULL || IA_TOTAL_COUNT(state) == 0)
+ PG_RETURN_NULL();
+
+ /*
+ * Aggregating infinities that all have the same sign produces infinity
+ * with that sign. Aggregating infinities with different signs results in
+ * an error.
+ */
+ if (state->pInfcount > 0 && state->nInfcount > 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range.")));
+
+ result = (Interval *) palloc(sizeof(Interval));
+
+ if (state->pInfcount > 0)
+ INTERVAL_NOEND(result);
+ else if (state->nInfcount > 0)
+ INTERVAL_NOBEGIN(result);
+ else
+ memcpy(result, &state->sumX, sizeof(Interval));
+
+ PG_RETURN_INTERVAL_P(result);
+}
/* timestamp_age()
* Calculate time difference while retaining year/month fields.
@@ -3726,8 +4233,36 @@ timestamp_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, NULL, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, NULL, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3846,8 +4381,36 @@ timestamptz_age(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
- if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
- timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
+ /*
+ * Handle infinities.
+ *
+ * We treat anything that amounts to "infinity - infinity" as an error,
+ * since the interval type has nothing equivalent to NaN.
+ */
+ if (TIMESTAMP_IS_NOBEGIN(dt1))
+ {
+ if (TIMESTAMP_IS_NOBEGIN(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOBEGIN(result);
+ }
+ else if (TIMESTAMP_IS_NOEND(dt1))
+ {
+ if (TIMESTAMP_IS_NOEND(dt2))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("interval out of range")));
+ else
+ INTERVAL_NOEND(result);
+ }
+ else if (TIMESTAMP_IS_NOBEGIN(dt2))
+ INTERVAL_NOEND(result);
+ else if (TIMESTAMP_IS_NOEND(dt2))
+ INTERVAL_NOBEGIN(result);
+ else if (timestamp2tm(dt1, &tz1, tm1, &fsec1, NULL, NULL) == 0 &&
+ timestamp2tm(dt2, &tz2, tm2, &fsec2, NULL, NULL) == 0)
{
/* form the symbolic difference */
tm->tm_usec = fsec1 - fsec2;
@@ -3972,6 +4535,11 @@ timestamp_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4155,6 +4723,11 @@ timestamptz_bin(PG_FUNCTION_ARGS)
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
errmsg("origin out of range")));
+ if (INTERVAL_NOT_FINITE(stride))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamps cannot be binned into infinite intervals")));
+
if (stride->month != 0)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4393,6 +4966,12 @@ interval_trunc(PG_FUNCTION_ARGS)
result = (Interval *) palloc(sizeof(Interval));
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ memcpy(result, interval, sizeof(Interval));
+ PG_RETURN_INTERVAL_P(result);
+ }
+
lowunits = downcase_truncate_identifier(VARDATA_ANY(units),
VARSIZE_ANY_EXHDR(units),
false);
@@ -4737,7 +5316,7 @@ timestamp_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
false);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5011,7 +5590,7 @@ timestamptz_part_common(PG_FUNCTION_ARGS, bool retnumeric)
TIMESTAMP_IS_NOBEGIN(timestamp),
true);
- if (r)
+ if (r != 0.0)
{
if (retnumeric)
{
@@ -5251,6 +5830,58 @@ extract_timestamptz(PG_FUNCTION_ARGS)
return timestamptz_part_common(fcinfo, true);
}
+/*
+ * NonFiniteIntervalPart
+ *
+ * Used by interval_part when extracting from infinite interval. Returns
+ * +/-Infinity if that is the appropriate result, otherwise returns zero
+ * (which should be taken as meaning to return NULL).
+ *
+ * Errors thrown here for invalid units should exactly match those that
+ * would be thrown in the calling functions, else there will be unexpected
+ * discrepancies between finite- and infinite-input cases.
+ */
+static float8
+NonFiniteIntervalPart(int type, int unit, char *lowunits, bool isNegative)
+{
+ if ((type != UNITS) && (type != RESERV))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unit \"%s\" not recognized for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+
+ switch (unit)
+ {
+ /* Oscillating units */
+ case DTK_MICROSEC:
+ case DTK_MILLISEC:
+ case DTK_SECOND:
+ case DTK_MINUTE:
+ case DTK_MONTH:
+ case DTK_QUARTER:
+ return 0.0;
+
+ /* Monotonically-increasing units */
+ case DTK_HOUR:
+ case DTK_DAY:
+ case DTK_YEAR:
+ case DTK_DECADE:
+ case DTK_CENTURY:
+ case DTK_MILLENNIUM:
+ case DTK_EPOCH:
+ if (isNegative)
+ return -get_float8_infinity();
+ else
+ return get_float8_infinity();
+
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unit \"%s\" not supported for type %s",
+ lowunits, format_type_be(INTERVALOID))));
+ return 0.0; /* keep compiler quiet */
+ }
+}
/* interval_part() and extract_interval()
* Extract specified field from interval.
@@ -5275,6 +5906,33 @@ interval_part_common(PG_FUNCTION_ARGS, bool retnumeric)
if (type == UNKNOWN_FIELD)
type = DecodeSpecial(0, lowunits, &val);
+ if (INTERVAL_NOT_FINITE(interval))
+ {
+ double r = NonFiniteIntervalPart(type, val, lowunits,
+ INTERVAL_IS_NOBEGIN(interval));
+
+ if (r != 0.0)
+ {
+ if (retnumeric)
+ {
+ if (r < 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("-Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ else if (r > 0)
+ return DirectFunctionCall3(numeric_in,
+ CStringGetDatum("Infinity"),
+ ObjectIdGetDatum(InvalidOid),
+ Int32GetDatum(-1));
+ }
+ else
+ PG_RETURN_FLOAT8(r);
+ }
+ else
+ PG_RETURN_NULL();
+ }
+
if (type == UNITS)
{
interval2itm(*interval, tm);
@@ -5517,6 +6175,13 @@ timestamp_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMPTZ(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5747,6 +6412,13 @@ timestamptz_izone(PG_FUNCTION_ARGS)
if (TIMESTAMP_NOT_FINITE(timestamp))
PG_RETURN_TIMESTAMP(timestamp);
+ if (INTERVAL_NOT_FINITE(zone))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("interval time zone \"%s\" must be finite",
+ DatumGetCString(DirectFunctionCall1(interval_out,
+ PointerGetDatum(zone))))));
+
if (zone->month != 0 || zone->day != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -5783,7 +6455,6 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
Timestamp finish = PG_GETARG_TIMESTAMP(1);
Interval *step = PG_GETARG_INTERVAL_P(2);
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5806,13 +6477,18 @@ generate_series_timestamp(PG_FUNCTION_ARGS)
fctx->step = *step;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
@@ -5864,7 +6540,6 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
Interval *step = PG_GETARG_INTERVAL_P(2);
text *zone = (PG_NARGS() == 4) ? PG_GETARG_TEXT_PP(3) : NULL;
MemoryContext oldcontext;
- const Interval interval_zero = {0};
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
@@ -5888,13 +6563,18 @@ generate_series_timestamptz_internal(FunctionCallInfo fcinfo)
fctx->attimezone = zone ? lookup_timezone(zone) : session_timezone;
/* Determine sign of the interval */
- fctx->step_sign = interval_cmp_internal(&fctx->step, &interval_zero);
+ fctx->step_sign = interval_sign(&fctx->step);
if (fctx->step_sign == 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("step size cannot equal zero")));
+ if (INTERVAL_NOT_FINITE((&fctx->step)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("step size cannot be infinite")));
+
funcctx->user_fctx = fctx;
MemoryContextSwitchTo(oldcontext);
}
diff --git a/src/include/catalog/pg_aggregate.dat b/src/include/catalog/pg_aggregate.dat
index 1bc1d97d74..e1a17cddd8 100644
--- a/src/include/catalog/pg_aggregate.dat
+++ b/src/include/catalog/pg_aggregate.dat
@@ -43,12 +43,13 @@
{ 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',
@@ -72,10 +73,13 @@
{ 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',
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index f14aed422a..d6a990e531 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -4917,17 +4917,29 @@
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' },
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 1a6390585c..b63acc0a2f 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -151,7 +151,7 @@ struct pg_itm_in
#define TIMESTAMP_INFINITY PG_INT64_MAX
/*
- * Historically these alias for infinity have been used.
+ * Historically these aliases for infinity have been used.
*/
#define DT_NOBEGIN TIMESTAMP_MINUS_INFINITY
#define DT_NOEND TIMESTAMP_INFINITY
@@ -168,6 +168,31 @@ struct pg_itm_in
#define TIMESTAMP_NOT_FINITE(j) (TIMESTAMP_IS_NOBEGIN(j) || TIMESTAMP_IS_NOEND(j))
+/*
+ * Infinite intervals are represented by setting all fields to the minimum or
+ * maximum integer values.
+ */
+#define INTERVAL_NOBEGIN(i) \
+ do { \
+ (i)->time = PG_INT64_MIN; \
+ (i)->day = PG_INT32_MIN; \
+ (i)->month = PG_INT32_MIN; \
+ } while (0)
+
+#define INTERVAL_IS_NOBEGIN(i) \
+ ((i)->month == PG_INT32_MIN && (i)->day == PG_INT32_MIN && (i)->time == PG_INT64_MIN)
+
+#define INTERVAL_NOEND(i) \
+ do { \
+ (i)->time = PG_INT64_MAX; \
+ (i)->day = PG_INT32_MAX; \
+ (i)->month = PG_INT32_MAX; \
+ } while (0)
+
+#define INTERVAL_IS_NOEND(i) \
+ ((i)->month == PG_INT32_MAX && (i)->day == PG_INT32_MAX && (i)->time == PG_INT64_MAX)
+
+#define INTERVAL_NOT_FINITE(i) (INTERVAL_IS_NOBEGIN(i) || INTERVAL_IS_NOEND(i))
/*
* Julian date support.
diff --git a/src/test/regress/expected/brin_multi.out b/src/test/regress/expected/brin_multi.out
index 80801cd4ee..8f4c95b9e6 100644
--- a/src/test/regress/expected/brin_multi.out
+++ b/src/test/regress/expected/brin_multi.out
@@ -941,6 +941,34 @@ SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
Index Cond: (a = '@ 30 years'::interval)
(4 rows)
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+SET enable_seqscan = off;
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years ago'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years ago'::interval)
+(4 rows)
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+ QUERY PLAN
+-----------------------------------------------------------------------------
+ Bitmap Heap Scan on brin_interval_test (actual rows=0 loops=1)
+ Recheck Cond: (a = '@ 30 years'::interval)
+ -> Bitmap Index Scan on brin_interval_test_a_idx (actual rows=0 loops=1)
+ Index Cond: (a = '@ 30 years'::interval)
+(4 rows)
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
diff --git a/src/test/regress/expected/horology.out b/src/test/regress/expected/horology.out
index 8f52661096..cfb4b205e4 100644
--- a/src/test/regress/expected/horology.out
+++ b/src/test/regress/expected/horology.out
@@ -1171,6 +1171,7 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
-------------+-------------------------------+-------------+-------------
@@ -1278,6 +1279,7 @@ SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
t | i | add | subtract
----------------+-------------------------------+----------------+----------------
@@ -1556,6 +1558,22 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
ORDER BY plus, "timestamp", "interval";
timestamp | interval | plus
------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | -infinity | -infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | -infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | -infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | -infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | -infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | -infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | -infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | -infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | -infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | -infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | -infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | -infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | -infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | -infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | -infinity
Thu Jan 01 00:00:00 1970 PST | @ 14 secs ago | Wed Dec 31 23:59:46 1969 PST
Thu Jan 01 00:00:00 1970 PST | @ 1 min | Thu Jan 01 00:01:00 1970 PST
Thu Jan 01 00:00:00 1970 PST | @ 5 hours | Thu Jan 01 05:00:00 1970 PST
@@ -1716,14 +1734,45 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
Sun Dec 31 17:32:01 2000 PST | @ 34 years | Sun Dec 31 17:32:01 2034 PST
Mon Jan 01 17:32:01 2001 PST | @ 34 years | Mon Jan 01 17:32:01 2035 PST
Sat Sep 22 18:19:20 2001 PDT | @ 34 years | Sat Sep 22 18:19:20 2035 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
timestamp | interval | minus
------------------------------+-------------------------------+------------------------------
+ Thu Jan 01 00:00:00 1970 PST | infinity | -infinity
+ Wed Feb 28 17:32:01 1996 PST | infinity | -infinity
+ Thu Feb 29 17:32:01 1996 PST | infinity | -infinity
+ Fri Mar 01 17:32:01 1996 PST | infinity | -infinity
+ Mon Dec 30 17:32:01 1996 PST | infinity | -infinity
+ Tue Dec 31 17:32:01 1996 PST | infinity | -infinity
+ Fri Dec 31 17:32:01 1999 PST | infinity | -infinity
+ Sat Jan 01 17:32:01 2000 PST | infinity | -infinity
+ Wed Mar 15 02:14:05 2000 PST | infinity | -infinity
+ Wed Mar 15 03:14:04 2000 PST | infinity | -infinity
+ Wed Mar 15 08:14:01 2000 PST | infinity | -infinity
+ Wed Mar 15 12:14:03 2000 PST | infinity | -infinity
+ Wed Mar 15 13:14:02 2000 PST | infinity | -infinity
+ Sun Dec 31 17:32:01 2000 PST | infinity | -infinity
+ Mon Jan 01 17:32:01 2001 PST | infinity | -infinity
+ Sat Sep 22 18:19:20 2001 PDT | infinity | -infinity
Thu Jan 01 00:00:00 1970 PST | @ 34 years | Wed Jan 01 00:00:00 1936 PST
Wed Feb 28 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
Thu Feb 29 17:32:01 1996 PST | @ 34 years | Wed Feb 28 17:32:01 1962 PST
@@ -1884,7 +1933,23 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
Sat Sep 22 18:19:20 2001 PDT | @ 5 hours | Sat Sep 22 13:19:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 1 min | Sat Sep 22 18:18:20 2001 PDT
Sat Sep 22 18:19:20 2001 PDT | @ 14 secs ago | Sat Sep 22 18:19:34 2001 PDT
-(160 rows)
+ Thu Jan 01 00:00:00 1970 PST | -infinity | infinity
+ Wed Feb 28 17:32:01 1996 PST | -infinity | infinity
+ Thu Feb 29 17:32:01 1996 PST | -infinity | infinity
+ Fri Mar 01 17:32:01 1996 PST | -infinity | infinity
+ Mon Dec 30 17:32:01 1996 PST | -infinity | infinity
+ Tue Dec 31 17:32:01 1996 PST | -infinity | infinity
+ Fri Dec 31 17:32:01 1999 PST | -infinity | infinity
+ Sat Jan 01 17:32:01 2000 PST | -infinity | infinity
+ Wed Mar 15 02:14:05 2000 PST | -infinity | infinity
+ Wed Mar 15 03:14:04 2000 PST | -infinity | infinity
+ Wed Mar 15 08:14:01 2000 PST | -infinity | infinity
+ Wed Mar 15 12:14:03 2000 PST | -infinity | infinity
+ Wed Mar 15 13:14:02 2000 PST | -infinity | infinity
+ Sun Dec 31 17:32:01 2000 PST | -infinity | infinity
+ Mon Jan 01 17:32:01 2001 PST | -infinity | infinity
+ Sat Sep 22 18:19:20 2001 PDT | -infinity | infinity
+(192 rows)
SELECT d.f1 AS "timestamp",
timestamp with time zone '1980-01-06 00:00 GMT' AS gpstime_zero,
diff --git a/src/test/regress/expected/interval.out b/src/test/regress/expected/interval.out
index 75d19d6594..a481781475 100644
--- a/src/test/regress/expected/interval.out
+++ b/src/test/regress/expected/interval.out
@@ -52,6 +52,18 @@ SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
9 years 1 mon -12 days +13:14:00
(1 row)
+SELECT INTERVAL 'infinity' AS "eternity";
+ eternity
+----------
+ infinity
+(1 row)
+
+SELECT INTERVAL '-infinity' AS "beginning of time";
+ beginning of time
+-------------------
+ -infinity
+(1 row)
+
CREATE TABLE INTERVAL_TBL (f1 interval);
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 1 minute');
INSERT INTO INTERVAL_TBL (f1) VALUES ('@ 5 hour');
@@ -63,6 +75,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
ERROR: invalid input syntax for type interval: "badly formatted interval"
@@ -117,7 +131,9 @@ SELECT * FROM INTERVAL_TBL;
6 years
5 mons
5 mons 12:00:00
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <> interval '@ 10 days';
@@ -132,7 +148,9 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+ -infinity
+(11 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 <= interval '@ 5 hours';
@@ -141,7 +159,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 < interval '@ 1 day';
@@ -150,7 +169,8 @@ SELECT * FROM INTERVAL_TBL
00:01:00
05:00:00
-00:00:14
-(3 rows)
+ -infinity
+(4 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 = interval '@ 34 years';
@@ -168,7 +188,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(5 rows)
+ infinity
+(6 rows)
SELECT * FROM INTERVAL_TBL
WHERE INTERVAL_TBL.f1 > interval '@ 3 seconds ago';
@@ -183,7 +204,8 @@ SELECT * FROM INTERVAL_TBL
6 years
5 mons
5 mons 12:00:00
-(9 rows)
+ infinity
+(10 rows)
SELECT r1.*, r2.*
FROM INTERVAL_TBL r1, INTERVAL_TBL r2
@@ -191,27 +213,35 @@ SELECT r1.*, r2.*
ORDER BY r1.f1, r2.f1;
f1 | f1
-----------------+-----------------
+ -00:00:14 | -infinity
+ 00:01:00 | -infinity
00:01:00 | -00:00:14
+ 05:00:00 | -infinity
05:00:00 | -00:00:14
05:00:00 | 00:01:00
+ 1 day 02:03:04 | -infinity
1 day 02:03:04 | -00:00:14
1 day 02:03:04 | 00:01:00
1 day 02:03:04 | 05:00:00
+ 10 days | -infinity
10 days | -00:00:14
10 days | 00:01:00
10 days | 05:00:00
10 days | 1 day 02:03:04
+ 3 mons | -infinity
3 mons | -00:00:14
3 mons | 00:01:00
3 mons | 05:00:00
3 mons | 1 day 02:03:04
3 mons | 10 days
+ 5 mons | -infinity
5 mons | -00:00:14
5 mons | 00:01:00
5 mons | 05:00:00
5 mons | 1 day 02:03:04
5 mons | 10 days
5 mons | 3 mons
+ 5 mons 12:00:00 | -infinity
5 mons 12:00:00 | -00:00:14
5 mons 12:00:00 | 00:01:00
5 mons 12:00:00 | 05:00:00
@@ -219,6 +249,7 @@ SELECT r1.*, r2.*
5 mons 12:00:00 | 10 days
5 mons 12:00:00 | 3 mons
5 mons 12:00:00 | 5 mons
+ 6 years | -infinity
6 years | -00:00:14
6 years | 00:01:00
6 years | 05:00:00
@@ -227,6 +258,7 @@ SELECT r1.*, r2.*
6 years | 3 mons
6 years | 5 mons
6 years | 5 mons 12:00:00
+ 34 years | -infinity
34 years | -00:00:14
34 years | 00:01:00
34 years | 05:00:00
@@ -236,8 +268,63 @@ SELECT r1.*, r2.*
34 years | 5 mons
34 years | 5 mons 12:00:00
34 years | 6 years
-(45 rows)
+ infinity | -infinity
+ infinity | -00:00:14
+ infinity | 00:01:00
+ infinity | 05:00:00
+ infinity | 1 day 02:03:04
+ infinity | 10 days
+ infinity | 3 mons
+ infinity | 5 mons
+ infinity | 5 mons 12:00:00
+ infinity | 6 years
+ infinity | 34 years
+(66 rows)
+
+-- test unary minus
+SELECT f1, -f1 FROM INTERVAL_TBL;
+ f1 | ?column?
+-----------------+-------------------
+ 00:01:00 | -00:01:00
+ 05:00:00 | -05:00:00
+ 10 days | -10 days
+ 34 years | -34 years
+ 3 mons | -3 mons
+ -00:00:14 | 00:00:14
+ 1 day 02:03:04 | -1 days -02:03:04
+ 6 years | -6 years
+ 5 mons | -5 mons
+ 5 mons 12:00:00 | -5 mons -12:00:00
+ infinity | -infinity
+ -infinity | infinity
+(12 rows)
+
+SELECT -('-2147483648 months'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-2147483647 months'::interval); -- ok
+ ?column?
+------------------------
+ 178956970 years 7 mons
+(1 row)
+
+SELECT -('-2147483648 days'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-2147483647 days'::interval); -- ok
+ ?column?
+-----------------
+ 2147483647 days
+(1 row)
+
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+ERROR: interval out of range
+SELECT -('-9223372036854775807 us'::interval); -- ok
+ ?column?
+-------------------------
+ 2562047788:00:54.775807
+(1 row)
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+ERROR: interval out of range
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
@@ -304,6 +391,17 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
(5 rows)
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+ ?column?
+----------
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+ 00:00:00
+(5 rows)
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
-- Floating point arithmetic rounding errors can lead to unexpected results,
@@ -386,12 +484,14 @@ SELECT * FROM INTERVAL_TBL;
@ 6 years
@ 5 mons
@ 5 mons 12 hours
-(10 rows)
+ infinity
+ -infinity
+(12 rows)
-- test avg(interval), which is somewhat fragile since people have been
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
avg
-------------------------------------------------
@ 4 years 1 mon 10 days 4 hours 18 mins 23 secs
@@ -820,8 +920,8 @@ SELECT interval '1 2:03:04.5678' minute to second(2);
SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
(f1 + INTERVAL '1 month')::INTERVAL MONTH::INTERVAL YEAR AS "years"
FROM interval_tbl;
- f1 | minutes | years
------------------+-----------------+----------
+ f1 | minutes | years
+-----------------+-----------------+-----------
00:01:00 | 00:01:00 | 00:00:00
05:00:00 | 05:00:00 | 00:00:00
10 days | 10 days | 00:00:00
@@ -832,7 +932,9 @@ SELECT f1, f1::INTERVAL DAY TO MINUTE AS "minutes",
6 years | 6 years | 6 years
5 mons | 5 mons | 00:00:00
5 mons 12:00:00 | 5 mons 12:00:00 | 00:00:00
-(10 rows)
+ infinity | infinity | infinity
+ -infinity | -infinity | -infinity
+(12 rows)
-- test inputting and outputting SQL standard interval literals
SET IntervalStyle TO sql_standard;
@@ -1616,31 +1718,31 @@ select make_interval(mins := -1, secs := -9223372036800.0);
ERROR: interval out of range
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
--------------------------------------------------------------------
- -178956970 years -8 mons -2147483648 days -2562047788:00:54.775808
+ -178956970 years -7 mons -2147483648 days -2562047788:00:54.775808
(1 row)
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
---------------------------------------------------
- -178956970-8 -2147483648 -2562047788:00:54.775808
+ -178956970-7 -2147483648 -2562047788:00:54.775808
(1 row)
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
-----------------------------------------------------
- P-178956970Y-8M-2147483648DT-2562047788H-54.775808S
+ P-178956970Y-7M-2147483648DT-2562047788H-54.775808S
(1 row)
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
interval
------------------------------------------------------------------------------
- @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775808 secs ago
+ @ 178956970 years 7 mons 2147483648 days 2562047788 hours 54.775808 secs ago
(1 row)
-- check that '30 days' equals '1 month' according to the hash function
@@ -1719,19 +1821,21 @@ SELECT f1,
EXTRACT(MILLENNIUM FROM f1) AS MILLENNIUM,
EXTRACT(EPOCH FROM f1) AS EPOCH
FROM INTERVAL_TBL;
- f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
--------------------------------+-------------+-------------+------------+--------+------+-----+-------+---------+------+--------+---------+------------+-------------------
- @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
- @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
- @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
- @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
- @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
- @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
- @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
- @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
- @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
- @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
-(10 rows)
+ f1 | microsecond | millisecond | second | minute | hour | day | month | quarter | year | decade | century | millennium | epoch
+-------------------------------+-------------+-------------+------------+--------+-----------+-----------+-------+---------+-----------+-----------+-----------+------------+-------------------
+ @ 1 min | 0 | 0.000 | 0.000000 | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 60.000000
+ @ 5 hours | 0 | 0.000 | 0.000000 | 0 | 5 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 18000.000000
+ @ 10 days | 0 | 0.000 | 0.000000 | 0 | 0 | 10 | 0 | 1 | 0 | 0 | 0 | 0 | 864000.000000
+ @ 34 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 34 | 3 | 0 | 0 | 1072958400.000000
+ @ 3 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 3 | 2 | 0 | 0 | 0 | 0 | 7776000.000000
+ @ 14 secs ago | -14000000 | -14000.000 | -14.000000 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | -14.000000
+ @ 1 day 2 hours 3 mins 4 secs | 4000000 | 4000.000 | 4.000000 | 3 | 2 | 1 | 0 | 1 | 0 | 0 | 0 | 0 | 93784.000000
+ @ 6 years | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 0 | 1 | 6 | 0 | 0 | 0 | 189345600.000000
+ @ 5 mons | 0 | 0.000 | 0.000000 | 0 | 0 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 12960000.000000
+ @ 5 mons 12 hours | 0 | 0.000 | 0.000000 | 0 | 12 | 0 | 5 | 2 | 0 | 0 | 0 | 0 | 13003200.000000
+ infinity | | | | | Infinity | Infinity | | | Infinity | Infinity | Infinity | Infinity | Infinity
+ -infinity | | | | | -Infinity | -Infinity | | | -Infinity | -Infinity | -Infinity | -Infinity | -Infinity
+(12 rows)
SELECT EXTRACT(FORTNIGHT FROM INTERVAL '2 days'); -- error
ERROR: unit "fortnight" not recognized for type interval
@@ -1805,7 +1909,9 @@ SELECT f1,
@ 6 years | 0 | 0 | 0 | 189345600
@ 5 mons | 0 | 0 | 0 | 12960000
@ 5 mons 12 hours | 0 | 0 | 0 | 13003200
-(10 rows)
+ infinity | | | | Infinity
+ -infinity | | | | -Infinity
+(12 rows)
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
@@ -1814,6 +1920,292 @@ SELECT extract(epoch from interval '1000000000 days');
86400000000000.000000
(1 row)
+--
+-- test infinite intervals
+--
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+ interval
+------------------------------------------------------------------------------
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+ interval
+--------------------------------------------------------------------------
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs
+(1 row)
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+ interval
+-----------
+ -infinity
+(1 row)
+
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+ interval
+----------
+ infinity
+(1 row)
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+ i | isfinite
+-------------------------+----------
+ infinity | f
+ -infinity | f
+ @ 1 year 2 days 3 hours | t
+(3 rows)
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ date | interval | plus | minus
+------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 | -infinity | -infinity | infinity
+ 1995-08-06 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+ interval1 | interval2 | plus | minus
+-----------+-----------+-----------------------+-----------------------
+ -infinity | -infinity | -infinity | interval out of range
+ -infinity | @ 10 days | -infinity | -infinity
+ -infinity | infinity | interval out of range | -infinity
+ @ 2 mons | -infinity | -infinity | infinity
+ @ 2 mons | @ 10 days | @ 2 mons 10 days | @ 2 mons -10 days
+ @ 2 mons | infinity | infinity | -infinity
+ infinity | -infinity | interval out of range | infinity
+ infinity | @ 10 days | infinity | infinity
+ infinity | infinity | infinity | interval out of range
+(9 rows)
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+ERROR: interval out of range
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+ERROR: interval out of range
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamp | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+ timestamptz | interval | plus | minus
+---------------------+-----------+------------------------+------------------------
+ -infinity | -infinity | -infinity | timestamp out of range
+ -infinity | infinity | timestamp out of range | -infinity
+ 1995-08-06 12:30:15 | -infinity | -infinity | infinity
+ 1995-08-06 12:30:15 | infinity | infinity | -infinity
+ infinity | -infinity | timestamp out of range | infinity
+ infinity | infinity | infinity | timestamp out of range
+(6 rows)
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT time '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT time '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' + interval 'infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' + interval '-infinity';
+ERROR: cannot add infinite interval to time
+SELECT timetz '11:27:42' - interval 'infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT timetz '11:27:42' - interval '-infinity';
+ERROR: cannot subtract infinite interval from time
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+ lhs | rhs | lt | le | eq | gt | ge | ne
+-----------+-------------------------+----+----+----+----+----+----
+ infinity | infinity | f | t | t | f | t | f
+ -infinity | infinity | t | t | f | f | f | t
+ infinity | -infinity | f | f | f | t | t | t
+ -infinity | -infinity | f | t | t | f | t | f
+ infinity | @ 1 year 2 days 3 hours | f | f | f | t | t | t
+ -infinity | @ 1 year 2 days 3 hours | t | t | f | f | f | t
+(6 rows)
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | um | mul | mul_neg | mul_inf | mul_inf_neg | div | div_neg
+-----------+-----------+-----------+-----------+-----------+-------------+-----------+-----------
+ infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity
+ -infinity | infinity | -infinity | infinity | -infinity | infinity | -infinity | infinity
+(2 rows)
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+ERROR: interval out of range
+SELECT interval 'infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' * 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+ERROR: interval out of range
+SELECT interval 'infinity' * 0;
+ERROR: interval out of range
+SELECT interval '-infinity' * 0;
+ERROR: interval out of range
+SELECT interval '0 days' * 'infinity'::float;
+ERROR: interval out of range
+SELECT interval '0 days' * '-infinity'::float;
+ERROR: interval out of range
+SELECT interval '5 days' * 'infinity'::float;
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT interval '5 days' * '-infinity'::float;
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT interval 'infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval 'infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / '-infinity';
+ERROR: interval out of range
+SELECT interval '-infinity' / 'nan';
+ERROR: interval out of range
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+ERROR: interval out of range
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+ERROR: timestamps cannot be binned into infinite intervals
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | date_trunc
+-----------+------------
+ infinity | infinity
+ -infinity | -infinity
+(2 rows)
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+ interval | justify_days | justify_hours | justify_interval
+-----------+--------------+---------------+------------------
+ infinity | infinity | infinity | infinity
+ -infinity | -infinity | -infinity | -infinity
+(2 rows)
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+ERROR: interval time zone "-infinity" must be finite
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "infinity" must be finite
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+ERROR: interval time zone "-infinity" must be finite
+SELECT 'infinity'::interval::time;
+ERROR: cannot convert infinite interval to time
+SELECT '-infinity'::interval::time;
+ERROR: cannot convert infinite interval to time
+SELECT to_char('infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
+SELECT to_char('-infinity'::interval, 'YYYY');
+ to_char
+---------
+
+(1 row)
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
ERROR: invalid input syntax for type interval: "42 days 2 seconds ago ago"
@@ -1832,3 +2224,42 @@ SELECT INTERVAL '1 year months days 5 hours';
ERROR: invalid input syntax for type interval: "1 year months days 5 hours"
LINE 1: SELECT INTERVAL '1 year months days 5 hours';
^
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+ERROR: invalid input syntax for type interval: "now"
+LINE 1: SELECT INTERVAL 'now';
+ ^
+SELECT INTERVAL 'today';
+ERROR: invalid input syntax for type interval: "today"
+LINE 1: SELECT INTERVAL 'today';
+ ^
+SELECT INTERVAL 'tomorrow';
+ERROR: invalid input syntax for type interval: "tomorrow"
+LINE 1: SELECT INTERVAL 'tomorrow';
+ ^
+SELECT INTERVAL 'allballs';
+ERROR: invalid input syntax for type interval: "allballs"
+LINE 1: SELECT INTERVAL 'allballs';
+ ^
+SELECT INTERVAL 'epoch';
+ERROR: invalid input syntax for type interval: "epoch"
+LINE 1: SELECT INTERVAL 'epoch';
+ ^
+SELECT INTERVAL 'yesterday';
+ERROR: invalid input syntax for type interval: "yesterday"
+LINE 1: SELECT INTERVAL 'yesterday';
+ ^
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+ERROR: invalid input syntax for type interval: "infinity years"
+LINE 1: SELECT INTERVAL 'infinity years';
+ ^
+SELECT INTERVAL 'infinity ago';
+ERROR: invalid input syntax for type interval: "infinity ago"
+LINE 1: SELECT INTERVAL 'infinity ago';
+ ^
+SELECT INTERVAL '+infinity -infinity';
+ERROR: invalid input syntax for type interval: "+infinity -infinity"
+LINE 1: SELECT INTERVAL '+infinity -infinity';
+ ^
diff --git a/src/test/regress/expected/timestamp.out b/src/test/regress/expected/timestamp.out
index c64bcb7c12..835f0e5762 100644
--- a/src/test/regress/expected/timestamp.out
+++ b/src/test/regress/expected/timestamp.out
@@ -2125,3 +2125,65 @@ select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '-infinity';
+ERROR: interval out of range
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp 'infinity', timestamp 'infinity');
+ERROR: interval out of range
+select age(timestamp 'infinity', timestamp '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+select age(timestamp '-infinity', timestamp '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 2ca2101dd4..a084357480 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2468,6 +2468,10 @@ select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
ERROR: step size cannot equal zero
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+ERROR: step size cannot be infinite
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
+ERROR: step size cannot be infinite
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
SELECT date_add('2022-10-30 00:00:00+01'::timestamptz,
@@ -3210,3 +3214,61 @@ select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
Tue Jan 17 16:00:00 2017 PST
(1 row)
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+ERROR: interval out of range
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+----------
+ infinity
+(1 row)
+
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+ ?column?
+-----------
+ -infinity
+(1 row)
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+ERROR: interval out of range
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+ age
+----------
+ infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+ age
+-----------
+ -infinity
+(1 row)
+
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
+ERROR: interval out of range
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 69a38df10b..2201740c18 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -2372,6 +2372,7 @@ create temp table datetimes(
f_timestamp timestamp
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -2381,14 +2382,16 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_time range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 1 | 11:00:00 | 1 | 3
+ 0 | 10:00:00 | 0 | 2
+ 1 | 11:00:00 | 0 | 3
2 | 12:00:00 | 1 | 4
3 | 13:00:00 | 2 | 6
4 | 14:00:00 | 3 | 6
@@ -2396,9 +2399,10 @@ window w as (order by f_time range between
6 | 15:00:00 | 4 | 7
7 | 17:00:00 | 7 | 9
8 | 18:00:00 | 7 | 10
- 9 | 19:00:00 | 8 | 10
- 10 | 20:00:00 | 9 | 10
-(10 rows)
+ 9 | 19:00:00 | 8 | 11
+ 10 | 20:00:00 | 9 | 11
+ 11 | 21:00:00 | 10 | 11
+(12 rows)
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2406,7 +2410,8 @@ window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
id | f_time | first_value | last_value
----+----------+-------------+------------
- 10 | 20:00:00 | 10 | 8
+ 11 | 21:00:00 | 11 | 9
+ 10 | 20:00:00 | 11 | 8
9 | 19:00:00 | 10 | 7
8 | 18:00:00 | 9 | 7
7 | 17:00:00 | 8 | 5
@@ -2414,17 +2419,90 @@ window w as (order by f_time desc range between
5 | 15:00:00 | 6 | 3
4 | 14:00:00 | 6 | 2
3 | 13:00:00 | 4 | 1
- 2 | 12:00:00 | 3 | 1
- 1 | 11:00:00 | 2 | 1
-(10 rows)
+ 2 | 12:00:00 | 3 | 0
+ 1 | 11:00:00 | 2 | 0
+ 0 | 10:00:00 | 1 | 0
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | 0 | 11
+ 1 | 11:00:00 | 0 | 11
+ 2 | 12:00:00 | 0 | 11
+ 3 | 13:00:00 | 0 | 11
+ 4 | 14:00:00 | 0 | 11
+ 5 | 15:00:00 | 0 | 11
+ 6 | 15:00:00 | 0 | 11
+ 7 | 17:00:00 | 0 | 11
+ 8 | 18:00:00 | 0 | 11
+ 9 | 19:00:00 | 0 | 11
+ 10 | 20:00:00 | 0 | 11
+ 11 | 21:00:00 | 0 | 11
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_time | first_value | last_value
+----+----------+-------------+------------
+ 0 | 10:00:00 | |
+ 1 | 11:00:00 | |
+ 2 | 12:00:00 | |
+ 3 | 13:00:00 | |
+ 4 | 14:00:00 | |
+ 5 | 15:00:00 | |
+ 6 | 15:00:00 | |
+ 7 | 17:00:00 | |
+ 8 | 18:00:00 | |
+ 9 | 19:00:00 | |
+ 10 | 20:00:00 | |
+ 11 | 21:00:00 | |
+(12 rows)
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
'70 min'::interval preceding and '2 hours'::interval following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 1 | 11:00:00+01 | 1 | 3
+ 0 | 10:00:00+01 | 0 | 2
+ 1 | 11:00:00+01 | 0 | 3
2 | 12:00:00+01 | 1 | 4
3 | 13:00:00+01 | 2 | 6
4 | 14:00:00+01 | 3 | 6
@@ -2432,9 +2510,10 @@ window w as (order by f_timetz range between
6 | 15:00:00+01 | 4 | 7
7 | 17:00:00+01 | 7 | 9
8 | 18:00:00+01 | 7 | 10
- 9 | 19:00:00+01 | 8 | 10
- 10 | 20:00:00+01 | 9 | 10
-(10 rows)
+ 9 | 19:00:00+01 | 8 | 11
+ 10 | 20:00:00+01 | 9 | 11
+ 11 | 21:00:00+01 | 10 | 11
+(12 rows)
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2442,7 +2521,8 @@ window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
id | f_timetz | first_value | last_value
----+-------------+-------------+------------
- 10 | 20:00:00+01 | 10 | 8
+ 11 | 21:00:00+01 | 11 | 9
+ 10 | 20:00:00+01 | 11 | 8
9 | 19:00:00+01 | 10 | 7
8 | 18:00:00+01 | 9 | 7
7 | 17:00:00+01 | 8 | 5
@@ -2450,16 +2530,89 @@ window w as (order by f_timetz desc range between
5 | 15:00:00+01 | 6 | 3
4 | 14:00:00+01 | 6 | 2
3 | 13:00:00+01 | 4 | 1
- 2 | 12:00:00+01 | 3 | 1
- 1 | 11:00:00+01 | 2 | 1
-(10 rows)
+ 2 | 12:00:00+01 | 3 | 0
+ 1 | 11:00:00+01 | 2 | 0
+ 0 | 10:00:00+01 | 1 | 0
+(12 rows)
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | 0 | 11
+ 1 | 11:00:00+01 | 0 | 11
+ 2 | 12:00:00+01 | 0 | 11
+ 3 | 13:00:00+01 | 0 | 11
+ 4 | 14:00:00+01 | 0 | 11
+ 5 | 15:00:00+01 | 0 | 11
+ 6 | 15:00:00+01 | 0 | 11
+ 7 | 17:00:00+01 | 0 | 11
+ 8 | 18:00:00+01 | 0 | 11
+ 9 | 19:00:00+01 | 0 | 11
+ 10 | 20:00:00+01 | 0 | 11
+ 11 | 21:00:00+01 | 0 | 11
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timetz | first_value | last_value
+----+-------------+-------------+------------
+ 0 | 10:00:00+01 | |
+ 1 | 11:00:00+01 | |
+ 2 | 12:00:00+01 | |
+ 3 | 13:00:00+01 | |
+ 4 | 14:00:00+01 | |
+ 5 | 15:00:00+01 | |
+ 6 | 15:00:00+01 | |
+ 7 | 17:00:00+01 | |
+ 8 | 18:00:00+01 | |
+ 9 | 19:00:00+01 | |
+ 10 | 20:00:00+01 | |
+ 11 | 21:00:00+01 | |
+(12 rows)
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | @ 1 year | 1 | 2
2 | @ 2 years | 1 | 3
3 | @ 3 years | 2 | 4
@@ -2470,7 +2623,8 @@ window w as (order by f_interval range between
8 | @ 8 years | 7 | 9
9 | @ 9 years | 8 | 10
10 | @ 10 years | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2478,6 +2632,7 @@ window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
id | f_interval | first_value | last_value
----+------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | @ 10 years | 10 | 9
9 | @ 9 years | 10 | 8
8 | @ 8 years | 9 | 7
@@ -2488,14 +2643,87 @@ window w as (order by f_interval desc range between
3 | @ 3 years | 4 | 2
2 | @ 2 years | 3 | 1
1 | @ 1 year | 2 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 0 | 11
+ 2 | @ 2 years | 0 | 11
+ 3 | @ 3 years | 0 | 11
+ 4 | @ 4 years | 0 | 11
+ 5 | @ 5 years | 0 | 11
+ 6 | @ 5 years | 0 | 11
+ 7 | @ 7 years | 0 | 11
+ 8 | @ 8 years | 0 | 11
+ 9 | @ 9 years | 0 | 11
+ 10 | @ 10 years | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | @ 1 year | 0 | 0
+ 2 | @ 2 years | 0 | 0
+ 3 | @ 3 years | 0 | 0
+ 4 | @ 4 years | 0 | 0
+ 5 | @ 5 years | 0 | 0
+ 6 | @ 5 years | 0 | 0
+ 7 | @ 7 years | 0 | 0
+ 8 | @ 8 years | 0 | 0
+ 9 | @ 9 years | 0 | 0
+ 10 | @ 10 years | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_interval | first_value | last_value
+----+------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | @ 1 year | 11 | 11
+ 2 | @ 2 years | 11 | 11
+ 3 | @ 3 years | 11 | 11
+ 4 | @ 4 years | 11 | 11
+ 5 | @ 5 years | 11 | 11
+ 6 | @ 5 years | 11 | 11
+ 7 | @ 7 years | 11 | 11
+ 8 | @ 8 years | 11 | 11
+ 9 | @ 9 years | 11 | 11
+ 10 | @ 10 years | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 02:23:54 2000 PDT | 1 | 3
2 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
3 | Fri Oct 19 02:23:54 2001 PDT | 1 | 4
@@ -2506,7 +2734,8 @@ window w as (order by f_timestamptz range between
8 | Thu Oct 19 02:23:54 2006 PDT | 7 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 8 | 10
10 | Sun Oct 19 02:23:54 2008 PDT | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2514,6 +2743,7 @@ window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
id | f_timestamptz | first_value | last_value
----+------------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 02:23:54 2008 PDT | 10 | 9
9 | Fri Oct 19 02:23:54 2007 PDT | 10 | 8
8 | Thu Oct 19 02:23:54 2006 PDT | 9 | 7
@@ -2524,14 +2754,87 @@ window w as (order by f_timestamptz desc range between
3 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
2 | Fri Oct 19 02:23:54 2001 PDT | 4 | 1
1 | Thu Oct 19 02:23:54 2000 PDT | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 0 | 0
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 0 | 0
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 0 | 0
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 0 | 0
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 0 | 0
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 0 | 0
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 0 | 0
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 0 | 0
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamptz | first_value | last_value
+----+------------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 02:23:54 2000 PDT | 11 | 11
+ 2 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 3 | Fri Oct 19 02:23:54 2001 PDT | 11 | 11
+ 4 | Sat Oct 19 02:23:54 2002 PDT | 11 | 11
+ 5 | Sun Oct 19 02:23:54 2003 PDT | 11 | 11
+ 6 | Tue Oct 19 02:23:54 2004 PDT | 11 | 11
+ 7 | Wed Oct 19 02:23:54 2005 PDT | 11 | 11
+ 8 | Thu Oct 19 02:23:54 2006 PDT | 11 | 11
+ 9 | Fri Oct 19 02:23:54 2007 PDT | 11 | 11
+ 10 | Sun Oct 19 02:23:54 2008 PDT | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
'1 year'::interval preceding and '1 year'::interval following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
1 | Thu Oct 19 10:23:54 2000 | 1 | 3
2 | Fri Oct 19 10:23:54 2001 | 1 | 4
3 | Fri Oct 19 10:23:54 2001 | 1 | 4
@@ -2542,7 +2845,8 @@ window w as (order by f_timestamp range between
8 | Thu Oct 19 10:23:54 2006 | 7 | 9
9 | Fri Oct 19 10:23:54 2007 | 8 | 10
10 | Sun Oct 19 10:23:54 2008 | 9 | 10
-(10 rows)
+ 11 | infinity | 11 | 11
+(12 rows)
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
@@ -2550,6 +2854,7 @@ window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
id | f_timestamp | first_value | last_value
----+--------------------------+-------------+------------
+ 11 | infinity | 11 | 11
10 | Sun Oct 19 10:23:54 2008 | 10 | 9
9 | Fri Oct 19 10:23:54 2007 | 10 | 8
8 | Thu Oct 19 10:23:54 2006 | 9 | 7
@@ -2560,8 +2865,80 @@ window w as (order by f_timestamp desc range between
3 | Fri Oct 19 10:23:54 2001 | 4 | 1
2 | Fri Oct 19 10:23:54 2001 | 4 | 1
1 | Thu Oct 19 10:23:54 2000 | 3 | 1
-(10 rows)
+ 0 | -infinity | 0 | 0
+(12 rows)
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 11
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 0
+ 1 | Thu Oct 19 10:23:54 2000 | 0 | 0
+ 2 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 3 | Fri Oct 19 10:23:54 2001 | 0 | 0
+ 4 | Sat Oct 19 10:23:54 2002 | 0 | 0
+ 5 | Sun Oct 19 10:23:54 2003 | 0 | 0
+ 6 | Tue Oct 19 10:23:54 2004 | 0 | 0
+ 7 | Wed Oct 19 10:23:54 2005 | 0 | 0
+ 8 | Thu Oct 19 10:23:54 2006 | 0 | 0
+ 9 | Fri Oct 19 10:23:54 2007 | 0 | 0
+ 10 | Sun Oct 19 10:23:54 2008 | 0 | 0
+ 11 | infinity | 0 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+ id | f_timestamp | first_value | last_value
+----+--------------------------+-------------+------------
+ 0 | -infinity | 0 | 11
+ 1 | Thu Oct 19 10:23:54 2000 | 11 | 11
+ 2 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 3 | Fri Oct 19 10:23:54 2001 | 11 | 11
+ 4 | Sat Oct 19 10:23:54 2002 | 11 | 11
+ 5 | Sun Oct 19 10:23:54 2003 | 11 | 11
+ 6 | Tue Oct 19 10:23:54 2004 | 11 | 11
+ 7 | Wed Oct 19 10:23:54 2005 | 11 | 11
+ 8 | Thu Oct 19 10:23:54 2006 | 11 | 11
+ 9 | Fri Oct 19 10:23:54 2007 | 11 | 11
+ 10 | Sun Oct 19 10:23:54 2008 | 11 | 11
+ 11 | infinity | 11 | 11
+(12 rows)
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+ERROR: invalid preceding or following size in window function
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -4375,6 +4752,52 @@ SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDE
4 |
(4 rows)
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+ x | curr_next_avg | prev_curr_avg | curr_next_sum | prev_curr_sum
+------------------------------------------------------------------------------+-------------------+-------------------+---------------+---------------
+ | infinity | | infinity |
+ infinity | infinity | infinity | infinity | infinity
+ @ 178956970 years 8 mons 2147483648 days 2562047788 hours 54.775807 secs ago | -infinity | infinity | -infinity | infinity
+ -infinity | -infinity | -infinity | -infinity | -infinity
+ @ 178956970 years 7 mons 2147483647 days 2562047788 hours 54.775806 secs | infinity | -infinity | infinity | -infinity
+ infinity | infinity | infinity | infinity | infinity
+ @ 6 days | @ 6 days 12 hours | infinity | @ 13 days | infinity
+ @ 7 days | @ 7 days | @ 6 days 12 hours | @ 7 days | @ 13 days
+ | -infinity | @ 7 days | -infinity | @ 7 days
+ -infinity | -infinity | -infinity | -infinity | -infinity
+(10 rows)
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+ERROR: interval out of range.
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
i | sum
diff --git a/src/test/regress/sql/brin_multi.sql b/src/test/regress/sql/brin_multi.sql
index b666dbad67..116106d30f 100644
--- a/src/test/regress/sql/brin_multi.sql
+++ b/src/test/regress/sql/brin_multi.sql
@@ -682,6 +682,25 @@ SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+DROP TABLE brin_interval_test;
+RESET enable_seqscan;
+
+-- test handling of infinite interval values
+CREATE TABLE brin_interval_test(a INTERVAL);
+
+INSERT INTO brin_interval_test VALUES ('-infinity'), ('infinity');
+INSERT INTO brin_interval_test SELECT (i || ' days')::interval FROM generate_series(100, 140) s(i);
+
+CREATE INDEX ON brin_interval_test USING brin (a interval_minmax_multi_ops) WITH (pages_per_range=1);
+
+SET enable_seqscan = off;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '-30 years'::interval;
+
+EXPLAIN (ANALYZE, TIMING OFF, COSTS OFF, SUMMARY OFF)
+SELECT * FROM brin_interval_test WHERE a = '30 years'::interval;
+
DROP TABLE brin_interval_test;
RESET enable_seqscan;
RESET datestyle;
diff --git a/src/test/regress/sql/horology.sql b/src/test/regress/sql/horology.sql
index 39a35a6b7c..252bce4b1c 100644
--- a/src/test/regress/sql/horology.sql
+++ b/src/test/regress/sql/horology.sql
@@ -213,10 +213,12 @@ SELECT t.d1 AS t, i.f1 AS i, t.d1 + i.f1 AS "add", t.d1 - i.f1 AS "subtract"
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIME_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
SELECT t.f1 AS t, i.f1 AS i, t.f1 + i.f1 AS "add", t.f1 - i.f1 AS "subtract"
FROM TIMETZ_TBL t, INTERVAL_TBL i
+ WHERE isfinite(i.f1)
ORDER BY 1,2;
-- SQL9x OVERLAPS operator
@@ -293,7 +295,6 @@ SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 + t.f1 AS plus
SELECT d.f1 AS "timestamp", t.f1 AS "interval", d.f1 - t.f1 AS minus
FROM TEMP_TIMESTAMP d, INTERVAL_TBL t
- WHERE isfinite(d.f1)
ORDER BY minus, "timestamp", "interval";
SELECT d.f1 AS "timestamp",
diff --git a/src/test/regress/sql/interval.sql b/src/test/regress/sql/interval.sql
index a0a373f08b..caad291890 100644
--- a/src/test/regress/sql/interval.sql
+++ b/src/test/regress/sql/interval.sql
@@ -14,6 +14,8 @@ SELECT INTERVAL '-1 days +02:03' AS "22 hours ago...";
SELECT INTERVAL '1.5 weeks' AS "Ten days twelve hours";
SELECT INTERVAL '1.5 months' AS "One month 15 days";
SELECT INTERVAL '10 years -11 month -12 days +13:14' AS "9 years...";
+SELECT INTERVAL 'infinity' AS "eternity";
+SELECT INTERVAL '-infinity' AS "beginning of time";
CREATE TABLE INTERVAL_TBL (f1 interval);
@@ -27,6 +29,8 @@ INSERT INTO INTERVAL_TBL (f1) VALUES ('1 day 2 hours 3 minutes 4 seconds');
INSERT INTO INTERVAL_TBL (f1) VALUES ('6 years');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months');
INSERT INTO INTERVAL_TBL (f1) VALUES ('5 months 12 hours');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('infinity');
+INSERT INTO INTERVAL_TBL (f1) VALUES ('-infinity');
-- badly formatted interval
INSERT INTO INTERVAL_TBL (f1) VALUES ('badly formatted interval');
@@ -66,6 +70,17 @@ SELECT r1.*, r2.*
WHERE r1.f1 > r2.f1
ORDER BY r1.f1, r2.f1;
+-- test unary minus
+
+SELECT f1, -f1 FROM INTERVAL_TBL;
+SELECT -('-2147483648 months'::interval); -- should fail
+SELECT -('-2147483647 months'::interval); -- ok
+SELECT -('-2147483648 days'::interval); -- should fail
+SELECT -('-2147483647 days'::interval); -- ok
+SELECT -('-9223372036854775808 us'::interval); -- should fail
+SELECT -('-9223372036854775807 us'::interval); -- ok
+SELECT -('-2147483647 months -2147483647 days -9223372036854775807 us'::interval); -- should fail
+
-- Test intervals that are large enough to overflow 64 bits in comparisons
CREATE TEMP TABLE INTERVAL_TBL_OF (f1 interval);
INSERT INTO INTERVAL_TBL_OF (f1) VALUES
@@ -95,6 +110,9 @@ SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
SELECT f1 FROM INTERVAL_TBL_OF r1 ORDER BY f1;
RESET enable_seqscan;
+-- subtracting about-to-overflow values should result in 0
+SELECT f1 - f1 FROM INTERVAL_TBL_OF;
+
DROP TABLE INTERVAL_TBL_OF;
-- Test multiplication and division with intervals.
@@ -140,7 +158,7 @@ SELECT * FROM INTERVAL_TBL;
-- known to change the allowed input syntax for type interval without
-- updating pg_aggregate.agginitval
-select avg(f1) from interval_tbl;
+select avg(f1) from interval_tbl where isfinite(f1);
-- test long interval input
select '4 millenniums 5 centuries 4 decades 1 year 4 months 4 days 17 minutes 31 seconds'::interval;
@@ -528,13 +546,13 @@ select make_interval(mins := -1, secs := -9223372036800.0);
-- test that INT_MIN number is formatted properly
SET IntervalStyle to postgres;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to sql_standard;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to iso_8601;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
SET IntervalStyle to postgres_verbose;
-select interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+select interval '-2147483647 months -2147483648 days -9223372036854775808 us';
-- check that '30 days' equals '1 month' according to the hash function
select '30 days'::interval = '1 month'::interval as t;
@@ -598,6 +616,157 @@ SELECT f1,
-- internal overflow test case
SELECT extract(epoch from interval '1000000000 days');
+--
+-- test infinite intervals
+--
+
+-- largest finite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775807 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775806 us';
+
+-- infinite intervals
+SELECT interval '-2147483648 months -2147483648 days -9223372036854775808 us';
+SELECT interval '2147483647 months 2147483647 days 9223372036854775807 us';
+
+CREATE TABLE INFINITE_INTERVAL_TBL (i interval);
+INSERT INTO INFINITE_INTERVAL_TBL VALUES ('infinity'), ('-infinity'), ('1 year 2 days 3 hours');
+
+SELECT i, isfinite(i) FROM INFINITE_INTERVAL_TBL;
+
+-- test basic arithmetic
+CREATE FUNCTION eval(expr text)
+RETURNS text AS
+$$
+DECLARE
+ result text;
+BEGIN
+ EXECUTE 'select '||expr INTO result;
+ RETURN result;
+EXCEPTION WHEN OTHERS THEN
+ RETURN SQLERRM;
+END
+$$
+LANGUAGE plpgsql;
+
+SELECT d AS date, i AS interval,
+ eval(format('date %L + interval %L', d, i)) AS plus,
+ eval(format('date %L - interval %L', d, i)) AS minus
+FROM (VALUES (date '-infinity'),
+ (date '1995-08-06'),
+ (date 'infinity')) AS t1(d),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT i1 AS interval1, i2 AS interval2,
+ eval(format('interval %L + interval %L', i1, i2)) AS plus,
+ eval(format('interval %L - interval %L', i1, i2)) AS minus
+FROM (VALUES (interval '-infinity'),
+ (interval '2 months'),
+ (interval 'infinity')) AS t1(i1),
+ (VALUES (interval '-infinity'),
+ (interval '10 days'),
+ (interval 'infinity')) AS t2(i2);
+
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' + interval '1 month 1 day 1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' + interval '-1 month -1 day -1 us';
+SELECT interval '2147483646 months 2147483646 days 9223372036854775806 us' - interval '-1 month -1 day -1 us';
+SELECT interval '-2147483647 months -2147483647 days -9223372036854775807 us' - interval '1 month 1 day 1 us';
+
+SELECT t AS timestamp, i AS interval,
+ eval(format('timestamp %L + interval %L', t, i)) AS plus,
+ eval(format('timestamp %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamp '-infinity'),
+ (timestamp '1995-08-06 12:30:15'),
+ (timestamp 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+SELECT t AT TIME ZONE 'GMT' AS timestamptz, i AS interval,
+ eval(format('timestamptz %L + interval %L', t, i)) AS plus,
+ eval(format('timestamptz %L - interval %L', t, i)) AS minus
+FROM (VALUES (timestamptz '-infinity'),
+ (timestamptz '1995-08-06 12:30:15 GMT'),
+ (timestamptz 'infinity')) AS t1(t),
+ (VALUES (interval '-infinity'),
+ (interval 'infinity')) AS t2(i);
+
+-- time +/- infinite interval not supported
+SELECT time '11:27:42' + interval 'infinity';
+SELECT time '11:27:42' + interval '-infinity';
+SELECT time '11:27:42' - interval 'infinity';
+SELECT time '11:27:42' - interval '-infinity';
+SELECT timetz '11:27:42' + interval 'infinity';
+SELECT timetz '11:27:42' + interval '-infinity';
+SELECT timetz '11:27:42' - interval 'infinity';
+SELECT timetz '11:27:42' - interval '-infinity';
+
+SELECT lhst.i lhs,
+ rhst.i rhs,
+ lhst.i < rhst.i AS lt,
+ lhst.i <= rhst.i AS le,
+ lhst.i = rhst.i AS eq,
+ lhst.i > rhst.i AS gt,
+ lhst.i >= rhst.i AS ge,
+ lhst.i <> rhst.i AS ne
+ FROM INFINITE_INTERVAL_TBL lhst CROSS JOIN INFINITE_INTERVAL_TBL rhst
+ WHERE NOT isfinite(lhst.i);
+
+SELECT i AS interval,
+ -i AS um,
+ i * 2.0 AS mul,
+ i * -2.0 AS mul_neg,
+ i * 'infinity' AS mul_inf,
+ i * '-infinity' AS mul_inf_neg,
+ i / 3.0 AS div,
+ i / -3.0 AS div_neg
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT -interval '-2147483647 months -2147483647 days -9223372036854775807 us';
+SELECT interval 'infinity' * 'nan';
+SELECT interval '-infinity' * 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' * 2;
+SELECT interval 'infinity' * 0;
+SELECT interval '-infinity' * 0;
+SELECT interval '0 days' * 'infinity'::float;
+SELECT interval '0 days' * '-infinity'::float;
+SELECT interval '5 days' * 'infinity'::float;
+SELECT interval '5 days' * '-infinity'::float;
+
+SELECT interval 'infinity' / 'infinity';
+SELECT interval 'infinity' / '-infinity';
+SELECT interval 'infinity' / 'nan';
+SELECT interval '-infinity' / 'infinity';
+SELECT interval '-infinity' / '-infinity';
+SELECT interval '-infinity' / 'nan';
+SELECT interval '-1073741824 months -1073741824 days -4611686018427387904 us' / 0.5;
+
+SELECT date_bin('infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+SELECT date_bin('-infinity', timestamp '2001-02-16 20:38:40', timestamp '2001-02-16 20:05:00');
+
+SELECT i AS interval, date_trunc('hour', i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT i AS interval, justify_days(i), justify_hours(i), justify_interval(i)
+ FROM INFINITE_INTERVAL_TBL
+ WHERE NOT isfinite(i);
+
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamp);
+SELECT timezone('infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('-infinity'::interval, '1995-08-06 12:12:12'::timestamptz);
+SELECT timezone('infinity'::interval, '12:12:12'::time);
+SELECT timezone('-infinity'::interval, '12:12:12'::time);
+SELECT timezone('infinity'::interval, '12:12:12'::timetz);
+SELECT timezone('-infinity'::interval, '12:12:12'::timetz);
+
+SELECT 'infinity'::interval::time;
+SELECT '-infinity'::interval::time;
+
+SELECT to_char('infinity'::interval, 'YYYY');
+SELECT to_char('-infinity'::interval, 'YYYY');
+
-- "ago" can only appear once at the end of an interval.
SELECT INTERVAL '42 days 2 seconds ago ago';
SELECT INTERVAL '2 minutes ago 5 days';
@@ -605,3 +774,17 @@ SELECT INTERVAL '2 minutes ago 5 days';
-- consecutive and dangling units are not allowed.
SELECT INTERVAL 'hour 5 months';
SELECT INTERVAL '1 year months days 5 hours';
+
+-- unacceptable reserved words in interval. Only "infinity", "+infinity" and
+-- "-infinity" are allowed.
+SELECT INTERVAL 'now';
+SELECT INTERVAL 'today';
+SELECT INTERVAL 'tomorrow';
+SELECT INTERVAL 'allballs';
+SELECT INTERVAL 'epoch';
+SELECT INTERVAL 'yesterday';
+
+-- infinity specification should be the only thing
+SELECT INTERVAL 'infinity years';
+SELECT INTERVAL 'infinity ago';
+SELECT INTERVAL '+infinity -infinity';
diff --git a/src/test/regress/sql/timestamp.sql b/src/test/regress/sql/timestamp.sql
index b9bcce9cfe..ea12ffd18d 100644
--- a/src/test/regress/sql/timestamp.sql
+++ b/src/test/regress/sql/timestamp.sql
@@ -397,3 +397,22 @@ select generate_series('2022-01-01 00:00'::timestamp,
select * from generate_series('2020-01-01 00:00'::timestamp,
'2020-01-02 03:00'::timestamp,
'0 hour'::interval);
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamp '1995-08-06 12:12:12', timestamp '1996-08-06 12:12:12', interval '-infinity');
+
+
+-- test arithmetic with infinite timestamps
+select timestamp 'infinity' - timestamp 'infinity';
+select timestamp 'infinity' - timestamp '-infinity';
+select timestamp '-infinity' - timestamp 'infinity';
+select timestamp '-infinity' - timestamp '-infinity';
+select timestamp 'infinity' - timestamp '1995-08-06 12:12:12';
+select timestamp '-infinity' - timestamp '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+select age(timestamp 'infinity');
+select age(timestamp '-infinity');
+select age(timestamp 'infinity', timestamp 'infinity');
+select age(timestamp 'infinity', timestamp '-infinity');
+select age(timestamp '-infinity', timestamp 'infinity');
+select age(timestamp '-infinity', timestamp '-infinity');
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index cdc57bc160..a2dcd5f5d8 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -458,6 +458,8 @@ select generate_series('2022-01-01 00:00'::timestamptz,
select * from generate_series('2020-01-01 00:00'::timestamptz,
'2020-01-02 03:00'::timestamptz,
'0 hour'::interval);
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval 'infinity');
+select generate_series(timestamptz '1995-08-06 12:12:12', timestamptz '1996-08-06 12:12:12', interval '-infinity');
-- Interval crossing time shift for Europe/Warsaw timezone (with DST)
SET TimeZone to 'UTC';
@@ -642,3 +644,19 @@ insert into tmptz values ('2017-01-18 00:00+00');
explain (costs off)
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
select * from tmptz where f1 at time zone 'utc' = '2017-01-18 00:00';
+
+-- test arithmetic with infinite timestamps
+SELECT timestamptz 'infinity' - timestamptz 'infinity';
+SELECT timestamptz 'infinity' - timestamptz '-infinity';
+SELECT timestamptz '-infinity' - timestamptz 'infinity';
+SELECT timestamptz '-infinity' - timestamptz '-infinity';
+SELECT timestamptz 'infinity' - timestamptz '1995-08-06 12:12:12';
+SELECT timestamptz '-infinity' - timestamptz '1995-08-06 12:12:12';
+
+-- test age() with infinite timestamps
+SELECT age(timestamptz 'infinity');
+SELECT age(timestamptz '-infinity');
+SELECT age(timestamptz 'infinity', timestamptz 'infinity');
+SELECT age(timestamptz 'infinity', timestamptz '-infinity');
+SELECT age(timestamptz '-infinity', timestamptz 'infinity');
+SELECT age(timestamptz '-infinity', timestamptz '-infinity');
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 9113a92ae0..437e948d6c 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -673,6 +673,7 @@ create temp table datetimes(
);
insert into datetimes values
+(0, '10:00', '10:00 BST', '-infinity', '-infinity', '-infinity'),
(1, '11:00', '11:00 BST', '1 year', '2000-10-19 10:23:54+01', '2000-10-19 10:23:54'),
(2, '12:00', '12:00 BST', '2 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
(3, '13:00', '13:00 BST', '3 years', '2001-10-19 10:23:54+01', '2001-10-19 10:23:54'),
@@ -682,7 +683,8 @@ insert into datetimes values
(7, '17:00', '17:00 BST', '7 years', '2005-10-19 10:23:54+01', '2005-10-19 10:23:54'),
(8, '18:00', '18:00 BST', '8 years', '2006-10-19 10:23:54+01', '2006-10-19 10:23:54'),
(9, '19:00', '19:00 BST', '9 years', '2007-10-19 10:23:54+01', '2007-10-19 10:23:54'),
-(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54');
+(10, '20:00', '20:00 BST', '10 years', '2008-10-19 10:23:54+01', '2008-10-19 10:23:54'),
+(11, '21:00', '21:00 BST', 'infinity', 'infinity', 'infinity');
select id, f_time, first_value(id) over w, last_value(id) over w
from datetimes
@@ -694,6 +696,32 @@ from datetimes
window w as (order by f_time desc range between
'70 min' preceding and '2 hours' following);
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time desc range between
+ '-70 min' preceding and '2 hours' following); -- error, negative offset disallowed
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_time, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_time range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timetz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timetz range between
@@ -704,6 +732,32 @@ from datetimes
window w as (order by f_timetz desc range between
'70 min' preceding and '2 hours' following);
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz desc range between
+ '70 min' preceding and '-2 hours' following); -- error, negative offset disallowed
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timetz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timetz range between
+ 'infinity'::interval following and
+ '-infinity'::interval following); -- error, negative offset disallowed
+
select id, f_interval, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_interval range between
@@ -714,6 +768,32 @@ from datetimes
window w as (order by f_interval desc range between
'1 year' preceding and '1 year' following);
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_interval, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_interval range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamptz, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamptz range between
@@ -724,6 +804,32 @@ from datetimes
window w as (order by f_timestamptz desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz desc range between
+ '1 year' preceding and '-1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamptz, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamptz range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
select id, f_timestamp, first_value(id) over w, last_value(id) over w
from datetimes
window w as (order by f_timestamp range between
@@ -734,6 +840,32 @@ from datetimes
window w as (order by f_timestamp desc range between
'1 year' preceding and '1 year' following);
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp desc range between
+ '-1 year' preceding and '1 year' following); -- error, negative offset disallowed
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval preceding and 'infinity'::interval preceding);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ 'infinity'::interval following and 'infinity'::interval following);
+
+select id, f_timestamp, first_value(id) over w, last_value(id) over w
+from datetimes
+window w as (order by f_timestamp range between
+ '-infinity'::interval following and
+ 'infinity'::interval following); -- error, negative offset disallowed
+
-- RANGE offset PRECEDING/FOLLOWING error cases
select sum(salary) over (order by enroll_date, salary range between '1 year'::interval preceding and '2 years'::interval following
exclude ties), salary, enroll_date from empsalary;
@@ -1591,6 +1723,39 @@ SELECT i,AVG(v::numeric) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED
SELECT i,AVG(v::interval) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,'1 sec'),(2,'2 sec'),(3,NULL),(4,NULL)) t(i,v);
+-- moving aggregates over infinite intervals
+SELECT x
+ ,avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_avg
+ ,avg(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_avg
+ ,sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING ) as curr_next_sum
+ ,sum(x) OVER(ROWS BETWEEN 1 PRECEDING AND CURRENT ROW ) as prev_curr_sum
+FROM (VALUES (NULL::interval),
+ ('infinity'::interval),
+ ('-2147483648 days -2147483648 months -9223372036854775807 usecs'), -- extreme interval value
+ ('-infinity'::interval),
+ ('2147483647 days 2147483647 months 9223372036854775806 usecs'), -- extreme interval value
+ ('infinity'::interval),
+ ('6 days'::interval),
+ ('7 days'::interval),
+ (NULL::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, avg(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
+--should fail.
+SELECT x, sum(x) OVER(ROWS BETWEEN CURRENT ROW AND 2 FOLLOWING)
+FROM (VALUES (NULL::interval),
+ ('3 days'::interval),
+ ('infinity'::timestamptz - now()),
+ ('6 days'::interval),
+ ('-infinity'::interval)) v(x);
+
SELECT i,SUM(v::smallint) OVER (ORDER BY i ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING)
FROM (VALUES(1,1),(2,2),(3,NULL),(4,NULL)) t(i,v);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bf50a32119..efc47bed10 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1205,6 +1205,7 @@ IntegerSet
InternalDefaultACL
InternalGrant
Interval
+IntervalAggState
IntoClause
InvalMessageArray
InvalidationMsgsGroup
--
2.35.3
On Thu, 9 Nov 2023 at 12:49, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
OK, I have pushed 0001 and 0002. Here's the remaining (main) patch.
OK, I have now pushed the main patch.
Regards,
Dean
On Tue, Nov 14, 2023 at 4:39 PM Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
On Thu, 9 Nov 2023 at 12:49, Dean Rasheed <dean.a.rasheed@gmail.com> wrote:
OK, I have pushed 0001 and 0002. Here's the remaining (main) patch.
OK, I have now pushed the main patch.
Thanks a lot Dean.
--
Best Wishes,
Ashutosh Bapat
On Thu, Nov 16, 2023 at 2:03 AM Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
wrote:
On Tue, Nov 14, 2023 at 4:39 PM Dean Rasheed <dean.a.rasheed@gmail.com>
wrote:
On Thu, 9 Nov 2023 at 12:49, Dean Rasheed <dean.a.rasheed@gmail.com>
wrote:
OK, I have pushed 0001 and 0002. Here's the remaining (main) patch.
OK, I have now pushed the main patch.
Thanks a lot Dean.
Yes, thanks Dean!