diff --git a/src/dimension.c b/src/dimension.c index ae68eb6c1..f76c4e718 100644 --- a/src/dimension.c +++ b/src/dimension.c @@ -5,6 +5,7 @@ */ #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include "hypertable_cache.h" #include "partitioning.h" #include "scanner.h" +#include "time_utils.h" #include "utils.h" #include "errors.h" @@ -214,37 +216,34 @@ create_range_datum(FunctionCallInfo fcinfo, DimensionSlice *slice) } static DimensionSlice * -calculate_open_range_default(Dimension *dim, int64 value) +calculate_open_range_default(const Dimension *dim, int64 value) { int64 range_start, range_end; + Oid dimtype = ts_dimension_get_partition_type(dim); if (value < 0) { + const int64 dim_min = ts_time_get_min(dimtype); + range_end = ((value + 1) / dim->fd.interval_length) * dim->fd.interval_length; /* prevent integer underflow */ - if (DIMENSION_SLICE_MINVALUE - range_end > -dim->fd.interval_length) - { + if (dim_min - range_end > -dim->fd.interval_length) range_start = DIMENSION_SLICE_MINVALUE; - } else - { range_start = range_end - dim->fd.interval_length; - } } else { + const int64 dim_end = ts_time_get_max(dimtype); + range_start = (value / dim->fd.interval_length) * dim->fd.interval_length; /* prevent integer overflow */ - if (DIMENSION_SLICE_MAXVALUE - range_start < dim->fd.interval_length) - { + if (dim_end - range_start < dim->fd.interval_length) range_end = DIMENSION_SLICE_MAXVALUE; - } else - { range_end = range_start + dim->fd.interval_length; - } } return ts_dimension_slice_create(dim->fd.id, range_start, range_end); @@ -263,6 +262,7 @@ ts_dimension_calculate_open_range_default(PG_FUNCTION_ARGS) .type = DIMENSION_TYPE_OPEN, .fd.id = 0, .fd.interval_length = PG_GETARG_INT64(1), + .fd.column_type = TypenameGetTypid(PG_GETARG_CSTRING(2)), }; DimensionSlice *slice = calculate_open_range_default(&dim, value); diff --git a/src/plan_expand_hypertable.c b/src/plan_expand_hypertable.c index 8d4d21d40..4b00ef6ea 100644 --- a/src/plan_expand_hypertable.c +++ b/src/plan_expand_hypertable.c @@ -45,6 +45,7 @@ #include "extension_constants.h" #include "partitioning.h" #include "cross_module_fn.h" +#include "time_utils.h" typedef struct CollectQualCtx { diff --git a/src/time_bucket.h b/src/time_bucket.h index e19234edb..63b6ff2cd 100644 --- a/src/time_bucket.h +++ b/src/time_bucket.h @@ -17,7 +17,6 @@ extern TSDLLEXPORT Datum ts_int64_bucket(PG_FUNCTION_ARGS); extern TSDLLEXPORT Datum ts_date_bucket(PG_FUNCTION_ARGS); extern TSDLLEXPORT Datum ts_timestamp_bucket(PG_FUNCTION_ARGS); extern TSDLLEXPORT Datum ts_timestamptz_bucket(PG_FUNCTION_ARGS); - extern TSDLLEXPORT int64 ts_time_bucket_by_type(int64 interval, int64 timestamp, Oid type); #endif /* TIMESCALEDB_TIME_BUCKET_H */ diff --git a/src/time_utils.c b/src/time_utils.c index a82eb029e..1d91bdb51 100644 --- a/src/time_utils.c +++ b/src/time_utils.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -100,8 +101,8 @@ ts_time_value_from_arg(Datum arg, Oid argtype, Oid timetype) default: ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("invalid window parameter"), - errhint("The window parameter requires an explicit cast."))); + errmsg("invalid time argument"), + errhint("Time argument requires an explicit cast."))); } } @@ -125,3 +126,351 @@ ts_time_value_from_arg(Datum arg, Oid argtype, Oid timetype) return ts_time_value_to_internal(arg, argtype); } + +/* + * Try to coerce a type to a supported time type. + * + * To support custom time types in hypertables, we need to know the type's + * boundaries in order to, e.g., construct dimensional chunk constraints. If + * the type is IMPLICITLY coercible to one of the supported time types we can + * just use the boundaries of the supported type. Thus, the custom time type + * will inherit the time boundaries of the first compatible time type found. + */ +static Oid +coerce_to_time_type(Oid type) +{ + static const Oid supported_time_types[] = { DATEOID, TIMESTAMPOID, TIMESTAMPTZOID, + INT2OID, INT4OID, INT8OID }; + + int i; + + for (i = 0; i < lengthof(supported_time_types); i++) + { + Oid targettype = supported_time_types[i]; + + if (can_coerce_type(1, &type, &targettype, COERCION_IMPLICIT)) + return targettype; + } + + elog(ERROR, "unsupported time type \"%s\"", format_type_be(type)); + pg_unreachable(); +} + +/* + * Get the min time datum for a time type. + * + * Note that the min is not the same the actual "min" of the underlying + * storage for date and timestamps. + */ +Datum +ts_time_datum_get_min(Oid timetype) +{ + switch (timetype) + { + case DATEOID: + return DateADTGetDatum(TS_DATE_MIN); + case TIMESTAMPOID: + return TimestampGetDatum(TS_TIMESTAMP_MIN); + case TIMESTAMPTZOID: + return TimestampTzGetDatum(TS_TIMESTAMP_MIN); + case INT2OID: + return Int16GetDatum(PG_INT16_MIN); + case INT4OID: + return Int32GetDatum(PG_INT32_MIN); + case INT8OID: + return Int64GetDatum(PG_INT64_MIN); + default: + break; + } + + return ts_time_datum_get_min(coerce_to_time_type(timetype)); +} + +/* + * Get the end time datum for a time type. + * + * Note that the end is not the same as "max" (hence not named max). The end + * is exclusive for date and timestamps, and might not be the same as the max + * value for the underlying storage type (e.g., TIMESTAMP_END is before + * PG_INT64_MAX). Instead, the max value for dates and timestamps represent + * -Infinity and +Infinity. + */ +Datum +ts_time_datum_get_end(Oid timetype) +{ + switch (timetype) + { + case DATEOID: + return DateADTGetDatum(TS_DATE_END); + case TIMESTAMPOID: + return TimestampGetDatum(TS_TIMESTAMP_END); + case TIMESTAMPTZOID: + return TimestampTzGetDatum(TS_TIMESTAMP_END); + case INT2OID: + case INT4OID: + case INT8OID: + elog(ERROR, "END is not defined for \"%s\"", format_type_be(timetype)); + break; + default: + break; + } + + return ts_time_datum_get_end(coerce_to_time_type(timetype)); +} + +Datum +ts_time_datum_get_max(Oid timetype) +{ + switch (timetype) + { + case DATEOID: + return DateADTGetDatum(TS_DATE_END - 1); + case TIMESTAMPOID: + return TimestampGetDatum(TS_TIMESTAMP_END - 1); + case TIMESTAMPTZOID: + return TimestampTzGetDatum(TS_TIMESTAMP_END - 1); + case INT2OID: + return Int16GetDatum(PG_INT16_MAX); + case INT4OID: + return Int32GetDatum(PG_INT32_MAX); + case INT8OID: + return Int64GetDatum(PG_INT64_MAX); + break; + default: + break; + } + + return ts_time_datum_get_max(coerce_to_time_type(timetype)); +} + +Datum +ts_time_datum_get_nobegin(Oid timetype) +{ + switch (timetype) + { + case DATEOID: + return DateADTGetDatum(DATEVAL_NOBEGIN); + case TIMESTAMPOID: + return TimestampGetDatum(DT_NOBEGIN); + case TIMESTAMPTZOID: + return TimestampTzGetDatum(DT_NOBEGIN); + case INT2OID: + case INT4OID: + case INT8OID: + elog(ERROR, "NOBEGIN is not defined for \"%s\"", format_type_be(timetype)); + break; + default: + break; + } + + return ts_time_datum_get_nobegin(coerce_to_time_type(timetype)); +} + +Datum +ts_time_datum_get_noend(Oid timetype) +{ + switch (timetype) + { + case DATEOID: + return DateADTGetDatum(DATEVAL_NOEND); + case TIMESTAMPOID: + return TimestampGetDatum(DT_NOEND); + case TIMESTAMPTZOID: + return TimestampTzGetDatum(DT_NOEND); + case INT2OID: + case INT4OID: + case INT8OID: + elog(ERROR, "NOEND is not defined for \"%s\"", format_type_be(timetype)); + break; + default: + break; + } + + return ts_time_datum_get_noend(coerce_to_time_type(timetype)); +} + +/* + * Get the min for a time type in internal time. + */ +int64 +ts_time_get_min(Oid timetype) +{ + switch (timetype) + { + case DATEOID: + return TS_DATE_INTERNAL_MIN; + case TIMESTAMPOID: + return TS_TIMESTAMP_INTERNAL_MIN; + case TIMESTAMPTZOID: + return TS_TIMESTAMP_INTERNAL_MIN; + case INT2OID: + return PG_INT16_MIN; + case INT4OID: + return PG_INT32_MIN; + case INT8OID: + return PG_INT64_MIN; + default: + break; + } + + return ts_time_get_min(coerce_to_time_type(timetype)); +} + +/* + * Get the max for a time type in internal time. + */ +int64 +ts_time_get_max(Oid timetype) +{ + switch (timetype) + { + case DATEOID: + return TS_DATE_INTERNAL_END - 1; + case TIMESTAMPOID: + return TS_TIMESTAMP_INTERNAL_END - 1; + case TIMESTAMPTZOID: + return TS_TIMESTAMP_INTERNAL_END - 1; + case INT2OID: + return PG_INT16_MAX; + case INT4OID: + return PG_INT32_MAX; + case INT8OID: + return PG_INT64_MAX; + default: + break; + } + + return ts_time_get_max(coerce_to_time_type(timetype)); +} + +/* + * Get the end value time for a time type in internal time. + * + * The end is not a valid time value (it is exclusive). + */ +int64 +ts_time_get_end(Oid timetype) +{ + switch (timetype) + { + case DATEOID: + return TS_DATE_INTERNAL_END; + case TIMESTAMPOID: + return TS_TIMESTAMP_INTERNAL_END; + case TIMESTAMPTZOID: + return TS_TIMESTAMP_INTERNAL_END; + case INT2OID: + case INT4OID: + case INT8OID: + elog(ERROR, "END is not defined for \"%s\"", format_type_be(timetype)); + break; + default: + break; + } + + return ts_time_get_end(coerce_to_time_type(timetype)); +} + +/* + * Return the end (exclusive) or fall back to max. + * + * Integer time types have no definition for END, so we fall back to max. + */ +int64 +ts_time_get_end_or_max(Oid timetype) +{ + if (TS_TIME_IS_INTEGER_TIME(timetype)) + return ts_time_get_max(timetype); + + return ts_time_get_end(timetype); +} + +int64 +ts_time_get_nobegin(Oid timetype) +{ + switch (timetype) + { + case DATEOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + return TS_TIME_NOBEGIN; + case INT2OID: + case INT4OID: + case INT8OID: + elog(ERROR, "-Infinity not defined for \"%s\"", format_type_be(timetype)); + break; + default: + break; + } + + return ts_time_get_nobegin(coerce_to_time_type(timetype)); +} + +int64 +ts_time_get_noend(Oid timetype) +{ + switch (timetype) + { + case DATEOID: + case TIMESTAMPOID: + case TIMESTAMPTZOID: + return TS_TIME_NOEND; + case INT2OID: + case INT4OID: + case INT8OID: + elog(ERROR, "+Infinity not defined for \"%s\"", format_type_be(timetype)); + break; + default: + break; + } + + return ts_time_get_noend(coerce_to_time_type(timetype)); +} + +/* + * Add an interval to a time value in a saturating way. + * + * In contrast to, e.g., PG's timestamp_pl_interval, this function adds an + * interval in a saturating way without throwing an error in case of + * overflow. Instead it clamps to max for integer types end NOEND for date and + * timestamp types. + */ +int64 +ts_time_saturating_add(int64 timeval, int64 interval, Oid timetype) +{ + if (TS_TIME_IS_INTEGER_TIME(timetype)) + { + int64 time_max = ts_time_get_max(timetype); + + if (timeval > (time_max - interval)) + return time_max; + } + else if (timeval >= (ts_time_get_end(timetype) - interval)) + return ts_time_get_noend(timetype); + + return timeval + interval; +} + +/* + * Subtract an interval from a time value in a saturating way. + * + * In contrast to, e.g., PG's timestamp_mi_interval, this function subtracts + * an interval in a saturating way without throwing an error in case of + * overflow. Instead, it clamps to min for integer types and NOBEGIN for date + * and timestamp types. + */ +int64 +ts_time_saturating_sub(int64 timeval, int64 interval, Oid timetype) +{ + if (TS_TIME_IS_INTEGER_TIME(timetype)) + { + int64 time_min = ts_time_get_min(timetype); + + if (timeval < (time_min + interval)) + return time_min; + } + else if (timeval < (ts_time_get_min(timetype) + interval)) + return ts_time_get_nobegin(timetype); + + return timeval - interval; +} diff --git a/src/time_utils.h b/src/time_utils.h index 78b5f8620..fa95a2d7c 100644 --- a/src/time_utils.h +++ b/src/time_utils.h @@ -10,6 +10,71 @@ #include "export.h" +/* TimescaleDB-specific ranges for valid timestamps and dates: */ +#define TS_EPOCH_DIFF (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) +#define TS_EPOCH_DIFF_MICROSECONDS (TS_EPOCH_DIFF * USECS_PER_DAY) + +/* For Timestamps, we need to be able to go from UNIX epoch to POSTGRES epoch + * and thus add the difference between the two epochs. This will constrain the + * max supported timestamp by the same amount. */ +#define TS_TIMESTAMP_MIN MIN_TIMESTAMP +#define TS_TIMESTAMP_END (END_TIMESTAMP - TS_EPOCH_DIFF_MICROSECONDS) +#define TS_TIMESTAMP_INTERNAL_MIN (TS_TIMESTAMP_MIN + TS_EPOCH_DIFF_MICROSECONDS) +#define TS_TIMESTAMP_INTERNAL_MAX (TS_TIMESTAMP_INTERNAL_END - 1) +#define TS_TIMESTAMP_INTERNAL_END (TS_TIMESTAMP_END + TS_EPOCH_DIFF_MICROSECONDS) + +/* For Dates, we're limited by the timestamp range (since we internally first + * convert dates to timestamps). Naturally the TimescaleDB-specific timestamp + * limits apply as well. */ +#define TS_DATE_MIN (DATETIME_MIN_JULIAN - POSTGRES_EPOCH_JDATE) +#define TS_DATE_MAX (TS_DATE_END - 1) +#define TS_DATE_END (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE - TS_EPOCH_DIFF) +#define TS_DATE_INTERNAL_MIN (TS_TIMESTAMP_MIN + TS_EPOCH_DIFF_MICROSECONDS) +#define TS_DATE_INTERNAL_MAX (TS_DATE_INTERNAL_END - 1) +#define TS_DATE_INTERNAL_END (TS_TIMESTAMP_END + TS_EPOCH_DIFF_MICROSECONDS) + +/* + * -Infinity and +Infinity in internal (Unix) time. + */ +#define TS_TIME_NOBEGIN (PG_INT64_MIN) +#define TS_TIME_NOEND (PG_INT64_MAX) + +#define TS_TIME_IS_INTEGER_TIME(type) (type == INT2OID || type == INT4OID || type == INT8OID) +#define TS_TIME_DATUM_IS_MIN(timeval, type) (timeval == ts_time_datum_get_min(type)) +#define TS_TIME_DATUM_IS_MAX(timeval, type) (timeval == ts_time_datum_get_max(type)) +#define TS_TIME_DATUM_IS_END(timeval, type) \ + (!TS_TIME_IS_INTEGER_TIME(type) && (timeval == ts_time_datum_get_end(type))) +#define TS_TIME_DATUM_IS_NOBEGIN(timeval, type) \ + (!TS_TIME_IS_INTEGER_TIME(type) && (timeval == ts_time_datum_get_nobegin(type))) +#define TS_TIME_DATUM_IS_NOEND(timeval, type) \ + (!TS_TIME_IS_INTEGER_TIME(type) && (timeval == ts_time_datum_get_noend(type))) + +#define TS_TIME_DATUM_NOT_FINITE(timeval, type) \ + (TS_TIME_IS_INTEGER_TIME(type) || TS_TIME_DATUM_IS_NOBEGIN(timeval, type) || \ + TS_TIME_DATUM_IS_NOEND(timeval, type)) + +#define TS_TIME_IS_NOBEGIN(timeval, type) \ + (!TS_TIME_IS_INTEGER_TIME(type) && timeval == ts_time_get_nobegin(type)) +#define TS_TIME_IS_NOEND(timeval, type) \ + (!TS_TIME_IS_INTEGER_TIME(type) && timeval == ts_time_get_noend(type)) + +#define TS_TIME_NOT_FINITE(timeval, type) \ + (TS_TIME_IS_INTEGER_TIME(type) || TS_TIME_IS_NOBEGIN(timeval, type) || \ + TS_TIME_IS_NOEND(timeval, type)) + extern TSDLLEXPORT int64 ts_time_value_from_arg(Datum arg, Oid argtype, Oid timetype); +extern TSDLLEXPORT Datum ts_time_datum_get_min(Oid timetype); +extern TSDLLEXPORT Datum ts_time_datum_get_max(Oid timetype); +extern TSDLLEXPORT Datum ts_time_datum_get_end(Oid timetype); +extern TSDLLEXPORT Datum ts_time_datum_get_nobegin(Oid timetype); +extern TSDLLEXPORT Datum ts_time_datum_get_noend(Oid timetype); +extern TSDLLEXPORT int64 ts_time_get_min(Oid timetype); +extern TSDLLEXPORT int64 ts_time_get_max(Oid timetype); +extern TSDLLEXPORT int64 ts_time_get_end(Oid timetype); +extern TSDLLEXPORT int64 ts_time_get_end_or_max(Oid timetype); +extern TSDLLEXPORT int64 ts_time_get_nobegin(Oid timetype); +extern TSDLLEXPORT int64 ts_time_get_noend(Oid timetype); +extern TSDLLEXPORT int64 ts_time_saturating_add(int64 timeval, int64 interval, Oid timetype); +extern TSDLLEXPORT int64 ts_time_saturating_sub(int64 timeval, int64 interval, Oid timetype); #endif /* TIMESCALEDB_TIME_UTILS_H */ diff --git a/src/utils.c b/src/utils.c index 16a7435f6..7eb2e0408 100644 --- a/src/utils.c +++ b/src/utils.c @@ -33,6 +33,7 @@ #include "compat.h" #include "chunk.h" #include "utils.h" +#include "time_utils.h" #include "guc.h" TS_FUNCTION_INFO_V1(ts_pg_timestamp_to_unix_microseconds); @@ -46,10 +47,10 @@ 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); + PG_RETURN_INT64(TS_TIME_NOBEGIN); if (TIMESTAMP_IS_NOEND(timestamp)) - PG_RETURN_INT64(PG_INT64_MAX); + PG_RETURN_INT64(TS_TIME_NOEND); if (timestamp < TS_TIMESTAMP_MIN) ereport(ERROR, @@ -74,11 +75,11 @@ 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 (TS_TIME_IS_NOBEGIN(microseconds, TIMESTAMPTZOID)) + PG_RETURN_DATUM(ts_time_datum_get_nobegin(TIMESTAMPTZOID)); - if (microseconds == PG_INT64_MAX) - PG_RETURN_TIMESTAMPTZ(DT_NOEND); + if (TS_TIME_IS_NOEND(microseconds, TIMESTAMPTZOID)) + PG_RETURN_DATUM(ts_time_datum_get_noend(TIMESTAMPTZOID)); /* * Test that the UNIX us timestamp is within bounds. Note that an int64 at @@ -86,7 +87,7 @@ ts_pg_unix_microseconds_to_timestamp(PG_FUNCTION_ARGS) * 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) + if (microseconds < TS_TIMESTAMP_INTERNAL_MIN) ereport(ERROR, (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE), errmsg("timestamp out of range"))); @@ -99,11 +100,11 @@ ts_pg_unix_microseconds_to_date(PG_FUNCTION_ARGS) int64 microseconds = PG_GETARG_INT64(0); Datum res; - if (microseconds == PG_INT64_MIN) - PG_RETURN_DATEADT(DATEVAL_NOBEGIN); + if (TS_TIME_IS_NOBEGIN(microseconds, DATEOID)) + PG_RETURN_DATUM(ts_time_datum_get_nobegin(DATEOID)); - if (microseconds == PG_INT64_MAX) - PG_RETURN_DATEADT(DATEVAL_NOEND); + if (TS_TIME_IS_NOEND(microseconds, DATEOID)) + PG_RETURN_DATUM(ts_time_datum_get_noend(DATEOID)); res = DirectFunctionCall1(ts_pg_unix_microseconds_to_timestamp, Int64GetDatum(microseconds)); res = DirectFunctionCall1(timestamp_date, res); @@ -118,6 +119,24 @@ ts_time_value_to_internal(Datum time_val, Oid type_oid) { Datum res, tz; + if (TS_TIME_IS_INTEGER_TIME(type_oid)) + { + /* Integer time types have no distinction between min, max and + * infinity. We don't want min and max to be turned into infinity for + * these types so check for those values first. */ + if (TS_TIME_DATUM_IS_MIN(time_val, type_oid)) + return ts_time_get_min(type_oid); + + if (TS_TIME_DATUM_IS_MAX(time_val, type_oid)) + return ts_time_get_max(type_oid); + } + + if (TS_TIME_DATUM_IS_NOBEGIN(time_val, type_oid)) + return ts_time_get_nobegin(type_oid); + + if (TS_TIME_DATUM_IS_NOEND(time_val, type_oid)) + return ts_time_get_noend(type_oid); + switch (type_oid) { case INT8OID: @@ -125,7 +144,6 @@ ts_time_value_to_internal(Datum time_val, Oid type_oid) case INT2OID: return ts_integer_to_internal(time_val, type_oid); case TIMESTAMPOID: - /* * for timestamps, ignore timezones, make believe the timestamp is * at UTC @@ -288,6 +306,12 @@ static Datum ts_integer_to_internal_value(int64 value, Oid type); TSDLLEXPORT Datum ts_internal_to_time_value(int64 value, Oid type) { + if (TS_TIME_IS_NOBEGIN(value, type)) + return ts_time_datum_get_nobegin(type); + + if (TS_TIME_IS_NOEND(value, type)) + return ts_time_datum_get_noend(type); + switch (type) { case INT2OID: diff --git a/src/utils.h b/src/utils.h index 582d9f2a8..dfc8d719f 100644 --- a/src/utils.h +++ b/src/utils.h @@ -26,24 +26,6 @@ #define TS_SET_LOCKTAG_ADVISORY(tag, id1, id2, id3) \ SET_LOCKTAG_ADVISORY((tag), (id1), (id2), (id3), 29749) -#define TS_EPOCH_DIFF (POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) -#define TS_EPOCH_DIFF_MICROSECONDS (TS_EPOCH_DIFF * USECS_PER_DAY) -#define TS_INTERNAL_TIMESTAMP_MIN ((int64) USECS_PER_DAY * (DATETIME_MIN_JULIAN - UNIX_EPOCH_JDATE)) - -/* TimescaleDB-specific ranges for valid timestamps and dates: */ - -/* For Timestamps, we need to be able to go from UNIX epoch to POSTGRES epoch - * and thus add the difference between the two epochs. This will constrain the - * max supported timestamp by the same amount. */ -#define TS_TIMESTAMP_MIN MIN_TIMESTAMP -#define TS_TIMESTAMP_END (END_TIMESTAMP - TS_EPOCH_DIFF_MICROSECONDS) - -/* For Dates, we're limited by the timestamp range (since we internally first - * convert dates to timestamps). Naturally the TimescaleDB-specific timestamp - * limits apply as well. */ -#define TS_DATE_MIN (DATETIME_MIN_JULIAN - POSTGRES_EPOCH_JDATE) -#define TS_DATE_END (TIMESTAMP_END_JULIAN - POSTGRES_EPOCH_JDATE - TS_EPOCH_DIFF) - /* find the length of a statically sized array */ #define TS_ARRAY_LEN(array) (sizeof(array) / sizeof(*array)) diff --git a/test/expected/c_unit_tests.out b/test/expected/c_unit_tests.out index 1b0f3e9f8..f494143fb 100644 --- a/test/expected/c_unit_tests.out +++ b/test/expected/c_unit_tests.out @@ -2,28 +2,36 @@ -- Please see the included NOTICE for copyright information and -- LICENSE-APACHE for a copy of the license. \c :TEST_DBNAME :ROLE_SUPERUSER -CREATE OR REPLACE FUNCTION ts_test_time_to_internal_conversion() RETURNS VOID -AS :MODULE_PATHNAME LANGUAGE C VOLATILE; -CREATE OR REPLACE FUNCTION ts_test_interval_to_internal_conversion() RETURNS VOID -AS :MODULE_PATHNAME LANGUAGE C VOLATILE; -CREATE OR REPLACE FUNCTION ts_test_adts() RETURNS VOID -AS :MODULE_PATHNAME LANGUAGE C VOLATILE; -\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER -SELECT ts_test_time_to_internal_conversion(); - ts_test_time_to_internal_conversion -------------------------------------- +CREATE OR REPLACE FUNCTION test.time_to_internal_conversion() RETURNS VOID +AS :MODULE_PATHNAME, 'ts_test_time_to_internal_conversion' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.interval_to_internal_conversion() RETURNS VOID +AS :MODULE_PATHNAME, 'ts_test_interval_to_internal_conversion' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.adts() RETURNS VOID +AS :MODULE_PATHNAME, 'ts_test_adts' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.time_utils() RETURNS VOID +AS :MODULE_PATHNAME, 'ts_test_time_utils' LANGUAGE C; +SET ROLE :ROLE_DEFAULT_PERM_USER; +SELECT test.time_to_internal_conversion(); + time_to_internal_conversion +----------------------------- (1 row) -SELECT ts_test_interval_to_internal_conversion(); - ts_test_interval_to_internal_conversion ------------------------------------------ +SELECT test.interval_to_internal_conversion(); + interval_to_internal_conversion +--------------------------------- (1 row) -SELECT ts_test_adts(); - ts_test_adts --------------- +SELECT test.adts(); + adts +------ + +(1 row) + +SELECT test.time_utils(); + time_utils +------------ (1 row) diff --git a/test/expected/custom_type-11.out b/test/expected/custom_type-11.out index 10fe45791..bc2e74141 100644 --- a/test/expected/custom_type-11.out +++ b/test/expected/custom_type-11.out @@ -25,39 +25,35 @@ CREATE TYPE customtype ( SEND = customtype_send, LIKE = TIMESTAMPTZ ); -CREATE CAST (customtype AS bigint) -WITHOUT FUNCTION AS ASSIGNMENT; -CREATE CAST (bigint AS customtype) -WITHOUT FUNCTION AS IMPLICIT; -CREATE CAST (customtype AS timestamptz) -WITHOUT FUNCTION AS ASSIGNMENT; -CREATE CAST (timestamptz AS customtype) -WITHOUT FUNCTION AS ASSIGNMENT; CREATE OR REPLACE FUNCTION customtype_lt(customtype, customtype) RETURNS bool AS 'timestamp_lt' LANGUAGE internal IMMUTABLE STRICT; CREATE OPERATOR < ( - LEFTARG = customtype, - RIGHTARG = customtype, - PROCEDURE = customtype_lt, - COMMUTATOR = >, - NEGATOR = >=, - RESTRICT = scalarltsel, - JOIN = scalarltjoinsel + LEFTARG = customtype, + RIGHTARG = customtype, + PROCEDURE = customtype_lt, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel ); CREATE OR REPLACE FUNCTION customtype_ge(customtype, customtype) RETURNS bool AS 'timestamp_ge' LANGUAGE internal IMMUTABLE STRICT; CREATE OPERATOR >= ( - LEFTARG = customtype, - RIGHTARG = customtype, - PROCEDURE = customtype_ge, - COMMUTATOR = <=, - NEGATOR = <, - RESTRICT = scalargtsel, - JOIN = scalargtjoinsel + LEFTARG = customtype, + RIGHTARG = customtype, + PROCEDURE = customtype_ge, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); -\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER +CREATE CAST (customtype AS bigint) +WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (bigint AS customtype) +WITHOUT FUNCTION AS ASSIGNMENT; +SET ROLE :ROLE_DEFAULT_PERM_USER; CREATE TABLE customtype_test(time_custom customtype, val int); SELECT create_hypertable('customtype_test', 'time_custom', chunk_time_interval => 10e6::bigint, create_default_indexes=>false); NOTICE: adding not-null constraint to column "time_custom" @@ -66,8 +62,17 @@ NOTICE: adding not-null constraint to column "time_custom" (1,public,customtype_test,t) (1 row) +\set ON_ERROR_STOP 0 INSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10); -INSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10); +ERROR: unsupported time type "customtype" +\set ON_ERROR_STOP 1 +-- Add implicit casts +RESET ROLE; +CREATE CAST (customtype AS timestamptz) +WITHOUT FUNCTION AS IMPLICIT; +CREATE CAST (timestamptz AS customtype) +WITHOUT FUNCTION AS IMPLICIT; +SET ROLE :ROLE_DEFAULT_PERM_USER; INSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10); EXPLAIN (costs off) SELECT * FROM customtype_test; QUERY PLAN @@ -88,9 +93,7 @@ EXPLAIN (costs off) SELECT * FROM customtype_test; SELECT * FROM customtype_test; time_custom | val ------------------------------+----- - Mon Jan 01 01:02:03 2001 PST | 10 - Mon Jan 01 01:02:03 2001 PST | 10 Mon Jan 01 01:02:03 2001 PST | 10 Mon Jan 01 01:02:23 2001 PST | 11 -(4 rows) +(2 rows) diff --git a/test/expected/custom_type-12.out b/test/expected/custom_type-12.out index 5fad88785..20f33075b 100644 --- a/test/expected/custom_type-12.out +++ b/test/expected/custom_type-12.out @@ -25,39 +25,35 @@ CREATE TYPE customtype ( SEND = customtype_send, LIKE = TIMESTAMPTZ ); -CREATE CAST (customtype AS bigint) -WITHOUT FUNCTION AS ASSIGNMENT; -CREATE CAST (bigint AS customtype) -WITHOUT FUNCTION AS IMPLICIT; -CREATE CAST (customtype AS timestamptz) -WITHOUT FUNCTION AS ASSIGNMENT; -CREATE CAST (timestamptz AS customtype) -WITHOUT FUNCTION AS ASSIGNMENT; CREATE OR REPLACE FUNCTION customtype_lt(customtype, customtype) RETURNS bool AS 'timestamp_lt' LANGUAGE internal IMMUTABLE STRICT; CREATE OPERATOR < ( - LEFTARG = customtype, - RIGHTARG = customtype, - PROCEDURE = customtype_lt, - COMMUTATOR = >, - NEGATOR = >=, - RESTRICT = scalarltsel, - JOIN = scalarltjoinsel + LEFTARG = customtype, + RIGHTARG = customtype, + PROCEDURE = customtype_lt, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel ); CREATE OR REPLACE FUNCTION customtype_ge(customtype, customtype) RETURNS bool AS 'timestamp_ge' LANGUAGE internal IMMUTABLE STRICT; CREATE OPERATOR >= ( - LEFTARG = customtype, - RIGHTARG = customtype, - PROCEDURE = customtype_ge, - COMMUTATOR = <=, - NEGATOR = <, - RESTRICT = scalargtsel, - JOIN = scalargtjoinsel + LEFTARG = customtype, + RIGHTARG = customtype, + PROCEDURE = customtype_ge, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); -\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER +CREATE CAST (customtype AS bigint) +WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (bigint AS customtype) +WITHOUT FUNCTION AS ASSIGNMENT; +SET ROLE :ROLE_DEFAULT_PERM_USER; CREATE TABLE customtype_test(time_custom customtype, val int); SELECT create_hypertable('customtype_test', 'time_custom', chunk_time_interval => 10e6::bigint, create_default_indexes=>false); NOTICE: adding not-null constraint to column "time_custom" @@ -66,8 +62,17 @@ NOTICE: adding not-null constraint to column "time_custom" (1,public,customtype_test,t) (1 row) +\set ON_ERROR_STOP 0 INSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10); -INSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10); +ERROR: unsupported time type "customtype" +\set ON_ERROR_STOP 1 +-- Add implicit casts +RESET ROLE; +CREATE CAST (customtype AS timestamptz) +WITHOUT FUNCTION AS IMPLICIT; +CREATE CAST (timestamptz AS customtype) +WITHOUT FUNCTION AS IMPLICIT; +SET ROLE :ROLE_DEFAULT_PERM_USER; INSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10); EXPLAIN (costs off) SELECT * FROM customtype_test; QUERY PLAN @@ -87,9 +92,7 @@ EXPLAIN (costs off) SELECT * FROM customtype_test; SELECT * FROM customtype_test; time_custom | val ------------------------------+----- - Mon Jan 01 01:02:03 2001 PST | 10 - Mon Jan 01 01:02:03 2001 PST | 10 Mon Jan 01 01:02:03 2001 PST | 10 Mon Jan 01 01:02:23 2001 PST | 11 -(4 rows) +(2 rows) diff --git a/test/expected/test_utils.out b/test/expected/test_utils.out index 7d76c7cd6..a852bc9dc 100644 --- a/test/expected/test_utils.out +++ b/test/expected/test_utils.out @@ -10,7 +10,7 @@ CREATE OR REPLACE FUNCTION test.ptr_eq() RETURNS VOID AS :MODULE_PATHNAME, 'ts_test_utils_ptr_eq' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION test.double_eq() RETURNS VOID AS :MODULE_PATHNAME, 'ts_test_utils_double_eq' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; -\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER; +SET ROLE :ROLE_DEFAULT_PERM_USER; -- We're testing that the test utils work and generate errors on -- failing conditions \set ON_ERROR_STOP 0 diff --git a/test/expected/timestamp_limits.out b/test/expected/timestamp_limits.out index 552fa20ec..a156ccfc4 100644 --- a/test/expected/timestamp_limits.out +++ b/test/expected/timestamp_limits.out @@ -10,6 +10,22 @@ CREATE OR REPLACE FUNCTION test.min_ts_timestamptz() RETURNS TIMESTAMPTZ AS :MODULE_PATHNAME, 'ts_timestamptz_min' LANGUAGE C VOLATILE; CREATE OR REPLACE FUNCTION test.end_ts_timestamptz() RETURNS TIMESTAMPTZ AS :MODULE_PATHNAME, 'ts_timestamptz_end' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.internal_min_ts_timestamptz() RETURNS BIGINT +AS :MODULE_PATHNAME, 'ts_timestamptz_internal_min' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.internal_end_ts_timestamptz() RETURNS BIGINT +AS :MODULE_PATHNAME, 'ts_timestamptz_internal_end' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.min_pg_timestamp() RETURNS TIMESTAMP +AS :MODULE_PATHNAME, 'ts_timestamp_pg_min' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.end_pg_timestamp() RETURNS TIMESTAMP +AS :MODULE_PATHNAME, 'ts_timestamp_pg_end' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.min_ts_timestamp() RETURNS TIMESTAMP +AS :MODULE_PATHNAME, 'ts_timestamp_min' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.end_ts_timestamp() RETURNS TIMESTAMP +AS :MODULE_PATHNAME, 'ts_timestamp_end' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.internal_min_ts_timestamp() RETURNS BIGINT +AS :MODULE_PATHNAME, 'ts_timestamp_internal_min' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.internal_end_ts_timestamp() RETURNS BIGINT +AS :MODULE_PATHNAME, 'ts_timestamp_internal_end' LANGUAGE C VOLATILE; CREATE OR REPLACE FUNCTION test.min_pg_date() RETURNS DATE AS :MODULE_PATHNAME, 'ts_date_pg_min' LANGUAGE C VOLATILE; CREATE OR REPLACE FUNCTION test.end_pg_date() RETURNS DATE @@ -18,24 +34,205 @@ CREATE OR REPLACE FUNCTION test.min_ts_date() RETURNS DATE AS :MODULE_PATHNAME, 'ts_date_min' LANGUAGE C VOLATILE; CREATE OR REPLACE FUNCTION test.end_ts_date() RETURNS DATE AS :MODULE_PATHNAME, 'ts_date_end' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.internal_min_ts_date() RETURNS BIGINT +AS :MODULE_PATHNAME, 'ts_date_internal_min' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.internal_end_ts_date() RETURNS BIGINT +AS :MODULE_PATHNAME, 'ts_date_internal_end' LANGUAGE C VOLATILE; SET ROLE :ROLE_DEFAULT_PERM_USER; --show PG and TimescaleDB-specific time boundaries. Note that we --internally convert to UNIX epoch, which restricts the range of --supported timestamps. SET datestyle TO 'ISO,YMD'; -SELECT test.min_pg_timestamptz(), test.end_pg_timestamptz(), test.min_ts_timestamptz(), test.end_ts_timestamptz(); - min_pg_timestamptz | end_pg_timestamptz | min_ts_timestamptz | end_ts_timestamptz ----------------------------+--------------------------+---------------------------+-------------------------- - 4714-11-23 16:00:00-08 BC | 294276-12-31 16:00:00-08 | 4714-11-23 16:00:00-08 BC | 294247-01-01 16:00:00-08 -(1 row) +\x +--To display end timestamps (which aren't valid timestamps), we need +--to be at UTC to avoid timezone conversion which would fail with +--out-of-range error. Being at UTC also makes it easier to compare +--TIMESTAMP and TIMESTAMP WITH TIME ZONE. +SET timezone TO 'UTC'; +-- MIN values (PostgreSQL). +SELECT test.min_pg_timestamptz(), + test.min_pg_timestamp(), + test.min_pg_date(); +-[ RECORD 1 ]------+-------------------------- +min_pg_timestamptz | 4714-11-24 00:00:00+00 BC +min_pg_timestamp | 4714-11-24 00:00:00 BC +min_pg_date | 4714-11-24 BC ---TimescaleDB-specific dates should be consistent with timestamps ---since we internally first convert dates to timestamps and then to ---UNIX epoch. -SELECT test.min_pg_date(), test.end_pg_date(), test.min_ts_date(), test.end_ts_date(); - min_pg_date | end_pg_date | min_ts_date | end_ts_date ----------------+---------------+---------------+-------------- - 4714-11-24 BC | 5874898-01-01 | 4714-11-24 BC | 294247-01-02 -(1 row) +-- MIN values (TimescaleDB). +SELECT test.min_ts_timestamptz(), + test.min_ts_timestamp(), + test.min_ts_date(), + _timescaledb_internal.to_timestamp(test.internal_min_ts_timestamptz()) AS min_ts_internal_timestamptz, + _timescaledb_internal.to_timestamp_without_timezone(test.internal_min_ts_timestamp()) AS min_ts_internal_timestamp, + _timescaledb_internal.to_date(test.internal_min_ts_date()) AS min_ts_internal_date; +-[ RECORD 1 ]---------------+-------------------------- +min_ts_timestamptz | 4714-11-24 00:00:00+00 BC +min_ts_timestamp | 4714-11-24 00:00:00 BC +min_ts_date | 4714-11-24 BC +min_ts_internal_timestamptz | 4714-11-24 00:00:00+00 BC +min_ts_internal_timestamp | 4714-11-24 00:00:00 BC +min_ts_internal_date | 4714-11-24 BC + +-- END values (PostgreSQL). Note that and values aren't valid +-- timestamps or dates (since, e.g., END_TIMESTAMP is exclusive). It +-- is possible to display them at UTC since no conversion is made. +SELECT test.end_pg_timestamptz(), + test.end_pg_timestamp(), + test.end_pg_date(); +-[ RECORD 1 ]------+------------------------- +end_pg_timestamptz | 294277-01-01 00:00:00+00 +end_pg_timestamp | 294277-01-01 00:00:00 +end_pg_date | 5874898-01-01 + +-- END values (TimescaleDB). Note that we convert DATE to TIMESTAMP +-- internally, and that limits the end to that of timestamp. +SELECT test.end_ts_timestamptz(), + test.end_ts_timestamp(), + test.end_ts_date(), + _timescaledb_internal.to_timestamp(test.internal_end_ts_timestamptz()) AS end_ts_internal_timestamptz, + _timescaledb_internal.to_timestamp_without_timezone(test.internal_end_ts_timestamp()) AS end_ts_internal_timestamp, + _timescaledb_internal.to_date(test.internal_end_ts_date()) AS end_ts_internal_date; +-[ RECORD 1 ]---------------+------------------------- +end_ts_timestamptz | 294247-01-02 00:00:00+00 +end_ts_timestamp | 294247-01-02 00:00:00 +end_ts_date | 294247-01-02 +end_ts_internal_timestamptz | 294247-01-02 00:00:00+00 +end_ts_internal_timestamp | 294247-01-02 00:00:00 +end_ts_internal_date | 294247-01-02 + +--Test insert of time values close to or at limits +--Suitable constraints should be created on chunks +CREATE TABLE smallint_table(time smallint); +SELECT create_hypertable('smallint_table', 'time', chunk_time_interval=>10); +NOTICE: adding not-null constraint to column "time" +-[ RECORD 1 ]-----+---------------------------- +create_hypertable | (1,public,smallint_table,t) + +INSERT INTO smallint_table VALUES (-32768), (32767); +SELECT chunk, pg_get_constraintdef(c.oid) +FROM show_chunks('smallint_table') chunk, pg_constraint c +WHERE c.conrelid = chunk +AND c.contype = 'c'; +-[ RECORD 1 ]--------+--------------------------------------- +chunk | _timescaledb_internal._hyper_1_1_chunk +pg_get_constraintdef | CHECK (("time" < '-32760'::smallint)) +-[ RECORD 2 ]--------+--------------------------------------- +chunk | _timescaledb_internal._hyper_1_2_chunk +pg_get_constraintdef | CHECK (("time" >= '32760'::smallint)) + +CREATE TABLE int_table(time int); +SELECT create_hypertable('int_table', 'time', chunk_time_interval=>10); +NOTICE: adding not-null constraint to column "time" +-[ RECORD 1 ]-----+----------------------- +create_hypertable | (2,public,int_table,t) + +INSERT INTO int_table VALUES (-2147483648), (2147483647); +SELECT chunk, pg_get_constraintdef(c.oid) +FROM show_chunks('int_table') chunk, pg_constraint c +WHERE c.conrelid = chunk +AND c.contype = 'c'; +-[ RECORD 1 ]--------+------------------------------------------ +chunk | _timescaledb_internal._hyper_2_3_chunk +pg_get_constraintdef | CHECK (("time" < '-2147483640'::integer)) +-[ RECORD 2 ]--------+------------------------------------------ +chunk | _timescaledb_internal._hyper_2_4_chunk +pg_get_constraintdef | CHECK (("time" >= 2147483640)) + +CREATE TABLE bigint_table(time bigint); +SELECT create_hypertable('bigint_table', 'time', chunk_time_interval=>10); +NOTICE: adding not-null constraint to column "time" +-[ RECORD 1 ]-----+-------------------------- +create_hypertable | (3,public,bigint_table,t) + +INSERT INTO bigint_table VALUES (-9223372036854775808), (9223372036854775807); +SELECT chunk, pg_get_constraintdef(c.oid) +FROM show_chunks('bigint_table') chunk, pg_constraint c +WHERE c.conrelid = chunk +AND c.contype = 'c'; +-[ RECORD 1 ]--------+-------------------------------------------------- +chunk | _timescaledb_internal._hyper_3_5_chunk +pg_get_constraintdef | CHECK (("time" < '-9223372036854775800'::bigint)) +-[ RECORD 2 ]--------+-------------------------------------------------- +chunk | _timescaledb_internal._hyper_3_6_chunk +pg_get_constraintdef | CHECK (("time" >= '9223372036854775800'::bigint)) + +CREATE TABLE date_table(time date); +SELECT create_hypertable('date_table', 'time'); +NOTICE: adding not-null constraint to column "time" +-[ RECORD 1 ]-----+------------------------ +create_hypertable | (4,public,date_table,t) + +INSERT INTO date_table VALUES (test.min_ts_date()), (test.end_ts_date() - INTERVAL '1 day'); +-- Test out-of-range dates +\set ON_ERROR_STOP 0 +INSERT INTO date_table VALUES (test.min_ts_date() - INTERVAL '1 day'); +ERROR: timestamp out of range +INSERT INTO date_table VALUES (test.end_ts_date()); +ERROR: timestamp out of range +\set ON_ERROR_STOP 1 +SELECT chunk, pg_get_constraintdef(c.oid) +FROM show_chunks('date_table') chunk, pg_constraint c +WHERE c.conrelid = chunk +AND c.contype = 'c'; +-[ RECORD 1 ]--------+----------------------------------------- +chunk | _timescaledb_internal._hyper_4_7_chunk +pg_get_constraintdef | CHECK (("time" < '4714-11-27 BC'::date)) +-[ RECORD 2 ]--------+----------------------------------------- +chunk | _timescaledb_internal._hyper_4_8_chunk +pg_get_constraintdef | CHECK (("time" >= '294246-12-31'::date)) + +CREATE TABLE timestamp_table(time timestamp); +SELECT create_hypertable('timestamp_table', 'time'); +NOTICE: adding not-null constraint to column "time" +-[ RECORD 1 ]-----+----------------------------- +create_hypertable | (5,public,timestamp_table,t) + +INSERT INTO timestamp_table VALUES (test.min_ts_timestamp()); +INSERT INTO timestamp_table VALUES (test.end_ts_timestamp() - INTERVAL '1 microsecond'); +-- Test out-of-range timestamps +\set ON_ERROR_STOP 0 +INSERT INTO timestamp_table VALUES (test.min_ts_timestamp() - INTERVAL '1 microsecond'); +ERROR: timestamp out of range +INSERT INTO timestamp_table VALUES (test.end_ts_timestamp()); +ERROR: timestamp out of range +\set ON_ERROR_STOP 1 +SELECT chunk, pg_get_constraintdef(c.oid) +FROM show_chunks('timestamp_table') chunk, pg_constraint c +WHERE c.conrelid = chunk +AND c.contype = 'c'; +-[ RECORD 1 ]--------+------------------------------------------------------------------------- +chunk | _timescaledb_internal._hyper_5_9_chunk +pg_get_constraintdef | CHECK (("time" < '4714-11-27 00:00:00 BC'::timestamp without time zone)) +-[ RECORD 2 ]--------+------------------------------------------------------------------------- +chunk | _timescaledb_internal._hyper_5_10_chunk +pg_get_constraintdef | CHECK (("time" >= '294246-12-31 00:00:00'::timestamp without time zone)) + +CREATE TABLE timestamptz_table(time timestamp); +SELECT create_hypertable('timestamptz_table', 'time'); +NOTICE: adding not-null constraint to column "time" +-[ RECORD 1 ]-----+------------------------------- +create_hypertable | (6,public,timestamptz_table,t) + +-- Need to be at UTC for this to work for timestamp with time zone +INSERT INTO timestamptz_table VALUES (test.min_ts_timestamptz()); +INSERT INTO timestamptz_table VALUES (test.end_ts_timestamptz() - INTERVAL '1 microsecond'); +-- Test out-of-range timestamps +\set ON_ERROR_STOP 0 +INSERT INTO timestamptz_table VALUES (test.min_ts_timestamptz() - INTERVAL '1 microsecond'); +ERROR: timestamp out of range +INSERT INTO timestamptz_table VALUES (test.end_ts_timestamptz()); +ERROR: timestamp out of range +\set ON_ERROR_STOP 1 +SELECT chunk, pg_get_constraintdef(c.oid) +FROM show_chunks('timestamptz_table') chunk, pg_constraint c +WHERE c.conrelid = chunk +AND c.contype = 'c'; +-[ RECORD 1 ]--------+------------------------------------------------------------------------- +chunk | _timescaledb_internal._hyper_6_11_chunk +pg_get_constraintdef | CHECK (("time" < '4714-11-27 00:00:00 BC'::timestamp without time zone)) +-[ RECORD 2 ]--------+------------------------------------------------------------------------- +chunk | _timescaledb_internal._hyper_6_12_chunk +pg_get_constraintdef | CHECK (("time" >= '294246-12-31 00:00:00'::timestamp without time zone)) RESET datestyle; +RESET timezone; diff --git a/test/sql/c_unit_tests.sql b/test/sql/c_unit_tests.sql index 6fc1ab6f7..593fec62f 100644 --- a/test/sql/c_unit_tests.sql +++ b/test/sql/c_unit_tests.sql @@ -3,18 +3,20 @@ -- LICENSE-APACHE for a copy of the license. \c :TEST_DBNAME :ROLE_SUPERUSER -CREATE OR REPLACE FUNCTION ts_test_time_to_internal_conversion() RETURNS VOID -AS :MODULE_PATHNAME LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.time_to_internal_conversion() RETURNS VOID +AS :MODULE_PATHNAME, 'ts_test_time_to_internal_conversion' LANGUAGE C VOLATILE; -CREATE OR REPLACE FUNCTION ts_test_interval_to_internal_conversion() RETURNS VOID -AS :MODULE_PATHNAME LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.interval_to_internal_conversion() RETURNS VOID +AS :MODULE_PATHNAME, 'ts_test_interval_to_internal_conversion' LANGUAGE C VOLATILE; -CREATE OR REPLACE FUNCTION ts_test_adts() RETURNS VOID -AS :MODULE_PATHNAME LANGUAGE C VOLATILE; -\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER +CREATE OR REPLACE FUNCTION test.adts() RETURNS VOID +AS :MODULE_PATHNAME, 'ts_test_adts' LANGUAGE C VOLATILE; -SELECT ts_test_time_to_internal_conversion(); +CREATE OR REPLACE FUNCTION test.time_utils() RETURNS VOID +AS :MODULE_PATHNAME, 'ts_test_time_utils' LANGUAGE C; +SET ROLE :ROLE_DEFAULT_PERM_USER; -SELECT ts_test_interval_to_internal_conversion(); - -SELECT ts_test_adts(); +SELECT test.time_to_internal_conversion(); +SELECT test.interval_to_internal_conversion(); +SELECT test.adts(); +SELECT test.time_utils(); diff --git a/test/sql/chunks.sql b/test/sql/chunks.sql index b886ff6cd..f312baa31 100644 --- a/test/sql/chunks.sql +++ b/test/sql/chunks.sql @@ -15,6 +15,7 @@ CREATE OR REPLACE FUNCTION _timescaledb_internal.dimension_calculate_default_range_open( dimension_value BIGINT, interval_length BIGINT, + dimension_type CSTRING, OUT range_start BIGINT, OUT range_end BIGINT) AS :MODULE_PATHNAME, 'ts_dimension_calculate_open_range_default' LANGUAGE C STABLE; @@ -29,36 +30,69 @@ CREATE OR REPLACE FUNCTION _timescaledb_internal.dimension_calculate_default_ran \c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER --open SELECT assert_equal(0::bigint, actual_range_start), assert_equal(10::bigint, actual_range_end) -FROM _timescaledb_internal.dimension_calculate_default_range_open(0,10) AS res(actual_range_start, actual_range_end); +FROM _timescaledb_internal.dimension_calculate_default_range_open(0,10, 'int8') AS res(actual_range_start, actual_range_end); SELECT assert_equal(0::bigint, actual_range_start), assert_equal(10::bigint, actual_range_end) -FROM _timescaledb_internal.dimension_calculate_default_range_open(9,10) AS res(actual_range_start, actual_range_end); +FROM _timescaledb_internal.dimension_calculate_default_range_open(9,10, 'int4') AS res(actual_range_start, actual_range_end); SELECT assert_equal(10::bigint, actual_range_start), assert_equal(20::bigint, actual_range_end) -FROM _timescaledb_internal.dimension_calculate_default_range_open(10,10) AS res(actual_range_start, actual_range_end); +FROM _timescaledb_internal.dimension_calculate_default_range_open(10,10, 'int2') AS res(actual_range_start, actual_range_end); SELECT assert_equal(-10::bigint, actual_range_start), assert_equal(0::bigint, actual_range_end) -FROM _timescaledb_internal.dimension_calculate_default_range_open(-1,10) AS res(actual_range_start, actual_range_end); +FROM _timescaledb_internal.dimension_calculate_default_range_open(-1,10, 'int8') AS res(actual_range_start, actual_range_end); SELECT assert_equal(-10::bigint, actual_range_start), assert_equal(0::bigint, actual_range_end) -FROM _timescaledb_internal.dimension_calculate_default_range_open(-10,10) AS res(actual_range_start, actual_range_end); +FROM _timescaledb_internal.dimension_calculate_default_range_open(-10,10, 'int4') AS res(actual_range_start, actual_range_end); SELECT assert_equal(-20::bigint, actual_range_start), assert_equal(-10::bigint, actual_range_end) -FROM _timescaledb_internal.dimension_calculate_default_range_open(-11,10) AS res(actual_range_start, actual_range_end); +FROM _timescaledb_internal.dimension_calculate_default_range_open(-11,10, 'int2') AS res(actual_range_start, actual_range_end); --test that the ends are cut as needed to prevent overflow/undeflow. + +--------------- +-- BIGINT +--------------- SELECT assert_equal(-9223372036854775808, actual_range_start), assert_equal(-9223372036854775800::bigint, actual_range_end) -FROM _timescaledb_internal.dimension_calculate_default_range_open(-9223372036854775808,10) AS res(actual_range_start, actual_range_end); +FROM _timescaledb_internal.dimension_calculate_default_range_open(-9223372036854775808,10, 'int8') AS res(actual_range_start, actual_range_end); SELECT assert_equal(-9223372036854775808, actual_range_start), assert_equal(-9223372036854775800::bigint, actual_range_end) -FROM _timescaledb_internal.dimension_calculate_default_range_open(-9223372036854775807,10) AS res(actual_range_start, actual_range_end); +FROM _timescaledb_internal.dimension_calculate_default_range_open(-9223372036854775807,10, 'int8') AS res(actual_range_start, actual_range_end); SELECT assert_equal(9223372036854775800::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end) -FROM _timescaledb_internal.dimension_calculate_default_range_open(9223372036854775807,10) AS res(actual_range_start, actual_range_end); +FROM _timescaledb_internal.dimension_calculate_default_range_open(9223372036854775807,10, 'int8') AS res(actual_range_start, actual_range_end); SELECT assert_equal(9223372036854775800::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end) -FROM _timescaledb_internal.dimension_calculate_default_range_open(9223372036854775806,10) AS res(actual_range_start, actual_range_end); +FROM _timescaledb_internal.dimension_calculate_default_range_open(9223372036854775806,10, 'int8') AS res(actual_range_start, actual_range_end); +--------------- +-- INT +--------------- +SELECT assert_equal(-9223372036854775808, actual_range_start), assert_equal(-2147483640::bigint, actual_range_end) +FROM _timescaledb_internal.dimension_calculate_default_range_open(-2147483648,10, 'int4') AS res(actual_range_start, actual_range_end); + +SELECT assert_equal(-9223372036854775808, actual_range_start), assert_equal(-2147483640::bigint, actual_range_end) +FROM _timescaledb_internal.dimension_calculate_default_range_open(-2147483647,10, 'int4') AS res(actual_range_start, actual_range_end); + +SELECT assert_equal(2147483640::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end) +FROM _timescaledb_internal.dimension_calculate_default_range_open(2147483647,10, 'int4') AS res(actual_range_start, actual_range_end); + +SELECT assert_equal(2147483640::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end) +FROM _timescaledb_internal.dimension_calculate_default_range_open(2147483646,10, 'int4') AS res(actual_range_start, actual_range_end); + +--------------- +-- SMALLINT +--------------- +SELECT assert_equal(-9223372036854775808, actual_range_start), assert_equal(-32760::bigint, actual_range_end) +FROM _timescaledb_internal.dimension_calculate_default_range_open(-32768,10, 'int2') AS res(actual_range_start, actual_range_end); + +SELECT assert_equal(-9223372036854775808, actual_range_start), assert_equal(-32760::bigint, actual_range_end) +FROM _timescaledb_internal.dimension_calculate_default_range_open(-32767,10, 'int2') AS res(actual_range_start, actual_range_end); + +SELECT assert_equal(32760::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end) +FROM _timescaledb_internal.dimension_calculate_default_range_open(32767,10, 'int2') AS res(actual_range_start, actual_range_end); + +SELECT assert_equal(32760::bigint, actual_range_start), assert_equal(9223372036854775807::bigint, actual_range_end) +FROM _timescaledb_internal.dimension_calculate_default_range_open(32766,10, 'int2') AS res(actual_range_start, actual_range_end); --closed diff --git a/test/sql/custom_type.sql.in b/test/sql/custom_type.sql.in index d4a1148f2..ca6bf13d9 100644 --- a/test/sql/custom_type.sql.in +++ b/test/sql/custom_type.sql.in @@ -26,49 +26,55 @@ CREATE TYPE customtype ( LIKE = TIMESTAMPTZ ); -CREATE CAST (customtype AS bigint) -WITHOUT FUNCTION AS ASSIGNMENT; -CREATE CAST (bigint AS customtype) -WITHOUT FUNCTION AS IMPLICIT; - -CREATE CAST (customtype AS timestamptz) -WITHOUT FUNCTION AS ASSIGNMENT; -CREATE CAST (timestamptz AS customtype) -WITHOUT FUNCTION AS ASSIGNMENT; CREATE OR REPLACE FUNCTION customtype_lt(customtype, customtype) RETURNS bool AS 'timestamp_lt' LANGUAGE internal IMMUTABLE STRICT; CREATE OPERATOR < ( - LEFTARG = customtype, - RIGHTARG = customtype, - PROCEDURE = customtype_lt, - COMMUTATOR = >, - NEGATOR = >=, - RESTRICT = scalarltsel, - JOIN = scalarltjoinsel + LEFTARG = customtype, + RIGHTARG = customtype, + PROCEDURE = customtype_lt, + COMMUTATOR = >, + NEGATOR = >=, + RESTRICT = scalarltsel, + JOIN = scalarltjoinsel ); CREATE OR REPLACE FUNCTION customtype_ge(customtype, customtype) RETURNS bool AS 'timestamp_ge' LANGUAGE internal IMMUTABLE STRICT; CREATE OPERATOR >= ( - LEFTARG = customtype, - RIGHTARG = customtype, - PROCEDURE = customtype_ge, - COMMUTATOR = <=, - NEGATOR = <, - RESTRICT = scalargtsel, - JOIN = scalargtjoinsel + LEFTARG = customtype, + RIGHTARG = customtype, + PROCEDURE = customtype_ge, + COMMUTATOR = <=, + NEGATOR = <, + RESTRICT = scalargtsel, + JOIN = scalargtjoinsel ); -\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER +CREATE CAST (customtype AS bigint) +WITHOUT FUNCTION AS ASSIGNMENT; +CREATE CAST (bigint AS customtype) +WITHOUT FUNCTION AS ASSIGNMENT; + +SET ROLE :ROLE_DEFAULT_PERM_USER; CREATE TABLE customtype_test(time_custom customtype, val int); SELECT create_hypertable('customtype_test', 'time_custom', chunk_time_interval => 10e6::bigint, create_default_indexes=>false); +\set ON_ERROR_STOP 0 INSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10); -INSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10); +\set ON_ERROR_STOP 1 + +-- Add implicit casts +RESET ROLE; +CREATE CAST (customtype AS timestamptz) +WITHOUT FUNCTION AS IMPLICIT; +CREATE CAST (timestamptz AS customtype) +WITHOUT FUNCTION AS IMPLICIT; +SET ROLE :ROLE_DEFAULT_PERM_USER; + INSERT INTO customtype_test VALUES ('2001-01-01 01:02:03'::customtype, 10); EXPLAIN (costs off) SELECT * FROM customtype_test; INSERT INTO customtype_test VALUES ('2001-01-01 01:02:23'::customtype, 11); diff --git a/test/sql/test_utils.sql b/test/sql/test_utils.sql index c5d2d583b..b0a47f8f3 100644 --- a/test/sql/test_utils.sql +++ b/test/sql/test_utils.sql @@ -11,8 +11,7 @@ CREATE OR REPLACE FUNCTION test.ptr_eq() RETURNS VOID AS :MODULE_PATHNAME, 'ts_test_utils_ptr_eq' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; CREATE OR REPLACE FUNCTION test.double_eq() RETURNS VOID AS :MODULE_PATHNAME, 'ts_test_utils_double_eq' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE; - -\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER; +SET ROLE :ROLE_DEFAULT_PERM_USER; -- We're testing that the test utils work and generate errors on -- failing conditions diff --git a/test/sql/timestamp_limits.sql b/test/sql/timestamp_limits.sql index 8c9e67b1f..d178a9a9b 100644 --- a/test/sql/timestamp_limits.sql +++ b/test/sql/timestamp_limits.sql @@ -15,6 +15,30 @@ AS :MODULE_PATHNAME, 'ts_timestamptz_min' LANGUAGE C VOLATILE; CREATE OR REPLACE FUNCTION test.end_ts_timestamptz() RETURNS TIMESTAMPTZ AS :MODULE_PATHNAME, 'ts_timestamptz_end' LANGUAGE C VOLATILE; +CREATE OR REPLACE FUNCTION test.internal_min_ts_timestamptz() RETURNS BIGINT +AS :MODULE_PATHNAME, 'ts_timestamptz_internal_min' LANGUAGE C VOLATILE; + +CREATE OR REPLACE FUNCTION test.internal_end_ts_timestamptz() RETURNS BIGINT +AS :MODULE_PATHNAME, 'ts_timestamptz_internal_end' LANGUAGE C VOLATILE; + +CREATE OR REPLACE FUNCTION test.min_pg_timestamp() RETURNS TIMESTAMP +AS :MODULE_PATHNAME, 'ts_timestamp_pg_min' LANGUAGE C VOLATILE; + +CREATE OR REPLACE FUNCTION test.end_pg_timestamp() RETURNS TIMESTAMP +AS :MODULE_PATHNAME, 'ts_timestamp_pg_end' LANGUAGE C VOLATILE; + +CREATE OR REPLACE FUNCTION test.min_ts_timestamp() RETURNS TIMESTAMP +AS :MODULE_PATHNAME, 'ts_timestamp_min' LANGUAGE C VOLATILE; + +CREATE OR REPLACE FUNCTION test.end_ts_timestamp() RETURNS TIMESTAMP +AS :MODULE_PATHNAME, 'ts_timestamp_end' LANGUAGE C VOLATILE; + +CREATE OR REPLACE FUNCTION test.internal_min_ts_timestamp() RETURNS BIGINT +AS :MODULE_PATHNAME, 'ts_timestamp_internal_min' LANGUAGE C VOLATILE; + +CREATE OR REPLACE FUNCTION test.internal_end_ts_timestamp() RETURNS BIGINT +AS :MODULE_PATHNAME, 'ts_timestamp_internal_end' LANGUAGE C VOLATILE; + CREATE OR REPLACE FUNCTION test.min_pg_date() RETURNS DATE AS :MODULE_PATHNAME, 'ts_date_pg_min' LANGUAGE C VOLATILE; @@ -26,16 +50,133 @@ AS :MODULE_PATHNAME, 'ts_date_min' LANGUAGE C VOLATILE; CREATE OR REPLACE FUNCTION test.end_ts_date() RETURNS DATE AS :MODULE_PATHNAME, 'ts_date_end' LANGUAGE C VOLATILE; + +CREATE OR REPLACE FUNCTION test.internal_min_ts_date() RETURNS BIGINT +AS :MODULE_PATHNAME, 'ts_date_internal_min' LANGUAGE C VOLATILE; + +CREATE OR REPLACE FUNCTION test.internal_end_ts_date() RETURNS BIGINT +AS :MODULE_PATHNAME, 'ts_date_internal_end' LANGUAGE C VOLATILE; + SET ROLE :ROLE_DEFAULT_PERM_USER; --show PG and TimescaleDB-specific time boundaries. Note that we --internally convert to UNIX epoch, which restricts the range of --supported timestamps. SET datestyle TO 'ISO,YMD'; -SELECT test.min_pg_timestamptz(), test.end_pg_timestamptz(), test.min_ts_timestamptz(), test.end_ts_timestamptz(); +\x + +--To display end timestamps (which aren't valid timestamps), we need +--to be at UTC to avoid timezone conversion which would fail with +--out-of-range error. Being at UTC also makes it easier to compare +--TIMESTAMP and TIMESTAMP WITH TIME ZONE. +SET timezone TO 'UTC'; + +-- MIN values (PostgreSQL). +SELECT test.min_pg_timestamptz(), + test.min_pg_timestamp(), + test.min_pg_date(); + +-- MIN values (TimescaleDB). +SELECT test.min_ts_timestamptz(), + test.min_ts_timestamp(), + test.min_ts_date(), + _timescaledb_internal.to_timestamp(test.internal_min_ts_timestamptz()) AS min_ts_internal_timestamptz, + _timescaledb_internal.to_timestamp_without_timezone(test.internal_min_ts_timestamp()) AS min_ts_internal_timestamp, + _timescaledb_internal.to_date(test.internal_min_ts_date()) AS min_ts_internal_date; + +-- END values (PostgreSQL). Note that and values aren't valid +-- timestamps or dates (since, e.g., END_TIMESTAMP is exclusive). It +-- is possible to display them at UTC since no conversion is made. +SELECT test.end_pg_timestamptz(), + test.end_pg_timestamp(), + test.end_pg_date(); + +-- END values (TimescaleDB). Note that we convert DATE to TIMESTAMP +-- internally, and that limits the end to that of timestamp. +SELECT test.end_ts_timestamptz(), + test.end_ts_timestamp(), + test.end_ts_date(), + _timescaledb_internal.to_timestamp(test.internal_end_ts_timestamptz()) AS end_ts_internal_timestamptz, + _timescaledb_internal.to_timestamp_without_timezone(test.internal_end_ts_timestamp()) AS end_ts_internal_timestamp, + _timescaledb_internal.to_date(test.internal_end_ts_date()) AS end_ts_internal_date; + +--Test insert of time values close to or at limits +--Suitable constraints should be created on chunks +CREATE TABLE smallint_table(time smallint); +SELECT create_hypertable('smallint_table', 'time', chunk_time_interval=>10); +INSERT INTO smallint_table VALUES (-32768), (32767); + +SELECT chunk, pg_get_constraintdef(c.oid) +FROM show_chunks('smallint_table') chunk, pg_constraint c +WHERE c.conrelid = chunk +AND c.contype = 'c'; + +CREATE TABLE int_table(time int); +SELECT create_hypertable('int_table', 'time', chunk_time_interval=>10); +INSERT INTO int_table VALUES (-2147483648), (2147483647); + +SELECT chunk, pg_get_constraintdef(c.oid) +FROM show_chunks('int_table') chunk, pg_constraint c +WHERE c.conrelid = chunk +AND c.contype = 'c'; + +CREATE TABLE bigint_table(time bigint); +SELECT create_hypertable('bigint_table', 'time', chunk_time_interval=>10); +INSERT INTO bigint_table VALUES (-9223372036854775808), (9223372036854775807); + +SELECT chunk, pg_get_constraintdef(c.oid) +FROM show_chunks('bigint_table') chunk, pg_constraint c +WHERE c.conrelid = chunk +AND c.contype = 'c'; + +CREATE TABLE date_table(time date); +SELECT create_hypertable('date_table', 'time'); +INSERT INTO date_table VALUES (test.min_ts_date()), (test.end_ts_date() - INTERVAL '1 day'); +-- Test out-of-range dates +\set ON_ERROR_STOP 0 +INSERT INTO date_table VALUES (test.min_ts_date() - INTERVAL '1 day'); +INSERT INTO date_table VALUES (test.end_ts_date()); +\set ON_ERROR_STOP 1 + +SELECT chunk, pg_get_constraintdef(c.oid) +FROM show_chunks('date_table') chunk, pg_constraint c +WHERE c.conrelid = chunk +AND c.contype = 'c'; + +CREATE TABLE timestamp_table(time timestamp); +SELECT create_hypertable('timestamp_table', 'time'); + +INSERT INTO timestamp_table VALUES (test.min_ts_timestamp()); +INSERT INTO timestamp_table VALUES (test.end_ts_timestamp() - INTERVAL '1 microsecond'); + +-- Test out-of-range timestamps +\set ON_ERROR_STOP 0 +INSERT INTO timestamp_table VALUES (test.min_ts_timestamp() - INTERVAL '1 microsecond'); +INSERT INTO timestamp_table VALUES (test.end_ts_timestamp()); +\set ON_ERROR_STOP 1 + +SELECT chunk, pg_get_constraintdef(c.oid) +FROM show_chunks('timestamp_table') chunk, pg_constraint c +WHERE c.conrelid = chunk +AND c.contype = 'c'; + +CREATE TABLE timestamptz_table(time timestamp); +SELECT create_hypertable('timestamptz_table', 'time'); + +-- Need to be at UTC for this to work for timestamp with time zone +INSERT INTO timestamptz_table VALUES (test.min_ts_timestamptz()); +INSERT INTO timestamptz_table VALUES (test.end_ts_timestamptz() - INTERVAL '1 microsecond'); + +-- Test out-of-range timestamps +\set ON_ERROR_STOP 0 +INSERT INTO timestamptz_table VALUES (test.min_ts_timestamptz() - INTERVAL '1 microsecond'); +INSERT INTO timestamptz_table VALUES (test.end_ts_timestamptz()); +\set ON_ERROR_STOP 1 + +SELECT chunk, pg_get_constraintdef(c.oid) +FROM show_chunks('timestamptz_table') chunk, pg_constraint c +WHERE c.conrelid = chunk +AND c.contype = 'c'; ---TimescaleDB-specific dates should be consistent with timestamps ---since we internally first convert dates to timestamps and then to ---UNIX epoch. -SELECT test.min_pg_date(), test.end_pg_date(), test.min_ts_date(), test.end_ts_date(); RESET datestyle; +RESET timezone; diff --git a/test/src/CMakeLists.txt b/test/src/CMakeLists.txt index d92e8c6fc..972108e63 100644 --- a/test/src/CMakeLists.txt +++ b/test/src/CMakeLists.txt @@ -3,6 +3,7 @@ set(SOURCES symbol_conflict.c test_time_to_internal.c test_with_clause_parser.c + test_time_utils.c test_utils.c ) diff --git a/test/src/test_time_to_internal.c b/test/src/test_time_to_internal.c index 87413c98f..539caf56f 100644 --- a/test/src/test_time_to_internal.c +++ b/test/src/test_time_to_internal.c @@ -11,6 +11,7 @@ #include "export.h" +#include "time_utils.h" #include "utils.h" #include "test_utils.h" @@ -111,16 +112,17 @@ ts_test_time_to_internal_conversion(PG_FUNCTION_ARGS) ts_time_value_to_internal(ts_internal_to_time_value(i64, TIMESTAMPOID), TIMESTAMPOID)); - TestAssertInt64Eq(PG_INT64_MIN, - ts_time_value_to_internal(Int64GetDatum(DT_NOBEGIN), TIMESTAMPOID)); - TestAssertInt64Eq(PG_INT64_MAX, - ts_time_value_to_internal(Int64GetDatum(DT_NOEND), TIMESTAMPOID)); + TestAssertInt64Eq(TS_TIME_NOBEGIN, + ts_time_value_to_internal(TimestampGetDatum(DT_NOBEGIN), TIMESTAMPOID)); + TestAssertInt64Eq(TS_TIME_NOEND, + ts_time_value_to_internal(TimestampGetDatum(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(DT_NOBEGIN, + DatumGetTimestamp(ts_internal_to_time_value(PG_INT64_MIN, TIMESTAMPOID))); + TestEnsureError(ts_internal_to_time_value(TS_TIMESTAMP_INTERNAL_MIN - 1, TIMESTAMPOID)); - TestAssertInt64Eq(TS_INTERNAL_TIMESTAMP_MIN, - ts_time_value_to_internal(ts_internal_to_time_value(TS_INTERNAL_TIMESTAMP_MIN, + TestAssertInt64Eq(TS_TIMESTAMP_INTERNAL_MIN, + ts_time_value_to_internal(ts_internal_to_time_value(TS_TIMESTAMP_INTERNAL_MIN, TIMESTAMPOID), TIMESTAMPOID)); @@ -145,16 +147,17 @@ ts_test_time_to_internal_conversion(PG_FUNCTION_ARGS) ts_time_value_to_internal(ts_internal_to_time_value(i64, TIMESTAMPTZOID), TIMESTAMPTZOID)); - TestAssertInt64Eq(PG_INT64_MIN, - ts_time_value_to_internal(Int64GetDatum(DT_NOBEGIN), TIMESTAMPTZOID)); - TestAssertInt64Eq(PG_INT64_MAX, - ts_time_value_to_internal(Int64GetDatum(DT_NOEND), TIMESTAMPTZOID)); + TestAssertInt64Eq(TS_TIME_NOBEGIN, + ts_time_value_to_internal(TimestampTzGetDatum(DT_NOBEGIN), TIMESTAMPTZOID)); + TestAssertInt64Eq(TS_TIME_NOEND, + ts_time_value_to_internal(TimestampTzGetDatum(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(DT_NOBEGIN, + DatumGetTimestampTz(ts_internal_to_time_value(PG_INT64_MIN, TIMESTAMPTZOID))); + TestEnsureError(ts_internal_to_time_value(TS_TIMESTAMP_INTERNAL_MIN - 1, TIMESTAMPTZOID)); - TestAssertInt64Eq(TS_INTERNAL_TIMESTAMP_MIN, - ts_time_value_to_internal(ts_internal_to_time_value(TS_INTERNAL_TIMESTAMP_MIN, + TestAssertInt64Eq(TS_TIMESTAMP_INTERNAL_MIN, + ts_time_value_to_internal(ts_internal_to_time_value(TS_TIMESTAMP_INTERNAL_MIN, TIMESTAMPTZOID), TIMESTAMPTZOID)); @@ -169,8 +172,10 @@ ts_test_time_to_internal_conversion(PG_FUNCTION_ARGS) TestAssertInt64Eq(i64, ts_time_value_to_internal(ts_internal_to_time_value(i64, DATEOID), 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)); + TestAssertInt64Eq(DATEVAL_NOBEGIN, + DatumGetDateADT(ts_internal_to_time_value(PG_INT64_MIN, DATEOID))); + TestAssertInt64Eq(DATEVAL_NOEND, + DatumGetDateADT(ts_internal_to_time_value(PG_INT64_MAX, DATEOID))); TestEnsureError(ts_time_value_to_internal(DateADTGetDatum(DATEVAL_NOBEGIN + 1), DATEOID)); TestEnsureError(ts_time_value_to_internal(DateADTGetDatum(DATEVAL_NOEND - 1), DATEOID)); @@ -282,70 +287,3 @@ ts_test_interval_to_internal_conversion(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } - -/* - * Functions to show TimescaleDB-specific limits of timestamps and dates: - */ -TS_FUNCTION_INFO_V1(ts_timestamptz_pg_min); - -Datum -ts_timestamptz_pg_min(PG_FUNCTION_ARGS) -{ - PG_RETURN_TIMESTAMPTZ(MIN_TIMESTAMP); -} - -TS_FUNCTION_INFO_V1(ts_timestamptz_pg_end); - -Datum -ts_timestamptz_pg_end(PG_FUNCTION_ARGS) -{ - PG_RETURN_TIMESTAMPTZ(END_TIMESTAMP); -} - -TS_FUNCTION_INFO_V1(ts_timestamptz_min); - -Datum -ts_timestamptz_min(PG_FUNCTION_ARGS) -{ - PG_RETURN_TIMESTAMPTZ(TS_TIMESTAMP_MIN); -} - -TS_FUNCTION_INFO_V1(ts_timestamptz_end); - -Datum -ts_timestamptz_end(PG_FUNCTION_ARGS) -{ - PG_RETURN_TIMESTAMPTZ(TS_TIMESTAMP_END); -} - -TS_FUNCTION_INFO_V1(ts_date_pg_min); - -Datum -ts_date_pg_min(PG_FUNCTION_ARGS) -{ - PG_RETURN_DATEADT(DATETIME_MIN_JULIAN - POSTGRES_EPOCH_JDATE); -} - -TS_FUNCTION_INFO_V1(ts_date_pg_end); - -Datum -ts_date_pg_end(PG_FUNCTION_ARGS) -{ - PG_RETURN_DATEADT(DATE_END_JULIAN - POSTGRES_EPOCH_JDATE); -} - -TS_FUNCTION_INFO_V1(ts_date_min); - -Datum -ts_date_min(PG_FUNCTION_ARGS) -{ - PG_RETURN_DATEADT(TS_DATE_MIN); -} - -TS_FUNCTION_INFO_V1(ts_date_end); - -Datum -ts_date_end(PG_FUNCTION_ARGS) -{ - PG_RETURN_DATEADT(TS_DATE_END); -} diff --git a/test/src/test_time_utils.c b/test/src/test_time_utils.c new file mode 100644 index 000000000..7cbd5e465 --- /dev/null +++ b/test/src/test_time_utils.c @@ -0,0 +1,321 @@ +/* + * This file and its contents are licensed under the Apache License 2.0. + * Please see the included NOTICE for copyright information and + * LICENSE-APACHE for a copy of the license. + */ +#include +#include +#include +#include +#include + +#include +#include + +#include "test_utils.h" + +/* + * Functions to show TimescaleDB-specific limits of timestamps and dates: + */ + +/* + * TIMESTAMP WITH TIME ZONE + */ +TS_FUNCTION_INFO_V1(ts_timestamptz_pg_min); + +Datum +ts_timestamptz_pg_min(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMPTZ(MIN_TIMESTAMP); +} + +TS_FUNCTION_INFO_V1(ts_timestamptz_pg_end); + +Datum +ts_timestamptz_pg_end(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMPTZ(END_TIMESTAMP); +} + +TS_FUNCTION_INFO_V1(ts_timestamptz_min); + +Datum +ts_timestamptz_min(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMPTZ(TS_TIMESTAMP_MIN); +} + +TS_FUNCTION_INFO_V1(ts_timestamptz_end); + +Datum +ts_timestamptz_end(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMPTZ(TS_TIMESTAMP_END); +} + +TS_FUNCTION_INFO_V1(ts_timestamptz_internal_min); + +Datum +ts_timestamptz_internal_min(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(TS_TIMESTAMP_INTERNAL_MIN); +} + +TS_FUNCTION_INFO_V1(ts_timestamptz_internal_end); + +Datum +ts_timestamptz_internal_end(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(TS_TIMESTAMP_INTERNAL_END); +} + +/* + * TIMESTAMP + */ +TS_FUNCTION_INFO_V1(ts_timestamp_pg_min); + +Datum +ts_timestamp_pg_min(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMP(MIN_TIMESTAMP); +} + +TS_FUNCTION_INFO_V1(ts_timestamp_pg_end); + +Datum +ts_timestamp_pg_end(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMP(END_TIMESTAMP); +} + +TS_FUNCTION_INFO_V1(ts_timestamp_min); + +Datum +ts_timestamp_min(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMP(TS_TIMESTAMP_MIN); +} + +TS_FUNCTION_INFO_V1(ts_timestamp_end); + +Datum +ts_timestamp_end(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMP(TS_TIMESTAMP_END); +} + +TS_FUNCTION_INFO_V1(ts_timestamp_internal_min); + +Datum +ts_timestamp_internal_min(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(TS_TIMESTAMP_INTERNAL_MIN); +} + +TS_FUNCTION_INFO_V1(ts_timestamp_internal_end); + +Datum +ts_timestamp_internal_end(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(TS_TIMESTAMP_INTERNAL_END); +} + +/* + * DATE + */ +TS_FUNCTION_INFO_V1(ts_date_pg_min); + +Datum +ts_date_pg_min(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATEADT(DATETIME_MIN_JULIAN - POSTGRES_EPOCH_JDATE); +} + +TS_FUNCTION_INFO_V1(ts_date_pg_end); + +Datum +ts_date_pg_end(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATEADT(DATE_END_JULIAN - POSTGRES_EPOCH_JDATE); +} + +TS_FUNCTION_INFO_V1(ts_date_min); + +Datum +ts_date_min(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATEADT(TS_DATE_MIN); +} + +TS_FUNCTION_INFO_V1(ts_date_end); + +Datum +ts_date_end(PG_FUNCTION_ARGS) +{ + PG_RETURN_DATEADT(TS_DATE_END); +} + +TS_FUNCTION_INFO_V1(ts_date_internal_min); + +Datum +ts_date_internal_min(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(TS_DATE_INTERNAL_MIN); +} + +TS_FUNCTION_INFO_V1(ts_date_internal_end); + +Datum +ts_date_internal_end(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(TS_DATE_INTERNAL_END); +} + +TS_FUNCTION_INFO_V1(ts_test_time_utils); + +Datum +ts_test_time_utils(PG_FUNCTION_ARGS) +{ + TestAssertInt64Eq(ts_time_get_min(INT8OID), PG_INT64_MIN); + TestAssertInt64Eq(ts_time_get_max(INT8OID), PG_INT64_MAX); + TestAssertInt64Eq(ts_time_get_end_or_max(INT8OID), PG_INT64_MAX); + TestEnsureError(ts_time_get_end(INT8OID)); + TestEnsureError(ts_time_get_nobegin(INT8OID)); + TestEnsureError(ts_time_get_noend(INT8OID)); + TestAssertInt64Eq(DatumGetInt64(ts_time_datum_get_min(INT8OID)), PG_INT64_MIN); + TestAssertInt64Eq(DatumGetInt64(ts_time_datum_get_max(INT8OID)), PG_INT64_MAX); + TestEnsureError(ts_time_datum_get_end(INT8OID)); + TestEnsureError(ts_time_datum_get_nobegin(INT8OID)); + TestEnsureError(ts_time_datum_get_noend(INT8OID)); + + TestAssertInt64Eq(ts_time_get_min(INT4OID), PG_INT32_MIN); + TestAssertInt64Eq(ts_time_get_max(INT4OID), PG_INT32_MAX); + TestAssertInt64Eq(ts_time_get_end_or_max(INT4OID), PG_INT32_MAX); + TestEnsureError(ts_time_get_end(INT4OID)); + TestEnsureError(ts_time_get_nobegin(INT4OID)); + TestEnsureError(ts_time_get_noend(INT4OID)); + TestAssertInt64Eq(DatumGetInt64(ts_time_datum_get_min(INT4OID)), PG_INT32_MIN); + TestAssertInt64Eq(DatumGetInt64(ts_time_datum_get_max(INT4OID)), PG_INT32_MAX); + TestEnsureError(ts_time_datum_get_end(INT4OID)); + TestEnsureError(ts_time_datum_get_nobegin(INT4OID)); + TestEnsureError(ts_time_datum_get_noend(INT4OID)); + + TestAssertInt64Eq(ts_time_get_min(INT2OID), PG_INT16_MIN); + TestAssertInt64Eq(ts_time_get_max(INT2OID), PG_INT16_MAX); + TestAssertInt64Eq(ts_time_get_end_or_max(INT2OID), PG_INT16_MAX); + TestEnsureError(ts_time_get_end(INT2OID)); + TestEnsureError(ts_time_get_nobegin(INT2OID)); + TestEnsureError(ts_time_get_noend(INT2OID)); + TestAssertInt64Eq(DatumGetInt64(ts_time_datum_get_min(INT2OID)), PG_INT16_MIN); + TestAssertInt64Eq(DatumGetInt64(ts_time_datum_get_max(INT2OID)), PG_INT16_MAX); + TestEnsureError(ts_time_datum_get_end(INT2OID)); + TestEnsureError(ts_time_datum_get_nobegin(INT2OID)); + TestEnsureError(ts_time_datum_get_noend(INT2OID)); + + TestAssertInt64Eq(ts_time_get_min(TIMESTAMPOID), TS_TIMESTAMP_INTERNAL_MIN); + TestAssertInt64Eq(ts_time_get_max(TIMESTAMPOID), TS_TIMESTAMP_INTERNAL_MAX); + TestAssertInt64Eq(ts_time_get_end(TIMESTAMPOID), TS_TIMESTAMP_INTERNAL_END); + TestAssertInt64Eq(ts_time_get_end_or_max(TIMESTAMPOID), TS_TIMESTAMP_INTERNAL_END); + TestAssertInt64Eq(ts_time_get_nobegin(TIMESTAMPOID), TS_TIME_NOBEGIN); + TestAssertInt64Eq(ts_time_get_noend(TIMESTAMPOID), TS_TIME_NOEND); + TestAssertInt64Eq(DatumGetTimestamp(ts_time_datum_get_min(TIMESTAMPOID)), TS_TIMESTAMP_MIN); + TestAssertInt64Eq(DatumGetTimestamp(ts_time_datum_get_end(TIMESTAMPOID)), TS_TIMESTAMP_END); + TestAssertInt64Eq(DatumGetTimestamp(ts_time_datum_get_nobegin(TIMESTAMPOID)), DT_NOBEGIN); + TestAssertInt64Eq(DatumGetTimestamp(ts_time_datum_get_noend(TIMESTAMPOID)), DT_NOEND); + + TestAssertInt64Eq(ts_time_get_min(TIMESTAMPTZOID), TS_TIMESTAMP_INTERNAL_MIN); + TestAssertInt64Eq(ts_time_get_max(TIMESTAMPTZOID), TS_TIMESTAMP_INTERNAL_MAX); + TestAssertInt64Eq(ts_time_get_end(TIMESTAMPTZOID), TS_TIMESTAMP_INTERNAL_END); + TestAssertInt64Eq(ts_time_get_end_or_max(TIMESTAMPTZOID), TS_TIMESTAMP_INTERNAL_END); + TestAssertInt64Eq(ts_time_get_nobegin(TIMESTAMPTZOID), TS_TIME_NOBEGIN); + TestAssertInt64Eq(ts_time_get_noend(TIMESTAMPTZOID), TS_TIME_NOEND); + TestAssertInt64Eq(DatumGetTimestampTz(ts_time_datum_get_min(TIMESTAMPTZOID)), TS_TIMESTAMP_MIN); + TestAssertInt64Eq(DatumGetTimestampTz(ts_time_datum_get_end(TIMESTAMPTZOID)), TS_TIMESTAMP_END); + TestAssertInt64Eq(DatumGetTimestampTz(ts_time_datum_get_nobegin(TIMESTAMPTZOID)), DT_NOBEGIN); + TestAssertInt64Eq(DatumGetTimestampTz(ts_time_datum_get_noend(TIMESTAMPTZOID)), DT_NOEND); + + TestAssertInt64Eq(ts_time_get_min(DATEOID), TS_DATE_INTERNAL_MIN); + TestAssertInt64Eq(ts_time_get_max(DATEOID), TS_DATE_INTERNAL_MAX); + TestAssertInt64Eq(ts_time_get_end(DATEOID), TS_DATE_INTERNAL_END); + TestAssertInt64Eq(ts_time_get_end_or_max(DATEOID), TS_DATE_INTERNAL_END); + TestAssertInt64Eq(ts_time_get_nobegin(DATEOID), TS_TIME_NOBEGIN); + TestAssertInt64Eq(ts_time_get_noend(DATEOID), TS_TIME_NOEND); + TestAssertInt64Eq(DatumGetDateADT(ts_time_datum_get_min(DATEOID)), TS_DATE_MIN); + TestAssertInt64Eq(DatumGetDateADT(ts_time_datum_get_end(DATEOID)), TS_DATE_END); + TestAssertInt64Eq(DatumGetDateADT(ts_time_datum_get_nobegin(DATEOID)), DATEVAL_NOBEGIN); + TestAssertInt64Eq(DatumGetDateADT(ts_time_datum_get_noend(DATEOID)), DATEVAL_NOEND); + + /* Test boundary routines with unsupported time type */ + TestEnsureError(ts_time_get_min(NUMERICOID)); + TestEnsureError(ts_time_get_max(NUMERICOID)); + TestEnsureError(ts_time_get_end(NUMERICOID)); + TestEnsureError(ts_time_get_nobegin(NUMERICOID)); + TestEnsureError(ts_time_get_noend(NUMERICOID)); + TestEnsureError(ts_time_datum_get_min(NUMERICOID)); + TestEnsureError(ts_time_datum_get_end(NUMERICOID)); + TestEnsureError(ts_time_datum_get_nobegin(NUMERICOID)); + TestEnsureError(ts_time_datum_get_noend(NUMERICOID)); + + /* Test conversion of min, end, nobegin, and noend between native and + * internal (Unix) time */ + + TestAssertInt64Eq(ts_time_value_to_internal(ts_time_datum_get_min(INT2OID), INT2OID), + ts_time_get_min(INT2OID)); + TestAssertInt64Eq(ts_time_value_to_internal(ts_time_datum_get_min(INT4OID), INT4OID), + ts_time_get_min(INT4OID)); + TestAssertInt64Eq(ts_time_value_to_internal(ts_time_datum_get_min(INT8OID), INT8OID), + ts_time_get_min(INT8OID)); + TestAssertInt64Eq(ts_time_value_to_internal(ts_time_datum_get_min(DATEOID), DATEOID), + ts_time_get_min(DATEOID)); + TestAssertInt64Eq(ts_time_value_to_internal(ts_time_datum_get_min(TIMESTAMPOID), TIMESTAMPOID), + ts_time_get_min(TIMESTAMPOID)); + TestAssertInt64Eq(ts_time_value_to_internal(ts_time_datum_get_min(TIMESTAMPTZOID), + TIMESTAMPTZOID), + ts_time_get_min(TIMESTAMPTZOID)); + + /* Test saturating addition */ + TestAssertInt64Eq(ts_time_saturating_add(ts_time_get_max(INT2OID), 1, INT2OID), + ts_time_get_max(INT2OID)); + TestAssertInt64Eq(ts_time_saturating_add(ts_time_get_max(INT4OID), 1, INT4OID), + ts_time_get_max(INT4OID)); + TestAssertInt64Eq(ts_time_saturating_add(ts_time_get_max(INT8OID), 1, INT8OID), + ts_time_get_max(INT8OID)); + TestAssertInt64Eq(ts_time_saturating_add(ts_time_get_max(DATEOID), 1, DATEOID), + ts_time_get_noend(DATEOID)); + TestAssertInt64Eq(ts_time_saturating_add(ts_time_get_end(DATEOID) - 1, 1, DATEOID), + ts_time_get_noend(DATEOID)); + TestAssertInt64Eq(ts_time_saturating_add(ts_time_get_end(TIMESTAMPOID) - 1, 1, TIMESTAMPOID), + ts_time_get_noend(TIMESTAMPOID)); + TestAssertInt64Eq(ts_time_saturating_add(ts_time_get_end(TIMESTAMPTZOID) - 1, 1, TIMESTAMPOID), + ts_time_get_noend(TIMESTAMPOID)); + TestAssertInt64Eq(ts_time_saturating_add(ts_time_get_end(DATEOID) - 2, 1, DATEOID), + ts_time_get_max(DATEOID)); + TestAssertInt64Eq(ts_time_saturating_add(ts_time_get_end(TIMESTAMPOID) - 2, 1, TIMESTAMPOID), + ts_time_get_max(TIMESTAMPOID)); + TestAssertInt64Eq(ts_time_saturating_add(ts_time_get_end(TIMESTAMPTZOID) - 2, 1, TIMESTAMPOID), + ts_time_get_max(TIMESTAMPOID)); + + /* Test saturating subtraction */ + TestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(INT2OID), 1, INT2OID), + ts_time_get_min(INT2OID)); + TestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(INT4OID), 1, INT4OID), + ts_time_get_min(INT4OID)); + TestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(INT8OID), 1, INT8OID), + ts_time_get_min(INT8OID)); + TestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(DATEOID), 1, DATEOID), + ts_time_get_nobegin(DATEOID)); + TestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(TIMESTAMPOID), 1, TIMESTAMPOID), + ts_time_get_nobegin(TIMESTAMPOID)); + TestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(TIMESTAMPTZOID), 1, TIMESTAMPTZOID), + ts_time_get_nobegin(TIMESTAMPTZOID)); + TestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(DATEOID) + 1, 1, DATEOID), + ts_time_get_min(DATEOID)); + TestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(TIMESTAMPOID) + 1, 1, TIMESTAMPOID), + ts_time_get_min(TIMESTAMPOID)); + TestAssertInt64Eq(ts_time_saturating_sub(ts_time_get_min(TIMESTAMPTZOID) + 1, + 1, + TIMESTAMPTZOID), + ts_time_get_min(TIMESTAMPTZOID)); + + PG_RETURN_VOID(); +} diff --git a/tsl/src/continuous_aggs/create.c b/tsl/src/continuous_aggs/create.c index 4b85be8a0..8c47956fa 100644 --- a/tsl/src/continuous_aggs/create.c +++ b/tsl/src/continuous_aggs/create.c @@ -74,6 +74,7 @@ #include "dimension.h" #include "continuous_agg.h" #include "options.h" +#include "time_utils.h" #include "utils.h" #include "errors.h" @@ -524,7 +525,7 @@ mattablecolumninfo_create_materialization_table(MatTableColumnInfo *matcolinfo, /* Add an infinite invalidation for the continuous aggregate. This is the * initial state of the aggregate before any refreshes. */ - invalidation_cagg_log_add_entry(mat_htid, current_time, PG_INT64_MIN, PG_INT64_MAX); + invalidation_cagg_log_add_entry(mat_htid, current_time, TS_TIME_NOBEGIN, TS_TIME_NOEND); ts_cache_release(hcache); return mat_htid; diff --git a/tsl/src/continuous_aggs/materialize.c b/tsl/src/continuous_aggs/materialize.c index 80f048e27..9b4f7d21c 100644 --- a/tsl/src/continuous_aggs/materialize.c +++ b/tsl/src/continuous_aggs/materialize.c @@ -32,6 +32,7 @@ #include "utils.h" #include "time_bucket.h" +#include "time_utils.h" #include "continuous_aggs/materialize.h" #include "continuous_aggs/invalidation_threshold.h" @@ -98,49 +99,10 @@ continuous_agg_materialize_window_max(Oid timetype) { InternalTimeRange maxrange = { .type = timetype, + .start = ts_time_get_min(timetype), + .end = ts_time_get_end_or_max(timetype), }; - /* PG checks for valid timestamps and dates in the range MIN <= time < - * END. So, for those types we subtract 1 from the MAX to get into the - * valid range. The MAX values account for ability to convert to UNIX time - * without overflow. */ - switch (timetype) - { - case DATEOID: - /* Since our internal conversion turns a date into a timestamp, - * dates are governed by the same limits as timestamps. This is - * probably a limitation we want to do away with, even though it - * has little effect in practice. */ - maxrange.start = TS_DATE_MIN; - maxrange.end = TS_DATE_END - 1; - break; - case TIMESTAMPTZOID: - case TIMESTAMPOID: - maxrange.start = TS_TIMESTAMP_MIN; - maxrange.end = TS_TIMESTAMP_END - 1; - break; - /* There is no "date"/"time" range for integers so they can take - * any value. */ - case INT8OID: - maxrange.start = PG_INT64_MIN; - maxrange.end = PG_INT64_MAX; - break; - case INT4OID: - maxrange.start = PG_INT32_MIN; - maxrange.end = PG_INT32_MAX; - break; - case INT2OID: - maxrange.start = PG_INT16_MIN; - maxrange.end = PG_INT16_MAX; - break; - default: - elog(ERROR, "unrecognized time type %d", timetype); - break; - } - - maxrange.start = ts_time_value_to_internal(Int64GetDatum(maxrange.start), timetype); - maxrange.end = ts_time_value_to_internal(Int64GetDatum(maxrange.end), timetype); - return maxrange; } diff --git a/tsl/src/continuous_aggs/refresh.c b/tsl/src/continuous_aggs/refresh.c index 17450fb48..417c1cc17 100644 --- a/tsl/src/continuous_aggs/refresh.c +++ b/tsl/src/continuous_aggs/refresh.c @@ -66,9 +66,9 @@ get_largest_bucketed_window(Oid timetype, int64 bucket_width) /* For the MIN value, the corresponding bucket either falls on the exact * MIN or it will be below it. Therefore, we add (bucket_width - 1) to * move to the next bucket to be within the allowed range. */ - maxwindow.start = maxwindow.start + bucket_width - 1; + maxwindow.start = ts_time_saturating_add(maxwindow.start, bucket_width - 1, timetype); maxbuckets.start = ts_time_bucket_by_type(bucket_width, maxwindow.start, timetype); - maxbuckets.end = ts_time_bucket_by_type(bucket_width, maxwindow.end, timetype); + maxbuckets.end = ts_time_get_end_or_max(timetype); return maxbuckets; } @@ -109,12 +109,12 @@ compute_bucketed_refresh_window(const InternalTimeRange *refresh_window, int64 b * we are at the start of the bucket (we don't want to remove a * bucket). The last */ if (result.end > result.start) - exclusive_end = int64_saturating_sub(result.end, 1); + exclusive_end = ts_time_saturating_sub(result.end, 1, result.type); bucketed_end = ts_time_bucket_by_type(bucket_width, exclusive_end, result.type); /* We get the time value for the start of the bucket, so need to add * bucket_width to get the end of it */ - result.end = bucketed_end + bucket_width; + result.end = ts_time_saturating_add(bucketed_end, bucket_width, refresh_window->type); } return result; @@ -172,8 +172,34 @@ continuous_agg_refresh_execute(const CaggRefreshState *refresh, } static void -continuous_agg_refresh_with_window(ContinuousAgg *cagg, const InternalTimeRange *refresh_window, - InvalidationStore *invalidations) +log_refresh_window(int elevel, const ContinuousAgg *cagg, const InternalTimeRange *refresh_window, + const char *msg) +{ + Datum start_ts; + Datum end_ts; + Oid outfuncid = InvalidOid; + bool isvarlena; + + if (client_min_messages > elevel) + return; + + start_ts = ts_internal_to_time_value(refresh_window->start, refresh_window->type); + end_ts = ts_internal_to_time_value(refresh_window->end, refresh_window->type); + getTypeOutputInfo(refresh_window->type, &outfuncid, &isvarlena); + Assert(!isvarlena); + + elog(elevel, + "%s \"%s\" in window [ %s, %s ]", + msg, + NameStr(cagg->data.user_view_name), + DatumGetCString(OidFunctionCall1(outfuncid, start_ts)), + DatumGetCString(OidFunctionCall1(outfuncid, end_ts))); +} + +static void +continuous_agg_refresh_with_window(const ContinuousAgg *cagg, + const InternalTimeRange *refresh_window, + const InvalidationStore *invalidations) { CaggRefreshState refresh; TupleTableSlot *slot; @@ -200,30 +226,12 @@ continuous_agg_refresh_with_window(ContinuousAgg *cagg, const InternalTimeRange .start = DatumGetInt64(start), /* Invalidations are inclusive at the end, while refresh windows * aren't, so add one to the end of the invalidated region */ - .end = int64_saturating_add(DatumGetInt64(end), 1), + .end = ts_time_saturating_add(DatumGetInt64(end), 1, refresh_window->type), }; InternalTimeRange bucketed_refresh_window = compute_bucketed_refresh_window(&invalidation, cagg->data.bucket_width); - if (client_min_messages <= DEBUG1) - { - Datum start_ts = - ts_internal_to_time_value(bucketed_refresh_window.start, refresh_window->type); - Datum end_ts = - ts_internal_to_time_value(bucketed_refresh_window.end, refresh_window->type); - Oid outfuncid = InvalidOid; - bool isvarlena; - - getTypeOutputInfo(refresh_window->type, &outfuncid, &isvarlena); - Assert(!isvarlena); - - elog(DEBUG1, - "refreshing continuous aggregate \"%s\" in window [ %s, %s ]", - NameStr(cagg->data.user_view_name), - DatumGetCString(OidFunctionCall1(outfuncid, start_ts)), - DatumGetCString(OidFunctionCall1(outfuncid, end_ts))); - } - + log_refresh_window(DEBUG1, cagg, &bucketed_refresh_window, "invalidation refresh on"); continuous_agg_refresh_execute(&refresh, &bucketed_refresh_window); } @@ -285,7 +293,8 @@ continuous_agg_refresh(PG_FUNCTION_ARGS) } void -continuous_agg_refresh_internal(ContinuousAgg *cagg, InternalTimeRange *refresh_window_arg) +continuous_agg_refresh_internal(const ContinuousAgg *cagg, + const InternalTimeRange *refresh_window_arg) { Catalog *catalog = ts_catalog_get(); Hypertable *cagg_ht; @@ -311,11 +320,7 @@ continuous_agg_refresh_internal(ContinuousAgg *cagg, InternalTimeRange *refresh_ errhint("The start of the window must be before the end."))); refresh_window = compute_bucketed_refresh_window(refresh_window_arg, cagg->data.bucket_width); - - elog(DEBUG1, - "computed refresh window at [ " INT64_FORMAT ", " INT64_FORMAT "]", - refresh_window.start, - refresh_window.end); + log_refresh_window(DEBUG1, cagg, &refresh_window, "refreshing continuous aggregate"); /* Perform the refresh across two transactions. * diff --git a/tsl/src/continuous_aggs/refresh.h b/tsl/src/continuous_aggs/refresh.h index fec2069d2..bdc3df2b8 100644 --- a/tsl/src/continuous_aggs/refresh.h +++ b/tsl/src/continuous_aggs/refresh.h @@ -13,6 +13,7 @@ #include "materialize.h" extern Datum continuous_agg_refresh(PG_FUNCTION_ARGS); -void continuous_agg_refresh_internal(ContinuousAgg *cagg, InternalTimeRange *refresh_window); +extern void continuous_agg_refresh_internal(const ContinuousAgg *cagg, + const InternalTimeRange *refresh_window); #endif /* TIMESCALEDB_TSL_CONTINUOUS_AGGS_REFRESH_H */ diff --git a/tsl/test/expected/continuous_aggs_invalidation.out b/tsl/test/expected/continuous_aggs_invalidation.out index 106be58ce..6d3fb135c 100644 --- a/tsl/test/expected/continuous_aggs_invalidation.out +++ b/tsl/test/expected/continuous_aggs_invalidation.out @@ -527,7 +527,7 @@ SELECT materialization_id AS cagg_id, cagg_id | start | end ---------+----------------------+---------------------- 3 | -9223372036854775808 | -9223372036854775801 - 3 | 9223372036854775800 | 9223372036854775807 + 3 | 9223372036854775807 | 9223372036854775807 4 | -9223372036854775808 | 19 4 | 15 | 42 4 | 20 | 25 diff --git a/tsl/test/expected/continuous_aggs_refresh.out b/tsl/test/expected/continuous_aggs_refresh.out index 1aa75e02e..7a7c3b363 100644 --- a/tsl/test/expected/continuous_aggs_refresh.out +++ b/tsl/test/expected/continuous_aggs_refresh.out @@ -82,9 +82,9 @@ ORDER BY day DESC, device; -- Refresh the rest (and try DEBUG output) SET client_min_messages TO DEBUG1; CALL refresh_continuous_aggregate('daily_temp', '2020-05-01', '2020-05-03'); -DEBUG: computed refresh window at [ 1588291200000000, 1588550400000000] +DEBUG: refreshing continuous aggregate "daily_temp" in window [ Thu Apr 30 17:00:00 2020 PDT, Sun May 03 17:00:00 2020 PDT ] DEBUG: hypertable 1 existing watermark >= new invalidation threshold 1588723200000000 1588550400000000 -DEBUG: refreshing continuous aggregate "daily_temp" in window [ Thu Apr 30 17:00:00 2020 PDT, Sat May 02 17:00:00 2020 PDT ] +DEBUG: invalidation refresh on "daily_temp" in window [ Thu Apr 30 17:00:00 2020 PDT, Sat May 02 17:00:00 2020 PDT ] RESET client_min_messages; -- Compare the aggregate to the equivalent query on the source table SELECT * FROM daily_temp @@ -177,8 +177,6 @@ CALL refresh_continuous_aggregate('daily_temp', '2020-05-03', 'xyz'); ERROR: invalid input syntax for type timestamp with time zone: "xyz" CALL refresh_continuous_aggregate('daily_temp', '2020-05-03', '2020-05-01'); ERROR: invalid refresh window -CALL refresh_continuous_aggregate('daily_temp', '2020-05-03', '2020-05-03'); -ERROR: invalid refresh window -- Bad time input CALL refresh_continuous_aggregate('daily_temp', '2020-05-01'::text, '2020-05-03'::text); ERROR: invalid type of time argument diff --git a/tsl/test/sql/continuous_aggs_refresh.sql b/tsl/test/sql/continuous_aggs_refresh.sql index 81b292ecf..8a7cc4f94 100644 --- a/tsl/test/sql/continuous_aggs_refresh.sql +++ b/tsl/test/sql/continuous_aggs_refresh.sql @@ -80,7 +80,6 @@ CALL refresh_continuous_aggregate('daily_temp', '2020-05-03'); CALL refresh_continuous_aggregate('daily_temp', 'xyz', '2020-05-05'); CALL refresh_continuous_aggregate('daily_temp', '2020-05-03', 'xyz'); CALL refresh_continuous_aggregate('daily_temp', '2020-05-03', '2020-05-01'); -CALL refresh_continuous_aggregate('daily_temp', '2020-05-03', '2020-05-03'); -- Bad time input CALL refresh_continuous_aggregate('daily_temp', '2020-05-01'::text, '2020-05-03'::text); CALL refresh_continuous_aggregate('daily_temp', 0, '2020-05-01');