Add timezone support to time_bucket

This patch adds a new function time_bucket(period,timestamp,timezone)
which supports bucketing for arbitrary timezones.
This commit is contained in:
Sven Klemm 2022-08-22 08:16:14 +02:00 committed by Sven Klemm
parent dc145b7485
commit 5d934baf1d
17 changed files with 507 additions and 225 deletions

View File

@ -11,6 +11,7 @@ accidentally triggering the load of a previous DB version.**
* #4393 Support intervals with day component when constifying now()
* #4397 Support intervals with month component when constifying now()
* #4641 Allow bucketing by month in time_bucket
* #4642 Add timezone support to time_bucket
**Bugfixes**
* #4416 Handle TRUNCATE TABLE on chunks

View File

@ -23,6 +23,10 @@ CREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts TIM
CREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts DATE, origin DATE) RETURNS DATE
AS '@MODULE_PATHNAME@', 'ts_date_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;
-- bucketing with timezone
CREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width INTERVAL, ts TIMESTAMPTZ, timezone TEXT, origin TIMESTAMPTZ DEFAULT NULL, "offset" INTERVAL DEFAULT NULL) RETURNS TIMESTAMPTZ
AS '@MODULE_PATHNAME@', 'ts_timestamptz_timezone_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE;
-- bucketing of int
CREATE OR REPLACE FUNCTION @extschema@.time_bucket(bucket_width SMALLINT, ts SMALLINT) RETURNS SMALLINT
AS '@MODULE_PATHNAME@', 'ts_int16_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE STRICT;

View File

@ -154,7 +154,6 @@ GRANT SELECT ON _timescaledb_catalog.chunk TO PUBLIC;
-- end recreate _timescaledb_catalog.chunk table --
ALTER TABLE _timescaledb_internal.bgw_job_stat
DROP CONSTRAINT bgw_job_stat_job_id_fkey;
ALTER TABLE _timescaledb_internal.bgw_policy_chunk_stats
@ -246,3 +245,6 @@ CREATE FUNCTION @extschema@.alter_job(
RETURNS TABLE (job_id INTEGER, schedule_interval INTERVAL, max_runtime INTERVAL, max_retries INTEGER, retry_period INTERVAL, scheduled BOOL, config JSONB, next_start TIMESTAMPTZ)
AS '@MODULE_PATHNAME@', 'ts_job_alter'
LANGUAGE C VOLATILE;
DROP FUNCTION @extschema@.time_bucket(INTERVAL, TIMESTAMPTZ, TEXT, TIMESTAMPTZ, INTERVAL);

View File

@ -355,6 +355,11 @@ get_reindex_options(ReindexStmt *stmt)
#define list_make5_int(x1, x2, x3, x4, x5) lappend_int(list_make4_int(x1, x2, x3, x4), x5)
#endif
/*
* define lfifth macro for convenience
*/
#define lfifth(l) lfirst(list_nth_cell(l, 4))
/* PG13 removes the natts parameter from map_variable_attnos */
#if PG13_LT
#define map_variable_attnos_compat(node, varno, sublevels_up, map, natts, rowtype, found_wholerow) \

View File

@ -20,6 +20,7 @@
#include <utils/selfuncs.h>
#include <utils/syscache.h>
#include "compat/compat.h"
#include "utils.h"
#include "cache.h"
#include "func_cache.h"
@ -108,6 +109,22 @@ time_bucket_sort_transform(FuncExpr *func)
return do_sort_transform(func);
}
/*
* time_bucket with timezone will always have 5 args. For the sort
* optimization to apply all args need to be Const except timestamp.
*/
static Expr *
time_bucket_tz_sort_transform(FuncExpr *func)
{
Assert(list_length(func->args) == 5);
if (!IsA(linitial((func)->args), Const) || !IsA(lthird(func->args), Const) ||
!IsA(lfourth(func->args), Const) || !IsA(lfifth(func->args), Const))
return (Expr *) func;
return do_sort_transform(func);
}
/* For time_bucket this estimate currently works by seeing how many possible
* buckets there will be if the data spans the entire hypertable. Note that
* this is an overestimate.
@ -295,6 +312,16 @@ static FuncInfo funcinfo[] = {
.group_estimate = time_bucket_group_estimate,
.sort_transform = time_bucket_sort_transform,
},
{
.origin = ORIGIN_TIMESCALE,
.is_bucketing_func = true,
.allowed_in_cagg_definition = true,
.funcname = "time_bucket",
.nargs = 5,
.arg_types = { INTERVALOID, TIMESTAMPTZOID, TEXTOID, TIMESTAMPTZOID, INTERVALOID },
.group_estimate = time_bucket_group_estimate,
.sort_transform = time_bucket_tz_sort_transform,
},
{
.origin = ORIGIN_TIMESCALE_EXPERIMENTAL,
.is_bucketing_func = true,

View File

@ -434,6 +434,9 @@ transform_time_bucket_comparison(PlannerInfo *root, OpExpr *op)
{
Interval *interval = DatumGetIntervalP(width->constvalue);
/*
* Optimization can't be applied when interval has month component.
*/
if (interval->month != 0)
return op;
@ -466,7 +469,7 @@ transform_time_bucket_comparison(PlannerInfo *root, OpExpr *op)
Assert(width->consttype == INTERVALOID);
/*
* intervals with month component are not supported by time_bucket
* Optimization can't be applied when interval has month component.
*/
if (interval->month != 0)
return op;
@ -513,7 +516,7 @@ transform_time_bucket_comparison(PlannerInfo *root, OpExpr *op)
Assert(width->consttype == INTERVALOID);
/*
* intervals with month component are not supported by time_bucket
* Optimization can't be applied when interval has month component.
*/
if (interval->month != 0)
return op;

