Reset SQL
This commit is contained in:
parent
75d242c041
commit
4c513a5106
12 changed files with 538 additions and 570 deletions
577
sql/00001.sql
577
sql/00001.sql
|
|
@ -1,48 +1,218 @@
|
||||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
--
|
||||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
-- Name: pg_trgm; Type: EXTENSION; Schema: -; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
CREATE SEQUENCE node_updates;
|
CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;
|
||||||
|
|
||||||
CREATE TABLE public."user" (
|
|
||||||
id serial4 NOT NULL,
|
--
|
||||||
username varchar NOT NULL,
|
-- Name: EXTENSION pg_trgm; Type: COMMENT; Schema: -; Owner:
|
||||||
"name" varchar NOT NULL,
|
--
|
||||||
"password" varchar NOT NULL,
|
|
||||||
last_login timestamp DEFAULT now() NOT NULL,
|
COMMENT ON EXTENSION pg_trgm IS 'text similarity measurement and index searching based on trigrams';
|
||||||
timezone varchar DEFAULT 'UTC'::character varying NOT NULL,
|
|
||||||
CONSTRAINT user_pk PRIMARY KEY (id)
|
|
||||||
|
--
|
||||||
|
-- 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[]
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE public.node (
|
--
|
||||||
id serial4 NOT NULL,
|
-- Name: add_nodes(integer, character varying, jsonb); Type: PROCEDURE; Schema: public; Owner: postgres
|
||||||
user_id int4 NOT NULL,
|
--
|
||||||
"uuid" bpchar(36) DEFAULT gen_random_uuid() NOT NULL,
|
|
||||||
parent_uuid bpchar(36) NULL,
|
|
||||||
|
|
||||||
created timestamptz DEFAULT NOW() NOT NULL,
|
CREATE PROCEDURE public.add_nodes(IN p_user_id integer, IN p_client_uuid character varying, IN p_nodes jsonb)
|
||||||
updated timestamptz DEFAULT NOW() NOT NULL,
|
LANGUAGE plpgsql
|
||||||
deleted timestamptz NULL,
|
AS $$
|
||||||
|
|
||||||
created_seq bigint NOT NULL DEFAULT nextval('node_updates'),
|
DECLARE
|
||||||
updated_seq bigint NOT NULL DEFAULT nextval('node_updates'),
|
node_data jsonb;
|
||||||
deleted_seq bigint NULL,
|
node_updated timestamptz;
|
||||||
|
db_updated timestamptz;
|
||||||
|
db_uuid bpchar;
|
||||||
|
db_client bpchar;
|
||||||
|
db_client_seq int;
|
||||||
|
node_uuid bpchar;
|
||||||
|
parent_uuid_nullable bpchar;
|
||||||
|
|
||||||
"name" varchar(256) DEFAULT ''::character varying NOT NULL,
|
BEGIN
|
||||||
"content" text DEFAULT ''::text NOT NULL,
|
RAISE NOTICE '--------------------------';
|
||||||
content_encrypted text DEFAULT ''::text NOT NULL,
|
FOR node_data IN SELECT * FROM jsonb_array_elements(p_nodes)
|
||||||
markdown bool DEFAULT false NOT NULL,
|
LOOP
|
||||||
|
node_uuid = (node_data->>'UUID')::bpchar;
|
||||||
|
node_updated = (node_data->>'Updated')::timestamptz;
|
||||||
|
|
||||||
CONSTRAINT name_length CHECK ((length(TRIM(BOTH FROM name)) > 0)),
|
IF node_data->>'ParentUUID' = '00000000-0000-0000-0000-000000000000' THEN
|
||||||
CONSTRAINT node_pk PRIMARY KEY (id),
|
parent_uuid_nullable = NULL;
|
||||||
CONSTRAINT user_fk FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE RESTRICT ON UPDATE RESTRICT
|
ELSE
|
||||||
);
|
parent_uuid_nullable = node_data->>'ParentUUID';
|
||||||
CREATE UNIQUE INDEX node_uuid_idx ON public.node USING btree (uuid);
|
END IF;
|
||||||
CREATE INDEX node_search_index ON public.node USING gin (name gin_trgm_ops, content gin_trgm_ops);
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION node_update_timestamp()
|
/* Retrieve the current modified timestamp for this node from the database. */
|
||||||
RETURNS TRIGGER
|
SELECT
|
||||||
LANGUAGE PLPGSQL
|
uuid, updated, client, client_sequence
|
||||||
AS $$
|
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
|
BEGIN
|
||||||
IF NEW.updated = OLD.updated THEN
|
IF NEW.updated = OLD.updated THEN
|
||||||
UPDATE node
|
UPDATE node
|
||||||
|
|
@ -56,6 +226,335 @@ BEGIN
|
||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
CREATE OR REPLACE TRIGGER node_update AFTER UPDATE ON node
|
|
||||||
FOR EACH ROW
|
--
|
||||||
EXECUTE PROCEDURE node_update_timestamp();
|
-- 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;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
CREATE FUNCTION public.password_hash(salt_hex char(32), pass bytea)
|
|
||||||
RETURNS char(96)
|
|
||||||
LANGUAGE plpgsql
|
|
||||||
AS
|
|
||||||
$$
|
|
||||||
BEGIN
|
|
||||||
RETURN (
|
|
||||||
SELECT
|
|
||||||
salt_hex ||
|
|
||||||
encode(
|
|
||||||
sha256(
|
|
||||||
decode(salt_hex, 'hex') || /* salt in binary */
|
|
||||||
pass /* password */
|
|
||||||
),
|
|
||||||
'hex'
|
|
||||||
)
|
|
||||||
);
|
|
||||||
END;
|
|
||||||
$$;
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
ALTER TABLE public.node ADD CONSTRAINT node_node_fk FOREIGN KEY (parent_uuid) REFERENCES public.node("uuid") ON DELETE SET NULL ON UPDATE SET NULL;
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
ALTER TABLE public.node ADD COLUMN history bool NOT NULL DEFAULT false;
|
|
||||||
CREATE INDEX node_history_idx ON public.node (history);
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
ALTER TABLE public.node ADD COLUMN client bpchar(36) NOT NULL DEFAULT '';
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
DROP INDEX public.node_uuid_idx;
|
|
||||||
162
sql/00007.sql
162
sql/00007.sql
|
|
@ -1,162 +0,0 @@
|
||||||
CREATE TYPE json_ancestor_array as ("Ancestors" varchar[]);
|
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE PROCEDURE add_nodes(p_user_id int4, p_client_uuid varchar, 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;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
/* 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,
|
|
||||||
(node_data->>'ParentUUID')::bpchar,
|
|
||||||
(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
|
|
||||||
$$;
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
ALTER TABLE node ADD COLUMN Client_sequence int NULL;
|
|
||||||
ALTER TABLE node_history ADD COLUMN Client_sequence int NULL;
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
CREATE UNIQUE INDEX node_history_client_idx ON public.node_history (client,client_sequence);
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
CREATE TABLE public.client (
|
|
||||||
id serial NOT NULL,
|
|
||||||
user_id int4 NOT NULL,
|
|
||||||
client_uuid bpchar(36) DEFAULT '' NOT NULL,
|
|
||||||
created timestamptz DEFAULT NOW() NOT NULL,
|
|
||||||
description varchar DEFAULT '' NOT NULL,
|
|
||||||
CONSTRAINT client_pk PRIMARY KEY (id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX client_uuid_idx ON public.client (client_uuid);
|
|
||||||
166
sql/00011.sql
166
sql/00011.sql
|
|
@ -1,166 +0,0 @@
|
||||||
CREATE OR REPLACE PROCEDURE add_nodes(p_user_id int4, p_client_uuid varchar, 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 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 = NULL;
|
|
||||||
ELSE
|
|
||||||
parent_uuid = 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,
|
|
||||||
(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
|
|
||||||
$$;
|
|
||||||
166
sql/00012.sql
166
sql/00012.sql
|
|
@ -1,166 +0,0 @@
|
||||||
CREATE OR REPLACE PROCEDURE add_nodes(p_user_id int4, p_client_uuid varchar, 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
|
|
||||||
$$;
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue