-- 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.
CREATE TABLE metrics (time TIMESTAMPTZ NOT NULL, device TEXT, value float) WITH (tsdb.hypertable, tsdb.orderby='time');
NOTICE:  using column "time" as partitioning column
-- first try without the GUCs
BEGIN;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz + (i || ' hour')::interval, 'd1', i::float FROM generate_series(0,100) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Append (actual rows=101.00 loops=1)
   ->  Seq Scan on _hyper_1_1_chunk (actual rows=16.00 loops=1)
   ->  Seq Scan on _hyper_1_2_chunk (actual rows=85.00 loops=1)

-- should have no status aka normal uncompressed chunk
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk;
 chunk_status_text 
-------------------
 {}

ROLLBACK;
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_sort_batches = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = false;
-- EXPLAIN with too small batch
EXPLAIN (BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) INSERT INTO metrics SELECT '2025-01-01'::timestamptz, 'd1', i::float FROM generate_series(0,5) i;
WARNING:  disabling direct compress because of too small batch size
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable)
   Direct Compress: false
   ->  Insert on metrics
         ->  Function Scan on generate_series i

-- EXPLAIN with large enough batch
EXPLAIN (BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) INSERT INTO metrics SELECT '2025-01-01'::timestamptz, 'd1', i::float FROM generate_series(0,500) i;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable)
   Direct Compress: true
   ->  Insert on metrics
         ->  Function Scan on generate_series i

