-- 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.
-- test conflict handling on compressed hypertables with unique constraints
set timescaledb.debug_compression_path_info to on;
-- test 1: single column primary key
CREATE TABLE comp_conflicts_1(time timestamptz, device text, value float, PRIMARY KEY(time));
SELECT table_name FROM create_hypertable('comp_conflicts_1','time');
    table_name    
------------------
 comp_conflicts_1

ALTER TABLE comp_conflicts_1 SET (timescaledb.compress);
-- implicitly create chunk
INSERT INTO comp_conflicts_1 VALUES ('2020-01-01','d1',0.1);
-- sanity check behaviour without compression
-- should fail due to multiple entries with same time value
\set ON_ERROR_STOP 0
INSERT INTO comp_conflicts_1 VALUES ('2020-01-01','d1',0.1);
ERROR:  duplicate key value violates unique constraint "1_1_comp_conflicts_1_pkey"
INSERT INTO comp_conflicts_1 VALUES
('2020-01-01','d1',0.1),
('2020-01-01','d2',0.2),
('2020-01-01','d3',0.3);
ERROR:  duplicate key value violates unique constraint "1_1_comp_conflicts_1_pkey"
\set ON_ERROR_STOP 1
-- should succeed since there are no conflicts in the values
BEGIN;
INSERT INTO comp_conflicts_1 VALUES
('2020-01-01 0:00:01','d1',0.1),
('2020-01-01 0:00:02','d2',0.2),
('2020-01-01 0:00:03','d3',0.3);
ROLLBACK;
SELECT compress_chunk(c) AS "CHUNK" FROM show_chunks('comp_conflicts_1') c
\gset
INFO:  using tuplesort to scan rows from "_hyper_1_1_chunk" for converting to columnstore
-- after compression no data should be in uncompressed chunk
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- repeat tests on an actual compressed chunk
-- should fail due to multiple entries with same time value
\set ON_ERROR_STOP 0
INSERT INTO comp_conflicts_1 VALUES ('2020-01-01','d1',0.1);
INFO:  Using table scan with scan keys: index 0, heap 2, memory 1. 
ERROR:  duplicate key value violates unique constraint "1_1_comp_conflicts_1_pkey"
INSERT INTO comp_conflicts_1 VALUES
('2020-01-01','d1',0.1),
('2020-01-01','d2',0.2),
('2020-01-01','d3',0.3);
INFO:  Using table scan with scan keys: index 0, heap 2, memory 1. 
ERROR:  duplicate key value violates unique constraint "1_1_comp_conflicts_1_pkey"
\set ON_ERROR_STOP 1
-- no data should be in uncompressed chunk since the inserts failed and their transaction rolled back
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- should succeed since there are no conflicts in the values
BEGIN;
  INSERT INTO comp_conflicts_1 VALUES
  ('2020-01-01 0:00:01','d1',0.1),
  ('2020-01-01 0:00:02','d2',0.2),
  ('2020-01-01 0:00:03','d3',0.3);
INFO:  Using table scan with scan keys: index 0, heap 2, memory 1. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
INFO:  Using table scan with scan keys: index 0, heap 2, memory 1. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
INFO:  Using table scan with scan keys: index 0, heap 2, memory 1. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
  -- no data should have moved into uncompressed chunk for conflict check
  -- since we used metadata optimization to guarantee uniqueness
  SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     3

ROLLBACK;
-- no data should be in uncompressed chunk since we did rollback
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- should fail since it conflicts with existing row
\set ON_ERROR_STOP 0
INSERT INTO comp_conflicts_1 VALUES ('2020-01-01','d1',0.1);
INFO:  Using table scan with scan keys: index 0, heap 2, memory 1. 
ERROR:  duplicate key value violates unique constraint "1_1_comp_conflicts_1_pkey"
\set ON_ERROR_STOP 1
INSERT INTO comp_conflicts_1 VALUES ('2020-01-01','d1',0.1) ON CONFLICT DO NOTHING;
INFO:  Using table scan with scan keys: index 0, heap 2, memory 1. 
-- data should have move into uncompressed chunk for conflict check
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- test 2: multi-column unique without segmentby
CREATE TABLE comp_conflicts_2(time timestamptz NOT NULL, device text, value float, UNIQUE(time, device));
SELECT table_name FROM create_hypertable('comp_conflicts_2','time');
    table_name    
