--
--  OSSP as -- Accounting System
--  Copyright (c) 2002-2003 Cable & Wireless Deutschland <http://www.cw.com/de/>
--  Copyright (c) 2002-2003 Ralf S. Engelschall <rse@engelschall.com>
--
--  This file is part of OSSP as, an accounting system which can be
--  found at http://www.ossp.org/pkg/tool/as/
--
--  This program is free software; you can redistribute it and/or
--  modify it under the terms of the GNU General Public License
--  as published by the Free Software Foundation; either version
--  2.0 of the License, or (at your option) any later version.
--
--  This program is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
--  General Public License for more details.
--
--  You should have received a copy of the GNU General Public License
--  along with this program; if not, write to the Free Software
--  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
--  USA, or contact The OSSP Project <ossp@ossp.org>.
--
--  bres-history.sql -- History Tracking Basic Research
--

--  TODO:
--  - delete entries from as_hs_ut for expiring history: does it still not correctly
--    and especially should only allow the oldest entry to be deleted or implicitly
--    delete also all older entries.

--  ##  _____________________________________________________________________
--  ##
--  ##  Database Schema
--  ##  _____________________________________________________________________
--  ##

--  cascadingly drop existing schema
--  (just ignore errors on console)
DROP SEQUENCE as_hs_st_seq CASCADE;
DROP TABLE as_hs_st CASCADE;
DROP TABLE as_hs_ut CASCADE;
DROP TABLE as_hs_ut_no CASCADE;
DROP TABLE as_account CASCADE;

--  System Time Sequence
CREATE SEQUENCE as_hs_st_seq
    INCREMENT 1 MINVALUE 1 START 1;

--  System Time Table
CREATE TABLE as_hs_st (
    st_oid         BIGINT
                   NOT NULL,
                   -- REFERENCES as_oid (id_oid) FIXME
                   -- MATCH FULL DEFERRABLE,     FIXME
                   -- object identifier
                   -- [1] (rw)
    st_hid         BIGINT
                   UNIQUE NOT NULL
                   DEFAULT nextval('as_hs_st_seq'),
                   -- history id associated with system time (discrete monoton increasing)
                   -- [742] (rw)
    st_beg         TIMESTAMP (4) WITH TIME ZONE
                   NOT NULL
                   DEFAULT 'now'
                   -- system time of point in history (not monoton increasing, could backstep)
                   -- [2002-01-01 00:00:00.0000] (rw)
);

--  User Time Table
CREATE TABLE as_hs_ut (
    ut_oid         BIGINT
                   NOT NULL,
                   -- REFERENCES as_oid (id_oid) FIXME
                   -- MATCH FULL DEFERRABLE,     FIXME
                   -- object identifier
                   -- [1] (rw)
    ut_beg         TIMESTAMP (4) WITH TIME ZONE
                   NOT NULL
                   DEFAULT 'now',
                   -- user time begin (inclusive)
                   -- [2002-01-01 00:00:00.0000] (rw)
    ut_end         TIMESTAMP (4) WITH TIME ZONE
                   NOT NULL
                   DEFAULT 'infinity',
                   -- user time end (exclusive)
                   -- [2002-01-01 00:00:00.0000] (rw)
    ut_hid         BIGINT
                   NOT NULL
                   REFERENCES as_hs_st (st_hid)
                   MATCH FULL DEFERRABLE
                   -- user time history identifier
                   -- [42] (rw)
);

--  User Time Table (non-overlapping)
CREATE TABLE as_hs_ut_no (
    ut_oid         BIGINT
                   NOT NULL,
                   -- REFERENCES as_oid (id_oid) FIXME
                   -- MATCH FULL DEFERRABLE,     FIXME
                   -- object identifier
                   -- [1] (rw)
    ut_beg         TIMESTAMP (4) WITH TIME ZONE
                   NOT NULL
                   DEFAULT 'now',
                   -- user time begin (inclusive)
                   -- [2002-01-01 00:00:00.0000] (rw)
    ut_end         TIMESTAMP (4) WITH TIME ZONE
                   NOT NULL
                   DEFAULT 'infinity',
                   -- user time end (exclusive)
                   -- [2002-01-01 00:00:00.0000] (rw)
    ut_hid         BIGINT
                   NOT NULL
                   REFERENCES as_hs_st (st_hid)
                   MATCH FULL DEFERRABLE,
                   -- user time history identifier
                   -- [42] (rw)
    ut_hid_max     BIGINT
                   NOT NULL
                   -- REFERENCES as_hs_st (st_hid) FIXME
                   -- MATCH FULL DEFERRABLE        FIXME
                   -- user time history identifier (maximum value until which this entry is valid)
                   -- [42] (rw)
);

