\set ECHO none
\pset format unaligned

-- Union of intervals semiring, Belkis Djeffal
-- See https://inria.hal.science/hal-04342025

CREATE TABLE personnel_intervals(
  id SERIAL PRIMARY KEY,
  name varchar,
  position varchar,
  city varchar,
  valid_time datemultirange
);

INSERT INTO personnel_intervals (name, position, city, valid_time) VALUES
  ('John', 'Director', 'New York', '{["2021-01-15","2023-03-22"],["2019-07-10","2020-12-05"]}'),
  ('Paul', 'Janitor', 'New York', '{["2020-05-28","2022-11-15"],["2018-03-20","2019-09-10"]}'),
  ('Dave', 'Analyst', 'Paris', '{["2019-12-01","2022-08-15"],["2017-06-10","2018-11-30"]}'),
  ('Ellen', 'Field agent', 'Berlin', '{["2020-10-15","2023-05-28"],["2018-04-05","2019-11-20"]}'),
  ('Magdalen', 'Double agent', 'Paris', '{["2018-11-22","2021-09-01"],["2016-07-15","2018-02-28"]}'),
  ('Nancy', 'HR', 'Paris', '{["2017-08-03","2019-10-25"],["2015-02-10","2017-05-15"]}'),
  ('Susan', 'Analyst', 'Berlin', '{["2019-04-12","2022-02-01"],["2016-10-05","2018-01-15"]}');

/* The union_intervals  semiring */
CREATE OR REPLACE FUNCTION union_intervals_plus_state(state datemultirange, value datemultirange)
  RETURNS datemultirange AS
$$
  SELECT CASE WHEN state IS NULL THEN value ELSE state + value END
$$ LANGUAGE SQL IMMUTABLE;

CREATE OR REPLACE FUNCTION union_intervals_times_state(state datemultirange, value datemultirange)
  RETURNS datemultirange AS
$$
SELECT CASE WHEN state IS NULL THEN value ELSE state * value END
$$ LANGUAGE SQL IMMUTABLE;

CREATE OR REPLACE AGGREGATE union_intervals_plus(datemultirange)
(
  sfunc = union_intervals_plus_state,
  stype = datemultirange,
  initcond = '{}'
);

CREATE OR REPLACE AGGREGATE union_intervals_times(datemultirange)
(
  sfunc = union_intervals_times_state,
  stype = datemultirange,
  initcond = '{(,)}'
);

CREATE OR REPLACE FUNCTION union_intervals_monus(state datemultirange, value datemultirange)
  RETURNS datemultirange AS
$$
SELECT CASE WHEN state <@ value THEN '{}'::datemultirange ELSE state - value END
$$ LANGUAGE SQL IMMUTABLE STRICT;

CREATE OR REPLACE FUNCTION union_intervals(token UUID, token2value regclass)
  RETURNS datemultirange AS
$$
BEGIN
  RETURN provenance_evaluate(
    token,
    token2value,
    '{(,)}'::datemultirange,
    'union_intervals_plus',
    'union_intervals_times',
    'union_intervals_monus');
END
$$ LANGUAGE plpgsql PARALLEL SAFE;

SELECT add_provenance('personnel_intervals');

SELECT create_provenance_mapping('time_validity','personnel_intervals','valid_time');

CREATE TABLE union_intervals_result AS
SELECT *,union_intervals(provenance(),'time_validity')
FROM (
  SELECT DISTINCT P1.city
  FROM personnel_intervals P1 JOIN personnel_intervals P2
  ON P1.city = P2.city
  WHERE P1.id < P2.id
) t;
SELECT remove_provenance('union_intervals_result');
SELECT * FROM union_intervals_result;
DROP TABLE union_intervals_result;

CREATE TABLE union_intervals_result AS
SELECT *,union_intervals(provenance(),'time_validity')
FROM (
  SELECT DISTINCT city FROM personnel_intervals
  EXCEPT
    SELECT DISTINCT P1.city
    FROM personnel_intervals P1 JOIN personnel_intervals P2
    ON P1.city = P2.city
    WHERE P1.city = P2.city AND P1.id < P2.id
) t;
SELECT remove_provenance('union_intervals_result');
SELECT * FROM union_intervals_result;
DROP TABLE union_intervals_result;
DROP TABLE time_validity;
