-- This file and its contents are licensed under the Apache License 2.0.
-- Please see the included NOTICE for copyright information and
-- LICENSE-APACHE for a copy of the license.

--
--
-- Test "time" partitioning on UUIDv7
--
--
CREATE TABLE uuid_events(id uuid primary key, device int, temp float);

\set ON_ERROR_STOP 0
-- Test invalid interval type
SELECT create_hypertable('uuid_events', 'id', chunk_time_interval => true);
\set ON_ERROR_STOP 1

SELECT create_hypertable('uuid_events', 'id', chunk_time_interval => interval '1 day');

SELECT time_interval
FROM timescaledb_information.dimensions
WHERE hypertable_name = 'uuid_events';

--
-- Test that inserting boundary values generates the right constraints
-- on chunks.
--

-- First value with min time: 00000000-0000-7000-8000-000000000000
BEGIN;
INSERT INTO uuid_events VALUES ('00000000-0000-7000-8000-000000000000', 1, 1.0);
SELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;
SELECT uuid_timestamp(id), device, temp
FROM uuid_events ORDER BY id;

-- Update v7 UUID to a v4 UUID that doesn't violate the chunk's range
-- constraint. Currently we don't prevent this "loophole".
UPDATE uuid_events SET id = '00000000-0001-4000-8000-000000000000'
WHERE id = '00000000-0000-7000-8000-000000000000';

SELECT uuid_timestamp(id), device, temp
FROM uuid_events ORDER BY id;

-- Update v7 UUID to a v4 that violates the chunk constraint:
\set ON_ERROR_STOP 0
UPDATE uuid_events SET id = 'ffff0000-0000-4000-8000-000000000000'
WHERE id = '00000000-0001-4000-8000-000000000000';
\set ON_ERROR_STOP 1

ROLLBACK;

-- Last value with min time: 00000000-0000-7fff-bfff-ffffffffffff
BEGIN;
INSERT INTO uuid_events VALUES ('00000000-0000-7fff-bfff-ffffffffffff', 1, 1.0);
SELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;
ROLLBACK;

-- First value with max time: ffffffff-ffff-7000-8000-000000000000
BEGIN;
INSERT INTO uuid_events VALUES ('ffffffff-ffff-7000-8000-000000000000', 1, 1.0);
SELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;
ROLLBACK;

-- (Max time with min value) + 1
BEGIN;
INSERT INTO uuid_events VALUES ('ffffffff-ffff-7000-8000-000000000001', 1, 1.0);
SELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;
ROLLBACK;

-- Last value with max time: ffffffff-ffff-7fff-bfff-ffffffffffff
BEGIN;
INSERT INTO uuid_events VALUES ('ffffffff-ffff-7fff-bfff-ffffffffffff', 1, 1.0);
SELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;
ROLLBACK;

--
-- It is possible to generate UUIDs like follows, but the random
-- generator used doesn't respect setseed() so used constant UUIDs for
-- determinism.
--
-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-01 01:00 PST'), 1, 1.0),
-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-01 02:00 PST'), 2, 2.0),
-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-02 01:00 PST'), 3, 3.0),
-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-02 02:00 PST'), 4, 4.0),
-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-03 03:00 PST'), 5, 5.0),
-- (_timescaledb_functions.uuid_v7_from_timestamptz('2025-01-03 10:00 PST'), 6, 6.0);
--
INSERT INTO uuid_events VALUES
       ('0194214e-cd00-7000-a9a7-63f1416dab45', 2, 2.0),
       ('01942117-de80-7000-8121-f12b2b69dd96', 1, 1.0),
       ('0194263e-3a80-7000-8f40-82c987b1bc1f', 3, 3.0),
       ('01942675-2900-7000-8db1-a98694b18785', 4, 4.0),
       ('01942bd2-7380-7000-9bc4-5f97443907b8', 5, 5.0),
       ('01942d52-f900-7000-866e-07d6404d53c1', 6, 6.0);

SELECT * FROM show_chunks('uuid_events');

SELECT (test.show_constraints(ch)).* from show_chunks('uuid_events') ch;
SELECT id, device, temp FROM uuid_events;

SELECT uuid_timestamp(id), device, temp
FROM uuid_events;

SELECT uuid_timestamp(id), device, temp
FROM uuid_events ORDER BY id;

SELECT
    _timescaledb_functions.to_timestamp(range_start) AS range_start,
    _timescaledb_functions.to_timestamp(range_end) AS range_end
FROM _timescaledb_catalog.dimension_slice ds
JOIN _timescaledb_catalog.dimension d ON (ds.dimension_id = d.id)
JOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)
WHERE h.table_name = 'uuid_events';

SELECT
    _timescaledb_functions.to_timestamp(range_start) AS chunk_range_start,
    _timescaledb_functions.to_timestamp(range_end) AS chunk_range_end
