1
0
mirror of https://github.com/timescale/timescaledb.git synced 2025-05-18 19:59:48 +08:00

Fix boundary handling in time types and constraints

Time types, like date and timestamps, have limits that aren't the same
as the underlying storage type. For instance, while a timestamp is
stored as an `int64` internally, its max supported time value is not
`INT64_MAX`. Instead, `INT64_MAX` represents `+Infinity` and the
actual largest possible timestamp is close to `INT64_MAX` (but not
`INT64_MAX-1` either). The same applies to min values.

Unfortunately, time handling code does not check for these boundaries;
in most cases, overflow handling when, e.g., bucketing, are checked
against the max integer values instead of type-specific boundaries. In
other cases, overflows simply throw errors instead of clamping to the
boundary values, which makes more sense in many situations.

Using integer time suffers from similar issues. To take one example,
simply inserting a valid `smallint` value close to the max into a
table with a `smallint` time column fails:

```
INSERT INTO smallint_table VALUES ('32765', 1, 2.0);
ERROR:  value "32770" is out of range for type smallint
```

This happens because the code that adds dimensional constraints always
checks for overflow against `INT64_MAX` instead of the type-specific
max value. Therefore, it tries to create a chunk constraint that ends
at `32770`, which is outside the allowed range of `smallint`.

The resolve these issues, several time-related utility functions have
been implemented that, e.g., return type-specific range boundaries,
and perform saturated addition and subtraction while clamping to
supported boundaries.

Fixes 
This commit is contained in:
Erik Nordström 2020-09-03 11:33:47 +02:00 committed by Erik Nordström
parent 1fa072acb4
commit 417b66e974
27 changed files with 1386 additions and 347 deletions

@ -5,6 +5,7 @@
*/
#include <postgres.h>
#include <catalog/pg_type.h>
#include <catalog/namespace.h>
#include <access/relscan.h>
#include <commands/tablecmds.h>
#include <utils/lsyscache.h>
@ -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);

@ -45,6 +45,7 @@
#include "extension_constants.h"
#include "partitioning.h"
#include "cross_module_fn.h"
#include "time_utils.h"
typedef struct CollectQualCtx
{

@ -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 */

@ -7,6 +7,7 @@
#include <access/xact.h>
#include <catalog/pg_type.h>
#include <utils/timestamp.h>
#include <utils/date.h>
#include <utils/builtins.h>
#include <parser/parse_coerce.h>
#include <fmgr.h>
@ -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;
}

@ -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 */

@ -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:

@ -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))

@ -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)

@ -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)

@ -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)

@ -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

@ -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;

@ -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();

@ -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

@ -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);

@ -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

@ -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;

@ -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
)

@ -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);
}

321
test/src/test_time_utils.c Normal file

@ -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 <postgres.h>
#include <catalog/pg_type.h>
#include <utils/date.h>
#include <fmgr.h>
#include <funcapi.h>
#include <time_utils.h>
#include <utils.h>
#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();
}

@ -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;

@ -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;
}

@ -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.
*

@ -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 */

@ -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

@ -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

@ -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');