CREATE UNIQUE INDEX node_history_user_id_idx ON public.node_history (user_id,"uuid",history_uuid); ALTER TABLE public.node ALTER COLUMN "uuid" TYPE uuid USING "uuid"::uuid::uuid; ALTER TABLE public.node ALTER COLUMN parent_uuid TYPE uuid USING parent_uuid::uuid::uuid; ALTER TABLE public.node ALTER COLUMN client TYPE uuid USING client::uuid::uuid; CREATE OR REPLACE PROCEDURE public.add_nodes(IN p_user_id integer, IN p_client_uuid uuid, IN p_nodes jsonb) LANGUAGE plpgsql AS $procedure$ DECLARE node_data jsonb; node_updated timestamptz; db_updated timestamptz; db_uuid uuid; db_client uuid; db_client_seq int; db_history_uuid uuid; node_uuid uuid; node_parent_uuid uuid; node_history_uuid uuid; BEGIN RAISE NOTICE '--------------------------'; FOR node_data IN SELECT * FROM jsonb_array_elements(p_nodes) LOOP node_uuid = (node_data->>'UUID')::uuid; node_history_uuid = (node_data->>'HistoryUUID')::uuid; node_updated = (node_data->>'Updated')::timestamptz; -- Frontend is using an all-zero UUID to define the root node. -- Database is using NULL. IF node_data->>'ParentUUID' = '00000000-0000-0000-0000-000000000000' THEN node_parent_uuid = NULL; ELSE node_parent_uuid = (node_data->>'ParentUUID')::uuid; END IF; -- Every jode has a new history UUID to keep the history entry uniquely identifiable -- across clients. A history entry could potentially be sent again, but should be -- safe to ignore as every change to a node should have a new history UUID. -- -- The current node is also stored as history. INSERT INTO node_history( user_id, "uuid", "history_uuid", parents, created, updated, "name", "content", markdown, "content_encrypted", client, client_sequence ) VALUES( p_user_id, -- combined key node_uuid, -- combined key node_history_uuid, -- combined key (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 ("user_id", "uuid", "history_uuid") DO NOTHING; -- 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::uuid = node_uuid::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_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; -- Update the public node as well if it was older than incoming node. IF node_updated > db_updated THEN 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::uuid = node_uuid::uuid; END IF; END LOOP; END $procedure$ ;