Fix semantics of time_bucket on DATE input

Previously, date was auto-cast to timestamptz when time_bucket was
called. This led to weird behavior with regards to timezones and
the return value was a timestamptz. This PR makes time_bucket return
a DATE on DATE input and avoids all timezone conversions.
This commit is contained in:
Matvey Arye 2017-08-23 16:17:32 -04:00 committed by Matvey Arye
parent 0137c92cdb
commit 3c69e4fc9e
5 changed files with 137 additions and 2 deletions

View File

@ -7,6 +7,10 @@ CREATE OR REPLACE FUNCTION public.time_bucket(bucket_width INTERVAL, ts TIMESTAM
CREATE OR REPLACE FUNCTION public.time_bucket(bucket_width INTERVAL, ts TIMESTAMPTZ) RETURNS TIMESTAMPTZ
AS '$libdir/timescaledb', 'timestamptz_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE;
--bucketing on date should not do any timezone conversion
CREATE OR REPLACE FUNCTION public.time_bucket(bucket_width INTERVAL, ts DATE) RETURNS DATE
AS '$libdir/timescaledb', 'date_bucket' LANGUAGE C IMMUTABLE PARALLEL SAFE;
-- If an interval is given as the third argument, the bucket alignment is offset by the interval.
CREATE OR REPLACE FUNCTION public.time_bucket(bucket_width INTERVAL, ts TIMESTAMP, "offset" INTERVAL)
RETURNS TIMESTAMP LANGUAGE SQL IMMUTABLE PARALLEL SAFE AS
@ -20,6 +24,13 @@ $BODY$
SELECT public.time_bucket(bucket_width, ts-"offset")+"offset";
$BODY$;
CREATE OR REPLACE FUNCTION public.time_bucket(bucket_width INTERVAL, ts DATE, "offset" INTERVAL)
RETURNS DATE LANGUAGE SQL IMMUTABLE PARALLEL SAFE AS
$BODY$
SELECT (public.time_bucket(bucket_width, ts-"offset")+"offset")::date;
$BODY$;
CREATE OR REPLACE FUNCTION public.time_bucket(bucket_width BIGINT, ts BIGINT)
RETURNS BIGINT LANGUAGE SQL IMMUTABLE PARALLEL SAFE AS
$BODY$

View File

@ -6,6 +6,7 @@
#include <catalog/pg_type.h>
#include <catalog/namespace.h>
#include <utils/guc.h>
#include <utils/date.h>
#include "utils.h"
#include "nodes/nodes.h"
@ -323,3 +324,52 @@ timestamptz_bucket(PG_FUNCTION_ARGS)
}
PG_RETURN_TIMESTAMPTZ(result);
}
static inline void
check_period_is_daily(int64 period)
{
#ifdef HAVE_INT64_TIMESTAMP
int64 day = USECS_PER_DAY;
#else
int64 day = SECS_PER_DAY;
#endif
if (period < day)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("interval must not have sub-day precision")
));
}
if (period % day != 0)
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("interval must be a multiple of a day")
));
}
}
PG_FUNCTION_INFO_V1(date_bucket);
Datum
date_bucket(PG_FUNCTION_ARGS)
{
Interval *interval = PG_GETARG_INTERVAL_P(0);
DateADT date = PG_GETARG_DATEADT(1);
Datum converted_ts,
bucketed;
int64 period = -1;
if (DATE_NOT_FINITE(date))
PG_RETURN_DATEADT(date);
period = get_interval_period(interval);
/* check the period aligns on a date */
check_period_is_daily(period);
/* convert to timestamp (NOT tz), bucket, convert back to date */
converted_ts = DirectFunctionCall1(date_timestamp, PG_GETARG_DATUM(1));
bucketed = DirectFunctionCall2(timestamp_bucket, PG_GETARG_DATUM(0), converted_ts);
return DirectFunctionCall1(timestamp_date, bucketed);
}

