timescaledb/test/sql/merge.sql
Bharathy b38c920266 MERGE support on hypertables
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
2023-05-27 10:29:11 +05:30

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;