
-- remove the whole Railroad schema and recreate it

DROP SCHEMA rr CASCADE ;
DROP SCHEMA log CASCADE ;

CREATE SCHEMA rr ;
REVOKE ALL ON SCHEMA rr FROM PUBLIC ;
GRANT USAGE ON SCHEMA rr TO PUBLIC ;

CREATE SCHEMA log ;
REVOKE ALL ON SCHEMA log FROM PUBLIC ;
GRANT USAGE ON SCHEMA log TO PUBLIC ;

--
--

-- The logging schema
-- All things related to logging are placed here

CREATE OR REPLACE FUNCTION log.auto_log_id () RETURNS TRIGGER
AS '
DECLARE
        tns     NAME;
        ftn     TEXT ;
BEGIN
        SELECT INTO tns n.nspname
          FROM pg_catalog.pg_class c
               LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
          WHERE c.oid = TG_RELID ;
        ftn := tns || ''.'' || TG_RELNAME ;
        IF TG_OP = ''DELETE'' THEN
             INSERT INTO log.changes_auto_id(tabname,taboid,action,old_id)
               VALUES (ftn,TG_RELID,TG_OP,OLD.id);
             RETURN OLD;
        END IF;
        IF TG_OP = ''UPDATE'' THEN
             INSERT INTO log.changes_auto_id(tabname,taboid,action,old_id,new_id)
               VALUES (ftn,TG_RELID,TG_OP,OLD.id,NEW.id);
             RETURN NEW ;
        END IF;
        IF TG_OP = ''INSERT'' THEN
             INSERT INTO log.changes_auto_id(tabname,taboid,action,new_id)
               VALUES (ftn,TG_RELID,TG_OP,NEW.id);
             RETURN NEW ;
        END IF;
END;
' LANGUAGE 'plpgsql' ;

CREATE TABLE log.changes_auto_id (
        uname   NAME NOT NULL DEFAULT CURRENT_USER,
        ctime   TIMESTAMP(3) WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
        tabname TEXT,
        taboid  OID NOT NULL,
        old_id OID,
        new_id OID,
        action  NAME NOT NULL
) WITHOUT OIDS ;

REVOKE ALL ON log.changes_auto_id FROM PUBLIC ;
GRANT SELECT, INSERT ON log.changes_auto_id TO PUBLIC ;

--
--

-- The rr schema
-- where the real data goes...

CREATE TABLE rr.repositories
-- Describes a repository
(
        id              SERIAL PRIMARY KEY,
        name            VARCHAR(25) NOT NULL, -- name of the repository
        master_user     VARCHAR(25) NOT NULL, -- name of master user
        master_pw       VARCHAR(25) NOT NULL, -- master password
        upload_user     VARCHAR(25), -- name of upload user
        upload_pw       VARCHAR(25), -- upload password
        fs_top_dir      TEXT NOT NULL, -- absolute path to the top directory on the filesystem
        host            TEXT NOT NULL, -- canonical name for the host incl. port
        path            TEXT NOT NULL, -- path to repository top dir
        upload_path     TEXT NOT NULL, -- path for uploads via POST
        incomming_path  TEXT, -- path to the bulk upload area
        admin_mail      TEXT  -- email of the person responsible for this repo
);

CREATE TRIGGER repositories_log
  AFTER INSERT OR UPDATE OR DELETE
  ON rr.repositories FOR EACH ROW EXECUTE PROCEDURE log.auto_log_id() ;

CREATE UNIQUE INDEX repositories_name_idx on rr.repositories(name);

REVOKE ALL ON rr.repositories FROM PUBLIC ;
GRANT SELECT, UPDATE, INSERT, DELETE ON rr.repositories TO PUBLIC ;
REVOKE ALL ON rr.repositories_id_seq FROM PUBLIC ;
GRANT SELECT, UPDATE, INSERT, DELETE ON rr.repositories_id_seq TO PUBLIC ;

---
---


CREATE TABLE rr.clients
-- Describes the client CMS registered with the RR server
(
        id              SERIAL PRIMARY KEY,
        name            TEXT NOT NULL, -- name of client system
        admin_mail      TEXT, -- email of cms admin
        auth_url        TEXT NOT NULL, -- URL to call for authorization
        auth_domain     TEXT NOT NULL  -- authorization domain to use for Basic Auth
);

CREATE TRIGGER clients_log
  AFTER INSERT OR UPDATE OR DELETE
  ON rr.clients FOR EACH ROW EXECUTE PROCEDURE log.auto_log_id() ;


CREATE UNIQUE INDEX clients_name_idx on rr.clients(name);

REVOKE ALL ON rr.clients FROM PUBLIC ;
GRANT SELECT, UPDATE, INSERT, DELETE ON rr.clients TO PUBLIC ;
REVOKE ALL ON rr.clients_id_seq FROM PUBLIC ;
GRANT SELECT, UPDATE, INSERT, DELETE ON rr.clients_id_seq TO PUBLIC ;

---
---

CREATE TABLE rr.repoclient
-- Mapping between client systems and repositories
(
        id              SERIAL NOT NULL,
        client_id       INTEGER REFERENCES rr.clients(id),
        repo_id         INTEGER REFERENCES rr.repositories(id),
        -- state describes if the client system is currently available, blocked
        -- by the repository admin or down.
        -- 'A' is active/available
        -- 'D' is down, not reachable
        --     will deny requests to all resources owned by this client!
        -- 'B' blocked, do not allow connections from/to the client
        --     will deny requests to all resources owned by this client!
        state           CHAR(1) DEFAULT 'A' CHECK (upper(state) IN ('D','B','A')),

        PRIMARY KEY (client_id, repo_id)
);