View File

@ -281,6 +281,64 @@ ts_timestamptz_bucket(PG_FUNCTION_ARGS)
}
}
TS_FUNCTION_INFO_V1(ts_timestamptz_timezone_bucket);
/*
* time_bucket(bucket_width INTERVAL, ts TIMESTAMPTZ, timezone TEXT, origin TIMESTAMPTZ DEFAULT
* NULL, "offset" INTERVAL DEFAULT NULL) RETURNS TIMESTAMPTZ
*/
TSDLLEXPORT Datum
ts_timestamptz_timezone_bucket(PG_FUNCTION_ARGS)
{
Datum period = PG_GETARG_DATUM(0);
Datum timestamp = PG_GETARG_DATUM(1);
Datum tzname = PG_GETARG_DATUM(2);
/*
* When called from SQL we will always have 5 args because default values
* will be filled in for missing arguments. When called from C with
* DirectFunctionCall number of arguments might be less than 5.
*/
bool have_origin = PG_NARGS() > 3 && !PG_ARGISNULL(3);
bool have_offset = PG_NARGS() > 4 && !PG_ARGISNULL(4);
/*
* We need to check for NULL arguments here because the function cannot be
* defined STRICT due to the optional arguments.
*/
if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2))
PG_RETURN_NULL();
/* Convert to local timestamp according to timezone */
timestamp = DirectFunctionCall2(timestamptz_zone, tzname, timestamp);
if (have_offset)
{
/* Apply offset. */
timestamp = DirectFunctionCall2(timestamp_mi_interval, timestamp, PG_GETARG_DATUM(4));
}
if (have_origin)
{
Datum origin = DirectFunctionCall2(timestamptz_zone, tzname, PG_GETARG_DATUM(3));
timestamp = DirectFunctionCall3(ts_timestamp_bucket, period, timestamp, origin);
}
else
{
timestamp = DirectFunctionCall2(ts_timestamp_bucket, period, timestamp);
}
if (have_offset)
{
/* Remove offset. */
timestamp = DirectFunctionCall2(timestamp_pl_interval, timestamp, PG_GETARG_DATUM(4));
}
/* Convert back to timezone */
timestamp = DirectFunctionCall2(timestamp_zone, tzname, timestamp);
PG_RETURN_DATUM(timestamp);
}
static inline void
check_period_is_daily(int64 period)
{

View File

@ -17,6 +17,7 @@ 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 Datum ts_timestamptz_timezone_bucket(PG_FUNCTION_ARGS);
extern TSDLLEXPORT int64 ts_time_bucket_by_type(int64 interval, int64 timestamp, Oid type);
extern TSDLLEXPORT Datum ts_time_bucket_ng_date(PG_FUNCTION_ARGS);
extern TSDLLEXPORT Datum ts_time_bucket_ng_timestamp(PG_FUNCTION_ARGS);

View File

@ -1541,11 +1541,52 @@ ts_continuous_agg_bucket_width(const ContinuousAgg *agg)
* a common procedure used by ts_compute_* below.
*/
static Datum
generic_time_bucket_ng(const ContinuousAggsBucketFunction *bf, Datum timestamp)
generic_time_bucket(const ContinuousAggsBucketFunction *bf, Datum timestamp)
{
/* bf->timezone can't be NULL. If timezone is not specified, "" is stored */
Assert(bf->timezone != NULL);
if (!bf->experimental)
{
if (strlen(bf->timezone) > 0)
{
if (TIMESTAMP_NOT_FINITE(bf->origin))
{
/* using default origin */
return DirectFunctionCall3(ts_timestamptz_timezone_bucket,
IntervalPGetDatum(bf->bucket_width),
timestamp,
CStringGetTextDatum(bf->timezone));
}
else
{
/* custom origin specified */
return DirectFunctionCall4(ts_timestamptz_timezone_bucket,
IntervalPGetDatum(bf->bucket_width),
timestamp,
CStringGetTextDatum(bf->timezone),
TimestampTzGetDatum((TimestampTz) bf->origin));
}
}
if (TIMESTAMP_NOT_FINITE(bf->origin))
{
/* using default origin */
return DirectFunctionCall2(ts_timestamp_bucket,
IntervalPGetDatum(bf->bucket_width),
timestamp);
}
else
{
/* custom origin specified */
return DirectFunctionCall3(ts_timestamp_bucket,
IntervalPGetDatum(bf->bucket_width),
timestamp,
TimestampGetDatum(bf->origin));
}
}
else
{
if (strlen(bf->timezone) > 0)
{
if (TIMESTAMP_NOT_FINITE(bf->origin))
@ -1583,6 +1624,7 @@ generic_time_bucket_ng(const ContinuousAggsBucketFunction *bf, Datum timestamp)
TimestampGetDatum(bf->origin));
}
}
}
/*
* Adds one bf->bucket_size interval to the timestamp. This is a common
@ -1650,8 +1692,8 @@ ts_compute_inscribed_bucketed_refresh_window_variable(int64 *start, int64 *end,
start_old = ts_internal_to_time_value(*start, TIMESTAMPOID);
end_old = ts_internal_to_time_value(*end, TIMESTAMPOID);
start_new = generic_time_bucket_ng(bf, start_old);
end_new = generic_time_bucket_ng(bf, end_old);
start_new = generic_time_bucket(bf, start_old);
end_new = generic_time_bucket(bf, end_old);
if (DatumGetTimestamp(start_new) != DatumGetTimestamp(start_old))
{
@ -1684,8 +1726,8 @@ ts_compute_circumscribed_bucketed_refresh_window_variable(int64 *start, int64 *e
*/
start_old = ts_internal_to_time_value(*start, TIMESTAMPOID);
end_old = ts_internal_to_time_value(*end, TIMESTAMPOID);
start_new = generic_time_bucket_ng(bf, start_old);
end_new = generic_time_bucket_ng(bf, end_old);
start_new = generic_time_bucket(bf, start_old);
end_new = generic_time_bucket(bf, end_old);
if (DatumGetTimestamp(end_new) != DatumGetTimestamp(end_old))
{
@ -1716,7 +1758,7 @@ ts_compute_beginning_of_the_next_bucket_variable(int64 timeval,
*/
val_old = ts_internal_to_time_value(timeval, TIMESTAMPOID);
val_new = generic_time_bucket_ng(bf, val_old);
val_new = generic_time_bucket(bf, val_old);
val_new = generic_add_interval(bf, val_new);
return ts_time_value_to_internal(val_new, TIMESTAMPOID);
}

View File

@ -1284,6 +1284,117 @@ FROM generate_series('1990-01-03'::timestamptz,'1990-06-03'::timestamptz,'1month
1990-06-03 00:00:00-04 | 1990-05-31 20:00:00-04 | 1990-04-30 20:00:00-04 | 1990-03-31 19:00:00-05 | 1990-05-31 20:00:00-04 | 1990-05-31 20:00:00-04 | 1990-04-30 20:00:00-04
(6 rows)
---------------------------------------
--- Test time_bucket with timezones ---
---------------------------------------
-- test NULL args
SELECT
time_bucket(NULL::interval,now(),'Europe/Berlin'),
time_bucket('1day',NULL::timestamptz,'Europe/Berlin'),
time_bucket('1day',now(),NULL::text),
time_bucket('1day','2020-02-03','Europe/Berlin',NULL),
time_bucket('1day','2020-02-03','Europe/Berlin','2020-04-01',NULL),
time_bucket('1day','2020-02-03','Europe/Berlin',NULL,NULL),
time_bucket('1day','2020-02-03','Europe/Berlin',"offset":=NULL::interval),
time_bucket('1day','2020-02-03','Europe/Berlin',origin:=NULL::timestamptz);
time_bucket | time_bucket | time_bucket | time_bucket | time_bucket | time_bucket | time_bucket | time_bucket
-------------+-------------+-------------+------------------------+------------------------+------------------------+------------------------+------------------------
| | | 2020-02-02 18:00:00-05 | 2020-02-03 00:00:00-05 | 2020-02-02 18:00:00-05 | 2020-02-02 18:00:00-05 | 2020-02-02 18:00:00-05
(1 row)
SET datestyle TO ISO;
SELECT
time_bucket('1day', ts) AS "UTC",
time_bucket('1day', ts, 'Europe/Berlin') AS "Berlin",
time_bucket('1day', ts, 'Europe/London') AS "London",
time_bucket('1day', ts, 'America/New_York') AS "New York",
time_bucket('1day', ts, 'PST') AS "PST",
time_bucket('1day', ts, current_setting('timezone')) AS "current"
FROM generate_series('1999-12-31 17:00'::timestamptz,'2000-01-02 3:00'::timestamptz, '1hour'::interval) ts;
UTC | Berlin | London | New York | PST | current
------------------------+------------------------+------------------------+------------------------+------------------------+------------------------
1999-12-30 19:00:00-05 | 1999-12-30 18:00:00-05 | 1999-12-30 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05
1999-12-30 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-30 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 1999-12-31 00:00:00-05 | 1999-12-31 03:00:00-05 | 1999-12-31 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-31 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
1999-12-31 19:00:00-05 | 2000-01-01 18:00:00-05 | 1999-12-31 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-01 00:00:00-05
2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05
2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05
2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-01 03:00:00-05 | 2000-01-02 00:00:00-05
2000-01-01 19:00:00-05 | 2000-01-01 18:00:00-05 | 2000-01-01 19:00:00-05 | 2000-01-02 00:00:00-05 | 2000-01-02 03:00:00-05 | 2000-01-02 00:00:00-05
(35 rows)
SELECT
time_bucket('1month', ts) AS "UTC",
time_bucket('1month', ts, 'Europe/Berlin') AS "Berlin",
time_bucket('1month', ts, 'America/New_York') AS "New York",
time_bucket('1month', ts, current_setting('timezone')) AS "current",
time_bucket('2month', ts, current_setting('timezone')) AS "2m",
time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp) AS "2m origin",
time_bucket('2month', ts, current_setting('timezone'), "offset":='14 day'::interval) AS "2m offset",
time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp, '7 day'::interval) AS "2m offset + origin"
FROM generate_series('1999-12-01'::timestamptz,'2000-09-01'::timestamptz, '9 day'::interval) ts;
UTC | Berlin | New York | current | 2m | 2m origin | 2m offset | 2m offset + origin
------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------+------------------------
1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-10-08 00:00:00-04
1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05
1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05
1999-11-30 19:00:00-05 | 1999-11-30 18:00:00-05 | 1999-12-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 1999-11-15 00:00:00-05 | 1999-12-08 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05
1999-12-31 19:00:00-05 | 1999-12-31 18:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 1999-12-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05
2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 1999-12-08 00:00:00-05
2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05
2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05
2000-01-31 19:00:00-05 | 2000-01-31 18:00:00-05 | 2000-02-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05
2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-01-15 00:00:00-05 | 2000-02-08 00:00:00-05
2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05
2000-02-29 19:00:00-05 | 2000-02-29 18:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-02-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05
2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-02-08 00:00:00-05
2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04
2000-03-31 19:00:00-05 | 2000-03-31 17:00:00-05 | 2000-04-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-01 00:00:00-05 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04
2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04
2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-03-15 00:00:00-05 | 2000-04-08 00:00:00-04
2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04
2000-04-30 20:00:00-04 | 2000-04-30 18:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-04-01 00:00:00-05 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04
2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-04-08 00:00:00-04
2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04
2000-05-31 20:00:00-04 | 2000-05-31 18:00:00-04 | 2000-06-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04
2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04
2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-05-15 00:00:00-04 | 2000-06-08 00:00:00-04
2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-06-08 00:00:00-04
2000-06-30 20:00:00-04 | 2000-06-30 18:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-06-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-06-08 00:00:00-04
2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04
2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04
2000-07-31 20:00:00-04 | 2000-07-31 18:00:00-04 | 2000-08-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-01 00:00:00-04 | 2000-08-01 00:00:00-04 | 2000-07-15 00:00:00-04 | 2000-08-08 00:00:00-04
(31 rows)
RESET datestyle;
------------------------------------------------------------
--- Test timescaledb_experimental.time_bucket_ng function --

