-- 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.
\set ANALYZE 'EXPLAIN (analyze, buffers off, costs off, timing off, summary off)'
-- test constraint exclusion with prepared statements and generic plans
CREATE TABLE i3719 (time timestamptz NOT NULL,data text);
SELECT table_name FROM create_hypertable('i3719', 'time');
 table_name 
------------
 i3719

ALTER TABLE i3719 SET (timescaledb.compress);
INSERT INTO i3719 VALUES('2021-01-01 00:00:00', 'chunk 1');
SELECT count(compress_chunk(c)) FROM show_chunks('i3719') c;
 count 
-------
     1

INSERT INTO i3719 VALUES('2021-02-22 08:00:00', 'chunk 2');
SET plan_cache_mode TO force_generic_plan;
PREPARE p1(timestamptz) AS UPDATE i3719 SET data = 'x' WHERE time=$1;
PREPARE p2(timestamptz) AS DELETE FROM i3719 WHERE time=$1;
EXECUTE p1('2021-02-22T08:00:00+00');
EXECUTE p2('2021-02-22T08:00:00+00');
DEALLOCATE p1;
DEALLOCATE p2;
DROP TABLE i3719;
-- github issue 4778
CREATE TABLE metric_5m (
    time TIMESTAMPTZ NOT NULL,
    value DOUBLE PRECISION NOT NULL,
    series_id BIGINT NOT NULL
);
SELECT table_name FROM create_hypertable(
                            'metric_5m'::regclass,
                            'time'::name, chunk_time_interval=>interval '5m',
                            create_default_indexes=> false);
 table_name 
------------
 metric_5m

-- enable compression
ALTER TABLE metric_5m SET (
    timescaledb.compress,
    timescaledb.compress_segmentby = 'series_id',
    timescaledb.compress_orderby = 'time, value'
);
SET work_mem TO '64kB';
SELECT '2022-10-10 14:33:44.1234+05:30' as start_date \gset
-- populate hypertable
INSERT INTO metric_5m (time, series_id, value)
    SELECT t, s,1 from generate_series(:'start_date'::timestamptz, :'start_date'::timestamptz + interval '1 day', '10s') t cross join generate_series(1,10, 1) s;
-- manually compress all chunks
SELECT count(compress_chunk(c)) FROM show_chunks('metric_5m') c;
 count 
-------
   289

-- populate into compressed hypertable, this should not crash
INSERT INTO metric_5m (time, series_id, value)
    SELECT t, s,1 from generate_series(:'start_date'::timestamptz, :'start_date'::timestamptz + interval '1 day', '10s') t cross join generate_series(1,10, 1) s;
-- clean up
RESET work_mem;
DROP TABLE metric_5m;
-- github issue 5134
CREATE TABLE mytab (time TIMESTAMPTZ NOT NULL, a INT, b INT, c INT);
SELECT table_name FROM create_hypertable('mytab', 'time', chunk_time_interval => interval '1 day');
 table_name 
------------
 mytab

INSERT INTO mytab
    SELECT time,
        CASE WHEN (:'start_date'::timestamptz - time < interval '1 days') THEN 1
             WHEN (:'start_date'::timestamptz - time < interval '2 days') THEN 2
             WHEN (:'start_date'::timestamptz - time < interval '3 days') THEN 3 ELSE 4 END as a
    from generate_series(:'start_date'::timestamptz - interval '3 days', :'start_date'::timestamptz, interval '5 sec') as g1(time);
-- enable compression
ALTER TABLE mytab SET (
    timescaledb.compress,
    timescaledb.compress_segmentby = 'a, c'
);
-- get first chunk name
SELECT chunk_schema || '.' || chunk_name as "chunk_table"
       FROM timescaledb_information.chunks
       WHERE hypertable_name = 'mytab' ORDER BY range_start limit 1 \gset
-- compress only the first chunk
SELECT count(compress_chunk(:'chunk_table'));
 count 
-------
     1

-- insert a row into first compressed chunk
INSERT INTO mytab SELECT '2022-10-07 05:30:10+05:30'::timestamp with time zone, 3, 3;
-- should not crash
EXPLAIN (buffers off, costs off) SELECT * FROM :chunk_table;
--- QUERY PLAN ---
 Append
   ->  Custom Scan (ColumnarScan) on _hyper_X_X_chunk
         ->  Seq Scan on compress_hyper_X_X_chunk
   ->  Seq Scan on _hyper_X_X_chunk

DROP TABLE mytab CASCADE;
-- test varchar segmentby
CREATE TABLE comp_seg_varchar (
  time timestamptz NOT NULL,
  source_id varchar(64) NOT NULL,
  label varchar NOT NULL,
  data jsonb
);
SELECT table_name FROM create_hypertable('comp_seg_varchar', 'time');
WARNING:  column type "character varying" used for "source_id" does not follow best practices
WARNING:  column type "character varying" used for "label" does not follow best practices
    table_name    
------------------
 comp_seg_varchar