--  Account Table
CREATE TABLE as_account(
    ac_oid         BIGINT
                   NOT NULL,
                   -- REFERENCES as_oid (id_oid)
                   -- MATCH FULL DEFERRABLE,
                   -- object identifier
                   -- [1] (rw)
    ac_name        TEXT
                   NOT NULL,
                   -- name of account
                   -- [foo] (rw)
    ac_hid         BIGINT
                   NOT NULL
                   REFERENCES as_hs_st (st_hid)
                   MATCH FULL DEFERRABLE
                   -- account history identifier
                   -- [42] (rw)
);

--  ##  _____________________________________________________________________
--  ##
--  ##  Automated Update of as_hs_ut_no Table
--  ##  _____________________________________________________________________
--  ##

--  as_hs_ut update function
DROP FUNCTION as_hs_ut_update(BIGINT, BIGINT) CASCADE;
CREATE FUNCTION as_hs_ut_update(BIGINT, BIGINT) 
RETURNS VOID LANGUAGE 'plpgsql' STABLE AS '
DECLARE
    oid BIGINT;
    hid BIGINT;
    pos_prev TIMESTAMP (4) WITH TIME ZONE;
    pos_this TIMESTAMP (4) WITH TIME ZONE;
    pos_next TIMESTAMP (4) WITH TIME ZONE;
    alt_this BIGINT;
    alt_prev BIGINT;
BEGIN
    oid := $1;
    hid := $2;

    --  delete all old entries corresponding to OID
    DELETE FROM as_hs_ut_no WHERE ut_oid = oid AND ut_hid_max = hid;

    --  initialize iteration variables
    pos_prev := ''-infinity'';
    pos_this := ''-infinity'';
    pos_next := NULL;
    alt_this := NULL;
    alt_prev := NULL;

    LOOP
        --  determine next position from existing begin/end positions
        pos_next := (
            SELECT min(v) FROM (
                    (SELECT min(ut_beg) FROM as_hs_ut WHERE ut_beg > pos_this AND ut_oid = oid AND ut_hid <= hid)
              UNION (SELECT min(ut_end) FROM as_hs_ut WHERE ut_end > pos_this AND ut_oid = oid AND ut_hid <= hid)
            ) AS result (v)
        );
        IF pos_next IS NULL THEN
            pos_next := ''infinity'';
        END IF;

        --  determine current altidude
        alt_this := (
            SELECT max(ut_hid) FROM as_hs_ut
            WHERE ut_beg <= pos_this AND pos_this < ut_end AND ut_oid = oid AND ut_hid <= hid
        );
        IF alt_this IS NULL THEN
            alt_this := -1;
        END IF;

        -- RAISE NOTICE ''pos_this = %, pos_next = %, alt_this = %'', pos_this, pos_next, alt_this;

        IF alt_this = -1 THEN
            -- RAISE NOTICE ''hit ground - pruning entry'';
        ELSE
            IF alt_this = alt_prev THEN
                -- RAISE NOTICE ''keeping altitude - consolidating entry'';
                UPDATE as_hs_ut_no SET ut_end = pos_next
                    WHERE ut_hid = alt_this AND ut_beg = pos_prev AND ut_end = pos_this AND ut_oid = oid AND ut_hid_max = hid;
            ELSE
                -- RAISE NOTICE ''changing altitude - new entry'';
                INSERT INTO as_hs_ut_no (ut_hid, ut_oid, ut_beg, ut_end, ut_hid_max)
                    VALUES (alt_this, oid, pos_this, pos_next, hid);
                pos_prev := pos_this;
            END IF;
        END IF;

        --  stop if maximum is reached
        IF pos_next >= ''infinity'' THEN
           EXIT;
        END IF;

        --  move one step
        pos_this := pos_next;
        alt_prev := alt_this;
    END LOOP;
    RETURN;
END;
';

