mirror of
https://github.com/timescale/timescaledb.git
synced 2025-05-15 01:53:41 +08:00
This patch does following: 1. Planner changes to create ChunkDispatch node when MERGE command has INSERT action. 2. Changes to map partition attributes from a tuple returned from child node of ChunkDispatch against physical targetlist, so that ChunkDispatch node can read the correct value from partition column. 3. Fixed issues with MERGE on compressed hypertable. 4. Added more testcases. 5. MERGE in distributed hypertables is not supported. 6. Since there is no Custom Scan (HypertableModify) node for MERGE with UPDATE/DELETE on compressed hypertables, we don't support this. Fixes #5139
894 lines
28 KiB
SQL
894 lines
28 KiB
SQL
-- This file and its contents are licensed under the Apache License 2.0.
|
|
-- Please see the included NOTICE for copyright information and
|
|
-- LICENSE-APACHE for a copy of the license.
|
|
|
|
\c :TEST_DBNAME :ROLE_SUPERUSER
|
|
|
|
-- Create target table with location and temperature
|
|
CREATE TABLE target (
|
|
time TIMESTAMPTZ NOT NULL,
|
|
location SMALLINT NOT NULL,
|
|
temperature DOUBLE PRECISION NULL,
|
|
val text default 'string -'
|
|
);
|
|
|
|
SELECT create_hypertable(
|
|
'target',
|
|
'time',
|
|
chunk_time_interval => INTERVAL '5 seconds');
|
|
|
|
INSERT INTO target
|
|
SELECT time, location, 14 as temperature
|
|
FROM generate_series(
|
|
'2021-01-01 00:00:00',
|
|
'2021-01-01 00:00:09',
|
|
INTERVAL '5 seconds'
|
|
) as time,
|
|
generate_series(1,4) as location;
|
|
|
|
-- Create source table with location and temperature
|
|
CREATE TABLE source (
|
|
time TIMESTAMPTZ NOT NULL,
|
|
location SMALLINT NOT NULL,
|
|
temperature DOUBLE PRECISION NULL
|
|
);
|
|
|
|
SELECT create_hypertable(
|
|
'source',
|
|
'time',
|
|
chunk_time_interval => INTERVAL '5 seconds');
|
|
|
|
-- Generate data that overlaps with target table
|
|
INSERT INTO source
|
|
SELECT time, location, 80 as temperature
|
|
FROM generate_series(
|
|
'2021-01-01 00:00:05',
|
|
'2021-01-01 00:00:14',
|
|
INTERVAL '5 seconds'
|
|
) as time,
|
|
generate_series(1,4) as location;
|
|
|
|
-- Print table/rows/num of chunks
|
|
select * from target order by time, location asc;
|
|
select * from source order by time, location asc;
|
|
|
|
-- CREATE normal PostgreSQL tables
|
|
CREATE TABLE target_pg AS SELECT * FROM target;
|
|
CREATE TABLE source_pg AS SELECT * FROM source;
|
|
|
|
-- Merge UPDATE matched rows for normal PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
UPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE';
|
|
|
|
-- Merge UPDATE matched rows for hypertables
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
UPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE';
|
|
|
|
-- ensure TARGET PG table and hypertable are same
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- Merge DELETE matched rows for normal PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
|
|
-- Merge DELETE matched rows for hypertables
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
|
|
-- ensure TARGET PG table and hypertable are same
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- clean up tables
|
|
DELETE FROM target_pg;
|
|
DELETE FROM target;
|
|
DELETE FROM source_pg;
|
|
DELETE FROM source;
|
|
|
|
INSERT INTO target
|
|
SELECT time, location, 14 as temperature
|
|
FROM generate_series(
|
|
'2021-01-01 00:00:00',
|
|
'2021-01-01 00:00:09',
|
|
INTERVAL '5 seconds'
|
|
) as time,
|
|
generate_series(1,4) as location;
|
|
|
|
INSERT INTO source
|
|
SELECT time, location, 80 as temperature
|
|
FROM generate_series(
|
|
'2021-01-01 00:00:05',
|
|
'2021-01-01 00:00:14',
|
|
INTERVAL '5 seconds'
|
|
) as time,
|
|
generate_series(1,4) as location;
|
|
|
|
INSERT INTO target_pg SELECT * FROM target;
|
|
INSERT INTO source_pg SELECT * FROM source;
|
|
|
|
-- Merge UPDATE matched rows and INSERT new row for unmatched rows for normal PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
UPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE'
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');
|
|
|
|
-- Merge UPDATE matched rows and INSERT new row for unmatched rows for hypertables
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
UPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE'
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');
|
|
|
|
-- ensure TARGET PG table and hypertable are same
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- Merge INSERT with constant literals for normal PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.location = 1234
|
|
WHEN NOT MATCHED THEN
|
|
INSERT VALUES ('2021-11-01 00:00:05'::timestamp with time zone, 5, 210, 'string - INSERTED BY MERGE');
|
|
|
|
-- Merge INSERT with constant literals for hypertables
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.location = 1234
|
|
WHEN NOT MATCHED THEN
|
|
INSERT VALUES ('2021-11-01 00:00:05'::timestamp with time zone, 5, 210, 'string - INSERTED BY MERGE');
|
|
|
|
-- ensure TARGET PG table and hypertable are same
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- Merge with INSERT/DELETE/UPDATE on PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED AND t.location = 560076 THEN
|
|
UPDATE SET temperature = (t.temperature + s.temperature) * 2, val = val || ' UPDATED BY MERGE'
|
|
WHEN MATCHED AND t.location = 560083 THEN
|
|
DELETE
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');
|
|
|
|
-- Merge with INSERT/DELETE/UPDATE on hypertables
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED AND t.location = 560076 THEN
|
|
UPDATE SET temperature = (t.temperature + s.temperature) * 2, val = val || ' UPDATED BY MERGE'
|
|
WHEN MATCHED AND t.location = 560083 THEN
|
|
DELETE
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');
|
|
|
|
-- ensure TARGET PG table and hypertable are same
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- Merge with Subqueries on PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.location > (SELECT count(*) FROM source_pg)
|
|
WHEN MATCHED AND t.temperature = 23 THEN
|
|
UPDATE SET temperature = (SELECT count(*) FROM target_pg) * 2, val = val || ' UPDATED BY MERGE'
|
|
WHEN MATCHED AND t.temperature = 47 THEN
|
|
DELETE
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'SUBQUERY string - INSERTED BY MERGE');
|
|
|
|
-- Merge with Subqueries on hypertables
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.location > (SELECT count(*) FROM source)
|
|
WHEN MATCHED AND t.temperature = 23 THEN
|
|
UPDATE SET temperature = (SELECT count(*) FROM target) * 2, val = val || ' UPDATED BY MERGE'
|
|
WHEN MATCHED AND t.temperature = 47 THEN
|
|
DELETE
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'SUBQUERY string - INSERTED BY MERGE');
|
|
|
|
-- ensure TARGET PG table and hypertable are same
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- clean up tables
|
|
DELETE FROM target_pg;
|
|
DELETE FROM target;
|
|
DELETE FROM source_pg;
|
|
DELETE FROM source;
|
|
|
|
-- TEST with target as hypertable and source as normal PG table
|
|
INSERT INTO target
|
|
SELECT time, location, 14 as temperature
|
|
FROM generate_series(
|
|
'2021-01-01 00:00:00',
|
|
'2021-01-01 00:00:09',
|
|
INTERVAL '5 seconds'
|
|
) as time,
|
|
generate_series(1,4) as location;
|
|
|
|
INSERT INTO source
|
|
SELECT time, location, 80 as temperature
|
|
FROM generate_series(
|
|
'2021-01-01 00:00:05',
|
|
'2021-01-01 00:00:14',
|
|
INTERVAL '5 seconds'
|
|
) as time,
|
|
generate_series(1,4) as location;
|
|
|
|
INSERT INTO target_pg SELECT * FROM target;
|
|
INSERT INTO source_pg SELECT * FROM source;
|
|
|
|
-- Merge UPDATE matched rows for normal PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
UPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE';
|
|
|
|
-- Merge UPDATE with target as hypertables and source as normal PG tables
|
|
MERGE INTO target t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
UPDATE SET temperature = (t.temperature + s.temperature)/2, val = val || ' UPDATED BY MERGE';
|
|
|
|
-- ensure TARGET PG table and hypertable are same
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- Merge DELETE matched rows for normal PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
|
|
-- Merge DELETE with target as hypertables and source as normal PG tables
|
|
MERGE INTO target t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
|
|
-- ensure TARGET PG table and hypertable are same
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- Merge INSERT with constant literals for normal PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.location = 1234
|
|
WHEN NOT MATCHED THEN
|
|
INSERT VALUES ('2021-11-01 00:00:05'::timestamp with time zone, 5, 210, 'string - INSERTED BY MERGE');
|
|
|
|
-- Merge INSERT with constant literals for target as hypertables and source as normal PG tables
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.location = 1234
|
|
WHEN NOT MATCHED THEN
|
|
INSERT VALUES ('2021-11-01 00:00:05'::timestamp with time zone, 5, 210, 'string - INSERTED BY MERGE');
|
|
|
|
-- ensure TARGET PG table and hypertable are same
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- Merge with INSERT/DELETE/UPDATE on PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED AND t.temperature = 23 THEN
|
|
UPDATE SET temperature = (t.temperature + s.temperature) * 2, val = val || ' UPDATED BY MERGE'
|
|
WHEN MATCHED AND t.temperature = 47 THEN
|
|
DELETE
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');
|
|
|
|
-- Merge with INSERT/DELETE/UPDATE on target as hypertables and source as normal PG tables
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED AND t.temperature = 23 THEN
|
|
UPDATE SET temperature = (t.temperature + s.temperature) * 2, val = val || ' UPDATED BY MERGE'
|
|
WHEN MATCHED AND t.temperature = 47 THEN
|
|
DELETE
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (time, location, temperature, val) VALUES (s.time, s.location, s.temperature, 'string - INSERTED BY MERGE');
|
|
|
|
-- ensure TARGET PG table and hypertable are same
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
DROP TABLE target_pg CASCADE;
|
|
DROP TABLE target CASCADE;
|
|
DROP TABLE source_pg CASCADE;
|
|
DROP TABLE source CASCADE;
|
|
|
|
-- test MERGE with source being a PARTITION table
|
|
CREATE TABLE source_pg(
|
|
id INT NOT NULL,
|
|
dev INT NOT NULL,
|
|
value INT,
|
|
CONSTRAINT cstr_source_pky PRIMARY KEY (id)
|
|
) PARTITION BY LIST (id);
|
|
|
|
CREATE TABLE source_1_2_3_4 PARTITION OF source_pg FOR VALUES IN (1,2,3,4);
|
|
CREATE TABLE source_5_6_7_8 PARTITION OF source_pg FOR VALUES IN (5,6,7,8);
|
|
|
|
INSERT INTO source_pg SELECT generate_series(1,8), 44,55;
|
|
|
|
CREATE TABLE target (
|
|
ts TIMESTAMP WITH TIME ZONE NOT NULL,
|
|
id INT NOT NULL,
|
|
dev INT NOT NULL,
|
|
FOREIGN KEY (id) REFERENCES source_pg(id) ON DELETE CASCADE
|
|
);
|
|
|
|
SELECT create_hypertable(
|
|
relation => 'target',
|
|
time_column_name => 'ts'
|
|
);
|
|
|
|
insert into target values ('2023-01-12 00:00:05'::timestamp with time zone, 1,2);
|
|
insert into target values ('2023-01-12 00:00:10'::timestamp with time zone, 2,2);
|
|
insert into target values ('2023-01-12 00:00:15'::timestamp with time zone, 3,2);
|
|
insert into target values ('2023-01-12 00:00:20'::timestamp with time zone, 4,2);
|
|
insert into target values ('2023-01-14 00:00:25'::timestamp with time zone, 5,2);
|
|
insert into target values ('2023-01-14 00:00:30'::timestamp with time zone, 6,2);
|
|
insert into target values ('2023-01-14 00:00:35'::timestamp with time zone, 7,2);
|
|
insert into target values ('2023-01-14 00:00:40'::timestamp with time zone, 8,2);
|
|
|
|
CREATE TABLE target_pg AS SELECT * FROM target;
|
|
|
|
-- Merge UPDATE matched rows for normal PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.id = s.id
|
|
WHEN MATCHED THEN
|
|
UPDATE SET dev = (t.dev + s.dev)/2;
|
|
|
|
-- Merge UPDATE matched rows for hypertables
|
|
MERGE INTO target t
|
|
USING source_pg s
|
|
ON t.id = s.id
|
|
WHEN MATCHED THEN
|
|
UPDATE SET dev = (t.dev + s.dev)/2;
|
|
|
|
-- ensure TARGET PG table and hypertable are same
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- Merge DELETE matched rows for normal PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.id = s.id
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
|
|
-- Merge DELETE matched rows for hypertables
|
|
MERGE INTO target t
|
|
USING source_pg s
|
|
ON t.id = s.id
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
|
|
-- ensure TARGET PG table and hypertable are same
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- clean up tables
|
|
DROP TABLE target_pg CASCADE;
|
|
DROP TABLE target CASCADE;
|
|
DROP TABLE source_pg CASCADE;
|
|
|
|
-- test MERGE with hypertables with time and space partitions
|
|
CREATE TABLE target (
|
|
filler_1 int,
|
|
filler_2 int,
|
|
filler_3 int,
|
|
time timestamptz NOT NULL,
|
|
device_id int,
|
|
device_id_peer int,
|
|
v0 int,
|
|
v1 float,
|
|
v2 float,
|
|
v3 float
|
|
);
|
|
|
|
SELECT create_hypertable ('target', 'time', 'device_id', 5);
|
|
SELECT add_dimension('target', 'device_id_peer', 5);
|
|
SELECT add_dimension('target', 'v2', 5);
|
|
INSERT INTO target (time, device_id, device_id_peer, v0, v1, v2, v3)
|
|
SELECT time,
|
|
device_id,
|
|
0,
|
|
device_id + 1,
|
|
device_id + 2,
|
|
device_id + 0.5,
|
|
NULL
|
|
FROM generate_series('2000-01-01 0:00:00+0'::timestamptz, '2000-01-05 23:55:00+0', '20m') gtime (time),
|
|
generate_series(1, 2, 1) gdevice (device_id);
|
|
|
|
CREATE TABLE source (
|
|
filler_1 int,
|
|
filler_2 int,
|
|
filler_3 int,
|
|
time timestamptz NOT NULL,
|
|
device_id int
|
|
);
|
|
SELECT create_hypertable ('source', 'time', 'device_id', 3);
|
|
|
|
INSERT INTO source (time, device_id, filler_2, filler_3, filler_1)
|
|
SELECT time,
|
|
device_id,
|
|
device_id + 134,
|
|
device_id + 209,
|
|
device_id + 0.50127
|
|
FROM generate_series('2000-01-01 0:00:00+0'::timestamptz, '2000-01-05 23:55:00+0', '20m') gtime (time),
|
|
generate_series(1, 5, 1) gdevice (device_id);
|
|
|
|
-- create PG tables to compare PG target and hypertable target tables
|
|
CREATE table target_pg as SELECT * FROM target;
|
|
CREATE table source_pg as SELECT * FROM source;
|
|
|
|
-- Merge UDPATE matched rows for normal PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN MATCHED THEN
|
|
UPDATE SET filler_2 = s.filler_1 + 100;
|
|
|
|
-- Merge UDPATE matched rows for space partitioned hypertables
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN MATCHED THEN
|
|
UPDATE SET filler_2 = s.filler_1 + 100;
|
|
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- Merge DELETE matched rows for normal PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
|
|
-- Merge DELETE matched rows for space partitioned hypertables
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- Merge INSERT matched rows for normal PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES
|
|
(s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);
|
|
|
|
-- Merge INSERT matched rows for space partitioned hypertables
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES
|
|
(s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);
|
|
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- Merge with INSERT/DELETE/UPDATE on PG tables
|
|
MERGE INTO target_pg t
|
|
USING source_pg s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN MATCHED AND t.device_id_peer = 2 THEN
|
|
UPDATE SET filler_2 = s.filler_1 + s.filler_2 + s.filler_3 + 100
|
|
WHEN MATCHED AND t.device_id_peer = 7 THEN
|
|
DELETE
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES
|
|
(s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);
|
|
|
|
-- Merge with INSERT/DELETE/UPDATE on space partitioned hypertables
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN MATCHED AND t.device_id_peer = 2 THEN
|
|
UPDATE SET filler_2 = s.filler_1 + s.filler_2 + s.filler_3 + 100
|
|
WHEN MATCHED AND t.device_id_peer = 7 THEN
|
|
DELETE
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES
|
|
(s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);
|
|
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
-- clean up tables
|
|
DROP TABLE target_pg CASCADE;
|
|
DROP TABLE target CASCADE;
|
|
DROP TABLE source_pg CASCADE;
|
|
DROP TABLE source CASCADE;
|
|
|
|
-- TEST with parition column place after similar data type column
|
|
CREATE TABLE target (
|
|
filler_1 int,
|
|
filler_2 int,
|
|
filler_3 int,
|
|
time timestamptz NOT NULL,
|
|
device_id int,
|
|
device_id_peer int,
|
|
v0 int,
|
|
v1 float,
|
|
v2 float,
|
|
v3 float,
|
|
partition_column TIMESTAMPTZ NOT NULL
|
|
);
|
|
|
|
SELECT create_hypertable ('target', 'partition_column');
|
|
|
|
INSERT INTO target (time, device_id, device_id_peer, v0, v1, v2, v3, partition_column)
|
|
SELECT time,
|
|
device_id,
|
|
0,
|
|
device_id + 1,
|
|
device_id + 2,
|
|
device_id + 0.5,
|
|
NULL,
|
|
time + interval '10m'
|
|
FROM generate_series('2000-01-01 0:00:00+0'::timestamptz, '2000-01-05 23:55:00+0', '20m') gtime (time),
|
|
generate_series(1, 2, 1) gdevice (device_id);
|
|
|
|
CREATE TABLE source (
|
|
filler_1 int,
|
|
filler_2 int,
|
|
filler_3 int,
|
|
time timestamptz NOT NULL,
|
|
device_id int
|
|
);
|
|
SELECT create_hypertable ('source', 'time', 'device_id', 3);
|
|
|
|
INSERT INTO source (time, device_id, filler_2, filler_3, filler_1)
|
|
SELECT time,
|
|
device_id,
|
|
device_id + 134,
|
|
device_id + 209,
|
|
device_id + 0.50127
|
|
FROM generate_series('2000-01-01 0:00:00+0'::timestamptz, '2000-01-05 23:55:00+0', '20m') gtime (time),
|
|
generate_series(1, 5, 1) gdevice (device_id);
|
|
|
|
-- create PG tables to compare PG target and hypertable target tables
|
|
CREATE table target_pg as SELECT * FROM target;
|
|
|
|
MERGE INTO target_pg t
|
|
USING source s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (time, device_id, device_id_peer, v0, v1, v2, v3, partition_column) VALUES
|
|
('2010-01-06 05:30:00+05:30', 23, 2, 11, 22, 33, 44, '2023-01-06 05:33:00+05:30');
|
|
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (time, device_id, device_id_peer, v0, v1, v2, v3, partition_column) VALUES
|
|
('2010-01-06 05:30:00+05:30', 23, 2, 11, 22, 33, 44, '2023-01-06 05:33:00+05:30');
|
|
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
MERGE INTO target_pg t
|
|
USING source s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
|
|
SELECT CASE WHEN EXISTS (TABLE target EXCEPT TABLE target_pg)
|
|
OR EXISTS (TABLE target_pg EXCEPT TABLE target)
|
|
THEN 'different'
|
|
ELSE 'same'
|
|
END AS result;
|
|
|
|
MERGE INTO target_pg t
|
|
USING source s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES
|
|
(s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);
|
|
|
|
-- time dimension column is NULL, this will report an null constraint violation error
|
|
\set ON_ERROR_STOP 0
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.device_id = s.device_id
|
|
WHEN NOT MATCHED THEN
|
|
INSERT (filler_1, filler_2, filler_3, time, device_id, device_id_peer, v0, v1, v2, v3) VALUES
|
|
(s.filler_1, s.filler_2, s.filler_3, s.time, s.device_id, s.device_id + 10, 1,2,3,4);
|
|
\set ON_ERROR_STOP 1
|
|
|
|
DROP TABLE target CASCADE;
|
|
DROP TABLE target_pg CASCADE;
|
|
DROP TABLE source CASCADE;
|
|
|
|
-- TEST with target table have CHECK constraints
|
|
CREATE TABLE target (
|
|
time TIMESTAMPTZ NOT NULL,
|
|
location SMALLINT NOT NULL,
|
|
temperature DOUBLE PRECISION NULL CHECK (temperature > 10),
|
|
val text default 'string -'
|
|
);
|
|
|
|
SELECT create_hypertable(
|
|
'target',
|
|
'time',
|
|
chunk_time_interval => INTERVAL '5 seconds');
|
|
|
|
INSERT INTO target
|
|
SELECT time, location, 14 as temperature
|
|
FROM generate_series(
|
|
'2021-01-01 00:00:00',
|
|
'2021-01-01 00:00:09',
|
|
INTERVAL '5 seconds'
|
|
) as time,
|
|
generate_series(1,4) as location;
|
|
|
|
-- Create source table with location and temperature
|
|
CREATE TABLE source (
|
|
time TIMESTAMPTZ NOT NULL,
|
|
location SMALLINT NOT NULL,
|
|
temperature DOUBLE PRECISION NULL
|
|
);
|
|
|
|
-- Generate data that overlaps with target table
|
|
INSERT INTO source
|
|
SELECT time, location, 80 as temperature
|
|
FROM generate_series(
|
|
'2021-01-01 00:00:05',
|
|
'2021-01-01 00:00:14',
|
|
INTERVAL '5 seconds'
|
|
) as time,
|
|
generate_series(1,4) as location;
|
|
|
|
-- CREATE normal PostgreSQL tables
|
|
CREATE TABLE target_pg AS SELECT * FROM target;
|
|
|
|
-- Merge UPDATE/DELETE with DO NOTHING on pg tables
|
|
MERGE INTO target_pg t
|
|
USING source s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
DO NOTHING
|
|
WHEN NOT MATCHED THEN
|
|
DO NOTHING;
|
|
|
|
-- Merge UPDATE/DELETE with DO NOTHING on hypertable
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
DO NOTHING
|
|
WHEN NOT MATCHED THEN
|
|
DO NOTHING;
|
|
|
|
-- Error cases for Merge
|
|
\set ON_ERROR_STOP 0
|
|
|
|
-- Merge UPDATE should fail with check constraint violation
|
|
MERGE INTO target_pg t
|
|
USING source s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
UPDATE SET temperature = 8, val = val || ' UPDATED BY MERGE';
|
|
|
|
-- Merge UPDATE should fail with check constraint violation
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.location = s.location
|
|
WHEN MATCHED THEN
|
|
UPDATE SET temperature = 8, val = val || ' UPDATED BY MERGE';
|
|
|
|
-- Merge error with unreachable WHEN clause on pg tables
|
|
MERGE INTO target_pg t
|
|
USING source s
|
|
ON t.time = s.time AND t.location != s.location
|
|
WHEN MATCHED THEN
|
|
UPDATE SET temperature = 8, val = val || ' UPDATED BY MERGE'
|
|
WHEN MATCHED AND t.time < now() THEN
|
|
DELETE
|
|
WHEN NOT MATCHED THEN
|
|
DO NOTHING;
|
|
|
|
-- Merge error with unreachable WHEN clause on hypertable
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.location != s.location
|
|
WHEN MATCHED THEN
|
|
UPDATE SET temperature = 8, val = val || ' UPDATED BY MERGE'
|
|
WHEN MATCHED AND t.time < now() THEN
|
|
DELETE
|
|
WHEN NOT MATCHED THEN
|
|
DO NOTHING;
|
|
|
|
-- Merge error with unknown action in MERGE WHEN MATCHED clause on pg tables
|
|
MERGE INTO target_pg t
|
|
USING source s
|
|
ON t.time = s.time AND t.location != s.location
|
|
WHEN MATCHED THEN
|
|
SELECT 1;
|
|
|
|
-- Merge error with unknown action in MERGE WHEN MATCHED clause on hypertable
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.time = s.time AND t.location != s.location
|
|
WHEN MATCHED THEN
|
|
SELECT 1;
|
|
|
|
-- Merge error cannot affect row a second time on pg tables
|
|
MERGE INTO target_pg t
|
|
USING source s
|
|
ON t.location = s.location
|
|
WHEN MATCHED THEN
|
|
UPDATE SET temperature = 28, val = val || ' UPDATED BY MERGE';
|
|
|
|
-- Merge error cannot affect row a second time on hypertable
|
|
MERGE INTO target t
|
|
USING source s
|
|
ON t.location = s.location
|
|
WHEN MATCHED THEN
|
|
UPDATE SET temperature = 28, val = val || ' UPDATED BY MERGE';
|
|
|
|
\set ON_ERROR_STOP 1
|
|
|
|
DROP TABLE target CASCADE;
|
|
DROP TABLE target_pg CASCADE;
|
|
DROP TABLE source CASCADE;
|
|
|
|
-- TEST for PERMISSIONS
|
|
CREATE USER priv_user;
|
|
CREATE USER non_priv_user;
|
|
|
|
CREATE TABLE target (
|
|
value DOUBLE PRECISION NOT NULL,
|
|
time TIMESTAMPTZ NOT NULL
|
|
);
|
|
|
|
SELECT table_name FROM create_hypertable(
|
|
'target'::regclass,
|
|
'time'::name, chunk_time_interval=>interval '8 hours',
|
|
create_default_indexes=> false);
|
|
|
|
SELECT '2022-10-10 14:33:44.1234+05:30' as start_date \gset
|
|
INSERT INTO target (value, time)
|
|
SELECT 1,t from generate_series(:'start_date'::timestamptz, :'start_date'::timestamptz + interval '1 day', '5m') t cross join
|
|
generate_series(1,3) s;
|
|
|
|
CREATE TABLE source (
|
|
time TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
|
value DOUBLE PRECISION NOT NULL
|
|
);
|
|
|
|
SELECT table_name FROM create_hypertable(
|
|
'source'::regclass,
|
|
'time'::name, chunk_time_interval=>interval '6 hours',
|
|
create_default_indexes=> false);
|
|
|
|
ALTER TABLE target OWNER TO priv_user;
|
|
ALTER TABLE source OWNER TO priv_user;
|
|
|
|
GRANT SELECT ON source TO non_priv_user;
|
|
SET SESSION AUTHORIZATION non_priv_user;
|
|
|
|
\set ON_ERROR_STOP 0
|
|
-- non_priv_user does not have UPDATE privilege on target table
|
|
MERGE INTO target
|
|
USING source
|
|
ON target.time = source.time
|
|
WHEN MATCHED THEN
|
|
UPDATE SET value = 0;
|
|
|
|
-- non_priv_user does not have DELETE privilege on target table
|
|
MERGE INTO target
|
|
USING source
|
|
ON target.time = source.time
|
|
WHEN MATCHED THEN
|
|
DELETE;
|
|
|
|
-- non_priv_user does not have INSERT privilege on target table
|
|
MERGE INTO target
|
|
USING source
|
|
ON target.time = source.time
|
|
WHEN NOT MATCHED THEN
|
|
INSERT VALUES (10, '2023-01-15 00:00:10'::timestamp with time zone);
|
|
|
|
\set ON_ERROR_STOP 1
|
|
|
|
RESET SESSION AUTHORIZATION;
|
|
DROP TABLE target;
|
|
DROP TABLE source;
|
|
DROP USER priv_user;
|
|
DROP USER non_priv_user;
|