CREATE UNIQUE INDEX ON comp_seg_varchar(source_id, label, "time" DESC);
ALTER TABLE comp_seg_varchar SET(timescaledb.compress, timescaledb.compress_segmentby = 'source_id, label', timescaledb.compress_orderby = 'time');
INSERT INTO comp_seg_varchar
SELECT time, source_id, label, '{}' AS data
FROM
generate_series('1990-01-01'::timestamptz, '1990-01-10'::timestamptz, INTERVAL '1 day') AS g1(time),
generate_series(1, 3, 1 ) AS g2(source_id),
generate_series(1, 3, 1 ) AS g3(label);
SELECT count(compress_chunk(c)) FROM show_chunks('comp_seg_varchar') c;
 count 
-------
     2

-- all tuples should come from compressed chunks
EXPLAIN (analyze,buffers off, costs off, timing off, summary off) SELECT * FROM comp_seg_varchar;
--- QUERY PLAN ---
 Append (actual rows=90.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_X_X_chunk (actual rows=27.00 loops=1)
         ->  Seq Scan on compress_hyper_X_X_chunk (actual rows=9.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_X_X_chunk (actual rows=63.00 loops=1)
         ->  Seq Scan on compress_hyper_X_X_chunk (actual rows=9.00 loops=1)

INSERT INTO comp_seg_varchar(time, source_id, label, data) VALUES ('1990-01-02 00:00:00+00', 'test', 'test', '{}'::jsonb)
ON CONFLICT (source_id, label, time) DO UPDATE SET data = '{"update": true}';
-- no tuples should be moved into uncompressed
EXPLAIN (analyze,buffers off, costs off, timing off, summary off) SELECT * FROM comp_seg_varchar;
--- QUERY PLAN ---
 Append (actual rows=91.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_X_X_chunk (actual rows=27.00 loops=1)
         ->  Seq Scan on compress_hyper_X_X_chunk (actual rows=9.00 loops=1)
   ->  Seq Scan on _hyper_X_X_chunk (actual rows=1.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_X_X_chunk (actual rows=63.00 loops=1)
         ->  Seq Scan on compress_hyper_X_X_chunk (actual rows=9.00 loops=1)

INSERT INTO comp_seg_varchar(time, source_id, label, data) VALUES ('1990-01-02 00:00:00+00', '1', '2', '{}'::jsonb)
ON CONFLICT (source_id, label, time) DO UPDATE SET data = '{"update": true}';
-- 1 batch should be moved into uncompressed
EXPLAIN (analyze,buffers off, costs off, timing off, summary off) SELECT * FROM comp_seg_varchar;
--- QUERY PLAN ---
 Append (actual rows=92.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_X_X_chunk (actual rows=27.00 loops=1)
         ->  Seq Scan on compress_hyper_X_X_chunk (actual rows=9.00 loops=1)
   ->  Seq Scan on _hyper_X_X_chunk (actual rows=2.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_X_X_chunk (actual rows=63.00 loops=1)
         ->  Seq Scan on compress_hyper_X_X_chunk (actual rows=9.00 loops=1)

DROP TABLE comp_seg_varchar;
-- test row locks for compressed tuples are blocked
CREATE TABLE row_locks(time timestamptz NOT NULL);
SELECT table_name FROM create_hypertable('row_locks', 'time');
 table_name 
------------
 row_locks

ALTER TABLE row_locks SET (timescaledb.compress);
INSERT INTO row_locks VALUES('2021-01-01 00:00:00');
SELECT count(compress_chunk(c)) FROM show_chunks('row_locks') c;
 count 
-------
     1

-- should succeed cause no compressed tuples are returned
SELECT FROM row_locks WHERE time < '2021-01-01 00:00:00' FOR UPDATE;
--

-- should be blocked
\set ON_ERROR_STOP 0
SELECT FROM row_locks FOR UPDATE;
ERROR:  locking compressed tuples is not supported
SELECT FROM row_locks FOR NO KEY UPDATE;
ERROR:  locking compressed tuples is not supported
SELECT FROM row_locks FOR SHARE;
ERROR:  locking compressed tuples is not supported
SELECT FROM row_locks FOR KEY SHARE;
ERROR:  locking compressed tuples is not supported
\set ON_ERROR_STOP 1
DROP TABLE row_locks;
CREATE TABLE lazy_decompress(time timestamptz not null, device text, value float, primary key (device,time));
SELECT table_name FROM create_hypertable('lazy_decompress', 'time');
   table_name    
-----------------
 lazy_decompress

ALTER TABLE lazy_decompress SET (timescaledb.compress, timescaledb.compress_segmentby = 'device');
INSERT INTO lazy_decompress SELECT '2024-01-01'::timestamptz + format('%s',i)::interval, 'd1', i FROM generate_series(1,6000) g(i);
SELECT count(compress_chunk(c)) FROM show_chunks('lazy_decompress') c;
 count 
-------
     1

-- no decompression cause no match in batch
BEGIN; :ANALYZE INSERT INTO lazy_decompress SELECT '2024-01-01 0:00:00.5','d1',random() ON CONFLICT DO NOTHING; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   ->  Insert on lazy_decompress (actual rows=0.00 loops=1)
         Conflict Resolution: NOTHING
         Tuples Inserted: 1
         Conflicting Tuples: 0
         ->  Subquery Scan on "*SELECT*" (actual rows=1.00 loops=1)
               ->  Result (actual rows=1.00 loops=1)

BEGIN; :ANALYZE INSERT INTO lazy_decompress SELECT '2024-01-01 0:00:00.5','d1',random() ON CONFLICT(time,device) DO UPDATE SET value=EXCLUDED.value; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   ->  Insert on lazy_decompress (actual rows=0.00 loops=1)
         Conflict Resolution: UPDATE
         Conflict Arbiter Indexes: lazy_decompress_pkey
         Tuples Inserted: 1
         Conflicting Tuples: 0
         ->  Subquery Scan on "*SELECT*" (actual rows=1.00 loops=1)
               ->  Result (actual rows=1.00 loops=1)

-- should decompress 1 batch cause there is match
BEGIN; :ANALYZE INSERT INTO lazy_decompress SELECT '2024-01-01 0:00:01','d1',random() ON CONFLICT DO NOTHING; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   ->  Insert on lazy_decompress (actual rows=0.00 loops=1)
         Conflict Resolution: NOTHING
         Tuples Inserted: 0
         Conflicting Tuples: 1
         ->  Subquery Scan on "*SELECT*" (actual rows=1.00 loops=1)
               ->  Result (actual rows=1.00 loops=1)

-- no decompression cause no match in batch
BEGIN; :ANALYZE UPDATE lazy_decompress SET value = 3.14 WHERE value = 0; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches filtered: 6
   ->  Update on lazy_decompress (actual rows=0.00 loops=1)
         Update on _hyper_X_X_chunk lazy_decompress_1
         ->  Result (actual rows=0.00 loops=1)
               ->  Seq Scan on _hyper_X_X_chunk lazy_decompress_1 (actual rows=0.00 loops=1)
                     Filter: (value = '0'::double precision)

BEGIN; :ANALYZE UPDATE lazy_decompress SET value = 3.14 WHERE value = 0 AND device='d1'; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches filtered: 6
   ->  Update on lazy_decompress (actual rows=0.00 loops=1)
         Update on _hyper_X_X_chunk lazy_decompress_1
         ->  Result (actual rows=0.00 loops=1)
               ->  Seq Scan on _hyper_X_X_chunk lazy_decompress_1 (actual rows=0.00 loops=1)
                     Filter: ((value = '0'::double precision) AND (device = 'd1'::text))

-- 1 batch decompression
BEGIN; :ANALYZE UPDATE lazy_decompress SET value = 3.14 WHERE value = 2300; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches filtered: 5
   Batches decompressed: 1
   Tuples decompressed: 1000
   ->  Update on lazy_decompress (actual rows=0.00 loops=1)
         Update on _hyper_X_X_chunk lazy_decompress_1
         ->  Result (actual rows=1.00 loops=1)
               ->  Seq Scan on _hyper_X_X_chunk lazy_decompress_1 (actual rows=1.00 loops=1)
                     Filter: (value = '2300'::double precision)
                     Rows Removed by Filter: 999

BEGIN; :ANALYZE UPDATE lazy_decompress SET value = 3.14 WHERE value > 3100 AND value < 3200; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches filtered: 5
   Batches decompressed: 1
   Tuples decompressed: 1000
   ->  Update on lazy_decompress (actual rows=0.00 loops=1)
         Update on _hyper_X_X_chunk lazy_decompress_1
         ->  Result (actual rows=99.00 loops=1)
               ->  Seq Scan on _hyper_X_X_chunk lazy_decompress_1 (actual rows=99.00 loops=1)
                     Filter: ((value > '3100'::double precision) AND (value < '3200'::double precision))
                     Rows Removed by Filter: 901

BEGIN; :ANALYZE UPDATE lazy_decompress SET value = 3.14 WHERE value BETWEEN 3100 AND 3200; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches filtered: 5
   Batches decompressed: 1
   Tuples decompressed: 1000
   ->  Update on lazy_decompress (actual rows=0.00 loops=1)
         Update on _hyper_X_X_chunk lazy_decompress_1
         ->  Result (actual rows=101.00 loops=1)
               ->  Seq Scan on _hyper_X_X_chunk lazy_decompress_1 (actual rows=101.00 loops=1)
                     Filter: ((value >= '3100'::double precision) AND (value <= '3200'::double precision))
                     Rows Removed by Filter: 899

-- check GUC is working, should be 6 batches and 6000 tuples decompresed
SET timescaledb.enable_dml_decompression_tuple_filtering TO off;
BEGIN; :ANALYZE UPDATE lazy_decompress SET value = 3.14 WHERE value = 0 AND device='d1'; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 6
   Tuples decompressed: 6000
   ->  Update on lazy_decompress (actual rows=0.00 loops=1)
         Update on _hyper_X_X_chunk lazy_decompress_1
         ->  Result (actual rows=0.00 loops=1)
               ->  Seq Scan on _hyper_X_X_chunk lazy_decompress_1 (actual rows=0.00 loops=1)
                     Filter: ((value = '0'::double precision) AND (device = 'd1'::text))
                     Rows Removed by Filter: 6000

RESET timescaledb.enable_dml_decompression_tuple_filtering;
-- no decompression cause no match in batch
BEGIN; :ANALYZE DELETE FROM lazy_decompress WHERE value = 0; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches filtered: 6
   ->  Delete on lazy_decompress (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk lazy_decompress_1
         ->  Seq Scan on _hyper_X_X_chunk lazy_decompress_1 (actual rows=0.00 loops=1)
               Filter: (value = '0'::double precision)

BEGIN; :ANALYZE DELETE FROM lazy_decompress WHERE value = 0 AND device='d1'; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches filtered: 6
   ->  Delete on lazy_decompress (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk lazy_decompress_1
         ->  Seq Scan on _hyper_X_X_chunk lazy_decompress_1 (actual rows=0.00 loops=1)
               Filter: ((value = '0'::double precision) AND (device = 'd1'::text))

-- 1 batch decompression
BEGIN; :ANALYZE DELETE FROM lazy_decompress WHERE value = 2300; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches filtered: 5
   Batches decompressed: 1
   Tuples decompressed: 1000
   ->  Delete on lazy_decompress (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk lazy_decompress_1
         ->  Seq Scan on _hyper_X_X_chunk lazy_decompress_1 (actual rows=1.00 loops=1)
               Filter: (value = '2300'::double precision)
               Rows Removed by Filter: 999

BEGIN; :ANALYZE DELETE FROM lazy_decompress WHERE value > 3100 AND value < 3200; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches filtered: 5
   Batches decompressed: 1
   Tuples decompressed: 1000
   ->  Delete on lazy_decompress (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk lazy_decompress_1
         ->  Seq Scan on _hyper_X_X_chunk lazy_decompress_1 (actual rows=99.00 loops=1)
               Filter: ((value > '3100'::double precision) AND (value < '3200'::double precision))
               Rows Removed by Filter: 901

BEGIN; :ANALYZE DELETE FROM lazy_decompress WHERE value BETWEEN 3100 AND 3200; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches filtered: 5
   Batches decompressed: 1
   Tuples decompressed: 1000
   ->  Delete on lazy_decompress (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk lazy_decompress_1
         ->  Seq Scan on _hyper_X_X_chunk lazy_decompress_1 (actual rows=101.00 loops=1)
               Filter: ((value >= '3100'::double precision) AND (value <= '3200'::double precision))
               Rows Removed by Filter: 899

-- check GUC is working, should be 6 batches and 6000 tuples decompresed
SET timescaledb.enable_dml_decompression_tuple_filtering TO off;
BEGIN; :ANALYZE DELETE FROM lazy_decompress WHERE value = 0 AND device='d1'; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 6
   Tuples decompressed: 6000
   ->  Delete on lazy_decompress (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk lazy_decompress_1
         ->  Seq Scan on _hyper_X_X_chunk lazy_decompress_1 (actual rows=0.00 loops=1)
               Filter: ((value = '0'::double precision) AND (device = 'd1'::text))
               Rows Removed by Filter: 6000

RESET timescaledb.enable_dml_decompression_tuple_filtering;
DROP TABLE lazy_decompress;
CREATE FUNCTION trigger_function() RETURNS TRIGGER LANGUAGE PLPGSQL
AS $$
BEGIN
  RAISE WARNING 'Trigger fired';
  RETURN NEW;
END;
$$;
-- test direct delete on compressed hypertable
CREATE TABLE direct_delete(time timestamptz not null, device text, reading text, value float);
SELECT table_name FROM create_hypertable('direct_delete', 'time');
  table_name   
---------------
 direct_delete

ALTER TABLE direct_delete SET (timescaledb.compress, timescaledb.compress_segmentby = 'device, reading');
INSERT INTO direct_delete VALUES
('2021-01-01', 'd1', 'r1', 1.0),
('2021-01-01', 'd1', 'r2', 1.0),
('2021-01-01', 'd1', 'r3', 1.0),
('2021-01-01', 'd1', NULL, 1.0),
('2021-01-01', 'd2', 'r1', 1.0),
('2021-01-01', 'd2', 'r2', 1.0),
('2021-01-01', 'd2', 'r3', 1.0),
('2021-01-01', 'd2', NULL, 1.0);
SELECT count(compress_chunk(c)) FROM show_chunks('direct_delete') c;
 count 
-------
     1

BEGIN;
-- should be 3 batches directly deleted
:ANALYZE DELETE FROM direct_delete WHERE device='d1';
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches deleted: 4
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=0.00 loops=1)
               Filter: (device = 'd1'::text)

-- double check its actually deleted
SELECT count(*) FROM direct_delete WHERE device='d1';
 count 
-------
     0

ROLLBACK;
BEGIN;
-- should be 2 batches directly deleted
:ANALYZE DELETE FROM direct_delete WHERE reading='r2';
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches deleted: 2
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=0.00 loops=1)
               Filter: (reading = 'r2'::text)

-- double check its actually deleted
SELECT count(*) FROM direct_delete WHERE reading='r2';
 count 
-------
     0

ROLLBACK;
-- issue #7644
-- make sure non-btree operators don't delete unrelated batches
BEGIN;
:ANALYZE DELETE FROM direct_delete WHERE reading <> 'r2';
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches deleted: 4
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=0.00 loops=1)
               Filter: (reading <> 'r2'::text)

-- 4 tuples should still be there
SELECT count(*) FROM direct_delete;
 count 
-------
     4

ROLLBACK;
-- test IS NULL
BEGIN;
:ANALYZE DELETE FROM direct_delete WHERE reading IS NULL;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 2
   Tuples decompressed: 2
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=2.00 loops=1)
               Filter: (reading IS NULL)

-- 6 tuples should still be there
SELECT count(*) FROM direct_delete;
 count 
-------
     6

ROLLBACK;
-- test IS NOT NULL
BEGIN;
:ANALYZE DELETE FROM direct_delete WHERE reading IS NOT NULL;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 6
   Tuples decompressed: 6
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=6.00 loops=1)
               Filter: (reading IS NOT NULL)

-- 2 tuples should still be there
SELECT count(*) FROM direct_delete;
 count 
-------
     2

ROLLBACK;
-- test IN
BEGIN;
:ANALYZE DELETE FROM direct_delete WHERE reading IN ('r1','r2');
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches deleted: 4
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=0.00 loops=1)
               Filter: (reading = ANY ('{r1,r2}'::text[]))

-- 4 tuples should still be there
SELECT count(*) FROM direct_delete;
 count 
-------
     4

ROLLBACK;
-- test IN
BEGIN;
:ANALYZE DELETE FROM direct_delete WHERE reading NOT IN ('r1');
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches deleted: 4
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=0.00 loops=1)
               Filter: (reading <> 'r1'::text)

-- 4 tuples should still be there
SELECT count(*) FROM direct_delete;
 count 
-------
     4

ROLLBACK;
-- combining constraints on segmentby columns should work
BEGIN;
-- should be 1 batches directly deleted
:ANALYZE DELETE FROM direct_delete WHERE device='d1' AND reading='r2';
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches deleted: 1
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=0.00 loops=1)
               Filter: ((device = 'd1'::text) AND (reading = 'r2'::text))

-- double check its actually deleted
SELECT count(*) FROM direct_delete WHERE device='d1' AND reading='r2';
 count 
-------
     0

ROLLBACK;
-- constraints involving non-segmentby columns should not directly delete
BEGIN; :ANALYZE DELETE FROM direct_delete WHERE value = '1.0'; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches deleted: 8
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=0.00 loops=1)
               Filter: (value = '1'::double precision)

BEGIN; :ANALYZE DELETE FROM direct_delete WHERE device = 'd1' AND value = '1.0'; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches deleted: 4
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=0.00 loops=1)
               Filter: ((device = 'd1'::text) AND (value = '1'::double precision))

BEGIN; :ANALYZE DELETE FROM direct_delete WHERE reading = 'r1' AND value = '1.0'; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches deleted: 2
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=0.00 loops=1)
               Filter: ((reading = 'r1'::text) AND (value = '1'::double precision))

BEGIN; :ANALYZE DELETE FROM direct_delete WHERE device = 'd2' AND reading = 'r3' AND value = '1.0'; ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches deleted: 1
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=0.00 loops=1)
               Filter: ((device = 'd2'::text) AND (reading = 'r3'::text) AND (value = '1'::double precision))

