-- This file and its contents are licensed under the Timescale License. -- Please see the included NOTICE for copyright information and -- LICENSE-TIMESCALE for a copy of the license. CREATE OR REPLACE FUNCTION part_func(id TEXT) RETURNS INTEGER LANGUAGE PLPGSQL IMMUTABLE AS $BODY$ DECLARE retval INTEGER; BEGIN retval := CAST(id AS INTEGER); RETURN retval; END $BODY$; -- test null handling \set ON_ERROR_STOP 0 CREATE TABLE n(); SELECT create_hypertable('n',NULL::_timescaledb_internal.dimension_info); ERROR: dimension cannot be NULL SELECT add_dimension('n',NULL::_timescaledb_internal.dimension_info); ERROR: dimension cannot be NULL \set ON_ERROR_STOP 1 SELECT by_range('id'); by_range ----------------- range//id//-//- (1 row) SELECT by_range('id', partition_func => 'part_func'); by_range ------------------------- range//id//-//part_func (1 row) SELECT by_range('id', '1 week'::interval); by_range ------------------------ range//id//@ 7 days//- (1 row) SELECT by_range('id', '1 week'::interval, 'part_func'::regproc); by_range -------------------------------- range//id//@ 7 days//part_func (1 row) SELECT by_hash('id', 3); by_hash ---------------- hash//id//3//- (1 row) SELECT by_hash('id', 3, partition_func => 'part_func'); by_hash ------------------------ hash//id//3//part_func (1 row) -- Check if we handle the varlength of the datatype properly SELECT * FROM by_range('id') WHERE by_range IS NOT NULL; by_range ----------------- range//id//-//- (1 row) SELECT * FROM by_range('id', partition_func => 'part_func') WHERE by_range IS NOT NULL; by_range ------------------------- range//id//-//part_func (1 row) SELECT * FROM by_hash('id', 3) WHERE by_hash IS NOT NULL; by_hash ---------------- hash//id//3//- (1 row) SELECT * FROM by_hash('id', 3, partition_func => 'part_func') WHERE by_hash IS NOT NULL; by_hash ------------------------ hash//id//3//part_func (1 row) \set ON_ERROR_STOP 0 SELECT 'hash//id//3//-'::_timescaledb_internal.dimension_info; ERROR: cannot construct type "dimension_info" from string at character 8 SELECT by_range(NULL::name); ERROR: column_name cannot be NULL SELECT by_hash(NULL::name, 3); ERROR: column_name cannot be NULL \set ON_ERROR_STOP 1 -- Validate generalized hypertable for smallint CREATE TABLE test_table_smallint(id SMALLINT, device INTEGER, time TIMESTAMPTZ); SELECT create_hypertable('test_table_smallint', by_range('id')); NOTICE: adding not-null constraint to column "id" create_hypertable ------------------- (1,t) (1 row) -- default interval SELECT integer_interval FROM timescaledb_information.dimensions WHERE hypertable_name = 'test_table_smallint'; integer_interval ------------------ 10000 (1 row) -- Add data with default partition (10000) INSERT INTO test_table_smallint VALUES (1, 10, '01-01-2023 11:00'::TIMESTAMPTZ); INSERT INTO test_table_smallint VALUES (9999, 10, '01-01-2023 11:00'::TIMESTAMPTZ); INSERT INTO test_table_smallint VALUES (10000, 10, '01-01-2023 11:00'::TIMESTAMPTZ); INSERT INTO test_table_smallint VALUES (20000, 10, '01-01-2023 11:00'::TIMESTAMPTZ); -- Number of chunks SELECT count(*) FROM timescaledb_information.chunks WHERE hypertable_name='test_table_smallint'; count ------- 3 (1 row) -- Validate generalized hypertable for int CREATE TABLE test_table_int(id INTEGER, device INTEGER, time TIMESTAMPTZ); SELECT create_hypertable('test_table_int', by_range('id')); NOTICE: adding not-null constraint to column "id" create_hypertable ------------------- (2,t) (1 row) -- Default interval SELECT integer_interval FROM timescaledb_information.dimensions WHERE hypertable_name = 'test_table_int'; integer_interval ------------------ 100000 (1 row) -- Add data INSERT INTO test_table_int VALUES (1, 10, '01-01-2023 11:00'::TIMESTAMPTZ); INSERT INTO test_table_int VALUES (99999, 10, '01-01-2023 11:00'::TIMESTAMPTZ); INSERT INTO test_table_int VALUES (100000, 10, '01-01-2023 11:00'::TIMESTAMPTZ); INSERT INTO test_table_int VALUES (200000, 10, '01-01-2023 11:00'::TIMESTAMPTZ); -- Number of chunks SELECT count(*) FROM timescaledb_information.chunks WHERE hypertable_name='test_table_int'; count ------- 3 (1 row) -- Validate generalized hypertable for bigint CREATE TABLE test_table_bigint(id BIGINT, device INTEGER, time TIMESTAMPTZ); SELECT create_hypertable('test_table_bigint', by_range('id')); NOTICE: adding not-null constraint to column "id" create_hypertable ------------------- (3,t) (1 row) -- Default interval SELECT integer_interval FROM timescaledb_information.dimensions WHERE hypertable_name = 'test_table_bigint'; integer_interval ------------------ 1000000 (1 row) -- Add data INSERT INTO test_table_bigint VALUES (1, 10, '01-01-2023 11:00'::TIMESTAMPTZ); INSERT INTO test_table_bigint VALUES (999999, 10, '01-01-2023 11:00'::TIMESTAMPTZ); INSERT INTO test_table_bigint VALUES (1000000, 10, '01-01-2023 11:00'::TIMESTAMPTZ); INSERT INTO test_table_bigint VALUES (2000000, 10, '01-01-2023 11:00'::TIMESTAMPTZ); -- Number of chunks SELECT count(*) FROM timescaledb_information.chunks WHERE hypertable_name='test_table_bigint'; count ------- 3 (1 row) DROP TABLE test_table_smallint; DROP TABLE test_table_int; DROP TABLE test_table_bigint; -- Create hypertable with SERIAL column CREATE TABLE jobs_serial (job_id SERIAL, device_id INTEGER, start_time TIMESTAMPTZ, end_time TIMESTAMPTZ, PRIMARY KEY (job_id)); SELECT create_hypertable('jobs_serial', by_range('job_id', partition_interval => 30)); create_hypertable ------------------- (4,t) (1 row) -- Insert data INSERT INTO jobs_serial (device_id, start_time, end_time) SELECT abs(timestamp_hash(t::timestamp)) % 10, t, t + INTERVAL '1 day' FROM generate_series('2018-03-02 1:00'::TIMESTAMPTZ, '2018-03-08 1:00':: TIMESTAMPTZ,'1 hour')t; -- Verify chunk pruning EXPLAIN VERBOSE SELECT * FROM jobs_serial WHERE job_id < 30; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Index Scan using "10_1_jobs_serial_pkey" on _timescaledb_internal._hyper_4_10_chunk (cost=0.15..20.30 rows=523 width=24) Output: _hyper_4_10_chunk.job_id, _hyper_4_10_chunk.device_id, _hyper_4_10_chunk.start_time, _hyper_4_10_chunk.end_time Index Cond: (_hyper_4_10_chunk.job_id < 30) (3 rows) EXPLAIN VERBOSE SELECT * FROM jobs_serial WHERE job_id >= 30 AND job_id < 90; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------- Append (cost=0.15..14.71 rows=16 width=24) -> Index Scan using "11_2_jobs_serial_pkey" on _timescaledb_internal._hyper_4_11_chunk (cost=0.15..7.31 rows=8 width=24) Output: _hyper_4_11_chunk.job_id, _hyper_4_11_chunk.device_id, _hyper_4_11_chunk.start_time, _hyper_4_11_chunk.end_time Index Cond: ((_hyper_4_11_chunk.job_id >= 30) AND (_hyper_4_11_chunk.job_id < 90)) -> Index Scan using "12_3_jobs_serial_pkey" on _timescaledb_internal._hyper_4_12_chunk (cost=0.15..7.31 rows=8 width=24) Output: _hyper_4_12_chunk.job_id, _hyper_4_12_chunk.device_id, _hyper_4_12_chunk.start_time, _hyper_4_12_chunk.end_time Index Cond: ((_hyper_4_12_chunk.job_id >= 30) AND (_hyper_4_12_chunk.job_id < 90)) (7 rows) EXPLAIN VERBOSE SELECT * FROM jobs_serial WHERE job_id > 90; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------- Append (cost=0.15..45.84 rows=1046 width=24) -> Index Scan using "13_4_jobs_serial_pkey" on _timescaledb_internal._hyper_4_13_chunk (cost=0.15..20.30 rows=523 width=24) Output: _hyper_4_13_chunk.job_id, _hyper_4_13_chunk.device_id, _hyper_4_13_chunk.start_time, _hyper_4_13_chunk.end_time Index Cond: (_hyper_4_13_chunk.job_id > 90) -> Index Scan using "14_5_jobs_serial_pkey" on _timescaledb_internal._hyper_4_14_chunk (cost=0.15..20.30 rows=523 width=24) Output: _hyper_4_14_chunk.job_id, _hyper_4_14_chunk.device_id, _hyper_4_14_chunk.start_time, _hyper_4_14_chunk.end_time Index Cond: (_hyper_4_14_chunk.job_id > 90) (7 rows) -- Update rows UPDATE jobs_serial SET end_time = end_time + INTERVAL '1 hour' where job_id = 1; UPDATE jobs_serial SET end_time = end_time + INTERVAL '1 hour' where job_id = 30; UPDATE jobs_serial SET end_time = end_time + INTERVAL '1 hour' where job_id = 90; SELECT start_time, end_time FROM jobs_serial WHERE job_id = 1; start_time | end_time ------------------------------+------------------------------ Fri Mar 02 01:00:00 2018 PST | Sat Mar 03 02:00:00 2018 PST (1 row) SELECT start_time, end_time FROM jobs_serial WHERE job_id = 30; start_time | end_time ------------------------------+------------------------------ Sat Mar 03 06:00:00 2018 PST | Sun Mar 04 07:00:00 2018 PST (1 row) SELECT start_time, end_time FROM jobs_serial WHERE job_id = 90; start_time | end_time ------------------------------+------------------------------ Mon Mar 05 18:00:00 2018 PST | Tue Mar 06 19:00:00 2018 PST (1 row) -- Test delete rows -- Existing tuple counts. We saves these and compare with the values -- after running the delete. CREATE TABLE counts AS SELECT (SELECT count(*) FROM jobs_serial) AS total_count, (SELECT count(*) FROM jobs_serial WHERE job_id < 10) AS remove_count; -- Perform the delete DELETE FROM jobs_serial WHERE job_id < 10; -- Ensure only the intended tuples are deleted. The two counts should be equal. SELECT (SELECT total_count FROM counts) - (SELECT count(*) FROM jobs_serial) AS total_removed, (SELECT remove_count FROM counts) - (SELECT count(*) FROM jobs_serial WHERE job_id < 10) AS matching_removed; total_removed | matching_removed ---------------+------------------ 9 | 9 (1 row) DROP TABLE jobs_serial; DROP TABLE counts; -- Create and validate hypertable with BIGSERIAL column CREATE TABLE jobs_big_serial (job_id BIGSERIAL, device_id INTEGER, start_time TIMESTAMPTZ, end_time TIMESTAMPTZ, PRIMARY KEY (job_id)); SELECT create_hypertable('jobs_big_serial', by_range('job_id', 100)); create_hypertable ------------------- (5,t) (1 row) -- Insert data INSERT INTO jobs_big_serial (device_id, start_time, end_time) SELECT abs(timestamp_hash(t::timestamp)) % 10, t, t + INTERVAL '1 day' FROM generate_series('2018-03-02 1:00'::TIMESTAMPTZ, '2018-03-08 1:00'::TIMESTAMPTZ,'30 mins')t; -- Verify #chunks SELECT count(*) FROM timescaledb_information.chunks; count ------- 3 (1 row) -- Get current sequence and verify updating sequence SELECT currval(pg_get_serial_sequence('jobs_big_serial', 'job_id')); currval --------- 289 (1 row) -- Update sequence value to 500 SELECT setval(pg_get_serial_sequence('jobs_big_serial', 'job_id'), 500, false); setval -------- 500 (1 row) -- Insert few rows and verify that the next sequence starts from 500 INSERT INTO jobs_big_serial (device_id, start_time, end_time) SELECT abs(timestamp_hash(t::timestamp)) % 10, t, t + INTERVAL '1 day' FROM generate_series('2018-03-09 1:00'::TIMESTAMPTZ, '2018-03-10 1:00'::TIMESTAMPTZ,'30 mins')t; -- No data should exist for job_id >= 290 to job_id < 500 SELECT count(*) FROM jobs_big_serial WHERE job_id >= 290 AND job_id < 500; count ------- 0 (1 row) -- The new rows should be added with job_id > 500 SELECT count(*) from jobs_big_serial WHERE job_id > 500; count ------- 48 (1 row) -- Verify show_chunks API SELECT show_chunks('jobs_big_serial', older_than => 100); show_chunks ----------------------------------------- _timescaledb_internal._hyper_5_15_chunk (1 row) SELECT show_chunks('jobs_big_serial', newer_than => 200, older_than => 300); show_chunks ----------------------------------------- _timescaledb_internal._hyper_5_17_chunk (1 row) SELECT show_chunks('jobs_big_serial', newer_than => 500); show_chunks ----------------------------------------- _timescaledb_internal._hyper_5_18_chunk (1 row) -- Verify drop_chunks API SELECT count(*) FROM timescaledb_information.chunks WHERE hypertable_name = 'jobs_big_serial'; count ------- 4 (1 row) SELECT drop_chunks('jobs_big_serial', newer_than => 500); drop_chunks ----------------------------------------- _timescaledb_internal._hyper_5_18_chunk (1 row) SELECT count(*) FROM timescaledb_information.chunks WHERE hypertable_name = 'jobs_big_serial'; count ------- 3 (1 row) SELECT drop_chunks('jobs_big_serial', newer_than => 200, older_than => 300); drop_chunks ----------------------------------------- _timescaledb_internal._hyper_5_17_chunk (1 row) SELECT count(*) FROM timescaledb_information.chunks WHERE hypertable_name = 'jobs_big_serial'; count ------- 2 (1 row) DROP TABLE jobs_big_serial; -- Verify partition function CREATE TABLE test_table_int(id TEXT, device INTEGER, time TIMESTAMPTZ); SELECT create_hypertable('test_table_int', by_range('id', 10, partition_func => 'part_func')); NOTICE: adding not-null constraint to column "id" create_hypertable ------------------- (6,t) (1 row) INSERT INTO test_table_int VALUES('1', 1, '01-01-2023 11:00'::TIMESTAMPTZ); INSERT INTO test_table_int VALUES('10', 10, '01-01-2023 11:00'::TIMESTAMPTZ); INSERT INTO test_table_int VALUES('29', 100, '01-01-2023 11:00'::TIMESTAMPTZ); SELECT count(*) FROM timescaledb_information.chunks WHERE hypertable_name = 'test_table_int'; count ------- 3 (1 row) DROP TABLE test_table_int; DROP FUNCTION part_func; -- Migrate data CREATE TABLE test_table_int(id INTEGER, device INTEGER, time TIMESTAMPTZ); INSERT INTO test_table_int SELECT t, t%10, '01-01-2023 11:00'::TIMESTAMPTZ FROM generate_series(1, 50, 1) t; SELECT create_hypertable('test_table_int', by_range('id', 10), migrate_data => true); NOTICE: adding not-null constraint to column "id" NOTICE: migrating data to chunks create_hypertable ------------------- (7,t) (1 row) -- Show default indexes created for hypertables. SELECT indexname FROM pg_indexes WHERE tablename = 'test_table_int'; indexname ----------------------- test_table_int_id_idx (1 row) SELECT count(*) FROM timescaledb_information.chunks WHERE hypertable_name = 'test_table_int'; count ------- 6 (1 row) DROP TABLE test_table_int; -- create_hypertable without default indexes CREATE TABLE test_table_int(id INTEGER, device INTEGER, time TIMESTAMPTZ); SELECT create_hypertable('test_table_int', by_range('id', 10), create_default_indexes => false); NOTICE: adding not-null constraint to column "id" create_hypertable ------------------- (8,t) (1 row) SELECT indexname FROM pg_indexes WHERE tablename = 'test_table_int'; indexname ----------- (0 rows) DROP TABLE test_table_int; -- if_not_exists CREATE TABLE test_table_int(id INTEGER, device INTEGER, time TIMESTAMPTZ); SELECT create_hypertable('test_table_int', by_range('id', 10)); NOTICE: adding not-null constraint to column "id" create_hypertable ------------------- (9,t) (1 row) -- No error when if_not_exists => true SELECT create_hypertable('test_table_int', by_range('id', 10), if_not_exists => true); NOTICE: table "test_table_int" is already a hypertable, skipping create_hypertable ------------------- (9,f) (1 row) SELECT * FROM _timescaledb_functions.get_create_command('test_table_int'); get_create_command -------------------------------------------------------------------------------------------------------------------- SELECT create_hypertable('public.test_table_int', 'id', chunk_time_interval => 10, create_default_indexes=>FALSE); (1 row) \set ON_ERROR_STOP 0 -- Should throw an error when if_not_exists is not set SELECT create_hypertable('test_table_int', by_range('id', 10)); ERROR: table "test_table_int" is already a hypertable -- Should error out when hash partitioning is used as the main partitioning scheme SELECT create_hypertable('test_table_int', by_hash('device', number_partitions => 2)); ERROR: cannot partition using a closed dimension on primary column \set ON_ERROR_STOP 1 DROP TABLE test_table_int; -- Add dimension CREATE TABLE test_table_int(id INTEGER, device INTEGER, time TIMESTAMPTZ); SELECT create_hypertable('test_table_int', by_range('id', 10), migrate_data => true); NOTICE: adding not-null constraint to column "id" create_hypertable ------------------- (10,t) (1 row) INSERT INTO test_table_int SELECT t, t%10, '01-01-2023 11:00'::TIMESTAMPTZ FROM generate_series(1, 50, 1) t; -- adding a space dimension via "by_hash" should work SELECT add_dimension('test_table_int', by_hash('device', number_partitions => 2)); add_dimension --------------- (11,t) (1 row) SELECT hypertable_name, dimension_number, column_name FROM timescaledb_information.dimensions WHERE hypertable_name = 'test_table_int'; hypertable_name | dimension_number | column_name -----------------+------------------+------------- test_table_int | 1 | id test_table_int | 2 | device (2 rows) SELECT count(*) FROM timescaledb_information.chunks WHERE hypertable_name='test_table_int'; count ------- 6 (1 row) SELECT set_partitioning_interval('test_table_int', 5, 'id'); set_partitioning_interval --------------------------- (1 row) SELECT set_number_partitions('test_table_int', 3, 'device'); set_number_partitions ----------------------- (1 row) SELECT integer_interval, num_partitions FROM timescaledb_information.dimensions WHERE column_name in ('id', 'device'); integer_interval | num_partitions ------------------+---------------- 5 | | 3 (2 rows) DROP TABLE test_table_int; -- Hypertable with time dimension using new API CREATE TABLE test_time(time TIMESTAMP NOT NULL, device INT, temp FLOAT); SELECT create_hypertable('test_time', by_range('time')); WARNING: column type "timestamp without time zone" used for "time" does not follow best practices create_hypertable ------------------- (11,t) (1 row) -- Default interval SELECT time_interval FROM timescaledb_information.dimensions WHERE hypertable_name = 'test_time'; time_interval --------------- @ 7 days (1 row) INSERT INTO test_time SELECT t, (abs(timestamp_hash(t::timestamp)) % 10) + 1, 0.10 FROM generate_series('2018-03-02 1:00'::TIMESTAMPTZ, '2018-03-08 1:00', '1 hour') t; SELECT count(*) FROM timescaledb_information.chunks WHERE hypertable_name='test_time'; count ------- 2 (1 row) SELECT add_dimension('test_time', by_range('device', partition_interval => 2)); NOTICE: adding not-null constraint to column "device" add_dimension --------------- (13,t) (1 row) SELECT hypertable_name, dimension_number, column_name FROM timescaledb_information.dimensions WHERE hypertable_name = 'test_time'; hypertable_name | dimension_number | column_name -----------------+------------------+------------- test_time | 1 | time test_time | 2 | device (2 rows) SELECT set_partitioning_interval('test_time', INTERVAL '1 day', 'time'); set_partitioning_interval --------------------------- (1 row) SELECT time_interval FROM timescaledb_information.dimensions WHERE hypertable_name = 'test_time' AND column_name = 'time'; time_interval --------------- @ 1 day (1 row) DROP TABLE test_time;