CREATE TRIGGER repoclient_log
  AFTER INSERT OR UPDATE OR DELETE
  ON rr.repoclient FOR EACH ROW EXECUTE PROCEDURE log.auto_log_id() ;

REVOKE ALL ON rr.repoclient FROM PUBLIC ;
GRANT SELECT, UPDATE, INSERT, DELETE ON rr.repoclient TO PUBLIC ;

REVOKE ALL ON rr.repoclient_id_seq FROM PUBLIC ;
GRANT SELECT, UPDATE, INSERT, DELETE ON rr.repoclient_id_seq TO PUBLIC ;

---
---


CREATE TABLE rr.resources
-- Description of a resource in a repository
(
        id              SERIAL NOT NULL PRIMARY KEY,
        repo_id         INTEGER NOT NULL REFERENCES rr.repositories(id),
        owner_client_id INTEGER REFERENCES rr.clients(id),
        path            TEXT NOT NULL, -- full absolute path to resource in repository
        owner           VARCHAR(25) DEFAULT NULL, -- user name of owner
        object_id       TEXT DEFAULT NULL, -- id of the object in the client cms
        related_res     INTEGER DEFAULT NULL -- id of the resource this res. is related to
) WITHOUT OIDS;

CREATE TRIGGER resources_log
  AFTER INSERT OR UPDATE OR DELETE
  ON rr.resources FOR EACH ROW EXECUTE PROCEDURE log.auto_log_id() ;

CREATE UNIQUE INDEX resource_repopath_idx on rr.resources(repo_id, path);
CREATE INDEX resource_objectid_idx on rr.resources(object_id);

REVOKE ALL ON rr.resources FROM PUBLIC ;
GRANT SELECT, UPDATE, INSERT, DELETE ON rr.resources TO PUBLIC ;
REVOKE ALL ON rr.resources_id_seq FROM PUBLIC ;
GRANT SELECT, UPDATE, INSERT, DELETE ON rr.resources_id_seq TO PUBLIC ;

---
---

-- Function to convert text value to TIMESTAMP (w/o timezone)
CREATE OR REPLACE FUNCTION rr.to_tsz ( text ) RETURNS timestamp IMMUTABLE
AS '
BEGIN
  RETURN $1::timestamp ;
END ;'
LANGUAGE 'plpgsql' ;

REVOKE ALL ON FUNCTION rr.to_tsz(text) FROM PUBLIC ;
GRANT EXECUTE ON FUNCTION rr.to_tsz(text) TO PUBLIC ;

-- Function to convert text value to TIMESTAMPTZ (w/ timezone)
CREATE OR REPLACE FUNCTION rr.to_ts ( text ) RETURNS timestamptz IMMUTABLE
AS '
BEGIN
  RETURN $1::timestamptz ;
END ;'
LANGUAGE 'plpgsql' ;

REVOKE ALL ON FUNCTION rr.to_ts(text) FROM PUBLIC ;
GRANT EXECUTE ON FUNCTION rr.to_ts(text) TO PUBLIC ;

-- Function to convert text value to INTEGER
CREATE OR REPLACE FUNCTION rr.to_int ( text ) RETURNS INTEGER IMMUTABLE
AS '
DECLARE
  t ALIAS FOR $1 ;
BEGIN
   RETURN $1::integer;
END;'
LANGUAGE 'plpgsql' ;

REVOKE ALL ON FUNCTION rr.to_int(text) FROM PUBLIC ;
GRANT EXECUTE ON FUNCTION rr.to_int(text) TO PUBLIC ;

---
---

CREATE TABLE rr.properties
-- Description of a property attached to a specific resource
(
        resource_id     INTEGER REFERENCES rr.resources(id) ON DELETE CASCADE,
        nsuri           TEXT NOT NULL, -- uri of XML namespace
        name            TEXT NOT NULL, -- name of the property w/o namespace
        qname           TEXT NOT NULL, -- fully qualified name of property
        value           TEXT DEFAULT NULL, -- the property value

        PRIMARY KEY (resource_id, qname)
) WITHOUT OIDS;

CREATE INDEX properties_qname_idx ON rr.properties(qname);
CREATE INDEX properties_value_idx ON rr.properties(value text_ops);

-- special partial indices for timestamp values using UTC
CREATE INDEX properties_ts1_idx ON rr.properties(rr.to_tsz(value))
    WHERE qname = 'DAV: creationdate' ;
CREATE INDEX properties_ts2_idx ON rr.properties(rr.to_tsz(value))
    WHERE qname = 'DAV: getlastmodified' ;
CREATE INDEX properties_ts3_idx ON rr.properties(rr.to_tsz(value))
    WHERE qname = 'RR: lastmodified' ;

-- special partial indices for integer values
CREATE INDEX properties_int1_idx ON rr.properties(rr.to_int(value))
    WHERE qname = 'DAV: getcontentlength' ; 

REVOKE ALL ON rr.properties FROM PUBLIC ;
GRANT SELECT, UPDATE, INSERT, DELETE ON rr.properties TO PUBLIC ;

--
--

-- insert fixed data

BEGIN;

-- dummy entries for unowned resources

INSERT INTO rr.repositories (id, name, master_user, master_pw, fs_top_dir, host, path, upload_path)
   VALUES (0, 'unowned', '', '', '', '', '', '');

INSERT INTO rr.clients(id, name, auth_url, auth_domain)
    VALUES (0, 'unowned', '', '');

INSERT INTO rr.repoclient(id, repo_id, client_id, state) VALUES (0,0,0,'A');

COMMIT ;

VACUUM FULL ANALYZE ;
