CREATE SCHEMA revision;

SET search_path = revision, public;

CREATE SEQUENCE object_id_seq;

CREATE SEQUENCE approve_order_seq;

CREATE TYPE commit_state_type AS ENUM (
    'draft',
    'approved'
);


CREATE TABLE attributes (
    id bigserial NOT NULL,
    contents public.hstore NOT NULL,

    CONSTRAINT attributes_pkey PRIMARY KEY(id)
);

CREATE INDEX attributes_contents_idx ON attributes USING gist (contents);
CREATE INDEX attributes_contents_btree_idx ON attributes USING btree (contents);

CREATE TABLE attributes_relations
(
    id bigint PRIMARY KEY REFERENCES revision.attributes (id),
    contents public.hstore NOT NULL
);

CREATE INDEX attributes_relations_gist_contents_idx ON attributes_relations USING gist (contents);
CREATE INDEX attributes_relations_btree_contents_idx ON attributes_relations USING btree (contents);


CREATE TABLE description (
    id bigserial NOT NULL,
    contents text NOT NULL,

    CONSTRAINT description_pkey PRIMARY KEY(id)
);


CREATE TABLE geometry (
    id bigserial NOT NULL,
    contents public.geometry NOT NULL,

    CONSTRAINT geometry_pkey PRIMARY KEY(id),

    CONSTRAINT enforce_dims_value CHECK ((st_coorddim(contents) = 2)),
    CONSTRAINT enforce_srid_value CHECK ((st_srid(contents) = 3395))
);

CREATE INDEX geometry_contents_idx ON geometry USING gist (contents);


CREATE TYPE branch_type_type AS ENUM (
    'trunk',
    'approved',
    'stable',
    'archive',
    'deleted'
);

CREATE TYPE branch_state_type AS ENUM (
    'unavailable',
    'normal',
    'progress'
);

CREATE SEQUENCE revision.branch_id_seq
    MINVALUE 0;
CREATE TABLE branch (
    id bigint NOT NULL DEFAULT(nextval('revision.branch_id_seq'::regclass)),
    type branch_type_type NOT NULL DEFAULT 'stable',
    state branch_state_type NOT NULL DEFAULT 'unavailable',
    created timestamp with time zone NOT NULL DEFAULT now(),
    created_by bigint NOT NULL,
    finished timestamp with time zone,
    finished_by bigint NOT NULL DEFAULT 0,
    attributes public.hstore,

    CONSTRAINT branch_pkey PRIMARY KEY(id),
    CONSTRAINT valid_trunk CHECK ((type='trunk' AND id=0 AND created_by=0) OR (type<>'trunk' AND id>0 AND created_by>0)),
    CONSTRAINT valid_finished CHECK ((type IN ('trunk','stable','approved') AND finished_by=0) OR (type IN ('archive','deleted') AND finished_by>0)),
    CONSTRAINT valid_deleted CHECK (type<>'deleted' OR state='unavailable')
);

CREATE UNIQUE INDEX one_open_branch_idx ON branch USING btree (type) WHERE finished_by=0;
CREATE INDEX branch_type_idx ON branch USING btree (type);

INSERT INTO branch (type, state, created_by) VALUES ('trunk', 'normal', 0);


CREATE TABLE commit (
    id bigserial NOT NULL,
    state commit_state_type NOT NULL DEFAULT 'draft',
    trunk boolean NOT NULL,
    created timestamp with time zone NOT NULL DEFAULT now(),
    created_by bigint NOT NULL,
    attributes public.hstore,
    stable_branch_id bigint DEFAULT NULL REFERENCES branch (id),
    approve_order bigint NOT NULL DEFAULT(0),

    CONSTRAINT commit_pkey PRIMARY KEY(id),
    CONSTRAINT valid_stable_branch_id CHECK (stable_branch_id > 0),
    CONSTRAINT stable_without_draft CHECK (stable_branch_id IS NULL OR state <> 'draft'),
    CONSTRAINT trunk_or_stable_branch_id CHECK (trunk OR (stable_branch_id IS NOT NULL AND stable_branch_id > 0))
);