View File

@ -634,6 +634,44 @@ SELECT
time_bucket('3 month', time, '2000-02-01'::timestamptz) AS "3m origin"
FROM generate_series('1990-01-03'::timestamptz,'1990-06-03'::timestamptz,'1month'::interval) time;
---------------------------------------
--- Test time_bucket with timezones ---
---------------------------------------
-- test NULL args
SELECT
time_bucket(NULL::interval,now(),'Europe/Berlin'),
time_bucket('1day',NULL::timestamptz,'Europe/Berlin'),
time_bucket('1day',now(),NULL::text),
time_bucket('1day','2020-02-03','Europe/Berlin',NULL),
time_bucket('1day','2020-02-03','Europe/Berlin','2020-04-01',NULL),
time_bucket('1day','2020-02-03','Europe/Berlin',NULL,NULL),
time_bucket('1day','2020-02-03','Europe/Berlin',"offset":=NULL::interval),
time_bucket('1day','2020-02-03','Europe/Berlin',origin:=NULL::timestamptz);
SET datestyle TO ISO;
SELECT
time_bucket('1day', ts) AS "UTC",
time_bucket('1day', ts, 'Europe/Berlin') AS "Berlin",
time_bucket('1day', ts, 'Europe/London') AS "London",
time_bucket('1day', ts, 'America/New_York') AS "New York",
time_bucket('1day', ts, 'PST') AS "PST",
time_bucket('1day', ts, current_setting('timezone')) AS "current"
FROM generate_series('1999-12-31 17:00'::timestamptz,'2000-01-02 3:00'::timestamptz, '1hour'::interval) ts;
SELECT
time_bucket('1month', ts) AS "UTC",
time_bucket('1month', ts, 'Europe/Berlin') AS "Berlin",
time_bucket('1month', ts, 'America/New_York') AS "New York",
time_bucket('1month', ts, current_setting('timezone')) AS "current",
time_bucket('2month', ts, current_setting('timezone')) AS "2m",
time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp) AS "2m origin",
time_bucket('2month', ts, current_setting('timezone'), "offset":='14 day'::interval) AS "2m offset",
time_bucket('2month', ts, current_setting('timezone'), '2000-02-01'::timestamp, '7 day'::interval) AS "2m offset + origin"
FROM generate_series('1999-12-01'::timestamptz,'2000-09-01'::timestamptz, '9 day'::interval) ts;
RESET datestyle;
------------------------------------------------------------