------------------
 comp_conflicts_2

ALTER TABLE comp_conflicts_2 SET (timescaledb.compress, timescaledb.compress_segmentby='');
-- implicitly create chunk
INSERT INTO comp_conflicts_2 VALUES ('2020-01-01','d1',0.1);
INSERT INTO comp_conflicts_2 VALUES ('2020-01-01','d2',0.2);
SELECT compress_chunk(c) AS "CHUNK" FROM show_chunks('comp_conflicts_2') c
\gset
INFO:  using tuplesort to scan rows from "_hyper_3_3_chunk" for converting to columnstore
-- after compression no data should be in uncompressed chunk
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- should fail due to multiple entries with same time, device value
\set ON_ERROR_STOP 0
INSERT INTO comp_conflicts_2 VALUES ('2020-01-01','d1',0.1);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
ERROR:  duplicate key value violates unique constraint "3_2_comp_conflicts_2_time_device_key"
INSERT INTO comp_conflicts_2 VALUES ('2020-01-01','d2',0.2);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
ERROR:  duplicate key value violates unique constraint "3_2_comp_conflicts_2_time_device_key"
INSERT INTO comp_conflicts_2 VALUES
('2020-01-01','d1',0.1),
('2020-01-01','d2',0.2),
('2020-01-01','d3',0.3);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
ERROR:  duplicate key value violates unique constraint "3_2_comp_conflicts_2_time_device_key"
\set ON_ERROR_STOP 1
-- no data should be in uncompressed chunk since the inserts failed and their transaction rolled back
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- should succeed since there are no conflicts in the values
BEGIN;
  INSERT INTO comp_conflicts_2 VALUES
  ('2020-01-01 0:00:01','d1',0.1),
  ('2020-01-01 0:00:01','d2',0.2),
  ('2020-01-01 0:00:01','d3',0.3);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
  -- no data should have move into uncompressed chunk for conflict check
  -- since we used metadata optimization to guarantee uniqueness
  SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     3

ROLLBACK;
-- no data should be in uncompressed chunk since we did rollback
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- should fail since it conflicts with existing row
\set ON_ERROR_STOP 0
INSERT INTO comp_conflicts_2 VALUES ('2020-01-01','d1',0.1);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
ERROR:  duplicate key value violates unique constraint "3_2_comp_conflicts_2_time_device_key"
\set ON_ERROR_STOP 1
INSERT INTO comp_conflicts_2 VALUES ('2020-01-01','d1',0.1) ON CONFLICT DO NOTHING;
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
-- data should have move into uncompressed chunk for conflict check
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- test 3: multi-column primary key with segmentby
CREATE TABLE comp_conflicts_3(time timestamptz NOT NULL, device text, label text DEFAULT 'label', value float, UNIQUE(time, device, label));
SELECT table_name FROM create_hypertable('comp_conflicts_3','time');
    table_name    
------------------
 comp_conflicts_3

ALTER TABLE comp_conflicts_3 SET (timescaledb.compress,timescaledb.compress_segmentby='device, label');
-- implicitly create chunk
INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d1', 'label', 0.1);
INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d2', 'label', 0.2);
INSERT INTO comp_conflicts_3 VALUES ('2020-01-01',NULL, 'label', 0.3);
SELECT compress_chunk(c) AS "CHUNK" FROM show_chunks('comp_conflicts_3') c
\gset
INFO:  using tuplesort to scan rows from "_hyper_5_5_chunk" for converting to columnstore
-- after compression no data should be in uncompressed chunk
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- should fail due to multiple entries with same time, device value
\set ON_ERROR_STOP 0
INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d1', 'label', 0.1);
INFO:  Using index scan with scan keys: index 2, heap 2, memory 1. 
ERROR:  duplicate key value violates unique constraint "5_3_comp_conflicts_3_time_device_label_key"
INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d2', 'label', 0.2);
INFO:  Using index scan with scan keys: index 2, heap 2, memory 1. 
ERROR:  duplicate key value violates unique constraint "5_3_comp_conflicts_3_time_device_label_key"
INSERT INTO comp_conflicts_3 VALUES
('2020-01-01','d1', 'label', 0.1),
('2020-01-01','d2', 'label', 0.2),
('2020-01-01','d3', 'label', 0.3);
INFO:  Using index scan with scan keys: index 2, heap 2, memory 1. 
ERROR:  duplicate key value violates unique constraint "5_3_comp_conflicts_3_time_device_label_key"
-- should work the same without the index present
BEGIN;
  DROP INDEX _timescaledb_internal.compress_hyper_6_6_chunk_device_label__ts_meta_min_1__ts_me_idx;
  INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d1', 'label', 0.1);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 1. 