--  as_hs_ut trigger stub function
DROP FUNCTION ut_trigger() CASCADE;
CREATE FUNCTION ut_trigger() 
RETURNS TRIGGER LANGUAGE 'plpgsql' STABLE AS '
BEGIN
    IF TG_OP = ''INSERT'' OR TG_OP = ''UPDATE'' THEN
        PERFORM as_hs_ut_update(NEW.ut_oid, NEW.ut_hid);
    ELSIF TG_OP = ''DELETE'' THEN
        PERFORM as_hs_ut_update(OLD.ut_oid, OLD.ut_hid);
    END IF;
    RETURN NULL;
END;
';

--  as_hs_ut trigger
--DROP TRIGGER ut_trigger ON as_hs_ut CASCADE;
CREATE TRIGGER ut_trigger
    AFTER INSERT OR UPDATE OR DELETE
    ON as_hs_ut FOR EACH ROW
    EXECUTE PROCEDURE ut_trigger ();

--  ##  _____________________________________________________________________
--  ##
--  ##  Convinience History Tracking Constructor/Destructor Functions
--  ##  hid := as_hs_make([oid[, ut_beg, ut_end[, st_beg]]]);
--  ##  as_hs_delete(oid, st_hid);
--  ##  _____________________________________________________________________
--  ##

--  hid := as_hs_make(oid, ut_beg, ut_end, st_beg)
CREATE OR REPLACE FUNCTION as_hs_make
(BIGINT, TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE) 
RETURNS BIGINT LANGUAGE 'plpgsql' STABLE AS '
DECLARE
    var_oid    BIGINT;
    var_ut_beg TIMESTAMP (4) WITH TIME ZONE;
    var_ut_end TIMESTAMP (4) WITH TIME ZONE;
    var_st_beg TIMESTAMP (4) WITH TIME ZONE;
    var_hid    BIGINT;
BEGIN
    var_oid    := $1;
    var_ut_beg := $2; IF var_ut_beg IS NULL THEN var_ut_beg := now();        END IF;
    var_ut_end := $3; IF var_ut_end IS NULL THEN var_ut_end := ''infinity''; END IF;
    var_st_beg := $4; IF var_st_beg IS NULL THEN var_st_beg := now();        END IF;
    var_hid    := NULL;
    IF var_oid IS NOT NULL THEN
        --  create new system time entry
        INSERT INTO as_hs_st (st_oid, st_beg)
            VALUES (var_oid, var_st_beg);
        var_hid := currval(''as_hs_st_seq'');
        --  create new user time entry
        INSERT INTO as_hs_ut (ut_oid, ut_beg, ut_end, ut_hid)
            VALUES (var_oid, var_ut_beg, var_ut_end, var_hid);
    ELSE
        --  return just the current/maximum history id
        var_hid := currval(''as_hs_st_seq'');
    END IF;
    RETURN var_hid;
END;
';

--  convenience function overloading
CREATE OR REPLACE FUNCTION as_hs_make
(BIGINT, TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE) 
RETURNS BIGINT LANGUAGE 'plpgsql' STABLE AS '
BEGIN
    RETURN as_hs_make($1, $2, $3, NULL);
END;
';
CREATE OR REPLACE FUNCTION as_hs_make
(BIGINT, TIMESTAMP (4) WITH TIME ZONE)
RETURNS BIGINT LANGUAGE 'plpgsql' STABLE AS '
BEGIN
    RETURN as_hs_make($1, $2, NULL, NULL);
END;
';
CREATE OR REPLACE FUNCTION as_hs_make(BIGINT) 
RETURNS BIGINT LANGUAGE 'plpgsql' STABLE AS '
BEGIN
    RETURN as_hs_make($1, NULL, NULL, NULL);
END;
';
CREATE OR REPLACE FUNCTION as_hs_make() 
RETURNS BIGINT LANGUAGE 'plpgsql' STABLE AS '
BEGIN
    RETURN as_hs_make(NULL, NULL, NULL, NULL);
END;
';

--  as_hs_delete(oid, st_hid);
CREATE OR REPLACE FUNCTION as_hs_delete (BIGINT, BIGINT)
RETURNS VOID LANGUAGE 'plpgsql' STABLE AS '
DECLARE
    var_oid BIGINT;
    var_hid BIGINT;
