timescaledb/tsl/test/expected/hypertable_generalization.out
Nikhil Sontakke 577b923822 Disallow hash partitioning on primary column
The new "create_hypertable" API using the dimension info inadvertantly
allowed creating hypertables with hash partitioning on the primary
column. Since the rest of the machinery (policies, tiering, etc.) does
not support hash partitions on primary column properly, we restrict it
now in the new API. The older "create_hypertable" API was disallowing
it earlier anyways.

Fixes #6993
2024-06-05 14:33:53 +05:30

560 lines
20 KiB
Plaintext

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