ERROR:  duplicate key value violates unique constraint "5_3_comp_conflicts_3_time_device_label_key"
ROLLBACK;
BEGIN;
  DROP INDEX _timescaledb_internal.compress_hyper_6_6_chunk_device_label__ts_meta_min_1__ts_me_idx;
  INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d2', 'label', 0.2);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 1. 
ERROR:  duplicate key value violates unique constraint "5_3_comp_conflicts_3_time_device_label_key"
ROLLBACK;
BEGIN;
  DROP INDEX _timescaledb_internal.compress_hyper_6_6_chunk_device_label__ts_meta_min_1__ts_me_idx;
  INSERT INTO comp_conflicts_3 VALUES
  ('2020-01-01','d1', 'label', 0.1),
  ('2020-01-01','d2', 'label', 0.2),
  ('2020-01-01','d3', 'label', 0.3);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 1. 
ERROR:  duplicate key value violates unique constraint "5_3_comp_conflicts_3_time_device_label_key"
ROLLBACK;
-- using superuser to create indexes on compressed chunks
\c :TEST_DBNAME :ROLE_SUPERUSER
set timescaledb.debug_compression_path_info to on;
-- ignore matching partial index
BEGIN;
  DROP INDEX _timescaledb_internal.compress_hyper_6_6_chunk_device_label__ts_meta_min_1__ts_me_idx;
  CREATE INDEX partial_index ON _timescaledb_internal.compress_hyper_6_6_chunk (device, label, _ts_meta_min_1 DESC, _ts_meta_max_1 DESC)
	WHERE label LIKE 'missing';
  INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d1', 'label', 0.1);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 1. 
ERROR:  duplicate key value violates unique constraint "5_3_comp_conflicts_3_time_device_label_key"
ROLLBACK;
-- ignore matching covering index
BEGIN;
  DROP INDEX _timescaledb_internal.compress_hyper_6_6_chunk_device_label__ts_meta_min_1__ts_me_idx;
  CREATE INDEX covering_index ON _timescaledb_internal.compress_hyper_6_6_chunk (device) INCLUDE (label, _ts_meta_min_1, _ts_meta_max_1);
  INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d1', 'label', 0.1);
INFO:  Using index scan with scan keys: index 1, heap 3, memory 1. 
ERROR:  duplicate key value violates unique constraint "5_3_comp_conflicts_3_time_device_label_key"
ROLLBACK;
-- out of order segmentby index, index is still usable
BEGIN;
  DROP INDEX _timescaledb_internal.compress_hyper_6_6_chunk_device_label__ts_meta_min_1__ts_me_idx;
  CREATE INDEX partial_index ON _timescaledb_internal.compress_hyper_6_6_chunk (label, device, _ts_meta_min_1 DESC, _ts_meta_max_1 DESC);
  INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d1', 'label', 0.1);
INFO:  Using index scan with scan keys: index 2, heap 2, memory 1. 
ERROR:  duplicate key value violates unique constraint "5_3_comp_conflicts_3_time_device_label_key"
ROLLBACK;
-- index with sequence number in the middle, index should be usable with single index scan key
BEGIN;
  DROP INDEX _timescaledb_internal.compress_hyper_6_6_chunk_device_label__ts_meta_min_1__ts_me_idx;
  CREATE INDEX covering_index ON _timescaledb_internal.compress_hyper_6_6_chunk (device, _ts_meta_min_1 DESC, _ts_meta_max_1 DESC, label);
  INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d1', 'label', 0.1);