BEGIN
    var_oid := $1;
    var_hid := $2;
    DELETE FROM as_hs_ut WHERE ut_oid = var_oid AND ut_hid = var_hid;
    DELETE FROM as_hs_st WHERE st_oid = var_oid AND st_hid = var_hid;
    RETURN;
END;
';

--  ##  _____________________________________________________________________
--  ##
--  ##  Parametrized History Table View
--  ##  <oid, hid-max>* := as_hs(ut_beg[, ut_end[, st_beg[, st_end[, st_max]]]])
--  ##  _____________________________________________________________________
--  ##

--  This finds (optionally only maximum) oid/hid combinations between
--  st_beg and st_end (both inclusive) system time, for which the
--  user time touches the time range between ut_beg and ut_end (both
--  inclusive).

--  parametrized view return type
DROP TYPE as_hs_t CASCADE;
CREATE TYPE as_hs_t AS (
    hs_oid BIGINT,
    hs_hid BIGINT
);
CREATE OR REPLACE FUNCTION as_hs
(TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE,
 TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE, INTEGER)
RETURNS SETOF as_hs_t LANGUAGE 'plpgsql' STABLE AS '
DECLARE
    arg_ut_beg TIMESTAMP (4) WITH TIME ZONE; -- user   time begin (including)
    arg_ut_end TIMESTAMP (4) WITH TIME ZONE; -- user   time end   (including)
    arg_st_beg TIMESTAMP (4) WITH TIME ZONE; -- system time begin (including)
    arg_st_end TIMESTAMP (4) WITH TIME ZONE; -- system time end   (including)
    arg_st_max INTEGER;   -- whether to take only maximum system time into account
    rec RECORD;
BEGIN
    --  pre-process input arguments by providing reasonable defaults
    arg_ut_beg := $1; IF arg_ut_beg IS NULL THEN arg_ut_beg := ''-infinity''; END IF;
    arg_ut_end := $2; IF arg_ut_end IS NULL THEN arg_ut_end :=  ''infinity''; END IF;
    arg_st_beg := $3; IF arg_st_beg IS NULL THEN arg_st_beg := ''-infinity''; END IF;
    arg_st_end := $4; IF arg_st_end IS NULL THEN arg_st_end :=  ''infinity''; END IF;
    arg_st_max := $5; IF arg_st_max IS NULL THEN arg_st_max := 1;             END IF;
    
    --  perform the query operation
    IF arg_st_max = 1 THEN
        --  variant 1: take only maximum system time for each user time and oid
        FOR rec IN
           SELECT ut_oid, ut_hid
           FROM as_hs_ut_no, (
               SELECT   st_oid, max(st_hid) AS st_hid
               FROM     as_hs_st 
               WHERE    (arg_st_beg <= st_beg AND st_beg <= arg_st_end)
               GROUP BY st_oid
           ) AS as_hs_st
           WHERE
               ut_oid = st_oid AND ut_hid_max = st_hid
               AND (ut_beg <= arg_ut_end AND arg_ut_beg < ut_end)
        LOOP
           RETURN NEXT rec;
        END LOOP;
    ELSE
        --  variant 2: take all system times for each user time and oid
        FOR rec IN
           SELECT DISTINCT ut_oid, ut_hid
           FROM as_hs_ut_no, (
               SELECT   st_oid, st_hid
               FROM     as_hs_st 
               WHERE    (arg_st_beg <= st_beg AND st_beg <= arg_st_end)
           ) AS as_hs_st
           WHERE
               ut_oid = st_oid AND ut_hid_max = st_hid
               AND (ut_beg <= arg_ut_end AND arg_ut_beg < ut_end)
        LOOP
           RETURN NEXT rec;
        END LOOP;
    END IF;
    RETURN;
END;
';

--  convenience overloading functions
CREATE OR REPLACE FUNCTION as_hs
(TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE,
 TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE)
RETURNS SETOF as_hs_t LANGUAGE 'sql' STABLE AS '
    SELECT * FROM as_hs($1, $2, $3, $4, NULL);
';
CREATE OR REPLACE FUNCTION as_hs
(TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE,
 TIMESTAMP (4) WITH TIME ZONE)
RETURNS SETOF as_hs_t LANGUAGE 'sql' STABLE AS '
    SELECT * FROM as_hs($1, $2, $3, NULL, NULL);
