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); 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) if (timestamp < MIN_TIMESTAMP)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); (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); 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 * Test that the UNIX us timestamp is within bounds. Note that an int64 at
* UNIX epoch and microsecond precision cannot represent the upper limit * 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. * natural upper bound for this function.
*/ */
if (microseconds < TS_INTERNAL_TIMESTAMP_MIN) if (microseconds < TS_INTERNAL_TIMESTAMP_MIN)
@ -85,8 +97,15 @@ Datum
ts_pg_unix_microseconds_to_date(PG_FUNCTION_ARGS) ts_pg_unix_microseconds_to_date(PG_FUNCTION_ARGS)
{ {
int64 microseconds = PG_GETARG_INT64(0); int64 microseconds = PG_GETARG_INT64(0);
Datum res = Datum res;
DirectFunctionCall1(ts_pg_unix_microseconds_to_timestamp, Int64GetDatum(microseconds));
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); res = DirectFunctionCall1(timestamp_date, res);
PG_RETURN_DATUM(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 1486480176236538
(1 row) (1 row)
-- In UNIX microseconds, BIGINT MAX is smaller than internal date upper bound -- For timestamps, BIGINT MAX represents +Infinity and BIGINT MIN
-- and should therefore be OK. Further, converting to the internal postgres -- -Infinity. We keep this notion for UNIX epoch time:
-- epoch cannot overflow a 64-bit INTEGER since the postgres epoch is at a SELECT _timescaledb_internal.to_unix_microseconds('+infinity');
-- later date compared to the UNIX epoch, and is therefore represented by a ERROR: invalid input syntax for type timestamp with time zone: "+infinity" at character 51
-- smaller number
SELECT _timescaledb_internal.to_timestamp(9223372036854775807); SELECT _timescaledb_internal.to_timestamp(9223372036854775807);
to_timestamp 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) (1 row)
-- Julian day zero is -210866803200000000 microseconds from UNIX epoch -- 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. -- Should be the inverse of the statement above.
SELECT _timescaledb_internal.to_unix_microseconds('2017-02-07 15:09:36.236538+00'); 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 -- For timestamps, BIGINT MAX represents +Infinity and BIGINT MIN
-- and should therefore be OK. Further, converting to the internal postgres -- -Infinity. We keep this notion for UNIX epoch time:
-- epoch cannot overflow a 64-bit INTEGER since the postgres epoch is at a SELECT _timescaledb_internal.to_unix_microseconds('+infinity');
-- later date compared to the UNIX epoch, and is therefore represented by a
-- smaller number
SELECT _timescaledb_internal.to_timestamp(9223372036854775807); 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 -- Julian day zero is -210866803200000000 microseconds from UNIX epoch
SELECT _timescaledb_internal.to_timestamp(-210866803200000000); 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), ts_time_value_to_internal(ts_internal_to_time_value(i64, TIMESTAMPOID),
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)); TestEnsureError(ts_internal_to_time_value(TS_INTERNAL_TIMESTAMP_MIN - 1, TIMESTAMPOID));
TestAssertInt64Eq(TS_INTERNAL_TIMESTAMP_MIN, TestAssertInt64Eq(TS_INTERNAL_TIMESTAMP_MIN,
@ -119,10 +122,10 @@ ts_test_time_to_internal_conversion(PG_FUNCTION_ARGS)
TIMESTAMPOID), TIMESTAMPOID),
TIMESTAMPOID)); TIMESTAMPOID));
TestAssertInt64Eq(PG_INT64_MAX - TS_EPOCH_DIFF_MICROSECONDS, TestAssertInt64Eq(DT_NOEND,
DatumGetInt64(ts_internal_to_time_value(PG_INT64_MAX, TIMESTAMPOID))); (ts_time_value_to_internal(ts_internal_to_time_value(PG_INT64_MAX,
TestEnsureError(ts_time_value_to_internal(ts_internal_to_time_value(PG_INT64_MAX, TIMESTAMPOID), TIMESTAMPOID),
TIMESTAMPOID)); TIMESTAMPOID)));
/* TIMESTAMPTZ */ /* TIMESTAMPTZ */
for (i64 = -100; i64 < 100; i64++) 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), ts_time_value_to_internal(ts_internal_to_time_value(i64, TIMESTAMPTZOID),
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)); TestEnsureError(ts_internal_to_time_value(TS_INTERNAL_TIMESTAMP_MIN - 1, TIMESTAMPTZOID));
TestAssertInt64Eq(TS_INTERNAL_TIMESTAMP_MIN, TestAssertInt64Eq(TS_INTERNAL_TIMESTAMP_MIN,
@ -148,21 +154,19 @@ ts_test_time_to_internal_conversion(PG_FUNCTION_ARGS)
TIMESTAMPTZOID), TIMESTAMPTZOID),
TIMESTAMPTZOID)); TIMESTAMPTZOID));
TestAssertInt64Eq(PG_INT64_MAX - TS_EPOCH_DIFF_MICROSECONDS, TestAssertInt64Eq(DT_NOEND,
DatumGetInt64(ts_internal_to_time_value(PG_INT64_MAX, TIMESTAMPTZOID))); ts_time_value_to_internal(ts_internal_to_time_value(PG_INT64_MAX,
TestEnsureError( TIMESTAMPTZOID),
ts_time_value_to_internal(ts_internal_to_time_value(PG_INT64_MAX, TIMESTAMPTZOID),
TIMESTAMPTZOID)); TIMESTAMPTZOID));
/* DATE */ /* DATE */
for (i64 = -100 * USECS_PER_DAY; i64 < 100 * USECS_PER_DAY; i64 += USECS_PER_DAY) for (i64 = -100 * USECS_PER_DAY; i64 < 100 * USECS_PER_DAY; i64 += USECS_PER_DAY)
TestAssertInt64Eq(i64, TestAssertInt64Eq(i64,
ts_time_value_to_internal(ts_internal_to_time_value(i64, DATEOID), ts_time_value_to_internal(ts_internal_to_time_value(i64, DATEOID),
DATEOID)); DATEOID));
TestAssertInt64Eq(DATEVAL_NOBEGIN, ts_internal_to_time_value(PG_INT64_MIN, DATEOID));
TestEnsureError(ts_internal_to_time_value(PG_INT64_MIN, DATEOID)); TestAssertInt64Eq(DATEVAL_NOEND, ts_internal_to_time_value(PG_INT64_MAX, DATEOID));
TestAssertInt64Eq(106741034, DatumGetDateADT(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_NOBEGIN + 1), DATEOID));
TestEnsureError(ts_time_value_to_internal((DATEVAL_NOEND - 1), DATEOID)); TestEnsureError(ts_time_value_to_internal((DATEVAL_NOEND - 1), DATEOID));