INFO:  Using index scan with scan keys: index 1, heap 3, memory 1. 
ERROR:  duplicate key value violates unique constraint "5_3_comp_conflicts_3_time_device_label_key"
ROLLBACK;
-- ignore expression index
BEGIN;
  DROP INDEX _timescaledb_internal.compress_hyper_6_6_chunk_device_label__ts_meta_min_1__ts_me_idx;
  CREATE INDEX partial_index ON _timescaledb_internal.compress_hyper_6_6_chunk (device, lower(label), _ts_meta_min_1 DESC, _ts_meta_max_1 DESC);
  INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d1', 'label', 0.1);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 1. 
ERROR:  duplicate key value violates unique constraint "5_3_comp_conflicts_3_time_device_label_key"
ROLLBACK;
-- ignore non-btree index
BEGIN;
  DROP INDEX _timescaledb_internal.compress_hyper_6_6_chunk_device_label__ts_meta_min_1__ts_me_idx;
  CREATE INDEX partial_index ON _timescaledb_internal.compress_hyper_6_6_chunk USING brin (device, label, _ts_meta_min_1, _ts_meta_max_1);
  INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d1', 'label', 0.1);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 1. 
ERROR:  duplicate key value violates unique constraint "5_3_comp_conflicts_3_time_device_label_key"
ROLLBACK;
\set ON_ERROR_STOP 1
\c :TEST_DBNAME :ROLE_DEFAULT_PERM_USER
set timescaledb.debug_compression_path_info to on;
-- no data should be in uncompressed chunk since the inserts failed and their transaction rolled back
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- NULL is considered distinct from other NULL so even though the next INSERT looks
-- like a conflict it is not a constraint violation (PG15 makes NULL behaviour configurable)
BEGIN;
  INSERT INTO comp_conflicts_3 VALUES ('2020-01-01',NULL, 'label', 0.3);
  -- data for 1 segment (count = 1 value + 1 inserted) should be present in uncompressed chunk
  -- we treat NULLs as NOT DISTINCT and let the constraint configuration handle the check
  SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     1

ROLLBACK;
-- check if NULL handling works the same with the compressed index dropped
BEGIN;
  DROP INDEX _timescaledb_internal.compress_hyper_6_6_chunk_device_label__ts_meta_min_1__ts_me_idx;
  INSERT INTO comp_conflicts_3 VALUES ('2020-01-01',NULL, 'label', 0.3);
  -- data for 1 segment (count = 1 value + 1 inserted) should be present in uncompressed chunk
  -- we treat NULLs as NOT DISTINCT and let the constraint configuration handle the check
  SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     1

ROLLBACK;
-- should succeed since there are no conflicts in the values
BEGIN;
  INSERT INTO comp_conflicts_3 VALUES ('2020-01-01 0:00:01','d1', 'label', 0.1);
INFO:  Using index scan with scan keys: index 2, heap 2, memory 1. 
INFO:  Number of compressed rows fetched from index: 1. Number of compressed rows filtered by heap filters: 1.
  -- no data should have move into uncompressed chunk for conflict check
  -- since we used metadata optimization to guarantee uniqueness
  SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     1

ROLLBACK;
-- same as above but no index
-- should succeed since there are no conflicts in the values
BEGIN;
  DROP INDEX _timescaledb_internal.compress_hyper_6_6_chunk_device_label__ts_meta_min_1__ts_me_idx;
  INSERT INTO comp_conflicts_3 VALUES ('2020-01-01 0:00:01','d1', 'label', 0.1);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 1. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
  -- no data should have move into uncompressed chunk for conflict check
  -- since we used metadata optimization to guarantee uniqueness
  SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     1

ROLLBACK;
BEGIN;
  INSERT INTO comp_conflicts_3 VALUES
  ('2020-01-01 0:00:01','d1', 'label', 0.1),
  ('2020-01-01 0:00:01','d2', 'label', 0.2),
  ('2020-01-01 0:00:01','d3', 'label', 0.3);
