-- -- Name: pg_trgm; Type: EXTENSION; Schema: -; Owner: - -- CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public; -- -- Name: EXTENSION pg_trgm; Type: COMMENT; Schema: -; Owner: -- COMMENT ON EXTENSION pg_trgm IS 'text similarity measurement and index searching based on trigrams'; -- -- Name: pgcrypto; Type: EXTENSION; Schema: -; Owner: - -- CREATE EXTENSION IF NOT EXISTS pgcrypto WITH SCHEMA public; -- -- Name: EXTENSION pgcrypto; Type: COMMENT; Schema: -; Owner: -- COMMENT ON EXTENSION pgcrypto IS 'cryptographic functions'; -- -- Name: json_ancestor_array; Type: TYPE; Schema: public; Owner: postgres -- CREATE TYPE public.json_ancestor_array AS ( "Ancestors" character varying[] ); -- -- Name: add_nodes(integer, character varying, jsonb); Type: PROCEDURE; Schema: public; Owner: postgres -- CREATE PROCEDURE public.add_nodes(IN p_user_id integer, IN p_client_uuid character varying, IN p_nodes jsonb) LANGUAGE plpgsql AS $$ DECLARE node_data jsonb; node_updated timestamptz; db_updated timestamptz; db_uuid bpchar; db_client bpchar; db_client_seq int; node_uuid bpchar; parent_uuid_nullable bpchar; BEGIN RAISE NOTICE '--------------------------'; FOR node_data IN SELECT * FROM jsonb_array_elements(p_nodes) LOOP node_uuid = (node_data->>'UUID')::bpchar; node_updated = (node_data->>'Updated')::timestamptz; IF node_data->>'ParentUUID' = '00000000-0000-0000-0000-000000000000' THEN parent_uuid_nullable = NULL; ELSE parent_uuid_nullable = node_data->>'ParentUUID'; END IF; /* Retrieve the current modified timestamp for this node from the database. */ SELECT uuid, updated, client, client_sequence INTO db_uuid, db_updated, db_client, db_client_seq FROM public."node" WHERE user_id = p_user_id AND uuid = node_uuid; /* Is the node not in database? It needs to be created. */ IF db_uuid IS NULL THEN RAISE NOTICE '01 New node %', node_uuid; INSERT INTO public."node" ( user_id, "uuid", parent_uuid, created, updated, "name", "content", markdown, "content_encrypted", client, client_sequence ) VALUES( p_user_id, node_uuid, parent_uuid_nullable, (node_data->>'Created')::timestamptz, (node_data->>'Updated')::timestamptz, (node_data->>'Name')::varchar, (node_data->>'Content')::text, (node_data->>'Markdown')::bool, '', /* content_encrypted */ p_client_uuid, (node_data->>'ClientSequence')::int ); CONTINUE; END IF; /* The client could send a specific node again if it didn't receive the OK from this procedure before. */ IF db_updated = node_updated AND db_client = p_client_uuid AND db_client_seq = (node_data->>'ClientSequence')::int THEN RAISE NOTICE '04, already recorded, %, %', db_client, db_client_seq; CONTINUE; END IF; /* Determine if the incoming node data is to go into history or replace the current node. */ IF db_updated > node_updated THEN RAISE NOTICE '02 DB newer, % > % (%))', db_updated, node_updated, node_uuid; /* Incoming node is going straight to history since it is older than the current node. */ INSERT INTO node_history( user_id, "uuid", parents, created, updated, "name", "content", markdown, "content_encrypted", client, client_sequence ) VALUES( p_user_id, node_uuid, (jsonb_populate_record(null::json_ancestor_array, node_data))."Ancestors", (node_data->>'Created')::timestamptz, (node_data->>'Updated')::timestamptz, (node_data->>'Name')::varchar, (node_data->>'Content')::text, (node_data->>'Markdown')::bool, '', /* content_encrypted */ p_client_uuid, (node_data->>'ClientSequence')::int ) ON CONFLICT (client, client_sequence) DO NOTHING; ELSE RAISE NOTICE '03 Client newer, % > % (%, %)', node_updated, db_updated, node_uuid, (node_data->>'ClientSequence'); /* Incoming node is newer and will replace the current node. * * The current node is copied to the node_history table and then modified in place * with the incoming data. */ INSERT INTO node_history( user_id, "uuid", parents, created, updated, "name", "content", markdown, "content_encrypted", client, client_sequence ) SELECT user_id, "uuid", ( WITH RECURSIVE nodes AS ( SELECT uuid, COALESCE(parent_uuid, '') AS parent_uuid, name, 0 AS depth FROM node WHERE uuid = node_uuid UNION SELECT n.uuid, COALESCE(n.parent_uuid, '') AS parent_uuid, n.name, nr.depth+1 AS depth FROM node n INNER JOIN nodes nr ON n.uuid = nr.parent_uuid ) SELECT ARRAY ( SELECT name FROM nodes ORDER BY depth DESC OFFSET 1 /* discard itself */ ) ), created, updated, name, content, markdown, content_encrypted, client, client_sequence FROM public."node" WHERE user_id = p_user_id AND uuid = node_uuid ON CONFLICT (client, client_sequence) DO NOTHING; /* Current node in database is updated with incoming data. */ UPDATE public."node" SET updated = (node_data->>'Updated')::timestamptz, updated_seq = nextval('node_updates'), name = (node_data->>'Name')::varchar, content = (node_data->>'Content')::text, markdown = (node_data->>'Markdown')::bool, client = p_client_uuid, client_sequence = (node_data->>'ClientSequence')::int WHERE user_id = p_user_id AND uuid = node_uuid; END IF; END LOOP; END $$; -- -- Name: node_update_timestamp(); Type: FUNCTION; Schema: public; Owner: postgres -- CREATE FUNCTION public.node_update_timestamp() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN IF NEW.updated = OLD.updated THEN UPDATE node SET updated = NOW(), updated_seq = nextval('node_updates') WHERE id=NEW.id; END IF; RETURN NEW; END; $$; -- -- Name: password_hash(character, bytea); Type: FUNCTION; Schema: public; Owner: postgres -- CREATE FUNCTION public.password_hash(salt_hex character, pass bytea) RETURNS character LANGUAGE plpgsql AS $$ BEGIN RETURN ( SELECT salt_hex || encode( sha256( decode(salt_hex, 'hex') || /* salt in binary */ pass /* password */ ), 'hex' ) ); END; $$; -- -- Name: client; Type: TABLE; Schema: public; Owner: postgres -- CREATE TABLE public.client ( id integer NOT NULL, user_id integer NOT NULL, client_uuid character(36) DEFAULT ''::bpchar NOT NULL, created timestamp with time zone DEFAULT now() NOT NULL, description character varying DEFAULT ''::character varying NOT NULL ); -- -- Name: client_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE public.client_id_seq AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; -- -- Name: client_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres -- ALTER SEQUENCE public.client_id_seq OWNED BY public.client.id; -- -- Name: node_updates; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE public.node_updates START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; -- -- Name: node; Type: TABLE; Schema: public; Owner: postgres -- CREATE TABLE public.node ( id integer NOT NULL, user_id integer NOT NULL, uuid character(36) DEFAULT gen_random_uuid() NOT NULL, parent_uuid character(36), created timestamp with time zone DEFAULT now() NOT NULL, updated timestamp with time zone DEFAULT now() NOT NULL, deleted timestamp with time zone, created_seq bigint DEFAULT nextval('public.node_updates'::regclass) NOT NULL, updated_seq bigint DEFAULT nextval('public.node_updates'::regclass) NOT NULL, deleted_seq bigint, name character varying(256) DEFAULT ''::character varying NOT NULL, content text DEFAULT ''::text NOT NULL, content_encrypted text DEFAULT ''::text NOT NULL, markdown boolean DEFAULT false NOT NULL, history boolean DEFAULT false NOT NULL, client character(36) DEFAULT ''::bpchar NOT NULL, client_sequence integer, CONSTRAINT name_length CHECK ((length(TRIM(BOTH FROM name)) > 0)) ); -- -- Name: node_history; Type: TABLE; Schema: public; Owner: postgres -- CREATE TABLE public.node_history ( id integer NOT NULL, user_id integer NOT NULL, uuid character(36) NOT NULL, parents character varying[], created timestamp with time zone NOT NULL, updated timestamp with time zone NOT NULL, name character varying(256) NOT NULL, content text NOT NULL, content_encrypted text NOT NULL, markdown boolean DEFAULT false NOT NULL, client character(36) DEFAULT ''::bpchar NOT NULL, client_sequence integer ); -- -- Name: node_history_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE public.node_history_id_seq AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; -- -- Name: node_history_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres -- ALTER SEQUENCE public.node_history_id_seq OWNED BY public.node_history.id; -- -- Name: node_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE public.node_id_seq AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; -- -- Name: node_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres -- ALTER SEQUENCE public.node_id_seq OWNED BY public.node.id; -- -- Name: test_data; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE public.test_data START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; -- -- Name: user; Type: TABLE; Schema: public; Owner: postgres -- CREATE TABLE public."user" ( id integer NOT NULL, username character varying NOT NULL, name character varying NOT NULL, password character varying NOT NULL, last_login timestamp without time zone DEFAULT now() NOT NULL, timezone character varying DEFAULT 'UTC'::character varying NOT NULL ); -- -- Name: user_id_seq; Type: SEQUENCE; Schema: public; Owner: postgres -- CREATE SEQUENCE public.user_id_seq AS integer START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1; -- -- Name: user_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: postgres -- ALTER SEQUENCE public.user_id_seq OWNED BY public."user".id; -- -- Name: client id; Type: DEFAULT; Schema: public; Owner: postgres -- ALTER TABLE ONLY public.client ALTER COLUMN id SET DEFAULT nextval('public.client_id_seq'::regclass); -- -- Name: node id; Type: DEFAULT; Schema: public; Owner: postgres -- ALTER TABLE ONLY public.node ALTER COLUMN id SET DEFAULT nextval('public.node_id_seq'::regclass); -- -- Name: node_history id; Type: DEFAULT; Schema: public; Owner: postgres -- ALTER TABLE ONLY public.node_history ALTER COLUMN id SET DEFAULT nextval('public.node_history_id_seq'::regclass); -- -- Name: user id; Type: DEFAULT; Schema: public; Owner: postgres -- ALTER TABLE ONLY public."user" ALTER COLUMN id SET DEFAULT nextval('public.user_id_seq'::regclass); -- -- Name: client client_pk; Type: CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY public.client ADD CONSTRAINT client_pk PRIMARY KEY (id); -- -- Name: node_history node_history_pk; Type: CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY public.node_history ADD CONSTRAINT node_history_pk PRIMARY KEY (id); -- -- Name: node node_pk; Type: CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY public.node ADD CONSTRAINT node_pk PRIMARY KEY (id); -- -- Name: user user_pk; Type: CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY public."user" ADD CONSTRAINT user_pk PRIMARY KEY (id); -- -- Name: client_uuid_idx; Type: INDEX; Schema: public; Owner: postgres -- CREATE UNIQUE INDEX client_uuid_idx ON public.client USING btree (client_uuid); -- -- Name: node_history_client_idx; Type: INDEX; Schema: public; Owner: postgres -- CREATE UNIQUE INDEX node_history_client_idx ON public.node_history USING btree (client, client_sequence); -- -- Name: node_history_idx; Type: INDEX; Schema: public; Owner: postgres -- CREATE INDEX node_history_idx ON public.node USING btree (history); -- -- Name: node_history_uuid_idx; Type: INDEX; Schema: public; Owner: postgres -- CREATE INDEX node_history_uuid_idx ON public.node USING btree (uuid); -- -- Name: node_search_index; Type: INDEX; Schema: public; Owner: postgres -- CREATE INDEX node_search_index ON public.node USING gin (name public.gin_trgm_ops, content public.gin_trgm_ops); -- -- Name: node_uuid_idx; Type: INDEX; Schema: public; Owner: postgres -- CREATE UNIQUE INDEX node_uuid_idx ON public.node USING btree (uuid); -- -- Name: node node_update; Type: TRIGGER; Schema: public; Owner: postgres -- CREATE TRIGGER node_update AFTER UPDATE ON public.node FOR EACH ROW EXECUTE FUNCTION public.node_update_timestamp(); -- -- Name: node_history node_history_user_fk; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY public.node_history ADD CONSTRAINT node_history_user_fk FOREIGN KEY (user_id) REFERENCES public."user"(id) ON UPDATE RESTRICT ON DELETE RESTRICT; -- -- Name: node node_node_fk; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY public.node ADD CONSTRAINT node_node_fk FOREIGN KEY (parent_uuid) REFERENCES public.node(uuid) ON UPDATE SET NULL ON DELETE SET NULL; -- -- Name: node user_fk; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- ALTER TABLE ONLY public.node ADD CONSTRAINT user_fk FOREIGN KEY (user_id) REFERENCES public."user"(id) ON UPDATE RESTRICT ON DELETE RESTRICT;