';
CREATE OR REPLACE FUNCTION as_hs
(TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE)
RETURNS SETOF as_hs_t LANGUAGE 'sql' STABLE AS '
    SELECT * FROM as_hs($1, $2, NULL, NULL, NULL);
';
CREATE OR REPLACE FUNCTION as_hs
(TIMESTAMP (4) WITH TIME ZONE)
RETURNS SETOF as_hs_t LANGUAGE 'sql' STABLE AS '
    SELECT * FROM as_hs($1, NULL, NULL, NULL, NULL);
';
CREATE OR REPLACE FUNCTION as_hs ()
RETURNS SETOF as_hs_t LANGUAGE 'sql' STABLE AS '
    SELECT * FROM as_hs(NULL, NULL, NULL, NULL, NULL);
';

--  ##  _____________________________________________________________________
--  ##
--  ##  Account Table Convinience Manipulation Triggers
--  ##  _____________________________________________________________________
--  ##

--  as_account trigger function
CREATE OR REPLACE FUNCTION as_account_trigger() 
RETURNS TRIGGER LANGUAGE 'plpgsql' STABLE AS '
DECLARE
    hid INTEGER;
BEGIN
    IF TG_WHEN = ''BEFORE'' AND TG_OP = ''INSERT'' THEN
        IF new.ac_hid IS NULL THEN
            --  auto-generate history id if not explicitly set by user
            new.ac_hid := as_hs_make(new.ac_oid);
        END IF;
        RETURN new;
    ELSIF TG_WHEN = ''BEFORE'' AND TG_OP = ''UPDATE'' THEN
        --  clone old entry to allow old entry to be overwritten as it would be new entry
        INSERT INTO as_account VALUES (old.ac_oid, old.ac_name, old.ac_hid);
        --  give new entry a new history id if not explicity set by user
        IF (new.ac_hid = old.ac_hid) THEN
            new.ac_hid := as_hs_make(new.ac_oid);
        END IF;
        RETURN new;
    ELSIF TG_WHEN = ''AFTER'' AND TG_OP = ''DELETE'' THEN
        --  delete corresponding history entries
        PERFORM as_hs_delete(old.ac_oid, old.ac_hid); 
        RETURN old;
    END IF;
END;
';

--  as_account triggers
CREATE TRIGGER as_account_trigger_before
    BEFORE INSERT OR UPDATE ON as_account FOR EACH ROW
    EXECUTE PROCEDURE as_account_trigger();
CREATE TRIGGER as_account_trigger_after
    AFTER DELETE ON as_account FOR EACH ROW
    EXECUTE PROCEDURE as_account_trigger();

--  ##  _____________________________________________________________________
--  ##
--  ##  Parametrized Account Table View
--  ##  <as_account>* := as_account([ut_beg[, ut_end[, st_beg[, st_end[, st_max]]]]])
--  ##  _____________________________________________________________________
--  ##

CREATE OR REPLACE FUNCTION as_account () 
RETURNS SETOF as_account LANGUAGE 'sql' STABLE AS '
    SELECT ac.* FROM as_account ac, as_hs() WHERE ac_hid = hs_hid;
';
CREATE OR REPLACE FUNCTION as_account
(TIMESTAMP (4) WITH TIME ZONE) 
RETURNS SETOF as_account LANGUAGE 'sql' STABLE AS '
    SELECT ac.* FROM as_account ac, as_hs($1) WHERE ac_hid = hs_hid;
';
CREATE OR REPLACE FUNCTION as_account
(TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE)
RETURNS SETOF as_account LANGUAGE 'sql' STABLE AS '
    SELECT ac.* FROM as_account ac, as_hs($1, $2, $3) WHERE ac_hid = hs_hid;
';
CREATE OR REPLACE FUNCTION as_account
(TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE,
 TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE)
RETURNS SETOF as_account LANGUAGE 'sql' STABLE AS '
    SELECT ac.* FROM as_account ac, as_hs($1, $2, $3, $4) WHERE ac_hid = hs_hid;
';
CREATE OR REPLACE FUNCTION as_account
(TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE,
 TIMESTAMP (4) WITH TIME ZONE, TIMESTAMP (4) WITH TIME ZONE, INTEGER)
RETURNS SETOF as_account LANGUAGE 'sql' STABLE AS '
    SELECT ac.* FROM as_account ac, as_hs($1, $2, $3, $4, $5) WHERE ac_hid = hs_hid;
