timescaledb/test/sql/merge.sql
Mats Kindahl 41198f62ba Release cache after accessing the cache entry
The cache entry is released while a pointer is still pointing into the
entry, which can potentially lead to accessing freed memory. The
scenario for triggering this is quite complicated, but it requires a
MERGE that has an INSERT statement on a table that either has dropped a
column or added a column with a non-volatile default value.

Co-authored-by: Gayathri Ayyappan <gayathri@timescale.com>
2024-09-04 22:01:18 +02:00

902 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,
to_be_dropped text
);
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;
-- This makes sure we have one column with attisdropped and one column
-- with atthasmissing set to true. These two cases can cause problems
-- with chunk dispatch execution when merging using a when-clause with
-- inserts. Unfortunately they are hard to trigger, so this is not a
-- definitive test.
ALTER TABLE target DROP COLUMN to_be_dropped;
ALTER TABLE target ADD COLUMN val text default 'string -';
-- 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;