View File

@ -23,6 +23,7 @@
#include <catalog/indexing.h>
#include <catalog/pg_aggregate.h>
#include <catalog/pg_collation.h>
#include <catalog/pg_namespace.h>
#include <catalog/pg_trigger.h>
#include <catalog/pg_type.h>
#include <catalog/toasting.h>
@ -184,6 +185,7 @@ typedef struct CAggTimebucketInfo
Interval *interval; /* stores the interval, NULL if not specified */
const char *timezone; /* the name of the timezone, NULL if not specified */
FuncExpr *bucket_func; /* function call expr of the bucketing function */
/*
* Custom origin value stored as UTC timestamp.
* If not specified, stores infinity.
@ -712,6 +714,24 @@ caggtimebucketinfo_init(CAggTimebucketInfo *src, int32 hypertable_id, Oid hypert
TIMESTAMP_NOBEGIN(src->origin); /* origin is not specified by default */
}
static Const *
check_time_bucket_argument(Node *arg, char *position)
{
if (IsA(arg, NamedArgExpr))
arg = (Node *) castNode(NamedArgExpr, arg)->arg;
Node *expr = eval_const_expressions(NULL, arg);
if (!IsA(expr, Const))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("only immutable expressions allowed in time bucket function"),
errhint("Use an immutable expression as %s argument to the time bucket function.",
position)));
return castNode(Const, expr);
}
/*
* Check if the group-by clauses has exactly 1 time_bucket(.., <col>) where
* <col> is the hypertable's partitioning column and other invariants. Then fill
@ -722,6 +742,7 @@ caggtimebucket_validate(CAggTimebucketInfo *tbinfo, List *groupClause, List *tar
{
ListCell *l;
bool found = false;
bool custom_origin = false;
/* Make sure tbinfo was initialized. This assumption is used below. */
Assert(tbinfo->bucket_width == 0);
@ -737,11 +758,18 @@ caggtimebucket_validate(CAggTimebucketInfo *tbinfo, List *groupClause, List *tar
FuncExpr *fe = ((FuncExpr *) tle->expr);
Node *width_arg;
Node *col_arg;
Node *tz_arg;
if (!function_allowed_in_cagg_definition(fe->funcid))
continue;
/*
* offset variants of time_bucket functions are not
* supported at the moment.
*/
if (list_length(fe->args) >= 5 ||
(list_length(fe->args) == 4 && exprType(lfourth(fe->args)) == INTERVALOID))
continue;
if (found)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@ -750,6 +778,8 @@ caggtimebucket_validate(CAggTimebucketInfo *tbinfo, List *groupClause, List *tar
else
found = true;
tbinfo->bucket_func = fe;
/*only column allowed : time_bucket('1day', <column> ) */
col_arg = lsecond(fe->args);
@ -759,54 +789,12 @@ caggtimebucket_validate(CAggTimebucketInfo *tbinfo, List *groupClause, List *tar
errmsg(
"time bucket function must reference a hypertable dimension column")));
if (list_length(fe->args) == 4)
{
/*
* Timezone and custom origin are specified. In this clause we
* save only the timezone. Origin is processed in the following
* clause.
*/
tz_arg = eval_const_expressions(NULL, lfourth(fe->args));
if (!IsA(tz_arg, Const))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("only immutable expressions allowed in time bucket function"),
errhint("Use an immutable expression as fourth argument"
" to the time bucket function.")));
Const *tz = castNode(Const, tz_arg);
/* This is assured by function_allowed_in_cagg_definition() above. */
Assert(tz->consttype == TEXTOID);
const char *tz_name = TextDatumGetCString(tz->constvalue);
if (!ts_is_valid_timezone_name(tz_name))
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid timezone name \"%s\"", tz_name)));
}
tbinfo->timezone = tz_name;
tbinfo->bucket_width = BUCKET_WIDTH_VARIABLE;
}
if (list_length(fe->args) >= 3)
{
tz_arg = eval_const_expressions(NULL, lthird(fe->args));
if (!IsA(tz_arg, Const))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("only immutable expressions allowed in time bucket function"),
errhint("Use an immutable expression as third argument"
" to the time bucket function.")));
Const *tz = castNode(Const, tz_arg);
if ((tz->consttype == TEXTOID) && (list_length(fe->args) == 3))
Const *arg = check_time_bucket_argument(lthird(fe->args), "third");
if (exprType((Node *) arg) == TEXTOID)
{
/* Timezone specified */
const char *tz_name = TextDatumGetCString(tz->constvalue);
const char *tz_name = TextDatumGetCString(arg->constvalue);
if (!ts_is_valid_timezone_name(tz_name))
{
ereport(ERROR,
@ -817,52 +805,70 @@ caggtimebucket_validate(CAggTimebucketInfo *tbinfo, List *groupClause, List *tar
tbinfo->timezone = tz_name;
tbinfo->bucket_width = BUCKET_WIDTH_VARIABLE;
}
else
{
/*
* Custom origin specified. This is always treated as
* a variable-sized bucket case.
*/
tbinfo->bucket_width = BUCKET_WIDTH_VARIABLE;
}
if (tz->constisnull)
if (list_length(fe->args) >= 4)
{
Const *arg = check_time_bucket_argument(lfourth(fe->args), "fourth");
if (exprType((Node *) arg) == TEXTOID)
{
const char *tz_name = TextDatumGetCString(arg->constvalue);
if (!ts_is_valid_timezone_name(tz_name))
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid origin value: null")));
errmsg("invalid timezone name \"%s\"", tz_name)));
}
switch (tz->consttype)
tbinfo->timezone = tz_name;
tbinfo->bucket_width = BUCKET_WIDTH_VARIABLE;
}
}
/* check for custom origin */
switch (exprType(col_arg))
{
case DATEOID:
/* origin is always 3rd arg for date variants */
if (list_length(fe->args) == 3)
{
custom_origin = true;
tbinfo->origin = DatumGetTimestamp(
DirectFunctionCall1(date_timestamp, tz->constvalue));
DirectFunctionCall1(date_timestamp,
castNode(Const, lthird(fe->args))->constvalue));
}
break;
case TIMESTAMPOID:
tbinfo->origin = DatumGetTimestamp(tz->constvalue);
/* origin is always 3rd arg for timestamp variants */
if (list_length(fe->args) == 3)
{
custom_origin = true;
tbinfo->origin =
DatumGetTimestamp(castNode(Const, lthird(fe->args))->constvalue);
}
break;
case TIMESTAMPTZOID:
tbinfo->origin = DatumGetTimestampTz(tz->constvalue);
break;
default:
/*
* This shouldn't happen. But if somehow it does
* make sure the execution will stop here even in
* the Release build.
*/
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unsupported time bucket function")));
/* origin can be 3rd or 4th arg for timestamptz variants */
if (list_length(fe->args) >= 3 && exprType(lthird(fe->args)) == TIMESTAMPTZOID)
{
custom_origin = true;
tbinfo->origin =
DatumGetTimestampTz(castNode(Const, lthird(fe->args))->constvalue);
}
if (TIMESTAMP_NOT_FINITE(tbinfo->origin))
else if (list_length(fe->args) >= 4 &&
exprType(lfourth(fe->args)) == TIMESTAMPTZOID)
{
custom_origin = true;
tbinfo->origin =
DatumGetTimestampTz(castNode(Const, lfourth(fe->args))->constvalue);
}
}
if (custom_origin && TIMESTAMP_NOT_FINITE(tbinfo->origin))
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid origin value: infinity")));
}
}
}
/*
* We constify width expression here so any immutable expression will be allowed
@ -895,35 +901,9 @@ caggtimebucket_validate(CAggTimebucketInfo *tbinfo, List *groupClause, List *tar
errhint("Use an immutable expression as first argument"
" to the time bucket function.")));
if ((tbinfo->bucket_width == BUCKET_WIDTH_VARIABLE) && (tbinfo->interval->month != 0))
if (tbinfo->interval && tbinfo->interval->month)
{
/* Monthly buckets case */
if (!TIMESTAMP_NOT_FINITE(tbinfo->origin))
{
/*
* Origin was specified - make sure it's the first day of the month.
* If a timezone was specified the check should be done in this timezone.
*/
Timestamp origin = tbinfo->origin;
if (tbinfo->timezone != NULL)
{
/* The code is equal to 'timestamptz AT TIME ZONE tzname'. */
origin = DatumGetTimestamp(
DirectFunctionCall2(timestamptz_zone,
CStringGetTextDatum(tbinfo->timezone),
TimestampTzGetDatum((TimestampTz) origin)));
}
const char *day =
TextDatumGetCString(DirectFunctionCall2(timestamp_to_char,
TimestampGetDatum(origin),
CStringGetTextDatum("DD")));
if (strcmp(day, "01") != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("for monthly buckets origin must be the first day of the "
"month")));
}
tbinfo->bucket_width = BUCKET_WIDTH_VARIABLE;
}
}
}
@ -2312,18 +2292,17 @@ cagg_create(const CreateTableAsStmt *create_stmt, ViewStmt *stmt, Query *panquer
}
/*
* `experimental` = true and `name` = "time_bucket_ng" are hardcoded
* rather than extracted from the query. We happen to know that
* monthly buckets can currently be created only with time_bucket_ng(),
* thus these values are correct. Besides, they are not used for
* These values are not used for
* anything except Assert's yet for the same reasons. Once the design
* of variable-sized buckets is finalized we will have a better idea
* of what schema is needed exactly. Until then the choice was made
* in favor of the most generic schema that can be optimized later.
*/
create_bucket_function_catalog_entry(materialize_hypertable_id,
true,
"time_bucket_ng",
get_func_namespace(
origquery_ht->bucket_func->funcid) !=
PG_PUBLIC_NAMESPACE,
get_func_name(origquery_ht->bucket_func->funcid),
bucket_width,
origin,
origquery_ht->timezone);