FROM _timescaledb_catalog.dimension_slice ds
JOIN _timescaledb_catalog.dimension d ON (ds.dimension_id = d.id)
JOIN _timescaledb_catalog.hypertable h ON (d.hypertable_id = h.id)
WHERE h.table_name = 'uuid_events'
LIMIT 1 OFFSET 1 \gset

-- Test that chunk exclusion on uuidv7 column works
SELECT :'chunk_range_start',  to_uuidv7_boundary(:'chunk_range_start');

-- Exclude all but one chunk
EXPLAIN (verbose, buffers off, costs off, timing off)
SELECT uuid_timestamp(id), device, temp
FROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_start');

SELECT uuid_timestamp(id), device, temp
FROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_start') ORDER BY id;

-- Exclude only one chunk. Add ordering (DESC)
EXPLAIN (verbose, buffers off, costs off, timing off)
SELECT uuid_timestamp(id), device, temp
FROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')
ORDER BY id DESC;

SELECT uuid_timestamp(id), device, temp
FROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')
ORDER BY id DESC;

SELECT time_bucket('1 day', id) AS day, avg(temp)
FROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')
GROUP BY id ORDER BY id DESC;

SELECT time_bucket('1 day', uuid_timestamp(id)) AS day, avg(temp)
FROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')
GROUP BY id ORDER BY id DESC;

-- Bucket with offset
SELECT time_bucket('1 day', id, "offset" => '1 week') AS day, avg(temp)
FROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')
GROUP BY id ORDER BY id DESC;

-- Bucket with origin
SELECT time_bucket('1 day', id, timestamptz '2000-01-01 00:00') AS day, avg(temp)
FROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')
GROUP BY id ORDER BY id DESC;

-- Bucket with time zone
SELECT time_bucket('1 day', id, 'Europe/Stockholm') at time zone 'Europe/Stockholm' as day, avg(temp)
FROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')
GROUP BY id ORDER BY id DESC;

-- Test NULL arguments
SELECT time_bucket('1 day', id, 'Europe/Stockholm'::text, NULL::timestamptz) AS day, avg(temp)
FROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')
GROUP BY id ORDER BY id DESC;

SELECT time_bucket('1 day', id, 'Europe/Stockholm'::text, '2000-01-01 00:00'::timestamptz, NULL::interval) AS day, avg(temp)
FROM uuid_events WHERE id < to_uuidv7_boundary(:'chunk_range_end')
GROUP BY id ORDER BY id DESC;

-- Test UUID time_bucket in WHERE clause. Note that there is currently no chunk
-- exclusion when using time_bucket() in the WHERE clause qual. This requires
-- special handling of the time_bucket() transform optimizatios for different
-- operators.
EXPLAIN (COSTS OFF) SELECT time_bucket('1 day', id) AS day, avg(temp)
FROM uuid_events WHERE time_bucket('1 day', id) >= :'chunk_range_end'
GROUP BY id ORDER BY id DESC;

SELECT time_bucket('1 day', id) AS day, avg(temp)
FROM uuid_events WHERE time_bucket('1 day', id) >= :'chunk_range_end'
GROUP BY id ORDER BY id DESC;

EXPLAIN (COSTS OFF) SELECT time_bucket('1 day', id) AS day, avg(temp)
FROM uuid_events WHERE time_bucket('1 day', id) > :'chunk_range_start'
GROUP BY id ORDER BY id DESC;

SELECT time_bucket('1 day', id) AS day, avg(temp)
FROM uuid_events WHERE time_bucket('1 day', id) > :'chunk_range_start'
GROUP BY id ORDER BY id DESC;

EXPLAIN (COSTS OFF) SELECT time_bucket('1 day', id) AS day, avg(temp)
FROM uuid_events WHERE time_bucket('1 day', id) < :'chunk_range_end'
GROUP BY id ORDER BY id DESC;

SELECT time_bucket('1 day', id) AS day, avg(temp)
FROM uuid_events WHERE time_bucket('1 day', id) < :'chunk_range_end'
GROUP BY id ORDER BY id DESC;

EXPLAIN (COSTS OFF) SELECT time_bucket('1 day', id) AS day, avg(temp)
FROM uuid_events WHERE time_bucket('1 day', id) <= :'chunk_range_start'
GROUP BY id ORDER BY id DESC;

SELECT time_bucket('1 day', id) AS day, avg(temp)
FROM uuid_events WHERE time_bucket('1 day', id) <= :'chunk_range_start'
GROUP BY id ORDER BY id DESC;

-- Test time_bucket on non-v7 UUID
\set ON_ERROR_STOP 0
SELECT time_bucket('1 day', 'ffff0000-0000-4000-8000-000000000000'::uuid);
\set ON_ERROR_STOP 1

CREATE VIEW chunk_ranges AS
SELECT
  chunk_name,
  range_start,
  range_end
FROM timescaledb_information.chunks
WHERE hypertable_name = 'uuid_events';

