Skip to content

Commit 512ee34

Browse files
maparenttrangdoan982
authored andcommitted
ENG-1475 Export relations to imported nodes (#817)
1 parent 5034a1b commit 512ee34

6 files changed

Lines changed: 248 additions & 28 deletions

File tree

apps/obsidian/src/utils/conceptConversion.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -231,13 +231,6 @@ export const relationInstanceToLocalConcept = ({
231231
return null;
232232
}
233233

234-
if (
235-
sourceNode.frontmatter.importedFromRid ||
236-
destinationNode.frontmatter.importedFromRid
237-
)
238-
return null; // punt relation to imported nodes for now.
239-
// otherwise put the importedFromRid in source, dest.
240-
241234
/* eslint-disable @typescript-eslint/naming-convention */
242235
const literal_content: Record<string, Json> = {};
243236
if (importedFromRid) literal_content.importedFromRid = importedFromRid;
@@ -252,8 +245,12 @@ export const relationInstanceToLocalConcept = ({
252245
last_modified: new Date(lastModified ?? created).toISOString(),
253246
literal_content,
254247
local_reference_content: {
255-
source,
256-
destination,
248+
source:
249+
(sourceNode.frontmatter.importedFromRid as string | undefined) ??
250+
source,
251+
destination:
252+
(destinationNode.frontmatter.importedFromRid as string | undefined) ??
253+
destination,
257254
},
258255
/* eslint-enable @typescript-eslint/naming-convention */
259256
};

apps/obsidian/src/utils/publishNode.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,6 @@ export const publishNodeRelations = async ({
182182
(fm.publishedToGroups as string[]) || [];
183183
if (!publishedToGroups.includes(myGroup)) return;
184184
}
185-
if (fm.importedFromRid) return; // temporary, should be removed after eng-1475
186185
relevantNodeTypeById[id] = fm.nodeTypeId as string;
187186
});
188187
relations.map((relation) => {
@@ -199,6 +198,7 @@ export const publishNodeRelations = async ({
199198
resourceIds.add(triple.id);
200199
}
201200
});
201+
if (resourceIds.size === 0) return;
202202
const publishResponse = await client.from("ResourceAccess").upsert(
203203
[...resourceIds.values()].map((sourceLocalId: string) => ({
204204
/* eslint-disable @typescript-eslint/naming-convention */

apps/obsidian/src/utils/syncDgNodesToSupabase.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ const buildChangedNodesFromNodes = async ({
365365
const changedNodes: ObsidianDiscourseNodeData[] = [];
366366

367367
for (const node of nodes) {
368+
if (node.frontmatter.importedFromRid) continue;
368369
const existingTitle = existingTitleMap.get(node.nodeInstanceId);
369370
const detectedChangeTypes = detectNodeChanges(
370371
node,
@@ -423,7 +424,7 @@ export const syncAllNodesAndRelations = async (
423424
}
424425
console.debug("Supabase client:", supabaseClient);
425426

426-
const allNodes = await collectDiscourseNodesFromVault(plugin);
427+
const allNodes = await collectDiscourseNodesFromVault(plugin, true);
427428

428429
const changedNodeInstances = relationsOnly
429430
? []
@@ -433,7 +434,6 @@ export const syncAllNodesAndRelations = async (
433434
context,
434435
});
435436

436-
console.log("changedNodeInstances", changedNodeInstances);
437437
console.debug(`Found ${changedNodeInstances.length} nodes to sync`);
438438

439439
const accountLocalId = plugin.settings.accountLocalId;
@@ -495,7 +495,7 @@ const convertDgToSupabaseConcepts = async ({
495495
const nodeTypes = plugin.settings.nodeTypes ?? [];
496496
const relationTypes = plugin.settings.relationTypes ?? [];
497497
const discourseRelations = plugin.settings.discourseRelations ?? [];
498-
allNodes = allNodes ?? (await collectDiscourseNodesFromVault(plugin));
498+
allNodes = allNodes ?? (await collectDiscourseNodesFromVault(plugin, true));
499499
const allNodesById = Object.fromEntries(
500500
allNodes.map((n) => [n.nodeInstanceId, n]),
501501
);

packages/database/src/dbTypes.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1586,6 +1586,20 @@ export type Database = {
15861586
}
15871587
Returns: string
15881588
}
1589+
rid_or_local_id_to_concept_db_id: {
1590+
Args: { default_space_id: number; rid: string }
1591+
Returns: number
1592+
}
1593+
rid_to_space_id_and_local_id: {
1594+
Args: { rid: string }
1595+
Returns: Database["public"]["CompositeTypes"]["accessible_resource"]
1596+
SetofOptions: {
1597+
from: "*"
1598+
to: "accessible_resource"
1599+
isOneToOne: true
1600+
isSetofReturn: false
1601+
}
1602+
}
15891603
schema_of_concept:
15901604
| {
15911605
Args: { concept: Database["public"]["Tables"]["Concept"]["Row"] }
@@ -1967,3 +1981,4 @@ export const Constants = {
19671981
},
19681982
},
19691983
} as const
1984+
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
CREATE OR REPLACE FUNCTION public.rid_to_space_id_and_local_id(rid VARCHAR)
2+
RETURNS public.accessible_resource STRICT STABLE
3+
SET search_path = ''
4+
LANGUAGE plpgsql AS $$
5+
DECLARE
6+
uri VARCHAR;
7+
source_local_id VARCHAR;
8+
source_id BIGINT;
9+
BEGIN
10+
source_local_id := split_part(rid, '/', -1);
11+
IF length(source_local_id) = length(rid) THEN
12+
RETURN (null, 'Not a Rid')::public.accessible_resource;
13+
END IF;
14+
uri := substr(rid, 1, length(rid) - length(source_local_id) - 1);
15+
IF rid ~ '^orn:\w+\.\w+:.*$' THEN
16+
uri := concat(split_part(split_part(uri, ':', 2), '.', 1), ':', split_part(uri, ':', 3));
17+
ELSE
18+
IF rid ~ '^orn:\w+:.*$' THEN
19+
uri := substr(uri, 5);
20+
END IF;
21+
END IF;
22+
SELECT id INTO source_id FROM public."Space" where url=uri;
23+
IF source_id IS NULL THEN
24+
RETURN (null, concat('Cannot find ', uri))::public.accessible_resource;
25+
END IF;
26+
RETURN (source_id, source_local_id);
27+
END;
28+
$$;
29+
30+
CREATE OR REPLACE FUNCTION public.rid_or_local_id_to_concept_db_id(rid VARCHAR, default_space_id BIGINT)
31+
RETURNS BIGINT STRICT STABLE
32+
SET search_path = ''
33+
LANGUAGE plpgsql AS $$
34+
DECLARE r public.accessible_resource;
35+
BEGIN
36+
r := (SELECT public.rid_to_space_id_and_local_id(rid));
37+
IF r.space_id IS NULL THEN
38+
RETURN (SELECT id FROM public."Concept" WHERE space_id = default_space_id AND source_local_id = rid);
39+
ELSE
40+
RETURN (SELECT id FROM public."Concept" WHERE space_id = r.space_id AND source_local_id = r.source_local_id);
41+
END IF;
42+
END;
43+
$$;
44+
45+
CREATE OR REPLACE FUNCTION public._local_concept_to_db_concept(data public.concept_local_input)
46+
RETURNS public."Concept" STABLE
47+
SET search_path = ''
48+
LANGUAGE plpgsql
49+
AS $$
50+
DECLARE
51+
concept public."Concept"%ROWTYPE;
52+
reference_content JSONB := jsonb_build_object();
53+
key varchar;
54+
value JSONB;
55+
ref_single_val BIGINT;
56+
ref_array_val BIGINT[];
57+
BEGIN
58+
-- not fan of going through json, but not finding how to populate a record by a different shape record
59+
concept := jsonb_populate_record(NULL::public."Concept", to_jsonb(data));
60+
IF data.author_local_id IS NOT NULL THEN
61+
SELECT id FROM public."PlatformAccount"
62+
WHERE account_local_id = data.author_local_id INTO concept.author_id;
63+
END IF;
64+
IF data.represented_by_id IS NOT NULL THEN
65+
SELECT space_id, source_local_id FROM public."Content"
66+
WHERE id = data.represented_by_id INTO concept.space_id, concept.source_local_id;
67+
END IF;
68+
IF data.space_url IS NOT NULL THEN
69+
SELECT id FROM public."Space"
70+
WHERE url = data.space_url INTO concept.space_id;
71+
END IF;
72+
IF concept.source_local_id = '' THEN
73+
concept.source_local_id := NULL;
74+
END IF;
75+
IF data.represented_by_local_id = '' THEN
76+
data.represented_by_local_id := NULL;
77+
END IF;
78+
IF data.schema_represented_by_local_id IS NOT NULL THEN
79+
SELECT public.rid_or_local_id_to_concept_db_id(
80+
data.schema_represented_by_local_id, concept.space_id) INTO concept.schema_id;
81+
END IF;
82+
concept.source_local_id = COALESCE(concept.source_local_id, data.represented_by_local_id); -- legacy input field
83+
concept.reference_content := coalesce(data.reference_content, '{}'::jsonb);
84+
IF data.local_reference_content IS NOT NULL THEN
85+
FOR key, value IN SELECT * FROM jsonb_each(data.local_reference_content) LOOP
86+
IF jsonb_typeof(value) = 'array' THEN
87+
WITH el AS (SELECT jsonb_array_elements_text(value) as x),
88+
el2 AS (SELECT public.rid_or_local_id_to_concept_db_id(x, concept.space_id) AS id FROM el)
89+
SELECT array_agg(DISTINCT el2.id) INTO STRICT ref_array_val
90+
FROM el2 WHERE el2.id IS NOT NULL;
91+
reference_content := jsonb_set(reference_content, ARRAY[key], to_jsonb(ref_array_val));
92+
ELSIF jsonb_typeof(value) = 'string' THEN
93+
SELECT public.rid_or_local_id_to_concept_db_id(value #>> '{}', concept.space_id) INTO STRICT ref_single_val;
94+
reference_content := jsonb_set(reference_content, ARRAY[key], to_jsonb(ref_single_val));
95+
ELSE
96+
RAISE EXCEPTION 'Invalid value in local_reference_content % %', value, jsonb_typeof(value);
97+
END IF;
98+
END LOOP;
99+
concept.reference_content := concept.reference_content || reference_content;
100+
END IF;
101+
RETURN concept;
102+
END;
103+
$$;
104+
105+
CREATE OR REPLACE FUNCTION public.upsert_concepts(v_space_id bigint, data jsonb)
106+
RETURNS SETOF BIGINT
107+
SET search_path = ''
108+
LANGUAGE plpgsql
109+
AS $$
110+
DECLARE
111+
v_platform public."Platform";
112+
local_concept public.concept_local_input;
113+
db_concept public."Concept"%ROWTYPE;
114+
concept_row JSONB;
115+
concept_id BIGINT;
116+
BEGIN
117+
SELECT platform INTO STRICT v_platform FROM public."Space" WHERE id=v_space_id;
118+
FOR concept_row IN SELECT * FROM jsonb_array_elements(data)
119+
LOOP
120+
-- first set defaults
121+
local_concept := jsonb_populate_record(NULL::public.concept_local_input, '{"epistemic_status": "unknown", "literal_content":{},"reference_content":{},"is_schema":false}');
122+
-- then input values
123+
local_concept := jsonb_populate_record(local_concept, concept_row);
124+
local_concept.space_id := v_space_id;
125+
BEGIN
126+
db_concept := public._local_concept_to_db_concept(local_concept);
127+
-- cannot use db_concept.* because of refs.
128+
INSERT INTO public."Concept" (
129+
epistemic_status, name, description, author_id, created, last_modified, space_id, schema_id, literal_content, is_schema, source_local_id, reference_content
130+
) VALUES (
131+
db_concept.epistemic_status, db_concept.name, db_concept.description, db_concept.author_id, db_concept.created, db_concept.last_modified, db_concept.space_id, db_concept.schema_id, db_concept.literal_content, db_concept.is_schema, db_concept.source_local_id, db_concept.reference_content
132+
)
133+
ON CONFLICT (space_id, source_local_id) DO UPDATE SET
134+
epistemic_status = db_concept.epistemic_status,
135+
name = db_concept.name,
136+
description = db_concept.description,
137+
author_id = db_concept.author_id,
138+
created = db_concept.created,
139+
last_modified = db_concept.last_modified,
140+
schema_id = db_concept.schema_id,
141+
literal_content = db_concept.literal_content,
142+
is_schema = db_concept.is_schema,
143+
reference_content = db_concept.reference_content
144+
-- If the syntax allowed two conflict clauses, I would add
145+
-- ON CONFLICT (space_id, name) DO NOTHING
146+
-- but since not, I have to handle it as an exception.
147+
RETURNING id INTO concept_id;
148+
RETURN NEXT concept_id;
149+
EXCEPTION
150+
WHEN unique_violation THEN
151+
-- a distinct unique constraint failed
152+
RAISE WARNING 'Concept with space_id: % and name % already exists', v_space_id, local_concept.name;
153+
RETURN NEXT -1; -- Return a special value to indicate conflict
154+
WHEN OTHERS THEN
155+
-- Null value; probably due to a missing concept.
156+
RAISE WARNING 'Error in concept upsert: (%) %', SQLSTATE, SQLERRM;
157+
RETURN NEXT -2; -- Return a special value to indicate error
158+
END;
159+
END LOOP;
160+
RAISE DEBUG 'Completed upsert_concepts successfully';
161+
END;
162+
$$;

packages/database/supabase/schemas/concept.sql

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,49 @@ $$;
240240
COMMENT ON FUNCTION public.author_of_concept(public.my_concepts)
241241
IS 'Computed one-to-one: returns the PlatformAccount which authored a given Concept.';
242242

243+
CREATE OR REPLACE FUNCTION public.rid_to_space_id_and_local_id(rid VARCHAR)
244+
RETURNS public.accessible_resource STRICT STABLE
245+
SET search_path = ''
246+
LANGUAGE plpgsql AS $$
247+
DECLARE
248+
uri VARCHAR;
249+
source_local_id VARCHAR;
250+
source_id BIGINT;
251+
BEGIN
252+
source_local_id := split_part(rid, '/', -1);
253+
IF length(source_local_id) = length(rid) THEN
254+
RETURN (null, 'Not a Rid')::public.accessible_resource;
255+
END IF;
256+
uri := substr(rid, 1, length(rid) - length(source_local_id) - 1);
257+
IF rid ~ '^orn:\w+\.\w+:.*$' THEN
258+
uri := concat(split_part(split_part(uri, ':', 2), '.', 1), ':', split_part(uri, ':', 3));
259+
ELSE
260+
IF rid ~ '^orn:\w+:.*$' THEN
261+
uri := substr(uri, 5);
262+
END IF;
263+
END IF;
264+
SELECT id INTO source_id FROM public."Space" where url=uri;
265+
IF source_id IS NULL THEN
266+
RETURN (null, concat('Cannot find ', uri))::public.accessible_resource;
267+
END IF;
268+
RETURN (source_id, source_local_id);
269+
END;
270+
$$;
271+
272+
CREATE OR REPLACE FUNCTION public.rid_or_local_id_to_concept_db_id(rid VARCHAR, default_space_id BIGINT)
273+
RETURNS BIGINT STRICT STABLE
274+
SET search_path = ''
275+
LANGUAGE plpgsql AS $$
276+
DECLARE r public.accessible_resource;
277+
BEGIN
278+
r := (SELECT public.rid_to_space_id_and_local_id(rid));
279+
IF r.space_id IS NULL THEN
280+
RETURN (SELECT id FROM public."Concept" WHERE space_id = default_space_id AND source_local_id = rid);
281+
ELSE
282+
RETURN (SELECT id FROM public."Concept" WHERE space_id = r.space_id AND source_local_id = r.source_local_id);
283+
END IF;
284+
END;
285+
$$;
243286

244287
CREATE TYPE public.concept_local_input AS (
245288
-- concept columns
@@ -292,37 +335,34 @@ BEGIN
292335
SELECT id FROM public."Space"
293336
WHERE url = data.space_url INTO concept.space_id;
294337
END IF;
295-
IF data.schema_represented_by_local_id IS NOT NULL THEN
296-
SELECT cpt.id FROM public."Concept" cpt
297-
WHERE cpt.source_local_id = data.schema_represented_by_local_id
298-
AND cpt.space_id = concept.space_id INTO concept.schema_id;
299-
END IF;
300338
IF concept.source_local_id = '' THEN
301339
concept.source_local_id := NULL;
302340
END IF;
303341
IF data.represented_by_local_id = '' THEN
304342
data.represented_by_local_id := NULL;
305343
END IF;
344+
IF data.schema_represented_by_local_id IS NOT NULL THEN
345+
SELECT public.rid_or_local_id_to_concept_db_id(
346+
data.schema_represented_by_local_id, concept.space_id) INTO concept.schema_id;
347+
END IF;
306348
concept.source_local_id = COALESCE(concept.source_local_id, data.represented_by_local_id); -- legacy input field
349+
concept.reference_content := coalesce(data.reference_content, '{}'::jsonb);
307350
IF data.local_reference_content IS NOT NULL THEN
308351
FOR key, value IN SELECT * FROM jsonb_each(data.local_reference_content) LOOP
309352
IF jsonb_typeof(value) = 'array' THEN
310353
WITH el AS (SELECT jsonb_array_elements_text(value) as x),
311-
ela AS (SELECT array_agg(x) AS a FROM el)
312-
SELECT array_agg(DISTINCT cpt.id) INTO STRICT ref_array_val
313-
FROM public."Concept" AS cpt
314-
JOIN ela ON (true) WHERE cpt.source_local_id = ANY(ela.a) AND cpt.space_id=concept.space_id;
354+
el2 AS (SELECT public.rid_or_local_id_to_concept_db_id(x, concept.space_id) AS id FROM el)
355+
SELECT array_agg(DISTINCT el2.id) INTO STRICT ref_array_val
356+
FROM el2 WHERE el2.id IS NOT NULL;
315357
reference_content := jsonb_set(reference_content, ARRAY[key], to_jsonb(ref_array_val));
316358
ELSIF jsonb_typeof(value) = 'string' THEN
317-
SELECT cpt.id INTO STRICT ref_single_val
318-
FROM public."Concept" AS cpt
319-
WHERE cpt.source_local_id = (value #>> '{}') AND cpt.space_id=concept.space_id;
359+
SELECT public.rid_or_local_id_to_concept_db_id(value #>> '{}', concept.space_id) INTO STRICT ref_single_val;
320360
reference_content := jsonb_set(reference_content, ARRAY[key], to_jsonb(ref_single_val));
321361
ELSE
322362
RAISE EXCEPTION 'Invalid value in local_reference_content % %', value, jsonb_typeof(value);
323363
END IF;
324364
END LOOP;
325-
SELECT reference_content INTO concept.reference_content;
365+
concept.reference_content := concept.reference_content || reference_content;
326366
END IF;
327367
RETURN concept;
328368
END;
@@ -351,8 +391,8 @@ BEGIN
351391
-- then input values
352392
local_concept := jsonb_populate_record(local_concept, concept_row);
353393
local_concept.space_id := v_space_id;
354-
db_concept := public._local_concept_to_db_concept(local_concept);
355394
BEGIN
395+
db_concept := public._local_concept_to_db_concept(local_concept);
356396
-- cannot use db_concept.* because of refs.
357397
INSERT INTO public."Concept" (
358398
epistemic_status, name, description, author_id, created, last_modified, space_id, schema_id, literal_content, is_schema, source_local_id, reference_content
@@ -370,14 +410,20 @@ BEGIN
370410
literal_content = db_concept.literal_content,
371411
is_schema = db_concept.is_schema,
372412
reference_content = db_concept.reference_content
373-
-- ON CONFLICT (space_id, name) DO NOTHING... why can't I specify two conflict clauses?
413+
-- If the syntax allowed two conflict clauses, I would add
414+
-- ON CONFLICT (space_id, name) DO NOTHING
415+
-- but since not, I have to handle it as an exception.
374416
RETURNING id INTO concept_id;
375417
RETURN NEXT concept_id;
376418
EXCEPTION
377419
WHEN unique_violation THEN
378420
-- a distinct unique constraint failed
379421
RAISE WARNING 'Concept with space_id: % and name % already exists', v_space_id, local_concept.name;
380422
RETURN NEXT -1; -- Return a special value to indicate conflict
423+
WHEN OTHERS THEN
424+
-- Null value; probably due to a missing concept.
425+
RAISE WARNING 'Error in concept upsert: (%) %', SQLSTATE, SQLERRM;
426+
RETURN NEXT -2; -- Return a special value to indicate error
381427
END;
382428
END LOOP;
383429
RAISE DEBUG 'Completed upsert_concepts successfully';

0 commit comments

Comments
 (0)