INFO:  Using index scan with scan keys: index 2, heap 2, memory 1. 
INFO:  Number of compressed rows fetched from index: 1. Number of compressed rows filtered by heap filters: 1.
INFO:  Using index scan with scan keys: index 2, heap 2, memory 1. 
INFO:  Number of compressed rows fetched from index: 1. Number of compressed rows filtered by heap filters: 1.
INFO:  Using index scan with scan keys: index 2, heap 2, memory 1. 
INFO:  Number of compressed rows fetched from index: 0. Number of compressed rows filtered by heap filters: 0.
  -- no data for should have move into uncompressed chunk for conflict check
  -- since we used metadata optimization to guarantee uniqueness
  SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     3

ROLLBACK;
-- same as above but no index
BEGIN;
  DROP INDEX _timescaledb_internal.compress_hyper_6_6_chunk_device_label__ts_meta_min_1__ts_me_idx;
  INSERT INTO comp_conflicts_3 VALUES
  ('2020-01-01 0:00:01','d1', 'label', 0.1),
  ('2020-01-01 0:00:01','d2', 'label', 0.2),
  ('2020-01-01 0:00:01','d3', 'label', 0.3);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 1. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
INFO:  Using table scan with scan keys: index 0, heap 4, memory 1. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
INFO:  Using table scan with scan keys: index 0, heap 4, memory 1. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
  -- no data for should have move into uncompressed chunk for conflict check
  -- since we used metadata optimization to guarantee uniqueness
  SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     3

ROLLBACK;
BEGIN;
  INSERT INTO comp_conflicts_3 VALUES ('2020-01-01 0:00:01','d3', 'label', 0.2);
INFO:  Using index scan with scan keys: index 2, heap 2, memory 1. 
INFO:  Number of compressed rows fetched from index: 0. Number of compressed rows filtered by heap filters: 0.
  -- count = 1 since no data should have move into uncompressed chunk for conflict check since d3 is new segment
  SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     1

ROLLBACK;
-- no data should be in uncompressed chunk since we did rollback
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- should fail since it conflicts with existing row
\set ON_ERROR_STOP 0
INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d1', 'label', 0.1);
INFO:  Using index scan with scan keys: index 2, heap 2, memory 1. 
ERROR:  duplicate key value violates unique constraint "5_3_comp_conflicts_3_time_device_label_key"
\set ON_ERROR_STOP 1
INSERT INTO comp_conflicts_3 VALUES ('2020-01-01','d1', 'label', 0.1) ON CONFLICT DO NOTHING;
INFO:  Using index scan with scan keys: index 2, heap 2, memory 1. 
-- data should have move into uncompressed chunk for conflict check
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- test 4: multi-column primary key with multi-column orderby compression
CREATE TABLE comp_conflicts_4(time timestamptz NOT NULL, device text, value float, UNIQUE(time, device));
SELECT table_name FROM create_hypertable('comp_conflicts_4','time');
    table_name    
------------------
 comp_conflicts_4

ALTER TABLE comp_conflicts_4 SET (timescaledb.compress,timescaledb.compress_segmentby='',timescaledb.compress_orderby='time,device');
-- implicitly create chunk
INSERT INTO comp_conflicts_4 SELECT generate_series('2020-01-01'::timestamp, '2020-01-01 2:00:00', '1s'), 'd1',0.1;
INSERT INTO comp_conflicts_4 VALUES ('2020-01-01','d2',0.2);
INSERT INTO comp_conflicts_4 VALUES ('2020-01-01',NULL,0.3);
SELECT compress_chunk(c) AS "CHUNK" FROM show_chunks('comp_conflicts_4') c
\gset
INFO:  using tuplesort to scan rows from "_hyper_7_7_chunk" for converting to columnstore
-- after compression no data should be in uncompressed chunk
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- NULL is considered distinct from other NULL so even though the next INSERT looks
-- like a conflict it is not a constraint violation (PG15 makes NULL behaviour configurable)
BEGIN;
  INSERT INTO comp_conflicts_4 VALUES ('2020-01-01',NULL,0.3);
  -- data for 1 segment (count = 1000 values + 1 inserted) should be present in uncompressed chunk
  -- we treat NULLs as NOT DISTINCT and let the constraint configuration handle the check
  SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     1

