mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-21 13:14:19 +08:00
The functions for adding and updating dimensions have been refactored in C to: - improve usage of proper error codes - make messages that better conform with the PostgreSQL standard. - improve security by avoiding that lots of code run under SECURITY DEFINER A new if_not_exists option has also been added to add_dimension() and a the number of partitions can now be set using the new set_number_partitions() function. A bug in the validation of smallint time intervals has been fixed. The previous code didn't check for intervals > 0 and smallint intervals accepted values up to UINT16_MAX instead of INT16_MAX.
377 lines
13 KiB
SQL
377 lines
13 KiB
SQL
-- Utility function for grouping/slotting time with a given interval.
|
|
CREATE OR REPLACE FUNCTION date_group(
|
|
field timestamp,
|
|
group_interval interval
|
|
)
|
|
RETURNS timestamp LANGUAGE SQL STABLE AS
|
|
$BODY$
|
|
SELECT to_timestamp((EXTRACT(EPOCH from $1)::int /
|
|
EXTRACT(EPOCH from group_interval)::int) *
|
|
EXTRACT(EPOCH from group_interval)::int)::timestamp;
|
|
$BODY$;
|
|
|
|
CREATE TABLE PUBLIC."testNs" (
|
|
"timeCustom" TIMESTAMP NOT NULL,
|
|
device_id TEXT NOT NULL,
|
|
series_0 DOUBLE PRECISION NULL,
|
|
series_1 DOUBLE PRECISION NULL,
|
|
series_2 DOUBLE PRECISION NULL,
|
|
series_bool BOOLEAN NULL
|
|
);
|
|
CREATE INDEX ON PUBLIC."testNs" (device_id, "timeCustom" DESC NULLS LAST) WHERE device_id IS NOT NULL;
|
|
|
|
\c single :ROLE_SUPERUSER
|
|
CREATE SCHEMA "testNs" AUTHORIZATION :ROLE_DEFAULT_PERM_USER;
|
|
\c single :ROLE_DEFAULT_PERM_USER
|
|
SELECT * FROM create_hypertable('"public"."testNs"', 'timeCustom', 'device_id', 2, associated_schema_name=>'testNs' );
|
|
|
|
\c single
|
|
INSERT INTO PUBLIC."testNs"("timeCustom", device_id, series_0, series_1) VALUES
|
|
('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 1),
|
|
('2009-11-12T01:00:00+00:00', 'dev1', 1.5, 2),
|
|
('2009-11-10T23:00:02+00:00', 'dev1', 2.5, 3);
|
|
|
|
INSERT INTO PUBLIC."testNs"("timeCustom", device_id, series_0, series_1) VALUES
|
|
('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 1),
|
|
('2009-11-10T23:00:00+00:00', 'dev2', 1.5, 2);
|
|
|
|
SELECT * FROM PUBLIC."testNs";
|
|
|
|
SET client_min_messages = WARNING;
|
|
|
|
\echo 'The next 2 queries will differ in output between UTC and EST since the mod is on the 100th hour UTC'
|
|
SET timezone = 'UTC';
|
|
SELECT date_group("timeCustom", '100 days') AS time, sum(series_0)
|
|
FROM PUBLIC."testNs" GROUP BY time ORDER BY time ASC;
|
|
|
|
SET timezone = 'EST';
|
|
SELECT date_group("timeCustom", '100 days') AS time, sum(series_0)
|
|
FROM PUBLIC."testNs" GROUP BY time ORDER BY time ASC;
|
|
|
|
\echo 'The rest of the queries will be the same in output between UTC and EST'
|
|
SET timezone = 'UTC';
|
|
SELECT date_group("timeCustom", '1 day') AS time, sum(series_0)
|
|
FROM PUBLIC."testNs" GROUP BY time ORDER BY time ASC;
|
|
|
|
SET timezone = 'EST';
|
|
SELECT date_group("timeCustom", '1 day') AS time, sum(series_0)
|
|
FROM PUBLIC."testNs" GROUP BY time ORDER BY time ASC;
|
|
|
|
SET timezone = 'UTC';
|
|
|
|
SELECT *
|
|
FROM PUBLIC."testNs"
|
|
WHERE "timeCustom" >= TIMESTAMP '2009-11-10T23:00:00'
|
|
AND "timeCustom" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY "timeCustom" DESC;
|
|
|
|
SET timezone = 'EST';
|
|
SELECT *
|
|
FROM PUBLIC."testNs"
|
|
WHERE "timeCustom" >= TIMESTAMP '2009-11-10T23:00:00'
|
|
AND "timeCustom" < TIMESTAMP '2009-11-12T01:00:00' ORDER BY "timeCustom" DESC;
|
|
|
|
SET timezone = 'UTC';
|
|
SELECT date_group("timeCustom", '1 day') AS time, sum(series_0)
|
|
FROM PUBLIC."testNs" GROUP BY time ORDER BY time ASC LIMIT 2;
|
|
|
|
SET timezone = 'EST';
|
|
SELECT date_group("timeCustom", '1 day') AS time, sum(series_0)
|
|
FROM PUBLIC."testNs" GROUP BY time ORDER BY time ASC LIMIT 2;
|
|
|
|
------------------------------------
|
|
-- Test time conversion functions --
|
|
------------------------------------
|
|
\set ON_ERROR_STOP 0
|
|
|
|
SET timezone = 'UTC';
|
|
|
|
-- Conversion to timestamp using Postgres built-in function taking
|
|
-- double. Gives inaccurate result on Postgres <= 9.6.2. Accurate on
|
|
-- Postgres >= 9.6.3.
|
|
SELECT to_timestamp(1486480176.236538);
|
|
|
|
-- extension-specific version taking microsecond UNIX timestamp
|
|
SELECT _timescaledb_internal.to_timestamp(1486480176236538);
|
|
|
|
-- Should be the inverse of the statement above.
|
|
SELECT _timescaledb_internal.to_unix_microseconds('2017-02-07 15:09:36.236538+00');
|
|
|
|
-- In UNIX microseconds, BIGINT MAX is smaller than internal date upper bound
|
|
-- and should therefore be OK. Further, converting to the internal postgres
|
|
-- epoch cannot overflow a 64-bit INTEGER since the postgres epoch is at a
|
|
-- later date compared to the UNIX epoch, and is therefore represented by a
|
|
-- smaller number
|
|
SELECT _timescaledb_internal.to_timestamp(9223372036854775807);
|
|
|
|
-- Julian day zero is -210866803200000000 microseconds from UNIX epoch
|
|
SELECT _timescaledb_internal.to_timestamp(-210866803200000000);
|
|
|
|
\set VERBOSITY default
|
|
-- Going beyond Julian day zero should give out-of-range error
|
|
SELECT _timescaledb_internal.to_timestamp(-210866803200000001);
|
|
|
|
-- Lower bound on date (should return the Julian day zero UNIX timestamp above)
|
|
SELECT _timescaledb_internal.to_unix_microseconds('4714-11-24 00:00:00+00 BC');
|
|
|
|
-- Going beyond lower bound on date should return out-of-range
|
|
SELECT _timescaledb_internal.to_unix_microseconds('4714-11-23 23:59:59.999999+00 BC');
|
|
|
|
-- The upper bound for Postgres TIMESTAMPTZ
|
|
SELECT timestamp '294276-12-31 23:59:59.999999+00';
|
|
|
|
-- Going beyond the upper bound, should fail
|
|
SELECT timestamp '294276-12-31 23:59:59.999999+00' + interval '1 us';
|
|
|
|
-- Cannot represent the upper bound timestamp with a UNIX microsecond timestamp
|
|
-- since the Postgres epoch is at a later date than the UNIX epoch.
|
|
SELECT _timescaledb_internal.to_unix_microseconds('294276-12-31 23:59:59.999999+00');
|
|
|
|
-- Subtracting the difference between the two epochs (10957 days) should bring
|
|
-- us within range.
|
|
SELECT timestamp '294276-12-31 23:59:59.999999+00' - interval '10957 days';
|
|
|
|
SELECT _timescaledb_internal.to_unix_microseconds('294247-01-01 23:59:59.999999');
|
|
|
|
-- Adding one microsecond should take us out-of-range again
|
|
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');
|
|
|
|
SELECT time, time_bucket(INTERVAL '2 day ', time)
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP '2011-01-01 01:01:01',
|
|
TIMESTAMP '2011-01-02 01:01:01',
|
|
TIMESTAMP '2011-01-03 01:01:01',
|
|
TIMESTAMP '2011-01-04 01:01:01'
|
|
]) AS time;
|
|
|
|
|
|
SELECT int_def, time_bucket(int_def,TIMESTAMP '2011-01-02 01:01:01.111')
|
|
FROM unnest(ARRAY[
|
|
INTERVAL '1 millisecond',
|
|
INTERVAL '1 second',
|
|
INTERVAL '1 minute',
|
|
INTERVAL '1 hour',
|
|
INTERVAL '1 day',
|
|
INTERVAL '2 millisecond',
|
|
INTERVAL '2 second',
|
|
INTERVAL '2 minute',
|
|
INTERVAL '2 hour',
|
|
INTERVAL '2 day'
|
|
]) AS int_def;
|
|
|
|
\set ON_ERROR_STOP 0
|
|
SELECT time_bucket(INTERVAL '1 year',TIMESTAMP '2011-01-02 01:01:01.111');
|
|
SELECT time_bucket(INTERVAL '1 month',TIMESTAMP '2011-01-02 01:01:01.111');
|
|
\set ON_ERROR_STOP 1
|
|
|
|
SELECT time, time_bucket(INTERVAL '5 minute', time)
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP '1970-01-01 00:59:59.999999',
|
|
TIMESTAMP '1970-01-01 01:01:00',
|
|
TIMESTAMP '1970-01-01 01:04:59.999999',
|
|
TIMESTAMP '1970-01-01 01:05:00'
|
|
]) AS time;
|
|
|
|
|
|
SELECT time, time_bucket(INTERVAL '5 minute', time)
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP '2011-01-02 01:04:59.999999',
|
|
TIMESTAMP '2011-01-02 01:05:00',
|
|
TIMESTAMP '2011-01-02 01:09:59.999999',
|
|
TIMESTAMP '2011-01-02 01:10:00'
|
|
]) AS time;
|
|
|
|
--offset with interval
|
|
SELECT time, time_bucket(INTERVAL '5 minute', time , INTERVAL '2 minutes')
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP '2011-01-02 01:01:59.999999',
|
|
TIMESTAMP '2011-01-02 01:02:00',
|
|
TIMESTAMP '2011-01-02 01:06:59.999999',
|
|
TIMESTAMP '2011-01-02 01:07:00'
|
|
]) AS time;
|
|
|
|
SELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2 minutes')
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP '2011-01-02 01:02:59.999999',
|
|
TIMESTAMP '2011-01-02 01:03:00',
|
|
TIMESTAMP '2011-01-02 01:07:59.999999',
|
|
TIMESTAMP '2011-01-02 01:08:00'
|
|
]) AS time;
|
|
|
|
--example to align with an origin
|
|
SELECT time, time_bucket(INTERVAL '5 minute', time - (TIMESTAMP '2011-01-02 00:02:00' - TIMESTAMP 'epoch')) + (TIMESTAMP '2011-01-02 00:02:00'-TIMESTAMP 'epoch')
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP '2011-01-02 01:01:59.999999',
|
|
TIMESTAMP '2011-01-02 01:02:00',
|
|
TIMESTAMP '2011-01-02 01:06:59.999999',
|
|
TIMESTAMP '2011-01-02 01:07:00'
|
|
]) AS time;
|
|
|
|
|
|
--rounding version
|
|
SELECT time, time_bucket(INTERVAL '5 minute', time , - INTERVAL '2.5 minutes') + INTERVAL '2 minutes 30 seconds'
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP '2011-01-02 01:05:01',
|
|
TIMESTAMP '2011-01-02 01:07:29',
|
|
TIMESTAMP '2011-01-02 01:02:30',
|
|
TIMESTAMP '2011-01-02 01:07:30',
|
|
TIMESTAMP '2011-01-02 01:02:29'
|
|
]) AS time;
|
|
|
|
--time_bucket with timezone should mimick date_trunc
|
|
SET timezone TO 'UTC';
|
|
SELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',
|
|
TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',
|
|
TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'
|
|
]) AS time;
|
|
|
|
SELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',
|
|
TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',
|
|
TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'
|
|
]) AS time;
|
|
|
|
--what happens with a local tz
|
|
SET timezone TO 'America/New_York';
|
|
SELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',
|
|
TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+01',
|
|
TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01+02'
|
|
]) AS time;
|
|
|
|
--Note the timestamp tz input is aligned with UTC day /not/ local day. different than date_trunc.
|
|
SELECT time, time_bucket(INTERVAL '1 day', time), date_trunc('day', time)
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',
|
|
TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',
|
|
TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'
|
|
]) AS time;
|
|
|
|
--can force local bucketing with simple cast.
|
|
SELECT time, time_bucket(INTERVAL '1 day', time::timestamp), date_trunc('day', time)
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',
|
|
TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',
|
|
TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'
|
|
]) AS time;
|
|
|
|
--can also use interval to correct
|
|
SELECT time, time_bucket(INTERVAL '1 day', time, -INTERVAL '19 hours'), date_trunc('day', time)
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP WITH TIME ZONE '2011-01-02 01:01:01',
|
|
TIMESTAMP WITH TIME ZONE '2011-01-03 01:01:01+01',
|
|
TIMESTAMP WITH TIME ZONE '2011-01-04 01:01:01+02'
|
|
]) AS time;
|
|
|
|
--dst: same local hour bucketed as two different hours.
|
|
SELECT time, time_bucket(INTERVAL '1 hour', time), date_trunc('hour', time)
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',
|
|
TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07'
|
|
]) AS time;
|
|
|
|
--local alignment changes when bucketing by UTC across dst boundary
|
|
SELECT time, time_bucket(INTERVAL '2 hour', time)
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',
|
|
TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',
|
|
TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',
|
|
TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'
|
|
]) AS time;
|
|
|
|
--local alignment is preserved when bucketing by local time across DST boundary.
|
|
SELECT time, time_bucket(INTERVAL '2 hour', time::timestamp)
|
|
FROM unnest(ARRAY[
|
|
TIMESTAMP WITH TIME ZONE '2017-11-05 10:05:00+07',
|
|
TIMESTAMP WITH TIME ZONE '2017-11-05 12:05:00+07',
|
|
TIMESTAMP WITH TIME ZONE '2017-11-05 13:05:00+07',
|
|
TIMESTAMP WITH TIME ZONE '2017-11-05 15:05:00+07'
|
|
]) AS time;
|
|
|
|
|
|
SELECT time, time_bucket(10, time)
|
|
FROM unnest(ARRAY[
|
|
'99',
|
|
'100',
|
|
'109',
|
|
'110'
|
|
]::int[]) AS time;
|
|
|
|
SELECT time, time_bucket(10, time,2)
|
|
FROM unnest(ARRAY[
|
|
'101',
|
|
'102',
|
|
'111',
|
|
'112'
|
|
]::int[]) AS time;
|
|
|
|
SELECT time, time_bucket(10, time, -2)
|
|
FROM unnest(ARRAY[
|
|
'97',
|
|
'98',
|
|
'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;
|
|
|
|
|
|
-------------------------------------
|
|
--- Test time input functions --
|
|
-------------------------------------
|
|
\c single :ROLE_SUPERUSER
|
|
CREATE OR REPLACE FUNCTION test.interval_to_internal(coltype REGTYPE, value ANYELEMENT = NULL::BIGINT) RETURNS BIGINT
|
|
AS :MODULE_PATHNAME, 'dimension_interval_to_internal_test' LANGUAGE C VOLATILE;
|
|
\c single :ROLE_DEFAULT_PERM_USER
|
|
|
|
SELECT test.interval_to_internal('TIMESTAMP'::regtype, INTERVAL '1 day');
|
|
SELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400000000);
|
|
---should give warning
|
|
SELECT test.interval_to_internal('TIMESTAMP'::regtype, 86400);
|
|
|
|
SELECT test.interval_to_internal('TIMESTAMP'::regtype);
|
|
SELECT test.interval_to_internal('BIGINT'::regtype, 2147483649::bigint);
|
|
|
|
\set VERBOSITY terse
|
|
\set ON_ERROR_STOP 0
|
|
SELECT test.interval_to_internal('INT'::regtype);
|
|
SELECT test.interval_to_internal('INT'::regtype, 2147483649::bigint);
|
|
SELECT test.interval_to_internal('SMALLINT'::regtype, 32768::bigint);
|
|
SELECT test.interval_to_internal('TEXT'::regtype, 32768::bigint);
|
|
SELECT test.interval_to_internal('INT'::regtype, INTERVAL '1 day');
|
|
\set ON_ERROR_STOP 1
|