\set ECHO none
\pset format unaligned

/* Symbolic representation of provenance (as a formula) */
CREATE TYPE formula_state AS (
  formula text,
  nbargs int
);

CREATE FUNCTION formula_plus_state(state text[], value text)
  RETURNS text[] AS
$$
BEGIN
  IF state IS NULL OR array_length(state, 1)=0 THEN
    RETURN ARRAY[value];
  ELSE
    RETURN array_append(state, value);
  END IF;
END
$$ LANGUAGE plpgsql IMMUTABLE;

CREATE FUNCTION formula_times_state(state formula_state, value text)
  RETURNS formula_state AS
$$
BEGIN
  IF state IS NULL OR state.nbargs=0 THEN
    RETURN (value,1);
  ELSE
    RETURN (concat(state.formula,' ⊗ ',value),state.nbargs+1);
  END IF;
END
$$ LANGUAGE plpgsql IMMUTABLE;

CREATE FUNCTION formula_array2formula(state text[])
  RETURNS text AS
$$
  BEGIN
    IF array_length(state,1) < 2 THEN
      RETURN state;
    ELSE
      RETURN concat(
        '(',
        array_to_string(ARRAY(SELECT unnest(state) t ORDER BY t), ' ⊕ '),
        ')'
      );
    END IF;
  END
$$ LANGUAGE plpgsql IMMUTABLE STRICT;

CREATE FUNCTION formula_state2formula(state formula_state)
  RETURNS text AS
$$
  BEGIN
    IF state.nbargs<2 THEN
      RETURN state.formula;
    ELSE
      RETURN concat('(',state.formula,')');
    END IF;
  END
$$ LANGUAGE plpgsql IMMUTABLE STRICT;

CREATE AGGREGATE formula_plus(text)
(
  sfunc = formula_plus_state,
  stype = text[],
  initcond = '{}',
  finalfunc = formula_array2formula
);

CREATE AGGREGATE formula_times(text)
(
  sfunc = formula_times_state,
  stype = formula_state,
  initcond = '(𝟙,0)',
  finalfunc = formula_state2formula
);

CREATE FUNCTION formula_delta(formula text) RETURNS text
    LANGUAGE sql IMMUTABLE STRICT
    AS $$
  SELECT concat('δ(',formula,')')
$$;

CREATE FUNCTION formula_monus(formula1 text, formula2 text) RETURNS text AS
$$
  SELECT concat('(',formula1,' ⊖ ',formula2,')')
$$ LANGUAGE SQL IMMUTABLE STRICT;

CREATE FUNCTION formula(token UUID, token2value regclass)
  RETURNS text AS
$$
BEGIN
  RETURN provenance_evaluate(
    token,
    token2value,
    '𝟙'::text,
    'formula_plus',
    'formula_times',
    'formula_monus',
    'formula_delta');
END
$$ LANGUAGE plpgsql PARALLEL SAFE;

/* Example of provenance evaluation */
CREATE TABLE result_formula AS SELECT
  p1.city,
  formula(provenance(), 'personnel_name')
FROM personnel p1, personnel p2
WHERE p1.city = p2.city AND p1.id < p2.id
GROUP BY p1.city
ORDER BY p1.city;

SELECT remove_provenance('result_formula');
SELECT * FROM result_formula;

DROP TABLE result_formula;