ROLLBACK;
-- should succeed since there are no conflicts in the values
BEGIN;
  INSERT INTO comp_conflicts_4 VALUES ('2020-01-01 2:00:01','d1',0.1);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
  -- no data should have move into uncompressed chunk for conflict check
  -- since we used metadata optimization to guarantee uniqueness
  SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     1

ROLLBACK;
BEGIN;
  INSERT INTO comp_conflicts_4 VALUES
  ('2020-01-01 2:00:01','d1',0.1),
  ('2020-01-01 2:00:01','d2',0.2),
  ('2020-01-01 2:00:01','d3',0.3);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
  -- no data for should have move into uncompressed chunk for conflict check
  -- since we used metadata optimization to guarantee uniqueness
  SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     3

ROLLBACK;
BEGIN;
  INSERT INTO comp_conflicts_4 VALUES ('2020-01-01 0:00:01','d3',0.2);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
INFO:  Number of compressed rows fetched from table scan: 0. Number of compressed rows filtered: 0.
  -- count = 1 since no data should have move into uncompressed chunk for conflict check since d3 is new segment
  SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     1

ROLLBACK;
-- no data should be in uncompressed chunk since we did rollback
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

-- should fail since it conflicts with existing row
\set ON_ERROR_STOP 0
INSERT INTO comp_conflicts_4 VALUES ('2020-01-01','d1',0.1);
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
ERROR:  duplicate key value violates unique constraint "7_4_comp_conflicts_4_time_device_key"
\set ON_ERROR_STOP 1
-- data not should have move into uncompressed chunk for conflict check
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

INSERT INTO comp_conflicts_4 VALUES ('2020-01-01 0:00:01','d1',0.1) ON CONFLICT DO NOTHING;
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
INSERT INTO comp_conflicts_4 VALUES ('2020-01-01 0:30:00','d1',0.1) ON CONFLICT DO NOTHING;
INFO:  Using table scan with scan keys: index 0, heap 4, memory 2. 
-- data should have move into uncompressed chunk for conflict check
-- 2 segments (count = 2000)
SELECT count(*) FROM ONLY :CHUNK;
 count 
-------
     0

CREATE OR REPLACE VIEW compressed_chunk_info_view AS
SELECT
   h.schema_name AS hypertable_schema,
   h.table_name AS hypertable_name,
   c.schema_name as chunk_schema,
   c.table_name as chunk_name,
   c.status as chunk_status,
   comp.schema_name as compressed_chunk_schema,
   comp.table_name as compressed_chunk_name
FROM
   _timescaledb_catalog.hypertable h JOIN
  _timescaledb_catalog.chunk c ON h.id = c.hypertable_id
   LEFT JOIN _timescaledb_catalog.chunk comp
ON comp.id = c.compressed_chunk_id;
CREATE TABLE compressed_ht (
       time TIMESTAMP WITH TIME ZONE NOT NULL,
       sensor_id INTEGER NOT NULL,
       cpu double precision null,
       temperature double precision null,
       name varchar(100) default 'this is a default string value'
);
CREATE UNIQUE INDEX sensor_id_time_idx on compressed_ht(time, sensor_id);
SELECT * FROM create_hypertable('compressed_ht', 'time',
       chunk_time_interval => INTERVAL '2 months');
WARNING:  column type "character varying" used for "name" does not follow best practices
 hypertable_id | schema_name |  table_name   | created 
---------------+-------------+---------------+---------
             9 | public      | compressed_ht | t