View File

@ -410,3 +410,58 @@ SELECT relname FROM pg_class WHERE oid = :mat_table;
-- Cleanup
DROP TABLE whatever;
-- END OF BASIC USAGE TESTS --
CREATE TABLE metrics(time timestamptz, device TEXT, value float);
SELECT table_name FROM create_hypertable('metrics','time');
NOTICE: adding not-null constraint to column "time"
table_name
------------
metrics
(1 row)
INSERT INTO metrics SELECT generate_series('1999-12-20'::timestamptz,'2000-02-01'::timestamptz,'12 day'::interval), 'dev1', 0.25;
SELECT current_setting('timezone');
current_setting
-----------------
PST8PDT
(1 row)
-- should be blocked because non-immutable expression
\set ON_ERROR_STOP 0
CREATE MATERIALIZED VIEW cagg1 WITH (timescaledb.continuous,timescaledb.materialized_only=true) AS SELECT time_bucket('1 day', time, current_setting('timezone')) FROM metrics GROUP BY 1;
ERROR: only immutable expressions allowed in time bucket function
\set ON_ERROR_STOP 1
CREATE MATERIALIZED VIEW cagg1 WITH (timescaledb.continuous,timescaledb.materialized_only=true) AS SELECT time_bucket('1 day', time, 'PST8PDT') FROM metrics GROUP BY 1;
NOTICE: refreshing continuous aggregate "cagg1"
SELECT * FROM cagg1;
time_bucket
------------------------------
Mon Dec 20 00:00:00 1999 PST
Sat Jan 01 00:00:00 2000 PST
Thu Jan 13 00:00:00 2000 PST
Tue Jan 25 00:00:00 2000 PST
(4 rows)
CREATE MATERIALIZED VIEW cagg2 WITH (timescaledb.continuous,timescaledb.materialized_only=true) AS SELECT time_bucket('1 month', time, 'PST8PDT') FROM metrics GROUP BY 1;
NOTICE: refreshing continuous aggregate "cagg2"
SELECT * FROM cagg2;
time_bucket
------------------------------
Wed Dec 01 00:00:00 1999 PST
Sat Jan 01 00:00:00 2000 PST
(2 rows)
-- custom origin
CREATE MATERIALIZED VIEW cagg3 WITH (timescaledb.continuous,timescaledb.materialized_only=true) AS SELECT time_bucket('1 month', time, 'PST8PDT', '2000-01-01'::timestamptz) FROM metrics GROUP BY 1;
NOTICE: refreshing continuous aggregate "cagg3"
SELECT * FROM cagg3;
time_bucket
------------------------------
Wed Dec 01 00:00:00 1999 PST
Sat Jan 01 00:00:00 2000 PST
(2 rows)
-- offset not supported atm
\set ON_ERROR_STOP 0
CREATE MATERIALIZED VIEW cagg4 WITH (timescaledb.continuous,timescaledb.materialized_only=true) AS SELECT time_bucket('1 month', time, 'PST8PDT', "offset":= INTERVAL '15 day') FROM metrics GROUP BY 1;
ERROR: continuous aggregate view must include a valid time bucket function
\set ON_ERROR_STOP 1