-- simple test with compressed insert enabled
BEGIN;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,3000) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Append (actual rows=3001.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_3_chunk (actual rows=960.00 loops=1)
         ->  Seq Scan on compress_hyper_2_4_chunk (actual rows=1.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_5_chunk (actual rows=2041.00 loops=1)
         ->  Seq Scan on compress_hyper_2_6_chunk (actual rows=3.00 loops=1)

SELECT first(time,rn), last(time,rn) FROM (SELECT ROW_NUMBER() OVER () as rn, time FROM metrics) sub;
            first             |             last             
------------------------------+------------------------------
 Wed Jan 01 00:00:00 2025 PST | Fri Jan 03 02:00:00 2025 PST

-- since the chunks are new status should be COMPRESSED, UNORDERED
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk;
   chunk_status_text    
------------------------
 {COMPRESSED,UNORDERED}

ROLLBACK;
-- simple test with compressed insert enabled and reversed order
BEGIN;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz - (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,3000) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Custom Scan (ColumnarScan) on _hyper_1_7_chunk (actual rows=3001.00 loops=1)
   ->  Seq Scan on compress_hyper_2_8_chunk (actual rows=4.00 loops=1)

SELECT first(time,rn), last(time,rn) FROM (SELECT ROW_NUMBER() OVER () as rn, time FROM metrics) sub;
            first             |             last             
------------------------------+------------------------------
 Sun Dec 29 22:00:00 2024 PST | Wed Jan 01 00:00:00 2025 PST

-- since the chunks are new status should be COMPRESSED, UNORDERED
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk;
   chunk_status_text    
------------------------
 {COMPRESSED,UNORDERED}

ROLLBACK;
SET timescaledb.enable_direct_compress_insert_sort_batches = false;
-- simple test with compressed insert enabled and without batch sorting
BEGIN;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,3000) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Append (actual rows=3001.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_9_chunk (actual rows=960.00 loops=1)
         ->  Seq Scan on compress_hyper_2_10_chunk (actual rows=1.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_11_chunk (actual rows=2041.00 loops=1)
         ->  Seq Scan on compress_hyper_2_12_chunk (actual rows=3.00 loops=1)

SELECT first(time,rn), last(time,rn) FROM (SELECT ROW_NUMBER() OVER () as rn, time FROM metrics) sub;
            first             |             last             
------------------------------+------------------------------
 Wed Jan 01 00:00:00 2025 PST | Fri Jan 03 02:00:00 2025 PST

-- since the chunks are new status should be COMPRESSED, UNORDERED
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk;
   chunk_status_text    
------------------------
 {COMPRESSED,UNORDERED}

ROLLBACK;
-- simple test with compressed insert enabled and reversed order and no batch sorting
BEGIN;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz - (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,3000) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Custom Scan (ColumnarScan) on _hyper_1_13_chunk (actual rows=3001.00 loops=1)
   ->  Seq Scan on compress_hyper_2_14_chunk (actual rows=4.00 loops=1)

SELECT first(time,rn), last(time,rn) FROM (SELECT ROW_NUMBER() OVER () as rn, time FROM metrics) sub;
            first             |             last             
------------------------------+------------------------------
 Wed Jan 01 00:00:00 2025 PST | Sun Dec 29 22:00:00 2024 PST

-- since the chunks are new status should be COMPRESSED, UNORDERED
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk;
   chunk_status_text    
------------------------
 {COMPRESSED,UNORDERED}

ROLLBACK;
-- test compressing into uncompressed chunk
RESET timescaledb.enable_direct_compress_insert;
RESET timescaledb.enable_direct_compress_insert_sort_batches;
RESET timescaledb.enable_direct_compress_insert_client_sorted;
BEGIN;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,3000) i;
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = true;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,3000) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Append (actual rows=6002.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_15_chunk (actual rows=960.00 loops=1)
         ->  Seq Scan on compress_hyper_2_17_chunk (actual rows=1.00 loops=1)
   ->  Seq Scan on _hyper_1_15_chunk (actual rows=960.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_16_chunk (actual rows=2041.00 loops=1)
         ->  Seq Scan on compress_hyper_2_18_chunk (actual rows=3.00 loops=1)
   ->  Seq Scan on _hyper_1_16_chunk (actual rows=2041.00 loops=1)

-- since the chunks are new status should be COMPRESSED, PARTIAL
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk;
  chunk_status_text   
----------------------
 {COMPRESSED,PARTIAL}

ROLLBACK;
-- simple test with compressed insert enabled and reversed order
BEGIN;
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = true;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz - (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,3000) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Custom Scan (ColumnarScan) on _hyper_1_19_chunk (actual rows=3001.00 loops=1)
   ->  Seq Scan on compress_hyper_2_20_chunk (actual rows=4.00 loops=1)

-- since the chunks are new status should be COMPRESSED
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk;
 chunk_status_text 
-------------------
 {COMPRESSED}

ROLLBACK;
-- simple test with compressed insert enabled and no presorted
BEGIN;
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = false;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz - (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,3000) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Custom Scan (ColumnarScan) on _hyper_1_21_chunk (actual rows=3001.00 loops=1)
   ->  Seq Scan on compress_hyper_2_22_chunk (actual rows=4.00 loops=1)

-- since the chunks are new status should be COMPRESSED, UNORDERED
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk;
   chunk_status_text    
------------------------
 {COMPRESSED,UNORDERED}

ROLLBACK;
-- simple test with compressed insert enabled and no presorted and with uncompressed data
BEGIN;
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = false;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz, 'd1', 0;
WARNING:  disabling direct compress because of too small batch size
INSERT INTO metrics SELECT '2025-01-01'::timestamptz - (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,3000) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Append (actual rows=3002.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_23_chunk (actual rows=3001.00 loops=1)
         ->  Seq Scan on compress_hyper_2_24_chunk (actual rows=4.00 loops=1)
   ->  Seq Scan on _hyper_1_23_chunk (actual rows=1.00 loops=1)

-- since the chunks are new status should be COMPRESSED, UNORDERED, PARTIAL
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk;
       chunk_status_text        
--------------------------------
 {COMPRESSED,UNORDERED,PARTIAL}

ROLLBACK;
-- simple test with compressed insert enabled and no presorted with partial and compressed chunks
BEGIN;
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = false;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz, 'd1', 0;
WARNING:  disabling direct compress because of too small batch size
INSERT INTO metrics SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,3000) i;
INSERT INTO metrics SELECT '2025-01-02'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,3000) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Append (actual rows=6003.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_25_chunk (actual rows=960.00 loops=1)
         ->  Seq Scan on compress_hyper_2_26_chunk (actual rows=1.00 loops=1)
   ->  Seq Scan on _hyper_1_25_chunk (actual rows=1.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_27_chunk (actual rows=5042.00 loops=1)
         ->  Seq Scan on compress_hyper_2_28_chunk (actual rows=7.00 loops=1)

SELECT first(time,rn), last(time,rn) FROM (SELECT ROW_NUMBER() OVER () as rn, time FROM metrics) sub;
            first             |             last             
------------------------------+------------------------------
 Wed Jan 01 00:00:00 2025 PST | Sat Jan 04 02:00:00 2025 PST

SELECT format('%I.%I',schema_name,table_name) AS "COMPRESSED_CHUNK" FROM _timescaledb_catalog.chunk where compressed_chunk_id IS NULL order by 1 desc limit 1 \gset
-- should see overlapping batches
select _ts_meta_count, _ts_meta_min_1, _ts_meta_max_1 from :COMPRESSED_CHUNK order by 2;
 _ts_meta_count |        _ts_meta_min_1        |        _ts_meta_max_1        
----------------+------------------------------+------------------------------
           1000 | Wed Jan 01 16:00:00 2025 PST | Thu Jan 02 08:39:00 2025 PST
           1000 | Thu Jan 02 00:00:00 2025 PST | Thu Jan 02 16:39:00 2025 PST
           1000 | Thu Jan 02 08:40:00 2025 PST | Fri Jan 03 01:19:00 2025 PST
           1000 | Thu Jan 02 16:40:00 2025 PST | Fri Jan 03 09:19:00 2025 PST
             41 | Fri Jan 03 01:20:00 2025 PST | Fri Jan 03 02:00:00 2025 PST
           1000 | Fri Jan 03 09:20:00 2025 PST | Sat Jan 04 01:59:00 2025 PST
              1 | Sat Jan 04 02:00:00 2025 PST | Sat Jan 04 02:00:00 2025 PST

-- since the chunks are new status should be COMPRESSED, UNORDERED, PARTIAL and COMPRESSED, UNORDERED
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk order by 1;
       chunk_status_text        
--------------------------------
 {COMPRESSED,UNORDERED}
 {COMPRESSED,UNORDERED,PARTIAL}

ROLLBACK;
-- simple test with compressed insert enabled and presorted with partial and compressed chunks
BEGIN;
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = true;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz, 'd1', 0;
WARNING:  disabling direct compress because of too small batch size
INSERT INTO metrics SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,3000) i;
INSERT INTO metrics SELECT '2025-01-05'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,3000) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Append (actual rows=6003.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_29_chunk (actual rows=960.00 loops=1)
         ->  Seq Scan on compress_hyper_2_30_chunk (actual rows=1.00 loops=1)
   ->  Seq Scan on _hyper_1_29_chunk (actual rows=1.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_31_chunk (actual rows=5042.00 loops=1)
         ->  Seq Scan on compress_hyper_2_32_chunk (actual rows=7.00 loops=1)

SELECT first(time,rn), last(time,rn) FROM (SELECT ROW_NUMBER() OVER () as rn, time FROM metrics) sub;
            first             |             last             
------------------------------+------------------------------
 Wed Jan 01 00:00:00 2025 PST | Tue Jan 07 02:00:00 2025 PST

SELECT format('%I.%I',schema_name,table_name) AS "COMPRESSED_CHUNK" FROM _timescaledb_catalog.chunk where compressed_chunk_id IS NULL order by 1 desc limit 1 \gset
-- should not see overlapping batches
select _ts_meta_count, _ts_meta_min_1, _ts_meta_max_1 from :COMPRESSED_CHUNK order by 2;
 _ts_meta_count |        _ts_meta_min_1        |        _ts_meta_max_1        
----------------+------------------------------+------------------------------
           1000 | Wed Jan 01 16:00:00 2025 PST | Thu Jan 02 08:39:00 2025 PST
           1000 | Thu Jan 02 08:40:00 2025 PST | Fri Jan 03 01:19:00 2025 PST
             41 | Fri Jan 03 01:20:00 2025 PST | Fri Jan 03 02:00:00 2025 PST
           1000 | Sun Jan 05 00:00:00 2025 PST | Sun Jan 05 16:39:00 2025 PST
           1000 | Sun Jan 05 16:40:00 2025 PST | Mon Jan 06 09:19:00 2025 PST
           1000 | Mon Jan 06 09:20:00 2025 PST | Tue Jan 07 01:59:00 2025 PST
              1 | Tue Jan 07 02:00:00 2025 PST | Tue Jan 07 02:00:00 2025 PST

-- since the chunks are new status should be COMPRESSED, UNORDERED, PARTIAL and COMPRESSED, UNORDERED
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk order by 1;
  chunk_status_text   
----------------------
 {COMPRESSED}
 {COMPRESSED,PARTIAL}

ROLLBACK;
-- test with segmentby
BEGIN;
ALTER TABLE metrics SET (tsdb.segmentby = 'device');
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = true;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz - (i || ' minute')::interval, floor(i), i::float FROM generate_series(0.0,9.8,0.2) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Custom Scan (ColumnarScan) on _hyper_1_33_chunk (actual rows=50.00 loops=1)
   ->  Seq Scan on compress_hyper_2_34_chunk (actual rows=10.00 loops=1)

SELECT format('%I.%I',schema_name,table_name) AS "COMPRESSED_CHUNK" FROM _timescaledb_catalog.chunk where compressed_chunk_id IS NULL \gset
-- should have 10 batches
SELECT count(*) FROM :COMPRESSED_CHUNK;
 count 
-------
    10

-- since the chunks are new status should be COMPRESSED
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk;
 chunk_status_text 
-------------------
 {COMPRESSED}

ROLLBACK;
-- segmentby with overlapping batches
BEGIN;
ALTER TABLE metrics SET (tsdb.segmentby = 'device');
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = false;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval, 'd'||i%2, i::float FROM generate_series(0,3000) i;
INSERT INTO metrics SELECT '2025-01-02'::timestamptz + (i || ' minute')::interval, 'd'||i%2, i::float FROM generate_series(0,3000) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Append (actual rows=6002.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_35_chunk (actual rows=960.00 loops=1)
         ->  Seq Scan on compress_hyper_2_36_chunk (actual rows=2.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_37_chunk (actual rows=5042.00 loops=1)
         ->  Seq Scan on compress_hyper_2_38_chunk (actual rows=8.00 loops=1)

SELECT format('%I.%I',schema_name,table_name) AS "COMPRESSED_CHUNK" FROM _timescaledb_catalog.chunk where compressed_chunk_id IS NULL order by 1 desc limit 1 \gset
-- should see overlapping batches per device
select _ts_meta_count, _ts_meta_min_1, _ts_meta_max_1, device from :COMPRESSED_CHUNK order by 4, 2;
 _ts_meta_count |        _ts_meta_min_1        |        _ts_meta_max_1        | device 
----------------+------------------------------+------------------------------+--------
           1000 | Wed Jan 01 16:00:00 2025 PST | Fri Jan 03 01:18:00 2025 PST | d0
           1000 | Thu Jan 02 00:00:00 2025 PST | Fri Jan 03 09:18:00 2025 PST | d0
             21 | Fri Jan 03 01:20:00 2025 PST | Fri Jan 03 02:00:00 2025 PST | d0
            501 | Fri Jan 03 09:20:00 2025 PST | Sat Jan 04 02:00:00 2025 PST | d0
           1000 | Wed Jan 01 16:01:00 2025 PST | Fri Jan 03 01:19:00 2025 PST | d1
           1000 | Thu Jan 02 00:01:00 2025 PST | Fri Jan 03 09:19:00 2025 PST | d1
             20 | Fri Jan 03 01:21:00 2025 PST | Fri Jan 03 01:59:00 2025 PST | d1
            500 | Fri Jan 03 09:21:00 2025 PST | Sat Jan 04 01:59:00 2025 PST | d1

-- since the chunks are new status should be COMPRESSED, UNORDERED
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk;
   chunk_status_text    
------------------------
 {COMPRESSED,UNORDERED}

ROLLBACK;
-- multikey orderby
BEGIN;
ALTER TABLE metrics SET (tsdb.orderby = 'device desc,time');
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = false;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval, 'd'||i%3, i::float FROM generate_series(0,3000) i;
INSERT INTO metrics SELECT '2025-01-02'::timestamptz - (i || ' minute')::interval, 'd'||i%3, i::float FROM generate_series(0,3000) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Append (actual rows=6002.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_39_chunk (actual rows=3480.00 loops=1)
         ->  Seq Scan on compress_hyper_2_40_chunk (actual rows=4.00 loops=1)
   ->  Custom Scan (ColumnarScan) on _hyper_1_41_chunk (actual rows=2522.00 loops=1)
         ->  Seq Scan on compress_hyper_2_42_chunk (actual rows=4.00 loops=1)

SELECT format('%I.%I',schema_name,table_name) AS "COMPRESSED_CHUNK" FROM _timescaledb_catalog.chunk where compressed_chunk_id IS NULL order by 1 limit 1 \gset
-- should see overlapping batches
select _ts_meta_count, _ts_meta_min_1, _ts_meta_max_1, _ts_meta_min_2, _ts_meta_max_2 from :COMPRESSED_CHUNK order by 2, 4;
 _ts_meta_count | _ts_meta_min_1 | _ts_meta_max_1 |        _ts_meta_min_2        |        _ts_meta_max_2        
----------------+----------------+----------------+------------------------------+------------------------------
           1000 | d0             | d1             | Mon Dec 30 22:00:00 2024 PST | Wed Jan 01 15:59:00 2025 PST
            520 | d0             | d0             | Tue Dec 31 14:00:00 2024 PST | Wed Jan 01 15:57:00 2025 PST
            960 | d0             | d2             | Wed Jan 01 00:00:00 2025 PST | Wed Jan 01 15:59:00 2025 PST
           1000 | d1             | d2             | Mon Dec 30 22:01:00 2024 PST | Wed Jan 01 15:58:00 2025 PST

-- since the chunks are new status should be COMPRESSED, UNORDERED
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk;
   chunk_status_text    
------------------------
 {COMPRESSED,UNORDERED}

ROLLBACK;
-- test unique constraints prevent direct compress
BEGIN;
SET timescaledb.enable_direct_compress_insert = true;
ALTER TABLE metrics ADD CONSTRAINT unique_time_device UNIQUE (time, device);
INSERT INTO metrics SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,100) i;
WARNING:  disabling direct compress because the destination table has unique constraints
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Seq Scan on _hyper_1_43_chunk (actual rows=101.00 loops=1)

SELECT DISTINCT status FROM _timescaledb_catalog.chunk WHERE compressed_chunk_id IS NOT NULL;
 status 
--------

ROLLBACK;
-- test triggers prevent direct compress
BEGIN;
SET timescaledb.enable_direct_compress_insert = true;
CREATE OR REPLACE FUNCTION test_trigger() RETURNS TRIGGER AS $$ BEGIN RETURN NEW; END; $$ LANGUAGE plpgsql;
CREATE TRIGGER metrics_trigger BEFORE INSERT OR UPDATE ON metrics FOR EACH ROW EXECUTE FUNCTION test_trigger();
INSERT INTO metrics SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,100) i;
WARNING:  disabling direct compress because the destination table has triggers
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Seq Scan on _hyper_1_44_chunk (actual rows=101.00 loops=1)

SELECT DISTINCT status FROM _timescaledb_catalog.chunk WHERE compressed_chunk_id IS NOT NULL;
 status 
--------

ROLLBACK;
-- test caggs with direct compress
BEGIN;
SET timescaledb.enable_direct_compress_insert = true;
CREATE MATERIALIZED VIEW metrics_cagg WITH (tsdb.continuous) AS SELECT time_bucket('1 hour', time) AS bucket, device, avg(value) AS avg_value FROM metrics GROUP BY bucket, device WITH NO DATA;
INSERT INTO metrics SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(0,100) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Custom Scan (ColumnarScan) on _hyper_1_45_chunk (actual rows=101.00 loops=1)
   ->  Seq Scan on compress_hyper_2_46_chunk (actual rows=1.00 loops=1)

SELECT DISTINCT status FROM _timescaledb_catalog.chunk WHERE compressed_chunk_id IS NOT NULL;
 status 
--------
      3

ROLLBACK;
-- test chunk status handling
CREATE TABLE metrics_status(time timestamptz) WITH (tsdb.hypertable,tsdb.partition_column='time');
INSERT INTO metrics_status SELECT '2025-01-01';
-- normal insert should result in chunk status 0
SELECT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics_status') chunk;
 chunk_status_text 
-------------------
 {}

SET timescaledb.enable_direct_compress_insert = true;
BEGIN;
INSERT INTO metrics_status SELECT '2025-01-01' FROM generate_series(1,9);
WARNING:  disabling direct compress because of too small batch size
-- small insert batches should not result in compressed chunk
SELECT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics_status') chunk;
 chunk_status_text 
-------------------
 {}

ROLLBACK;
BEGIN;
INSERT INTO metrics_status SELECT '2025-01-01' FROM generate_series(1,10);
-- status should be COMPRESSED, UNORDERED, PARTIAL since we have more than 10 rows in the chunk
SELECT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics_status') chunk;
       chunk_status_text        
--------------------------------
 {COMPRESSED,UNORDERED,PARTIAL}

ROLLBACK;
BEGIN;
-- compressed sorted copy into uncompressed chunk should result in chunk status 9 (compressed,partial)
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = true;
INSERT INTO metrics_status SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval FROM generate_series(0,100) i;
-- status should be COMPRESSED, PARTIAL
SELECT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics_status') chunk;
  chunk_status_text   
----------------------
 {COMPRESSED,PARTIAL}

ROLLBACK;
TRUNCATE metrics_status;
BEGIN;
-- compressed insert into new chunk should result in chunk status 3 (compressed,unordered)
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = false;
INSERT INTO metrics_status SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval FROM generate_series(0,100) i;
-- status should be COMPRESSED, UNORDERED
SELECT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics_status') chunk;
   chunk_status_text    
------------------------
 {COMPRESSED,UNORDERED}

ROLLBACK;
BEGIN;
-- compressed sorted copy into new chunk should result in chunk status 1 (compressed)
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = true;
INSERT INTO metrics_status SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval FROM generate_series(0,100) i;
-- status should be COMPRESSED
SELECT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics_status') chunk;
 chunk_status_text 
-------------------
 {COMPRESSED}

ROLLBACK;
SET timescaledb.enable_direct_compress_insert = false;
SET timescaledb.enable_direct_compress_insert_client_sorted = false;
INSERT INTO metrics_status SELECT '2025-01-01';
-- no status aka normal uncompressed chunk
SELECT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics_status') chunk;
 chunk_status_text 
-------------------
 {}

SELECT compress_chunk(show_chunks('metrics_status'));
             compress_chunk              
-----------------------------------------
 _timescaledb_internal._hyper_4_54_chunk

-- status should be COMPRESSED
SELECT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics_status') chunk;
 chunk_status_text 
-------------------
 {COMPRESSED}

BEGIN;
-- compressed insert into fully compressed chunk should result in chunk status 3 (compressed,unordered)
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = false;
INSERT INTO metrics_status SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval FROM generate_series(0,100) i;
-- status should be COMPRESSED, UNORDERED
SELECT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics_status') chunk;
   chunk_status_text    
------------------------
 {COMPRESSED,UNORDERED}

ROLLBACK;
BEGIN;
-- compressed insert new chunk should result in chunk status 1 (compressed)
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = true;
INSERT INTO metrics_status SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval FROM generate_series(0,100) i;
-- status should be COMPRESSED
SELECT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics_status') chunk;
 chunk_status_text 
-------------------
 {COMPRESSED}

ROLLBACK;
-- test direct compress into chunk directly
CREATE TABLE metrics_chunk(time timestamptz) WITH (tsdb.hypertable,tsdb.partition_column='time');
SET timescaledb.enable_direct_compress_insert = true;
-- create uncompressed chunk
INSERT INTO metrics_chunk SELECT '2025-01-01';
WARNING:  disabling direct compress because of too small batch size
-- status should be normal uncompressed chunk since it was single tuple insert
SELECT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics_chunk') chunk;
 chunk_status_text 
-------------------
 {}

SELECT show_chunks('metrics_chunk') AS "CHUNK" \gset
EXPLAIN (costs off,summary off,timing off) INSERT INTO :CHUNK SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval FROM generate_series(0,100) i;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable)
   Direct Compress: true
   ->  Insert on _hyper_6_56_chunk
         ->  Function Scan on generate_series i

BEGIN;
INSERT INTO :CHUNK SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval FROM generate_series(0,100) i;
-- status should be COMPRESSED, UNORDERED, PARTIAL
SELECT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics_chunk') chunk;
       chunk_status_text        
--------------------------------
 {COMPRESSED,UNORDERED,PARTIAL}

-- delete should propagate to compressed chunk
SELECT count(*) FROM :CHUNK;
 count 
-------
   102

EXPLAIN (analyze,buffers off,costs off,summary off,timing off) DELETE FROM :CHUNK WHERE time > '2025-01-01'::timestamptz;
--- QUERY PLAN ---
 Custom Scan (ModifyHypertable) (actual rows=0.00 loops=1)
   Batches decompressed: 1
   Tuples decompressed: 101
   ->  Delete on _hyper_6_56_chunk (actual rows=0.00 loops=1)
         ->  Index Scan using _hyper_6_56_chunk_metrics_chunk_time_idx on _hyper_6_56_chunk (actual rows=100.00 loops=1)
               Index Cond: ("time" > 'Wed Jan 01 00:00:00 2025 PST'::timestamp with time zone)

SELECT count(*) FROM :CHUNK;
 count 
-------
     2

ROLLBACK;
BEGIN;
SET timescaledb.enable_direct_compress_insert_client_sorted = true;
INSERT INTO :CHUNK SELECT '2025-01-01'::timestamptz + (i || ' minute')::interval FROM generate_series(0,100) i;
-- status should be COMPRESSED, PARTIAL
SELECT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics_chunk') chunk;
  chunk_status_text   
----------------------
 {COMPRESSED,PARTIAL}

ROLLBACK;
-- simple test with compressed insert enabled and sorting limited to 500
-- batches should be limited to that amount so we have more compressed batches
-- dataset is tweaked to fall into single chunk with exactly 3 batches
BEGIN;
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.direct_compress_insert_tuple_sort_limit = 1000;
INSERT INTO metrics SELECT '2025-01-02'::timestamptz + (i || ' minute')::interval, 'd1', i::float FROM generate_series(1,3000) i;
EXPLAIN (ANALYZE, BUFFERS OFF, COSTS OFF, SUMMARY OFF, TIMING OFF) SELECT * FROM metrics;
--- QUERY PLAN ---
 Custom Scan (ColumnarScan) on _hyper_1_59_chunk (actual rows=3000.00 loops=1)
   ->  Seq Scan on compress_hyper_2_60_chunk (actual rows=3.00 loops=1)

SELECT first(time,rn), last(time,rn) FROM (SELECT ROW_NUMBER() OVER () as rn, time FROM metrics) sub;
            first             |             last             
------------------------------+------------------------------
 Thu Jan 02 00:01:00 2025 PST | Sat Jan 04 02:00:00 2025 PST

-- since the chunks are new status should be COMPRESSED, UNORDERED
SELECT DISTINCT _timescaledb_functions.chunk_status_text(chunk) FROM show_chunks('metrics') chunk;
   chunk_status_text    
------------------------
 {COMPRESSED,UNORDERED}

ROLLBACK;