-- create chunk 1
INSERT INTO compressed_ht VALUES ('2017-12-28 01:10:28.192199+05:30', '1', 0.876, 4.123, 'chunk 1');
INSERT INTO compressed_ht VALUES ('2017-12-24 01:10:28.192199+05:30', '1', 0.876, 4.123, 'chunk 1');
-- create chunk 2
INSERT INTO compressed_ht VALUES ('2017-03-28 01:10:28.192199+05:30', '2', 0.876, 4.123, 'chunk 2');
INSERT INTO compressed_ht VALUES ('2017-03-12 01:10:28.192199+05:30', '3', 0.876, 4.123, 'chunk 2');
-- create chunk 3
INSERT INTO compressed_ht VALUES ('2022-01-18 01:10:28.192199+05:30', '4', 0.876, 4.123, 'chunk 3');
INSERT INTO compressed_ht VALUES ('2022-01-08 01:10:28.192199+05:30', '4', 0.876, 4.123, 'chunk 3');
INSERT INTO compressed_ht VALUES ('2022-01-11 01:10:28.192199+05:30', '5', 0.876, 4.123, 'chunk 3');
INSERT INTO compressed_ht VALUES ('2022-01-24 01:10:28.192199+05:30', '6', 0.876, 4.123, 'chunk 3');
ALTER TABLE compressed_ht SET (
	timescaledb.compress,
	timescaledb.compress_segmentby = 'sensor_id'
);
SELECT COMPRESS_CHUNK(SHOW_CHUNKS('compressed_ht'));
INFO:  using tuplesort to scan rows from "_hyper_9_9_chunk" for converting to columnstore
INFO:  using tuplesort to scan rows from "_hyper_9_10_chunk" for converting to columnstore
INFO:  using tuplesort to scan rows from "_hyper_9_11_chunk" for converting to columnstore
             compress_chunk              
-----------------------------------------
 _timescaledb_internal._hyper_9_9_chunk
 _timescaledb_internal._hyper_9_10_chunk
 _timescaledb_internal._hyper_9_11_chunk

-- check compression status
SELECT chunk_status,
       chunk_name as "CHUNK_NAME"
FROM compressed_chunk_info_view
WHERE hypertable_name = 'compressed_ht' ORDER BY chunk_name;
 chunk_status |    CHUNK_NAME     
--------------+-------------------
            1 | _hyper_9_10_chunk
            1 | _hyper_9_11_chunk
            1 | _hyper_9_9_chunk

-- should report 0 row
SELECT COUNT(*) FROM compressed_ht WHERE name = 'ON CONFLICT DO UPDATE';
 count 
-------
     0

INSERT INTO compressed_ht VALUES ('2017-12-28 01:10:28.192199+05:30', '1', 0.876, 4.123, 'new insert row')
  ON conflict(sensor_id, time)
DO UPDATE SET sensor_id = excluded.sensor_id , name = 'ON CONFLICT DO UPDATE';
INFO:  Using index scan with scan keys: index 1, heap 2, memory 1. 
INFO:  Number of compressed rows fetched from index: 1. Number of compressed rows filtered by heap filters: 0.
-- should report 1 row
SELECT COUNT(*) FROM compressed_ht WHERE name = 'ON CONFLICT DO UPDATE';
 count 
-------
     1

-- check that chunk 1 compression status is set to partial
SELECT chunk_status,
       chunk_name as "CHUNK_NAME"
FROM compressed_chunk_info_view
WHERE hypertable_name = 'compressed_ht' ORDER BY chunk_name;
 chunk_status |    CHUNK_NAME     
--------------+-------------------
            1 | _hyper_9_10_chunk
            1 | _hyper_9_11_chunk
            9 | _hyper_9_9_chunk

INSERT INTO compressed_ht VALUES ('2022-01-24 01:10:28.192199+05:30', '6', 0.876, 4.123, 'new insert row')
  ON conflict(sensor_id, time)
DO UPDATE SET sensor_id = excluded.sensor_id , name = 'ON CONFLICT DO UPDATE' RETURNING *;
INFO:  Using index scan with scan keys: index 1, heap 2, memory 1. 
INFO:  Number of compressed rows fetched from index: 1. Number of compressed rows filtered by heap filters: 0.
                time                 | sensor_id |  cpu  | temperature |         name          
-------------------------------------+-----------+-------+-------------+-----------------------
 Sun Jan 23 11:40:28.192199 2022 PST |         6 | 0.876 |       4.123 | ON CONFLICT DO UPDATE

-- check that chunks 1 and 3 compression status is set to partial
SELECT chunk_status,
       chunk_name as "CHUNK_NAME"
FROM compressed_chunk_info_view
WHERE hypertable_name = 'compressed_ht' ORDER BY chunk_name;
 chunk_status |    CHUNK_NAME     
