Add support for infinite timestamps

The internal conversion functions for timestamps didn't account for
timestamps that are infinite (`-Infinity` or `+Infinity`), and they
would therefore generate an error if such timestamps were
encountered. This change adds extra checks to the conversion functions
to allow infinite timestamps.
This commit is contained in:
Erik Nordström 2020-08-06 22:03:13 +02:00 committed by Erik Nordström
parent 418f283443
commit e1c94484cf
4 changed files with 83 additions and 29 deletions

View File

@ -45,6 +45,12 @@ ts_pg_timestamp_to_unix_microseconds(PG_FUNCTION_ARGS)
{
TimestampTz timestamp = PG_GETARG_TIMESTAMPTZ(0);
if (TIMESTAMP_IS_NOBEGIN(timestamp))
PG_RETURN_INT64(PG_INT64_MIN);
if (TIMESTAMP_IS_NOEND(timestamp))
PG_RETURN_INT64(PG_INT64_MAX);
if (timestamp < MIN_TIMESTAMP)
ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range")));
@ -68,10 +74,16 @@ ts_pg_unix_microseconds_to_timestamp(PG_FUNCTION_ARGS)
{
int64 microseconds = PG_GETARG_INT64(0);
if (microseconds == PG_INT64_MIN)
PG_RETURN_TIMESTAMPTZ(DT_NOBEGIN);
if (microseconds == PG_INT64_MAX)
PG_RETURN_TIMESTAMPTZ(DT_NOEND);
/*
* Test that the UNIX us timestamp is within bounds. Note that an int64 at
* UNIX epoch and microsecond precision cannot represent the upper limit
* of the supported date range (Julian end date), so INT64_MAX is the
* of the supported date range (Julian end date), so INT64_MAX-1 is the
* natural upper bound for this function.
*/
if (microseconds < TS_INTERNAL_TIMESTAMP_MIN)
@ -85,8 +97,15 @@ Datum
ts_pg_unix_microseconds_to_date(PG_FUNCTION_ARGS)
{
int64 microseconds = PG_GETARG_INT64(0);
Datum res =
DirectFunctionCall1(ts_pg_unix_microseconds_to_timestamp, Int64GetDatum(microseconds));
Datum res;
if (microseconds == PG_INT64_MIN)
PG_RETURN_DATEADT(DATEVAL_NOBEGIN);
if (microseconds == PG_INT64_MAX)
PG_RETURN_DATEADT(DATEVAL_NOEND);
res = DirectFunctionCall1(ts_pg_unix_microseconds_to_timestamp, Int64GetDatum(microseconds));
res = DirectFunctionCall1(timestamp_date, res);
PG_RETURN_DATUM(res);
}

View File

@ -157,15 +157,38 @@ SELECT _timescaledb_internal.to_unix_microseconds('2017-02-07 15:09:36.236538+00
1486480176236538
(1 row)
-- In UNIX microseconds, BIGINT MAX is smaller than internal date upper bound
-- and should therefore be OK. Further, converting to the internal postgres
-- epoch cannot overflow a 64-bit INTEGER since the postgres epoch is at a
-- later date compared to the UNIX epoch, and is therefore represented by a
-- smaller number
-- For timestamps, BIGINT MAX represents +Infinity and BIGINT MIN
-- -Infinity. We keep this notion for UNIX epoch time:
SELECT _timescaledb_internal.to_unix_microseconds('+infinity');
ERROR: invalid input syntax for type timestamp with time zone: "+infinity" at character 51
SELECT _timescaledb_internal.to_timestamp(9223372036854775807);
to_timestamp
--------------
infinity
(1 row)
SELECT _timescaledb_internal.to_unix_microseconds('-infinity');
to_unix_microseconds
----------------------
-9223372036854775808
(1 row)
SELECT _timescaledb_internal.to_timestamp(-9223372036854775808);
to_timestamp
--------------
-infinity
(1 row)
-- In UNIX microseconds, the largest bigint value below infinity
-- (BIGINT MAX) is smaller than internal date upper bound and should
-- therefore be OK. Further, converting to the internal postgres epoch
-- cannot overflow a 64-bit INTEGER since the postgres epoch is at a
-- later date compared to the UNIX epoch, and is therefore represented
-- by a smaller number
SELECT _timescaledb_internal.to_timestamp(9223372036854775806);
to_timestamp
---------------------------------------
Sun Jan 10 04:00:54.775807 294247 UTC
Sun Jan 10 04:00:54.775806 294247 UTC
(1 row)
-- Julian day zero is -210866803200000000 microseconds from UNIX epoch

View File

@ -100,12 +100,20 @@ SELECT _timescaledb_internal.to_timestamp(1486480176236538);
-- Should be the inverse of the statement above.
SELECT _timescaledb_internal.to_unix_microseconds('2017-02-07 15:09:36.236538+00');
-- In UNIX microseconds, BIGINT MAX is smaller than internal date upper bound
-- and should therefore be OK. Further, converting to the internal postgres
-- epoch cannot overflow a 64-bit INTEGER since the postgres epoch is at a
-- later date compared to the UNIX epoch, and is therefore represented by a
-- smaller number
-- For timestamps, BIGINT MAX represents +Infinity and BIGINT MIN
-- -Infinity. We keep this notion for UNIX epoch time:
SELECT _timescaledb_internal.to_unix_microseconds('+infinity');
SELECT _timescaledb_internal.to_timestamp(9223372036854775807);
SELECT _timescaledb_internal.to_unix_microseconds('-infinity');
SELECT _timescaledb_internal.to_timestamp(-9223372036854775808);
-- In UNIX microseconds, the largest bigint value below infinity
-- (BIGINT MAX) is smaller than internal date upper bound and should
-- therefore be OK. Further, converting to the internal postgres epoch
-- cannot overflow a 64-bit INTEGER since the postgres epoch is at a
-- later date compared to the UNIX epoch, and is therefore represented
-- by a smaller number
SELECT _timescaledb_internal.to_timestamp(9223372036854775806);
-- Julian day zero is -210866803200000000 microseconds from UNIX epoch
SELECT _timescaledb_internal.to_timestamp(-210866803200000000);

View File

@ -111,7 +111,10 @@ ts_test_time_to_internal_conversion(PG_FUNCTION_ARGS)
ts_time_value_to_internal(ts_internal_to_time_value(i64, TIMESTAMPOID),
TIMESTAMPOID));
TestEnsureError(ts_internal_to_time_value(PG_INT64_MIN, TIMESTAMPOID));
TestAssertInt64Eq(PG_INT64_MIN, ts_time_value_to_internal(DT_NOBEGIN, TIMESTAMPOID));
TestAssertInt64Eq(PG_INT64_MAX, ts_time_value_to_internal(DT_NOEND, TIMESTAMPOID));
TestAssertInt64Eq(DT_NOBEGIN, ts_internal_to_time_value(PG_INT64_MIN, TIMESTAMPOID));
TestEnsureError(ts_internal_to_time_value(TS_INTERNAL_TIMESTAMP_MIN - 1, TIMESTAMPOID));
TestAssertInt64Eq(TS_INTERNAL_TIMESTAMP_MIN,
@ -119,10 +122,10 @@ ts_test_time_to_internal_conversion(PG_FUNCTION_ARGS)
TIMESTAMPOID),
TIMESTAMPOID));
TestAssertInt64Eq(PG_INT64_MAX - TS_EPOCH_DIFF_MICROSECONDS,
DatumGetInt64(ts_internal_to_time_value(PG_INT64_MAX, TIMESTAMPOID)));
TestEnsureError(ts_time_value_to_internal(ts_internal_to_time_value(PG_INT64_MAX, TIMESTAMPOID),
TIMESTAMPOID));
TestAssertInt64Eq(DT_NOEND,
(ts_time_value_to_internal(ts_internal_to_time_value(PG_INT64_MAX,
TIMESTAMPOID),
TIMESTAMPOID)));
/* TIMESTAMPTZ */
for (i64 = -100; i64 < 100; i64++)
@ -140,7 +143,10 @@ ts_test_time_to_internal_conversion(PG_FUNCTION_ARGS)
ts_time_value_to_internal(ts_internal_to_time_value(i64, TIMESTAMPTZOID),
TIMESTAMPTZOID));
TestEnsureError(ts_internal_to_time_value(PG_INT64_MIN, TIMESTAMPTZOID));
TestAssertInt64Eq(PG_INT64_MIN, ts_time_value_to_internal(DT_NOBEGIN, TIMESTAMPTZOID));
TestAssertInt64Eq(PG_INT64_MAX, ts_time_value_to_internal(DT_NOEND, TIMESTAMPTZOID));
TestAssertInt64Eq(DT_NOBEGIN, ts_internal_to_time_value(PG_INT64_MIN, TIMESTAMPTZOID));
TestEnsureError(ts_internal_to_time_value(TS_INTERNAL_TIMESTAMP_MIN - 1, TIMESTAMPTZOID));
TestAssertInt64Eq(TS_INTERNAL_TIMESTAMP_MIN,
@ -148,21 +154,19 @@ ts_test_time_to_internal_conversion(PG_FUNCTION_ARGS)
TIMESTAMPTZOID),
TIMESTAMPTZOID));
TestAssertInt64Eq(PG_INT64_MAX - TS_EPOCH_DIFF_MICROSECONDS,
DatumGetInt64(ts_internal_to_time_value(PG_INT64_MAX, TIMESTAMPTZOID)));
TestEnsureError(
ts_time_value_to_internal(ts_internal_to_time_value(PG_INT64_MAX, TIMESTAMPTZOID),
TIMESTAMPTZOID));
TestAssertInt64Eq(DT_NOEND,
ts_time_value_to_internal(ts_internal_to_time_value(PG_INT64_MAX,
TIMESTAMPTZOID),
TIMESTAMPTZOID));
/* DATE */
for (i64 = -100 * USECS_PER_DAY; i64 < 100 * USECS_PER_DAY; i64 += USECS_PER_DAY)
TestAssertInt64Eq(i64,
ts_time_value_to_internal(ts_internal_to_time_value(i64, DATEOID),
DATEOID));
TestEnsureError(ts_internal_to_time_value(PG_INT64_MIN, DATEOID));
TestAssertInt64Eq(106741034, DatumGetDateADT(ts_internal_to_time_value(PG_INT64_MAX, DATEOID)));
TestAssertInt64Eq(DATEVAL_NOBEGIN, ts_internal_to_time_value(PG_INT64_MIN, DATEOID));
TestAssertInt64Eq(DATEVAL_NOEND, ts_internal_to_time_value(PG_INT64_MAX, DATEOID));
TestEnsureError(ts_time_value_to_internal((DATEVAL_NOBEGIN + 1), DATEOID));
TestEnsureError(ts_time_value_to_internal((DATEVAL_NOEND - 1), DATEOID));