-- presence of trigger should prevent direct delete
CREATE TRIGGER direct_delete_trigger BEFORE DELETE ON direct_delete FOR EACH ROW EXECUTE FUNCTION trigger_function();
BEGIN; :ANALYZE DELETE FROM direct_delete WHERE device = 'd1'; ROLLBACK;
WARNING:  Trigger fired
WARNING:  Trigger fired
WARNING:  Trigger fired
WARNING:  Trigger fired
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 4
   Tuples decompressed: 4
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=4.00 loops=1)
               Filter: (device = 'd1'::text)
 Trigger direct_delete_trigger on _hyper_X_X_chunk: calls=4

DROP TRIGGER direct_delete_trigger ON direct_delete;
CREATE TRIGGER direct_delete_trigger AFTER DELETE ON direct_delete FOR EACH ROW EXECUTE FUNCTION trigger_function();
BEGIN; :ANALYZE DELETE FROM direct_delete WHERE device = 'd1'; ROLLBACK;
WARNING:  Trigger fired
WARNING:  Trigger fired
WARNING:  Trigger fired
WARNING:  Trigger fired
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 4
   Tuples decompressed: 4
   ->  Delete on direct_delete (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk direct_delete_1
         ->  Seq Scan on _hyper_X_X_chunk direct_delete_1 (actual rows=4.00 loops=1)
               Filter: (device = 'd1'::text)
 Trigger direct_delete_trigger on _hyper_X_X_chunk: calls=4

DROP TRIGGER direct_delete_trigger ON direct_delete;
DROP TABLE direct_delete;
DROP FUNCTION trigger_function;
-- test DML on metadata columns
CREATE TABLE compress_dml(time timestamptz NOT NULL, device text, reading text, value float);
SELECT table_name FROM create_hypertable('compress_dml', 'time');
  table_name  
--------------
 compress_dml

ALTER TABLE compress_dml SET (timescaledb.compress, timescaledb.compress_segmentby='device', timescaledb.compress_orderby='time DESC, reading');
INSERT INTO compress_dml VALUES
('2025-01-01','d1','r1',0.01),
('2025-01-01','d2','r2',0.01),
('2025-01-01','d3','r1',0.01),
('2025-01-01','d3','r2',0.01),
('2025-01-01','d4','r1',0.01),
('2025-01-01','d4',NULL,0.01),
('2025-01-01','d5','r2',0.01),
('2025-01-01','d5',NULL,0.01),
('2025-01-01','d6','r1',0.01),
('2025-01-01','d6','r2',0.01),
('2025-01-01','d6',NULL,0.01);
SELECT count(compress_chunk(ch)) FROM show_chunks('compress_dml') ch;
 count 
-------
     1

BEGIN;
:ANALYZE DELETE FROM compress_dml WHERE reading = 'r1';
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 3
   Tuples decompressed: 7
   Batches deleted: 1
   ->  Delete on compress_dml (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk compress_dml_1
         ->  Seq Scan on _hyper_X_X_chunk compress_dml_1 (actual rows=3.00 loops=1)
               Filter: (reading = 'r1'::text)
               Rows Removed by Filter: 4

SELECT * FROM compress_dml t ORDER BY t;
             time             | device | reading | value 
------------------------------+--------+---------+-------
 Wed Jan 01 00:00:00 2025 PST | d2     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d3     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d4     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d5     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d5     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     |         |  0.01

ROLLBACK;
BEGIN;
:ANALYZE DELETE FROM compress_dml WHERE reading <> 'r1';
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches filtered: 2
   Batches decompressed: 3
   Tuples decompressed: 7
   Batches deleted: 1
   ->  Delete on compress_dml (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk compress_dml_1
         ->  Seq Scan on _hyper_X_X_chunk compress_dml_1 (actual rows=3.00 loops=1)
               Filter: (reading <> 'r1'::text)
               Rows Removed by Filter: 4

SELECT * FROM compress_dml t ORDER BY t;
             time             | device | reading | value 
------------------------------+--------+---------+-------
 Wed Jan 01 00:00:00 2025 PST | d1     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d3     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d4     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d4     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d5     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     |         |  0.01

ROLLBACK;
BEGIN;
:ANALYZE DELETE FROM compress_dml WHERE reading IS NULL;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 6
   Tuples decompressed: 11
   ->  Delete on compress_dml (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk compress_dml_1
         ->  Seq Scan on _hyper_X_X_chunk compress_dml_1 (actual rows=3.00 loops=1)
               Filter: (reading IS NULL)
               Rows Removed by Filter: 8

SELECT * FROM compress_dml t ORDER BY t;
             time             | device | reading | value 
------------------------------+--------+---------+-------
 Wed Jan 01 00:00:00 2025 PST | d1     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d2     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d3     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d3     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d4     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d5     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     | r2      |  0.01

ROLLBACK;
BEGIN;
:ANALYZE DELETE FROM compress_dml WHERE reading IS NOT NULL;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 6
   Tuples decompressed: 11
   ->  Delete on compress_dml (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk compress_dml_1
         ->  Seq Scan on _hyper_X_X_chunk compress_dml_1 (actual rows=8.00 loops=1)
               Filter: (reading IS NOT NULL)
               Rows Removed by Filter: 3

SELECT * FROM compress_dml t ORDER BY t;
             time             | device | reading | value 
------------------------------+--------+---------+-------
 Wed Jan 01 00:00:00 2025 PST | d4     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d5     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     |         |  0.01

ROLLBACK;
BEGIN;
:ANALYZE DELETE FROM compress_dml WHERE reading IN ('r2','r3');
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 6
   Tuples decompressed: 11
   ->  Delete on compress_dml (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk compress_dml_1
         ->  Seq Scan on _hyper_X_X_chunk compress_dml_1 (actual rows=4.00 loops=1)
               Filter: (reading = ANY ('{r2,r3}'::text[]))
               Rows Removed by Filter: 7

SELECT * FROM compress_dml t ORDER BY t;
             time             | device | reading | value 
------------------------------+--------+---------+-------
 Wed Jan 01 00:00:00 2025 PST | d1     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d3     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d4     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d4     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d5     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     |         |  0.01

ROLLBACK;
BEGIN;
:ANALYZE DELETE FROM compress_dml WHERE reading = ANY('{r2,r3}');
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 6
   Tuples decompressed: 11
   ->  Delete on compress_dml (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk compress_dml_1
         ->  Seq Scan on _hyper_X_X_chunk compress_dml_1 (actual rows=4.00 loops=1)
               Filter: (reading = ANY ('{r2,r3}'::text[]))
               Rows Removed by Filter: 7

SELECT * FROM compress_dml t ORDER BY t;
             time             | device | reading | value 
------------------------------+--------+---------+-------
 Wed Jan 01 00:00:00 2025 PST | d1     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d3     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d4     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d4     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d5     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     | r1      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     |         |  0.01

ROLLBACK;
BEGIN;
:ANALYZE DELETE FROM compress_dml WHERE reading NOT IN ('r2','r3');
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 6
   Tuples decompressed: 11
   ->  Delete on compress_dml (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk compress_dml_1
         ->  Seq Scan on _hyper_X_X_chunk compress_dml_1 (actual rows=4.00 loops=1)
               Filter: (reading <> ALL ('{r2,r3}'::text[]))
               Rows Removed by Filter: 7

SELECT * FROM compress_dml t ORDER BY t;
             time             | device | reading | value 
------------------------------+--------+---------+-------
 Wed Jan 01 00:00:00 2025 PST | d2     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d3     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d4     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d5     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d5     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     |         |  0.01

ROLLBACK;
BEGIN;
:ANALYZE DELETE FROM compress_dml WHERE reading <> ALL('{r2,r3}');
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 6
   Tuples decompressed: 11
   ->  Delete on compress_dml (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk compress_dml_1
         ->  Seq Scan on _hyper_X_X_chunk compress_dml_1 (actual rows=4.00 loops=1)
               Filter: (reading <> ALL ('{r2,r3}'::text[]))
               Rows Removed by Filter: 7

SELECT * FROM compress_dml t ORDER BY t;
             time             | device | reading | value 
------------------------------+--------+---------+-------
 Wed Jan 01 00:00:00 2025 PST | d2     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d3     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d4     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d5     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d5     |         |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     | r2      |  0.01
 Wed Jan 01 00:00:00 2025 PST | d6     |         |  0.01

ROLLBACK;
DROP TABLE compress_dml;
-- Test issue #8241: invalidate generic plans for prepared statements
-- when partial chunk is created from compressed chunk via UPDATE/DELETE
CREATE TABLE t8241(time timestamptz primary key, device text, value float);
SELECT table_name FROM create_hypertable('t8241', 'time');
 table_name 
------------
 t8241

INSERT INTO t8241(time, device, value) VALUES ('2020-01-01 0:00:00', 'd1', 1.0);
INSERT INTO t8241(time, device, value) VALUES ('2020-01-01 1:00:00', 'd1', 1.0);
ALTER TABLE t8241 SET(timescaledb.compress, timescaledb.compress_segmentby='device');
SELECT count(compress_chunk(ch)) FROM show_chunks('t8241') ch;
 count 
-------
     1

PREPARE prep AS SELECT * FROM t8241;
-- Should only see compressed chunk in the plan, this generic plan is now cached
EXECUTE prep;
             time             | device | value 
------------------------------+--------+-------
 Wed Jan 01 01:00:00 2020 PST | d1     |     1
 Wed Jan 01 00:00:00 2020 PST | d1     |     1

EXPLAIN (buffers off, costs off, timing off, summary off) EXECUTE prep;
--- QUERY PLAN ---
 Custom Scan (ColumnarScan) on _hyper_X_X_chunk
   ->  Seq Scan on compress_hyper_X_X_chunk

-- Test Update
BEGIN;
-- This will add decompressed row to the compressed chunk
UPDATE t8241 SET time = '2020-01-01 00:30:00' WHERE time = '2020-01-01';
-- generic plan created for compressed chunk should be invalidated even though we didn't commit
-- we should get correct result and the plan should have both compressed and decompressed parts
EXECUTE prep;
             time             | device | value 
------------------------------+--------+-------
 Wed Jan 01 01:00:00 2020 PST | d1     |     1
 Wed Jan 01 00:30:00 2020 PST | d1     |     1

EXPLAIN (buffers off, costs off, timing off, summary off) EXECUTE prep;
--- QUERY PLAN ---
 Append
   ->  Custom Scan (ColumnarScan) on _hyper_X_X_chunk
         ->  Seq Scan on compress_hyper_X_X_chunk
   ->  Seq Scan on _hyper_X_X_chunk

ROLLBACK;
-- Should see the compressed chunk only
EXPLAIN (buffers off, costs off, timing off, summary off) EXECUTE prep;
--- QUERY PLAN ---
 Custom Scan (ColumnarScan) on _hyper_X_X_chunk
   ->  Seq Scan on compress_hyper_X_X_chunk

-- Test Insert
BEGIN;
-- This will add decompressed row to the compressed chunk
INSERT INTO t8241(time, device, value) VALUES ('2020-01-01 00:30:00', 'd1', 1.0);
-- generic plan created for compressed chunk should be invalidated even though we didn't commit
-- we should get correct result and the plan should have both compressed and decompressed parts
EXECUTE prep;
             time             | device | value 
------------------------------+--------+-------
 Wed Jan 01 01:00:00 2020 PST | d1     |     1
 Wed Jan 01 00:00:00 2020 PST | d1     |     1
 Wed Jan 01 00:30:00 2020 PST | d1     |     1

EXPLAIN (buffers off, costs off, timing off, summary off) EXECUTE prep;
--- QUERY PLAN ---
 Append
   ->  Custom Scan (ColumnarScan) on _hyper_X_X_chunk
         ->  Seq Scan on compress_hyper_X_X_chunk
   ->  Seq Scan on _hyper_X_X_chunk

ROLLBACK;
DEALLOCATE prep;
DROP TABLE t8241 cascade;
RESET plan_cache_mode;
-- test multiple constraints on same column
BEGIN; EXPLAIN (analyze, buffers off, costs off, timing off, summary off) DELETE FROM metrics_compressed where device_id IS NOT NULL AND device_id = 1;ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 14
   Tuples decompressed: 12076
   ->  Delete on metrics_compressed (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk metrics_compressed_1
         Delete on _hyper_X_X_chunk metrics_compressed_2
         Delete on _hyper_X_X_chunk metrics_compressed_3
         ->  Append (actual rows=13674.00 loops=1)
               ->  Seq Scan on _hyper_X_X_chunk metrics_compressed_1 (actual rows=3598.00 loops=1)
                     Filter: ((device_id IS NOT NULL) AND (device_id = 1))
               ->  Seq Scan on _hyper_X_X_chunk metrics_compressed_2 (actual rows=5038.00 loops=1)
                     Filter: ((device_id IS NOT NULL) AND (device_id = 1))
               ->  Seq Scan on _hyper_X_X_chunk metrics_compressed_3 (actual rows=5038.00 loops=1)
                     Filter: ((device_id IS NOT NULL) AND (device_id = 1))

BEGIN; EXPLAIN (analyze, buffers off, costs off, timing off, summary off) DELETE FROM metrics_compressed where device_id <> 1 AND device_id <> 2;ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches deleted: 48
   ->  Delete on metrics_compressed (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk metrics_compressed_1
         Delete on _hyper_X_X_chunk metrics_compressed_2
         Delete on _hyper_X_X_chunk metrics_compressed_3
         ->  Append (actual rows=0.00 loops=1)
               ->  Seq Scan on _hyper_X_X_chunk metrics_compressed_1 (actual rows=0.00 loops=1)
                     Filter: ((device_id <> 1) AND (device_id <> 2))
                     Rows Removed by Filter: 1598
               ->  Seq Scan on _hyper_X_X_chunk metrics_compressed_2 (actual rows=0.00 loops=1)
                     Filter: ((device_id <> 1) AND (device_id <> 2))
               ->  Seq Scan on _hyper_X_X_chunk metrics_compressed_3 (actual rows=0.00 loops=1)
                     Filter: ((device_id <> 1) AND (device_id <> 2))

BEGIN; EXPLAIN (analyze, buffers off, costs off, timing off, summary off) DELETE FROM metrics_compressed where device_id > 1 AND device_id < 3;ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches deleted: 16
   ->  Delete on metrics_compressed (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk metrics_compressed_1
         Delete on _hyper_X_X_chunk metrics_compressed_2
         Delete on _hyper_X_X_chunk metrics_compressed_3
         ->  Append (actual rows=0.00 loops=1)
               ->  Index Scan using _hyper_X_X_chunk_metrics_compressed_device_id_time_idx on _hyper_X_X_chunk metrics_compressed_1 (actual rows=0.00 loops=1)
                     Index Cond: ((device_id > 1) AND (device_id < 3))
               ->  Seq Scan on _hyper_X_X_chunk metrics_compressed_2 (actual rows=0.00 loops=1)
                     Filter: ((device_id > 1) AND (device_id < 3))
               ->  Seq Scan on _hyper_X_X_chunk metrics_compressed_3 (actual rows=0.00 loops=1)
                     Filter: ((device_id > 1) AND (device_id < 3))

BEGIN; EXPLAIN (analyze, buffers off, costs off, timing off, summary off) DELETE FROM metrics_compressed where v3 IS NOT NULL AND device_id IS NOT NULL AND device_id = 1;ROLLBACK;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 14
   Tuples decompressed: 12076
   ->  Delete on metrics_compressed (actual rows=0.00 loops=1)
         Delete on _hyper_X_X_chunk metrics_compressed_1
         Delete on _hyper_X_X_chunk metrics_compressed_2
         Delete on _hyper_X_X_chunk metrics_compressed_3
         ->  Append (actual rows=719.00 loops=1)
               ->  Seq Scan on _hyper_X_X_chunk metrics_compressed_1 (actual rows=719.00 loops=1)
                     Filter: ((v3 IS NOT NULL) AND (device_id IS NOT NULL) AND (device_id = 1))
                     Rows Removed by Filter: 2879
               ->  Seq Scan on _hyper_X_X_chunk metrics_compressed_2 (actual rows=0.00 loops=1)
                     Filter: ((v3 IS NOT NULL) AND (device_id IS NOT NULL) AND (device_id = 1))
                     Rows Removed by Filter: 5038
               ->  Seq Scan on _hyper_X_X_chunk metrics_compressed_3 (actual rows=0.00 loops=1)
                     Filter: ((v3 IS NOT NULL) AND (device_id IS NOT NULL) AND (device_id = 1))
                     Rows Removed by Filter: 5038

-- clean up dml artefacts to prevent plan switches on subsequent tests
VACUUM FULL ANALYZE metrics_compressed;