--------------+-------------------
            1 | _hyper_9_10_chunk
            9 | _hyper_9_11_chunk
            9 | _hyper_9_9_chunk

-- test for disabling DML decompression
SHOW timescaledb.enable_dml_decompression;
 timescaledb.enable_dml_decompression 
--------------------------------------
 on

SET timescaledb.enable_dml_decompression = false;
\set ON_ERROR_STOP 0
-- Should error because we disabled the DML decompression
INSERT INTO compressed_ht VALUES ('2022-01-24 01:10:28.192199+05:30', '6', 0.876, 4.123, 'new insert row')
  ON conflict(sensor_id, time)
DO UPDATE SET sensor_id = excluded.sensor_id , name = 'ON CONFLICT DO UPDATE' RETURNING *;
ERROR:  inserting into compressed chunk with unique constraints disabled
INSERT INTO compressed_ht VALUES ('2022-01-24 01:10:28.192199+05:30', '6', 0.876, 4.123, 'new insert row')
  ON conflict(sensor_id, time)
DO NOTHING;
ERROR:  inserting into compressed chunk with unique constraints disabled
-- Even a regular insert will fail due to unique constrant checks for dml decompression
INSERT INTO compressed_ht VALUES ('2022-01-24 01:10:28.192199+05:30', '7', 0.876, 4.123, 'new insert row');
ERROR:  inserting into compressed chunk with unique constraints disabled
\set ON_ERROR_STOP 1
RESET timescaledb.enable_dml_decompression;
-- gh issue #7342
CREATE TABLE test_collation (
        time int8 NOT NULL,
        device_id int4 NOT NULL,
        name TEXT NOT NULL,
        CONSTRAINT test_collation_pkey PRIMARY KEY (time, device_id, name)
);
SELECT create_hypertable('test_collation', 'time', chunk_time_interval => 2419200000);
      create_hypertable       
------------------------------
 (11,public,test_collation,t)

ALTER TABLE test_collation
SET (
        timescaledb.compress,
        timescaledb.compress_segmentby = 'device_id',
        timescaledb.compress_orderby = 'time DESC, name'
);
INSERT INTO "test_collation"
  ("time", "device_id", "name")
VALUES
  (1609477200000, 41, 'val1'),
  (1609478100000, 41, 'val1')
ON CONFLICT DO NOTHING;
SELECT compress_chunk(ch) FROM show_chunks('test_collation') ch;
INFO:  using tuplesort to scan rows from "_hyper_11_15_chunk" for converting to columnstore
              compress_chunk              
------------------------------------------
 _timescaledb_internal._hyper_11_15_chunk

INSERT INTO "test_collation"
  ("device_id", "time", "name")
VALUES
  (41, 1609477200000, 'val1'),
  (41, 1609478100000, 'val1')
ON CONFLICT DO NOTHING;
INFO:  Using index scan with scan keys: index 1, heap 4, memory 2. 
INFO:  Using index scan with scan keys: index 1, heap 4, memory 2. 
RESET timescaledb.debug_compression_path_info;
-- gh issue #7672
-- check additional INSERTS after hitting ON CONFLICT clause still go through
CREATE TABLE test_i7672(time timestamptz, device text, primary key(time,device));
SELECT create_hypertable('test_i7672', 'time');
    create_hypertable     
--------------------------
 (13,public,test_i7672,t)

ALTER TABLE test_i7672 SET (timescaledb.compress, timescaledb.compress_orderby='time DESC', timescaledb.compress_segmentby='device');
INSERT INTO test_i7672 VALUES ('2025-01-01','d1');
SELECT count(*) FROM (SELECT compress_chunk(show_chunks('test_i7672'))) c;
 count 
-------
     1

INSERT INTO test_i7672 VALUES
('2025-01-01','d1'),
('2025-01-01','d2')
ON CONFLICT DO NOTHING;
SELECT * FROM test_i7672 t ORDER BY t;
             time             | device 
------------------------------+--------
 Wed Jan 01 00:00:00 2025 PST | d1
 Wed Jan 01 00:00:00 2025 PST | d2