View File

@ -43,7 +43,7 @@ SELECT count(*)
AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb');
count
-------
124
126
(1 row)
\c postgres
@ -67,7 +67,7 @@ SELECT count(*)
AND refobjid = (SELECT oid FROM pg_extension WHERE extname = 'timescaledb');
count
-------
124
126
(1 row)
\c single

View File

@ -239,6 +239,11 @@ SELECT timestamp '294247-01-01 23:59:59.999999' + interval '1 us';
SELECT _timescaledb_internal.to_unix_microseconds(timestamp '294247-01-01 23:59:59.999999' + interval '1 us');
ERROR: timestamp out of range
--no time_bucketing of dates not by integer # of days
SELECT time_bucket('1 hour', DATE '2012-01-01');
ERROR: interval must not have sub-day precision
SELECT time_bucket('25 hour', DATE '2012-01-01');
ERROR: interval must be a multiple of a day
\set ON_ERROR_STOP 1
SELECT time_bucket(INTERVAL '1 day', TIMESTAMP '2011-01-02 01:01:01');
time_bucket
@ -563,3 +568,44 @@ FROM unnest(ARRAY[
108 | 108
(4 rows)
SELECT time, time_bucket(INTERVAL '1 day', time::date)
FROM unnest(ARRAY[
date '2017-11-05',
date '2017-11-06'
]) AS time;
time | time_bucket
------------+-------------
11-05-2017 | 11-05-2017
11-06-2017 | 11-06-2017
(2 rows)
SELECT time, time_bucket(INTERVAL '4 day', time::date)
FROM unnest(ARRAY[
date '2017-11-02',
date '2017-11-03',
date '2017-11-06',
date '2017-11-07'
]) AS time;
time | time_bucket
------------+-------------
11-02-2017 | 10-30-2017
11-03-2017 | 11-03-2017
11-06-2017 | 11-03-2017
11-07-2017 | 11-07-2017
(4 rows)
SELECT time, time_bucket(INTERVAL '4 day', time::date, INTERVAL '2 day')
FROM unnest(ARRAY[
date '2017-11-04',
date '2017-11-05',
date '2017-11-08',
date '2017-11-09'
]) AS time;
time | time_bucket
------------+-------------
11-04-2017 | 11-01-2017
11-05-2017 | 11-05-2017
11-08-2017 | 11-05-2017
11-09-2017 | 11-09-2017
(4 rows)

View File

@ -144,6 +144,11 @@ SELECT _timescaledb_internal.to_unix_microseconds('294247-01-01 23:59:59.999999'
SELECT timestamp '294247-01-01 23:59:59.999999' + interval '1 us';
SELECT _timescaledb_internal.to_unix_microseconds(timestamp '294247-01-01 23:59:59.999999' + interval '1 us');
--no time_bucketing of dates not by integer # of days
SELECT time_bucket('1 hour', DATE '2012-01-01');
SELECT time_bucket('25 hour', DATE '2012-01-01');
\set ON_ERROR_STOP 1
SELECT time_bucket(INTERVAL '1 day', TIMESTAMP '2011-01-02 01:01:01');
@ -328,3 +333,26 @@ FROM unnest(ARRAY[
'107',
'108'
]::int[]) AS time;
SELECT time, time_bucket(INTERVAL '1 day', time::date)
FROM unnest(ARRAY[
date '2017-11-05',
date '2017-11-06'
]) AS time;
SELECT time, time_bucket(INTERVAL '4 day', time::date)
FROM unnest(ARRAY[
date '2017-11-02',
date '2017-11-03',
date '2017-11-06',
date '2017-11-07'
]) AS time;
SELECT time, time_bucket(INTERVAL '4 day', time::date, INTERVAL '2 day')
FROM unnest(ARRAY[
date '2017-11-04',
date '2017-11-05',
date '2017-11-08',
date '2017-11-09'
]) AS time;