CREATE INDEX commit_state_idx ON commit USING btree (state);
CREATE INDEX commit_stable_branch_id_idx ON commit USING btree (stable_branch_id);
CREATE INDEX commit_attributes_idx ON commit USING gin (attributes) WITH (fastupdate=off);
CREATE INDEX ON commit USING btree(approve_order);

CREATE TABLE object_revision (
    object_id bigint NOT NULL,
    commit_id bigint NOT NULL,
    prev_commit_id bigint NOT NULL,
    next_commit_id bigint NOT NULL DEFAULT 0,
    deleted boolean NOT NULL,
    geometry_id bigint NOT NULL,
    attributes_id bigint NOT NULL,
    description_id bigint NOT NULL,
    master_object_id bigint NOT NULL,
    slave_object_id bigint NOT NULL,

    CONSTRAINT object_revision_pkey PRIMARY KEY(object_id, commit_id)
);


CREATE TABLE object_revision_relation
(
    CONSTRAINT object_revision_relation_pkey PRIMARY KEY (object_id, commit_id),
    CONSTRAINT relation_no_description_id CHECK (description_id = 0),
    CONSTRAINT relation_no_geometry_id CHECK (geometry_id = 0),
    CONSTRAINT relation_valid_master_object_id CHECK (master_object_id <> 0),
    CONSTRAINT relation_valid_slave_object_id CHECK (slave_object_id <> 0)
)
INHERITS (object_revision);


CREATE INDEX object_revision_relation_attributes_id_idx
    ON object_revision_relation USING btree (attributes_id);

CREATE INDEX object_revision_relation_commit_id_idx
    ON object_revision_relation USING btree (commit_id);

CREATE INDEX object_revision_relation_master_object_id_idx
    ON object_revision_relation USING btree (master_object_id);

CREATE INDEX object_revision_relation_slave_object_id_idx
    ON object_revision_relation USING btree (slave_object_id);


CREATE TABLE object_revision_with_geometry
(
    CONSTRAINT object_revision_with_geometry_pkey PRIMARY KEY (object_id, commit_id),
    CONSTRAINT geom_valid_geometry_id CHECK (geometry_id > 0),
    CONSTRAINT geom_no_master_object_id CHECK (master_object_id = 0),
    CONSTRAINT geom_no_slave_object_id CHECK (slave_object_id = 0)
)
INHERITS (object_revision);


CREATE INDEX object_revision_with_geometry_attributes_id_idx
    ON object_revision_with_geometry USING btree (attributes_id);

CREATE INDEX object_revision_with_geometry_commit_id_idx
    ON object_revision_with_geometry USING btree (commit_id);

CREATE INDEX object_revision_with_geometry_geometry_id_idx
    ON object_revision_with_geometry USING btree (geometry_id);


CREATE TABLE object_revision_without_geometry
(
    CONSTRAINT object_revision_without_geometry_pkey PRIMARY KEY (object_id, commit_id),
    CONSTRAINT no_geometry_id CHECK (geometry_id = 0),
    CONSTRAINT no_master_object_id CHECK (master_object_id = 0),
    CONSTRAINT no_slave_object_id CHECK (slave_object_id = 0)
)
INHERITS (object_revision);


CREATE INDEX object_revision_without_geometry_attributes_id_idx
    ON object_revision_without_geometry USING btree (attributes_id);

CREATE INDEX object_revision_without_geometry_commit_id_idx
    ON object_revision_without_geometry USING btree (commit_id);


CREATE OR REPLACE FUNCTION object_revision_insert_trigger()
  RETURNS trigger AS
$BODY$
BEGIN
    IF (NEW.geometry_id > 0) THEN
        INSERT INTO revision.object_revision_with_geometry VALUES (NEW.*);
    ELSIF (NEW.slave_object_id <> 0) THEN
        INSERT INTO revision.object_revision_relation VALUES (NEW.*);
    ELSE
        INSERT INTO revision.object_revision_without_geometry VALUES (NEW.*);
    END IF;
    RETURN NULL;
END;
$BODY$
  LANGUAGE plpgsql VOLATILE;

CREATE TRIGGER object_revision_on_insert
    BEFORE INSERT ON object_revision
    FOR EACH ROW EXECUTE PROCEDURE object_revision_insert_trigger();
