|
| 1 | +-- This file and its contents are licensed under the Apache License 2.0. |
| 2 | +-- Please see the included NOTICE for copyright information and |
| 3 | +-- LICENSE-APACHE for a copy of the license. |
| 4 | +-- Tests for MERGE ... WHEN NOT MATCHED BY SOURCE on hypertables (PG17+). |
| 5 | +-- A NOT MATCHED BY SOURCE action is only run by TimescaleDB when the MERGE is |
| 6 | +-- routed through ModifyHypertable, which happens when it also has an INSERT |
| 7 | +-- (NOT MATCHED BY TARGET) action. Each MERGE is run against both a plain table |
| 8 | +-- and a hypertable and the results are compared. |
| 9 | +-- Exact reproduction from the issue: the NOT MATCHED BY SOURCE DELETE must |
| 10 | +-- remove the unmatched target row instead of being silently skipped. |
| 11 | +create table merge_plain (ts timestamptz not null, k int, v int); |
| 12 | +create table merge_ht (ts timestamptz not null, k int, v int); |
| 13 | +select create_hypertable('merge_ht', 'ts', chunk_time_interval => interval '100 days'); |
| 14 | + create_hypertable |
| 15 | +----------------------- |
| 16 | + (1,public,merge_ht,t) |
| 17 | + |
| 18 | +insert into merge_plain values ('2024-01-01', 1, 10), ('2024-01-01', 2, 20); |
| 19 | +insert into merge_ht select * from merge_plain; |
| 20 | +create table merge_src (k int, v int); |
| 21 | +insert into merge_src values (1, 100), (3, 300); |
| 22 | +merge into merge_plain t using merge_src s on t.k = s.k |
| 23 | +when not matched by target then insert (ts, k, v) values ('2024-01-01', s.k, s.v) |
| 24 | +when not matched by source then delete; |
| 25 | +merge into merge_ht t using merge_src s on t.k = s.k |
| 26 | +when not matched by target then insert (ts, k, v) values ('2024-01-01', s.k, s.v) |
| 27 | +when not matched by source then delete; |
| 28 | +select k, v from merge_ht order by k; |
| 29 | + k | v |
| 30 | +---+----- |
| 31 | + 1 | 10 |
| 32 | + 3 | 300 |
| 33 | + |
| 34 | +select case when exists (table merge_ht except table merge_plain) |
| 35 | + or exists (table merge_plain except table merge_ht) |
| 36 | + then 'different' else 'same' end as result; |
| 37 | + result |
| 38 | +-------- |
| 39 | + same |
| 40 | + |
| 41 | +drop table merge_plain, merge_ht, merge_src; |
| 42 | +-- All three match kinds together, with a conditional NOT MATCHED BY SOURCE, |
| 43 | +-- spanning multiple chunks. |
| 44 | +create table target_pg (time timestamptz not null, location int, temperature int); |
| 45 | +create table target_ht (time timestamptz not null, location int, temperature int); |
| 46 | +select create_hypertable('target_ht', 'time', chunk_time_interval => interval '1 day'); |
| 47 | + create_hypertable |
| 48 | +------------------------ |
| 49 | + (2,public,target_ht,t) |
| 50 | + |
| 51 | +insert into target_pg |
| 52 | +select time, location, 14 |
| 53 | +from generate_series('2021-01-01'::timestamptz, '2021-01-04', interval '1 day') time, |
| 54 | + generate_series(1, 4) location; |
| 55 | +insert into target_ht select * from target_pg; |
| 56 | +-- Source matches some target rows, misses others, and adds a new one. |
| 57 | +create table source (location int, temperature int); |
| 58 | +insert into source values (1, 80), (2, 80), (5, 80); |
| 59 | +merge into target_pg t using source s on t.location = s.location |
| 60 | +when matched then update set temperature = (t.temperature + s.temperature) / 2 |
| 61 | +when not matched by target then insert (time, location, temperature) |
| 62 | + values ('2021-01-01', s.location, s.temperature) |
| 63 | +when not matched by source and t.location = 3 then delete |
| 64 | +when not matched by source then update set temperature = 99; |
| 65 | +merge into target_ht t using source s on t.location = s.location |
| 66 | +when matched then update set temperature = (t.temperature + s.temperature) / 2 |
| 67 | +when not matched by target then insert (time, location, temperature) |
| 68 | + values ('2021-01-01', s.location, s.temperature) |
| 69 | +when not matched by source and t.location = 3 then delete |
| 70 | +when not matched by source then update set temperature = 99; |
| 71 | +select case when exists (table target_ht except table target_pg) |
| 72 | + or exists (table target_pg except table target_ht) |
| 73 | + then 'different' else 'same' end as result; |
| 74 | + result |
| 75 | +-------- |
| 76 | + same |
| 77 | + |
| 78 | +drop table target_pg, target_ht, source; |
| 79 | +-- INSERT takes the partitioning column from a source column. |
| 80 | +create table merge_plain (ts timestamptz not null, k int, v int); |
| 81 | +create table merge_ht (ts timestamptz not null, k int, v int); |
| 82 | +select create_hypertable('merge_ht', 'ts', chunk_time_interval => interval '1 day'); |
| 83 | + create_hypertable |
| 84 | +----------------------- |
| 85 | + (3,public,merge_ht,t) |
| 86 | + |
| 87 | +insert into merge_plain values ('2024-01-01', 1, 10), ('2024-01-02', 2, 20), ('2024-01-03', 3, 30); |
| 88 | +insert into merge_ht select * from merge_plain; |
| 89 | +create table merge_src (ts timestamptz, k int, v int); |
| 90 | +insert into merge_src values ('2024-02-01', 1, 100), ('2024-02-05', 9, 900); |
| 91 | +merge into merge_plain t using merge_src s on t.k = s.k |
| 92 | +when not matched by target then insert (ts, k, v) values (s.ts, s.k, s.v) |
| 93 | +when not matched by source then delete; |
| 94 | +merge into merge_ht t using merge_src s on t.k = s.k |
| 95 | +when not matched by target then insert (ts, k, v) values (s.ts, s.k, s.v) |
| 96 | +when not matched by source then delete; |
| 97 | +select case when exists (table merge_ht except table merge_plain) |
| 98 | + or exists (table merge_plain except table merge_ht) |
| 99 | + then 'different' else 'same' end as result; |
| 100 | + result |
| 101 | +-------- |
| 102 | + same |
| 103 | + |
| 104 | +drop table merge_plain, merge_ht, merge_src; |
| 105 | +-- Empty source: every target row is NOT MATCHED BY SOURCE. |
| 106 | +create table merge_plain (ts timestamptz not null, k int, v int); |
| 107 | +create table merge_ht (ts timestamptz not null, k int, v int); |
| 108 | +select create_hypertable('merge_ht', 'ts', chunk_time_interval => interval '1 day'); |
| 109 | + create_hypertable |
| 110 | +----------------------- |
| 111 | + (4,public,merge_ht,t) |
| 112 | + |
| 113 | +insert into merge_plain values ('2024-01-01', 1, 10), ('2024-01-02', 2, 20), ('2024-01-03', 3, 30); |
| 114 | +insert into merge_ht select * from merge_plain; |
| 115 | +create table merge_src (ts timestamptz, k int, v int); |
| 116 | +merge into merge_plain t using merge_src s on t.k = s.k |
| 117 | +when not matched by target then insert (ts, k, v) values (s.ts, s.k, s.v) |
| 118 | +when not matched by source then delete; |
| 119 | +merge into merge_ht t using merge_src s on t.k = s.k |
| 120 | +when not matched by target then insert (ts, k, v) values (s.ts, s.k, s.v) |
| 121 | +when not matched by source then delete; |
| 122 | +select count(*) as merge_ht_rows from merge_ht; |
| 123 | + merge_ht_rows |
| 124 | +--------------- |
| 125 | + 0 |
| 126 | + |
| 127 | +select case when exists (table merge_ht except table merge_plain) |
| 128 | + or exists (table merge_plain except table merge_ht) |
| 129 | + then 'different' else 'same' end as result; |
| 130 | + result |
| 131 | +-------- |
| 132 | + same |
| 133 | + |
| 134 | +drop table merge_plain, merge_ht, merge_src; |
0 commit comments