';

--  ##  _____________________________________________________________________
--  ##
--  ##  Test Data
--  ##  _____________________________________________________________________
--  ##

-- whole test data as view
-- #  ^          ^
-- #  |          |
-- #  as_hs_st         hid      |          |          |         |
-- #  2007-01-01 #6       |          [==20:bar3=[         |
-- #  2006-01-01 #5       |          [==20:bar2===========[
-- #  2005-01-01 #4       [=============20:bar1===========[
-- #  2004-01-01 #3       [=============10:foo============[
-- #  2002-01-01 #2       |          [==10:bar==[         |
-- #  2001-01-01 #1       [==10:foo==[          |         |
-- #           as_hs_ut-->  0100-01-01 0200-01-01 0300-01-01 infinity

-- whole test data as raw data
-- #  1st (systime hid=1) create record for oid=10, name="foo",  100 <= usertime < 200
-- #  2nd (systime hid=2) create record for oid=10, name="bar",  200 <= usertime < 300
-- #  3rd (systime hid=3) create record for oid=10, name="foo",  100 <= usertime < oo
-- #  4th (systime hid=4) create record for oid=20, name="bar1", 100 <= usertime < oo
-- #  5th (systime hid=5) create record for oid=20, name="bar2", 200 <= usertime < oo
-- #  6th (systime hid=6) create record for oid=20, name="bar3", 200 <= usertime < 300

-- whole test data in prosa
-- #  1st create a new record "foo" containing payload valid between usertime 100 upto not including 200
-- #  2nd rename "foo" to "bar" and move usertime frame to 200 upto n. i. 300
-- #  3rd rename "bar" back to "foo", extend usertime begin back to original 100 and extend usertime end to infinity
-- #  4th create a new record "bar1" containing payload valid between usertime 100 and infinity
-- #  5th give "bar1" a new name "bar2" beginning with usertime 200
-- #  6th another new name "bar3" valid for usertime frame 200 upto n. i. 300

-- whole test data injected
INSERT INTO as_account VALUES (10, 'foo',  as_hs_make(10, '0100-01-01', '0200-01-01', '2001-01-01'));
INSERT INTO as_account VALUES (10, 'bar',  as_hs_make(10, '0200-01-01', '0300-01-01', '2002-01-01'));
INSERT INTO as_account VALUES (10, 'foo',  as_hs_make(10, '0100-01-01', 'infinity',   '2004-01-01'));
INSERT INTO as_account VALUES (20, 'bar1', as_hs_make(20, '0100-01-01', 'infinity',   '2005-01-01'));
INSERT INTO as_account VALUES (20, 'bar2', as_hs_make(20, '0200-01-01', 'infinity',   '2006-01-01'));
INSERT INTO as_account VALUES (20, 'bar3', as_hs_make(20, '0200-01-01', '0300-01-01', '2007-01-01'));

--  ##  _____________________________________________________________________
--  ##
--  ##  Test Data Dumping
--  ##  _____________________________________________________________________
--  ##

select * from as_account;
-- #  ac_oid | ac_name | ac_hid 
-- # --------+---------+--------
-- #      10 | foo     |      1
-- #      10 | bar     |      2
-- #      10 | foo     |      3
-- #      20 | bar1    |      4
-- #      20 | bar2    |      5
-- #      20 | bar3    |      6
-- # (6 rows)

select * from as_hs_ut;
-- #  ut_oid |       ut_beg        |       ut_end        | ut_hid 
-- # --------+---------------------+---------------------+--------
-- #      10 | 0100-01-01 00:00:00 | 0200-01-01 00:00:00 |      1
-- #      10 | 0200-01-01 00:00:00 | 0300-01-01 00:00:00 |      2
-- #      10 | 0100-01-01 00:00:00 | infinity            |      3
-- #      20 | 0100-01-01 00:00:00 | infinity            |      4
-- #      20 | 0200-01-01 00:00:00 | infinity            |      5
-- #      20 | 0200-01-01 00:00:00 | 0300-01-01 00:00:00 |      6
-- # (6 rows)