SELECT * FROM chunk_ranges;
SELECT show_chunks('uuid_events', older_than => INTERVAL '1 day');
SELECT show_chunks('uuid_events', older_than => '2025-01-02');
SELECT show_chunks('uuid_events', newer_than => '2025-01-02');
SELECT drop_chunks('uuid_events', older_than => '2025-01-02');
SELECT show_chunks('uuid_events');

-- Insert non-v7 UUIDs
\set ON_ERROR_STOP 0
INSERT INTO uuid_events SELECT 'a8961135-cd89-4c4b-aa05-79df642407dd', 5, 5.0;
\set ON_ERROR_STOP 1

-- Insert as v7 UUID and later change to non-v7 to show effect on show_chunks()
-- and drop_chunks()
INSERT INTO uuid_events SELECT 'a8961135-cd89-7000-aa05-79df642407dd', 5, 5.0;
SELECT * FROM chunk_ranges;
UPDATE uuid_events
SET id = 'a8961135-cd89-4c4b-aa05-79df642407dd'
WHERE id = 'a8961135-cd89-7000-aa05-79df642407dd';

SELECT show_chunks('uuid_events', newer_than => '2025-01-02');
SELECT drop_chunks('uuid_events', newer_than => '2025-01-02');
SELECT * FROM chunk_ranges;

INSERT INTO uuid_events SELECT to_uuidv7(now()), 6, 6.0;
SELECT show_chunks('uuid_events', newer_than => INTERVAL '2 months');
SELECT drop_chunks('uuid_events', newer_than => INTERVAL '2 months');
SELECT show_chunks('uuid_events', newer_than => INTERVAL '2 months');

DROP TABLE uuid_events;

BEGIN;
-- Test UUID partition when using CREATE TABLE ... WITH
CREATE TABLE IF NOT EXISTS events (
     event_id UUID NOT NULL,
     entity_id VARCHAR(100) NOT NULL,
     ts TIMESTAMPTZ NOT NULL,
     event_type VARCHAR(100) NOT NULL,
     metadata JSONB,
     PRIMARY KEY (event_id)
)
WITH (
     tsdb.hypertable,
     tsdb.partition_column='event_id',
     tsdb.chunk_interval='2 hours'
);

-- Verify that the chunk time interval is two hours
SELECT ((interval_length/1000000)/60)/60 AS hours
FROM _timescaledb_catalog.dimension d JOIN _timescaledb_catalog.hypertable h ON (h.id = d.hypertable_id)
WHERE h.table_name='events';
ROLLBACK;

-- Test a different interval
BEGIN;
CREATE TABLE IF NOT EXISTS events (
     event_id UUID NOT NULL,
     entity_id VARCHAR(100) NOT NULL,
     ts TIMESTAMPTZ NOT NULL,
     event_type VARCHAR(100) NOT NULL,
     metadata JSONB,
     PRIMARY KEY (event_id)
)
WITH (
     tsdb.hypertable,
     tsdb.partition_column='event_id',
     tsdb.chunk_interval='2 months'
);

-- Verify that the chunk time interval is two hours
SELECT (((interval_length/1000000)/60)/60)/24 AS days
FROM _timescaledb_catalog.dimension d JOIN _timescaledb_catalog.hypertable h ON (h.id = d.hypertable_id)
WHERE h.table_name='events';
ROLLBACK;

-- Verify same behavior without CREATE TABLE WITH:
BEGIN;
CREATE TABLE IF NOT EXISTS events (
     event_id UUID NOT NULL,
     entity_id VARCHAR(100) NOT NULL,
     ts TIMESTAMPTZ NOT NULL,
     event_type VARCHAR(100) NOT NULL,
     metadata JSONB,
     PRIMARY KEY (event_id)
);

SELECT create_hypertable('events', 'event_id', chunk_time_interval => interval '2 hours');
-- Verify that the chunk time interval is two hours
SELECT ((interval_length/1000000)/60)/60 AS hours
FROM _timescaledb_catalog.dimension d JOIN _timescaledb_catalog.hypertable h ON (h.id = d.hypertable_id)
WHERE h.table_name='events';
ROLLBACK;

BEGIN;
CREATE TABLE IF NOT EXISTS events (
     event_id UUID NOT NULL,
     entity_id VARCHAR(100) NOT NULL,
     ts TIMESTAMPTZ NOT NULL,
     event_type VARCHAR(100) NOT NULL,
     metadata JSONB,
     PRIMARY KEY (event_id)
);

SELECT create_hypertable('events', 'event_id', chunk_time_interval => interval '2 months');
-- Verify that the chunk time interval is two hours
SELECT (((interval_length/1000000)/60)/60)/24 AS days
FROM _timescaledb_catalog.dimension d JOIN _timescaledb_catalog.hypertable h ON (h.id = d.hypertable_id)
WHERE h.table_name='events';

ROLLBACK;