View File

@ -30,17 +30,6 @@ INSERT INTO conditions (day, city, temperature) VALUES
('2021-06-26', 'Moscow', 32),
('2021-06-27', 'Moscow', 31);
\set ON_ERROR_STOP 0
-- Make sure NULL can't be specified as an origin
CREATE MATERIALIZED VIEW conditions_summary_weekly
WITH (timescaledb.continuous, timescaledb.materialized_only=true) AS
SELECT city,
timescaledb_experimental.time_bucket_ng('7 days', day, null) AS bucket,
MIN(temperature),
MAX(temperature)
FROM conditions
GROUP BY city, bucket
WITH NO DATA;
ERROR: invalid origin value: null
-- Make sure 'infinity' can't be specified as an origin
CREATE MATERIALIZED VIEW conditions_summary_weekly
WITH (timescaledb.continuous, timescaledb.materialized_only=true) AS
@ -52,17 +41,6 @@ FROM conditions
GROUP BY city, bucket
WITH NO DATA;
ERROR: invalid origin value: infinity
-- For monthly buckets origin should be the first day of the month
CREATE MATERIALIZED VIEW conditions_summary_weekly
WITH (timescaledb.continuous, timescaledb.materialized_only=true) AS
SELECT city,
timescaledb_experimental.time_bucket_ng('1 month', day, '2021-06-03') AS bucket,
MIN(temperature),
MAX(temperature)
FROM conditions
GROUP BY city, bucket
WITH NO DATA;
ERROR: for monthly buckets origin must be the first day of the month
-- Make sure buckets like '1 months 15 days" (fixed+variable-sized) are not allowed
CREATE MATERIALIZED VIEW conditions_summary_weekly
WITH (timescaledb.continuous, timescaledb.materialized_only=true) AS
@ -95,24 +73,6 @@ SELECT mat_hypertable_id AS cagg_id, raw_hypertable_id AS ht_id
FROM _timescaledb_catalog.continuous_agg
WHERE user_view_name = 'conditions_summary_weekly'
\gset
-- Make sure this is treated as a variable-sized bucket case
SELECT bucket_width
FROM _timescaledb_catalog.continuous_agg
WHERE mat_hypertable_id = :cagg_id;
bucket_width
--------------
-1
(1 row)
-- Make sure the origin is saved in the catalog table
SELECT experimental, name, bucket_width, origin, timezone
FROM _timescaledb_catalog.continuous_aggs_bucket_function
WHERE mat_hypertable_id = :cagg_id;
experimental | name | bucket_width | origin | timezone
--------------+----------------+--------------+--------------------------+----------
t | time_bucket_ng | @ 7 days | Mon Jan 03 00:00:00 2000 |
(1 row)
-- Make sure truncating of the refresh window works
\set ON_ERROR_STOP 0
CALL refresh_continuous_aggregate('conditions_summary_weekly', '2021-06-14', '2021-06-20');
@ -955,7 +915,7 @@ SELECT city,
MAX(temperature)
FROM conditions_timestamptz
GROUP BY city, bucket;
ERROR: for monthly buckets origin must be the first day of the month
ERROR: origin must be the first day of the month
-- Make sure buckets like '1 months 15 days" (fixed+variable-sized) are not allowed
CREATE MATERIALIZED VIEW conditions_summary_timestamptz
WITH (timescaledb.continuous, timescaledb.materialized_only=true) AS
@ -1139,7 +1099,7 @@ SELECT add_continuous_aggregate_policy('conditions_summary_timestamptz',
DROP TABLE conditions_timestamptz CASCADE;
NOTICE: drop cascades to 3 other objects
NOTICE: drop cascades to 3 other objects
NOTICE: drop cascades to table _timescaledb_internal._hyper_18_248_chunk
NOTICE: drop cascades to table _timescaledb_internal._hyper_19_248_chunk
\c :TEST_DBNAME :ROLE_CLUSTER_SUPERUSER
DROP DATABASE :DATA_NODE_1;
DROP DATABASE :DATA_NODE_2;

View File

@ -196,6 +196,7 @@ ORDER BY pronamespace::regnamespace::text COLLATE "C", p.oid::regprocedure::text
time_bucket(interval,date,interval)
time_bucket(interval,timestamp with time zone)
time_bucket(interval,timestamp with time zone,interval)
time_bucket(interval,timestamp with time zone,text,timestamp with time zone,interval)
time_bucket(interval,timestamp with time zone,timestamp with time zone)
time_bucket(interval,timestamp without time zone)
time_bucket(interval,timestamp without time zone,interval)

View File

@ -268,3 +268,30 @@ SELECT relname FROM pg_class WHERE oid = :mat_table;
DROP TABLE whatever;
-- END OF BASIC USAGE TESTS --
CREATE TABLE metrics(time timestamptz, device TEXT, value float);
SELECT table_name FROM create_hypertable('metrics','time');
INSERT INTO metrics SELECT generate_series('1999-12-20'::timestamptz,'2000-02-01'::timestamptz,'12 day'::interval), 'dev1', 0.25;
SELECT current_setting('timezone');
-- should be blocked because non-immutable expression
\set ON_ERROR_STOP 0
CREATE MATERIALIZED VIEW cagg1 WITH (timescaledb.continuous,timescaledb.materialized_only=true) AS SELECT time_bucket('1 day', time, current_setting('timezone')) FROM metrics GROUP BY 1;
\set ON_ERROR_STOP 1
CREATE MATERIALIZED VIEW cagg1 WITH (timescaledb.continuous,timescaledb.materialized_only=true) AS SELECT time_bucket('1 day', time, 'PST8PDT') FROM metrics GROUP BY 1;
SELECT * FROM cagg1;
CREATE MATERIALIZED VIEW cagg2 WITH (timescaledb.continuous,timescaledb.materialized_only=true) AS SELECT time_bucket('1 month', time, 'PST8PDT') FROM metrics GROUP BY 1;
SELECT * FROM cagg2;
-- custom origin
CREATE MATERIALIZED VIEW cagg3 WITH (timescaledb.continuous,timescaledb.materialized_only=true) AS SELECT time_bucket('1 month', time, 'PST8PDT', '2000-01-01'::timestamptz) FROM metrics GROUP BY 1;
SELECT * FROM cagg3;
-- offset not supported atm
\set ON_ERROR_STOP 0
CREATE MATERIALIZED VIEW cagg4 WITH (timescaledb.continuous,timescaledb.materialized_only=true) AS SELECT time_bucket('1 month', time, 'PST8PDT', "offset":= INTERVAL '15 day') FROM metrics GROUP BY 1;
\set ON_ERROR_STOP 1

View File

@ -30,17 +30,6 @@ INSERT INTO conditions (day, city, temperature) VALUES
\set ON_ERROR_STOP 0
-- Make sure NULL can't be specified as an origin
CREATE MATERIALIZED VIEW conditions_summary_weekly
WITH (timescaledb.continuous, timescaledb.materialized_only=true) AS
SELECT city,
timescaledb_experimental.time_bucket_ng('7 days', day, null) AS bucket,
MIN(temperature),
MAX(temperature)
FROM conditions
GROUP BY city, bucket
WITH NO DATA;
-- Make sure 'infinity' can't be specified as an origin
CREATE MATERIALIZED VIEW conditions_summary_weekly
WITH (timescaledb.continuous, timescaledb.materialized_only=true) AS
@ -52,17 +41,6 @@ FROM conditions
GROUP BY city, bucket
WITH NO DATA;
-- For monthly buckets origin should be the first day of the month
CREATE MATERIALIZED VIEW conditions_summary_weekly
WITH (timescaledb.continuous, timescaledb.materialized_only=true) AS
SELECT city,
timescaledb_experimental.time_bucket_ng('1 month', day, '2021-06-03') AS bucket,
MIN(temperature),
MAX(temperature)
FROM conditions
GROUP BY city, bucket
WITH NO DATA;
-- Make sure buckets like '1 months 15 days" (fixed+variable-sized) are not allowed
CREATE MATERIALIZED VIEW conditions_summary_weekly
WITH (timescaledb.continuous, timescaledb.materialized_only=true) AS
@ -95,16 +73,6 @@ FROM _timescaledb_catalog.continuous_agg
WHERE user_view_name = 'conditions_summary_weekly'
\gset
-- Make sure this is treated as a variable-sized bucket case
SELECT bucket_width
FROM _timescaledb_catalog.continuous_agg
WHERE mat_hypertable_id = :cagg_id;
-- Make sure the origin is saved in the catalog table
SELECT experimental, name, bucket_width, origin, timezone
FROM _timescaledb_catalog.continuous_aggs_bucket_function
WHERE mat_hypertable_id = :cagg_id;
-- Make sure truncating of the refresh window works
\set ON_ERROR_STOP 0
CALL refresh_continuous_aggregate('conditions_summary_weekly', '2021-06-14', '2021-06-20');