diff --git a/expected/35_no_gaps_timestamp_test.out b/expected/35_no_gaps_timestamp_test.out new file mode 100644 index 0000000..b0d9503 --- /dev/null +++ b/expected/35_no_gaps_timestamp_test.out @@ -0,0 +1,253 @@ +CREATE TABLE timestamp_shifts ( + job_id INTEGER, + worker_id INTEGER, + valid_from TIMESTAMP, + valid_to TIMESTAMP +); +-- Add era and unique key via sql_saga +SELECT sql_saga.add_era('timestamp_shifts', 'valid_from', 'valid_to'); + add_era +--------- + t +(1 row) + +SELECT sql_saga.add_unique_key('timestamp_shifts', ARRAY['job_id', 'worker_id']); + add_unique_key +----------------------------------------- + timestamp_shifts_job_id_worker_id_valid +(1 row) + +TABLE sql_saga.era; + table_name | era_name | start_column_name | end_column_name | range_type | bounds_check_constraint | audit_table_name +------------------+----------+-------------------+-----------------+------------+------------------------------+------------------ + date_shifts | valid | valid_from | valid_to | daterange | date_shifts_valid_check | + timestamp_shifts | valid | valid_from | valid_to | tsrange | timestamp_shifts_valid_check | +(2 rows) + +TABLE sql_saga.unique_keys; + key_name | table_name | column_names | era_name | unique_constraint | exclude_constraint +-----------------------------------------+------------------+--------------------+----------+-----------------------------------------------------------+------------------------------------------------ + date_shifts_job_id_worker_id_valid | date_shifts | {job_id,worker_id} | valid | date_shifts_job_id_worker_id_valid_from_valid_to_key | date_shifts_job_id_worker_id_daterange_excl + timestamp_shifts_job_id_worker_id_valid | timestamp_shifts | {job_id,worker_id} | valid | timestamp_shifts_job_id_worker_id_valid_from_valid_to_key | timestamp_shifts_job_id_worker_id_tsrange_excl +(2 rows) + +INSERT INTO timestamp_shifts(job_id, worker_id, valid_from, valid_to) VALUES + (1, 1, '2017-11-27 06:00:00', '2017-11-27 12:00:00'), + (1, 2, '2017-11-27 12:00:00', '2017-11-27 17:00:00'), + (2, 3, '2017-11-27 06:00:00', '2017-11-27 12:00:00'), + (2, 4, '2017-11-27 13:00:00', '2017-11-27 17:00:00'), + (3, 5, '-infinity', '2017-11-27 12:00:00'), + (3, 5, '2017-11-27 12:00:00', '2017-11-27 17:00:00'), + (4, 6, '2017-11-27 06:00:00', '2017-11-27 12:00:00'), + (4, 7, '2017-11-27 12:00:00', 'infinity') +; +-- TRUE: +-- it covers when the range matches one exactly: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 12:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + no_gaps +--------- + t +(1 row) + +-- it covers when the range matches two exactly: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + no_gaps +--------- + t +(1 row) + +-- it covers when the range has extra in front: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 08:00:00', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + no_gaps +--------- + t +(1 row) + +-- it covers when the range has extra behind: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 14:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + no_gaps +--------- + t +(1 row) + +-- it covers when the range has extra on both sides: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 08:00:00', '2017-11-27 14:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + no_gaps +--------- + t +(1 row) + +-- an infinite start will cover a finite target: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 3; + no_gaps +--------- + t +(1 row) + +-- an infinite start will cover an infinite target: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('-infinity', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 3; + no_gaps +--------- + t +(1 row) + +-- an infinite end will cover a finite target: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 4; + no_gaps +--------- + t +(1 row) + +-- an infinite end will cover an infinite target: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', 'infinity')) +FROM timestamp_shifts +WHERE job_id = 4; + no_gaps +--------- + t +(1 row) + +-- FALSE: +-- it does not cover when the range is null: +SELECT sql_saga.no_gaps(NULL, tstzrange('2017-11-27 08:00:00', '2017-11-27 14:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + no_gaps +--------- + f +(1 row) + +-- it does not cover when the range misses completely: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-29 08:00:00', '2017-11-29 14:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + no_gaps +--------- + f +(1 row) + +-- it does not cover when the range has something at the beginning: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 04:00:00', '2017-11-27 14:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + no_gaps +--------- + f +(1 row) + +-- it does not cover when the range has something at the end: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 20:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + no_gaps +--------- + f +(1 row) + +-- it does not cover when the range has something in the middle: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 2; + no_gaps +--------- + f +(1 row) + +-- it does not cover when the range is lower-unbounded: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('-infinity', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + no_gaps +--------- + f +(1 row) + +-- it does not cover when the range is upper-unbounded: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', 'infinity')) +FROM timestamp_shifts +WHERE job_id = 1; + no_gaps +--------- + f +(1 row) + +-- it does not cover when the range is both-sides-unbounded: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('-infinity', 'infinity')) +FROM timestamp_shifts +WHERE job_id = 1; + no_gaps +--------- + f +(1 row) + +-- an infinite start will not cover a finite target if there is uncovered time at the end: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 20:00:00')) +FROM timestamp_shifts +WHERE job_id = 3; + no_gaps +--------- + f +(1 row) + +-- an infinite start will not cover an infinite target if there is uncovered time at the end: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('-infinity', '2017-11-27 20:00:00')) +FROM timestamp_shifts +WHERE job_id = 3; + no_gaps +--------- + f +(1 row) + +-- an infinite end will not cover a finite target if there is uncovered time at the beginning: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 03:00:00', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 4; + no_gaps +--------- + f +(1 row) + +-- an infinite end will not cover an infinite target if there is uncovered time at the beginning: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 03:00:00', 'infinity')) +FROM timestamp_shifts +WHERE job_id = 4; + no_gaps +--------- + f +(1 row) + +-- NULL: +-- it is unknown when the target is null: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), null) +FROM timestamp_shifts +WHERE job_id = 1; + no_gaps +--------- + +(1 row) + +-- Errors: +-- it fails if the input ranges go backwards: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 13:00:00', '2017-11-27 20:00:00') ORDER BY worker_id DESC) +FROM timestamp_shifts +WHERE job_id = 1; +ERROR: no_gaps first argument should be sorted but got a range ending before the last covered_to +-- TODO: handle an empty target range? e.g. [5, 5) +-- Or maybe since that is a self-contradiction maybe ignore that case? +DELETE FROM timestamp_shifts; diff --git a/sql/35_no_gaps_timestamp_test.sql b/sql/35_no_gaps_timestamp_test.sql new file mode 100644 index 0000000..681c632 --- /dev/null +++ b/sql/35_no_gaps_timestamp_test.sql @@ -0,0 +1,152 @@ +CREATE TABLE timestamp_shifts ( + job_id INTEGER, + worker_id INTEGER, + valid_from TIMESTAMP, + valid_to TIMESTAMP +); +-- Add era and unique key via sql_saga +SELECT sql_saga.add_era('timestamp_shifts', 'valid_from', 'valid_to'); +SELECT sql_saga.add_unique_key('timestamp_shifts', ARRAY['job_id', 'worker_id']); + +TABLE sql_saga.era; +TABLE sql_saga.unique_keys; + +INSERT INTO timestamp_shifts(job_id, worker_id, valid_from, valid_to) VALUES + (1, 1, '2017-11-27 06:00:00', '2017-11-27 12:00:00'), + (1, 2, '2017-11-27 12:00:00', '2017-11-27 17:00:00'), + (2, 3, '2017-11-27 06:00:00', '2017-11-27 12:00:00'), + (2, 4, '2017-11-27 13:00:00', '2017-11-27 17:00:00'), + (3, 5, '-infinity', '2017-11-27 12:00:00'), + (3, 5, '2017-11-27 12:00:00', '2017-11-27 17:00:00'), + (4, 6, '2017-11-27 06:00:00', '2017-11-27 12:00:00'), + (4, 7, '2017-11-27 12:00:00', 'infinity') +; + +-- TRUE: + +-- it covers when the range matches one exactly: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 12:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + +-- it covers when the range matches two exactly: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + +-- it covers when the range has extra in front: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 08:00:00', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + +-- it covers when the range has extra behind: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 14:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + +-- it covers when the range has extra on both sides: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 08:00:00', '2017-11-27 14:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + +-- an infinite start will cover a finite target: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 3; + +-- an infinite start will cover an infinite target: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('-infinity', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 3; + +-- an infinite end will cover a finite target: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 4; + +-- an infinite end will cover an infinite target: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', 'infinity')) +FROM timestamp_shifts +WHERE job_id = 4; + +-- FALSE: + +-- it does not cover when the range is null: +SELECT sql_saga.no_gaps(NULL, tstzrange('2017-11-27 08:00:00', '2017-11-27 14:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + +-- it does not cover when the range misses completely: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-29 08:00:00', '2017-11-29 14:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + +-- it does not cover when the range has something at the beginning: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 04:00:00', '2017-11-27 14:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + +-- it does not cover when the range has something at the end: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 20:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + +-- it does not cover when the range has something in the middle: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 2; + +-- it does not cover when the range is lower-unbounded: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('-infinity', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 1; + +-- it does not cover when the range is upper-unbounded: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', 'infinity')) +FROM timestamp_shifts +WHERE job_id = 1; + +-- it does not cover when the range is both-sides-unbounded: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('-infinity', 'infinity')) +FROM timestamp_shifts +WHERE job_id = 1; + +-- an infinite start will not cover a finite target if there is uncovered time at the end: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 06:00:00', '2017-11-27 20:00:00')) +FROM timestamp_shifts +WHERE job_id = 3; + +-- an infinite start will not cover an infinite target if there is uncovered time at the end: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('-infinity', '2017-11-27 20:00:00')) +FROM timestamp_shifts +WHERE job_id = 3; + +-- an infinite end will not cover a finite target if there is uncovered time at the beginning: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 03:00:00', '2017-11-27 17:00:00')) +FROM timestamp_shifts +WHERE job_id = 4; + +-- an infinite end will not cover an infinite target if there is uncovered time at the beginning: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 03:00:00', 'infinity')) +FROM timestamp_shifts +WHERE job_id = 4; + + +-- NULL: + +-- it is unknown when the target is null: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), null) +FROM timestamp_shifts +WHERE job_id = 1; + +-- Errors: + +-- it fails if the input ranges go backwards: +SELECT sql_saga.no_gaps(tstzrange(valid_from,valid_to), tstzrange('2017-11-27 13:00:00', '2017-11-27 20:00:00') ORDER BY worker_id DESC) +FROM timestamp_shifts +WHERE job_id = 1; + +-- TODO: handle an empty target range? e.g. [5, 5) +-- Or maybe since that is a self-contradiction maybe ignore that case? + +DELETE FROM timestamp_shifts;