select * from as_hs_ut_no;
-- #  ut_oid |       ut_beg        |       ut_end        | ut_hid | ut_hid_max 
-- # --------+---------------------+---------------------+--------+------------
-- #      10 | 0100-01-01 00:00:00 | 0200-01-01 00:00:00 |      1 |          1
-- #      10 | 0100-01-01 00:00:00 | 0200-01-01 00:00:00 |      1 |          2
-- #      10 | 0200-01-01 00:00:00 | 0300-01-01 00:00:00 |      2 |          2
-- #      10 | 0100-01-01 00:00:00 | infinity            |      3 |          3
-- #      20 | 0100-01-01 00:00:00 | infinity            |      4 |          4
-- #      20 | 0100-01-01 00:00:00 | 0200-01-01 00:00:00 |      4 |          5
-- #      20 | 0200-01-01 00:00:00 | infinity            |      5 |          5
-- #      20 | 0100-01-01 00:00:00 | 0200-01-01 00:00:00 |      4 |          6
-- #      20 | 0200-01-01 00:00:00 | 0300-01-01 00:00:00 |      6 |          6
-- #      20 | 0300-01-01 00:00:00 | infinity            |      5 |          6
-- # (10 rows)

select * from as_hs_st;
-- #  st_oid | st_hid |         st_beg         
-- # --------+--------+------------------------
-- #      10 |      1 | 2001-01-01 00:00:00+01
-- #      10 |      2 | 2002-01-01 00:00:00+01
-- #      10 |      3 | 2004-01-01 00:00:00+01
-- #      20 |      4 | 2005-01-01 00:00:00+01
-- #      20 |      5 | 2006-01-01 00:00:00+01
-- #      20 |      6 | 2007-01-01 00:00:00+01
-- # (6 rows)

-- ##
-- ##  Test Usage
-- ##

-- whole test data retrieved
SELECT * FROM as_account;
-- #   ac_oid | ac_name | ac_hid 
-- #  --------+---------+--------
-- #       10 | foo     |      1
-- #       10 | bar     |      2
-- #       10 | foo     |      3
-- #       20 | bar1    |      4
-- #       20 | bar2    |      5
-- #       20 | bar3    |      6
-- #  (6 rows)

-- for any usertime and any systime, list any valid oid for every point in systime (all oids with full history)
SELECT * FROM as_hs('-infinity','infinity','-infinity','infinity',0);
-- #   hs_oid | hs_hid 
-- #  --------+--------
-- #       10 |      1
-- #       10 |      2
-- #       10 |      3
-- #       20 |      4
-- #       20 |      5
-- #       20 |      6
-- #  (6 rows)

-- for any usertime and any systime, list max valid oid for every point in systime (oids with highest altitude)
SELECT * FROM as_hs('-infinity','infinity','-infinity','infinity',1); -- same as SELECT FROM as_hs();
-- #   hs_oid | hs_hid 
-- #  --------+--------
-- #       10 |      3
-- #       20 |      4
-- #       20 |      6
-- #       20 |      5
-- #  (4 rows)

-- same as previous but join with payload to retrieve more details (details from oids with hightes altitude)
SELECT * FROM as_account, as_hs() WHERE ac_hid = hs_hid;
-- #   ac_oid | ac_name | ac_hid | hs_oid | hs_hid 
-- #  --------+---------+--------+--------+--------
-- #       10 | foo     |      3 |     10 |      3
-- #       20 | bar1    |      4 |     20 |      4
-- #       20 | bar2    |      5 |     20 |      5
-- #       20 | bar3    |      6 |     20 |      6
-- #  (4 rows)

-- same as previous but we are only interested in oids valid for usertime point 222
SELECT * FROM as_account, as_hs('0222-01-01','0222-01-01','-infinity','infinity',1) WHERE ac_hid = hs_hid;
-- #   ac_oid | ac_name | ac_hid | hs_oid | hs_hid 
-- #  --------+---------+--------+--------+--------
-- #       10 | foo     |      3 |     10 |      3
-- #       20 | bar3    |      6 |     20 |      6
-- #  (2 rows)

-- same as previous but we look back in systime 2006
SELECT * FROM as_account, as_hs('0222-01-01','0222-01-01','-infinity','2006-01-01',1) WHERE ac_hid = hs_hid;
-- #   ac_oid | ac_name | ac_hid | hs_oid | hs_hid 
-- #  --------+---------+--------+--------+--------
-- #       10 | foo     |      3 |     10 |      3
-- #       20 | bar2    |      5 |     20 |      5
-- #  (2 rows)

