@_linked/core 2.8.0-next.20260620064950 → 2.8.0-next.20260624100616
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/lib/cjs/index.d.ts +3 -0
- package/lib/cjs/index.js +9 -1
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/ontologies/linked-core.d.ts +3 -0
- package/lib/cjs/ontologies/linked-core.js +10 -0
- package/lib/cjs/ontologies/linked-core.js.map +1 -1
- package/lib/cjs/ontologies/shacl.d.ts +7 -0
- package/lib/cjs/ontologies/shacl.js +14 -0
- package/lib/cjs/ontologies/shacl.js.map +1 -1
- package/lib/cjs/shapes/List.d.ts +27 -8
- package/lib/cjs/shapes/List.js +36 -28
- package/lib/cjs/shapes/List.js.map +1 -1
- package/lib/cjs/shapes/PathNode.d.ts +20 -0
- package/lib/cjs/shapes/PathNode.js +81 -0
- package/lib/cjs/shapes/PathNode.js.map +1 -0
- package/lib/cjs/shapes/SHACL.d.ts +19 -0
- package/lib/cjs/shapes/SHACL.js +3 -0
- package/lib/cjs/shapes/SHACL.js.map +1 -1
- package/lib/cjs/shapes/serializePathToNodeData.d.ts +24 -0
- package/lib/cjs/shapes/serializePathToNodeData.js +51 -0
- package/lib/cjs/shapes/serializePathToNodeData.js.map +1 -0
- package/lib/cjs/shapes/syncShapes.d.ts +14 -0
- package/lib/cjs/shapes/syncShapes.js +141 -0
- package/lib/cjs/shapes/syncShapes.js.map +1 -0
- package/lib/cjs/sparql/irToAlgebra.d.ts +10 -1
- package/lib/cjs/sparql/irToAlgebra.js +148 -6
- package/lib/cjs/sparql/irToAlgebra.js.map +1 -1
- package/lib/cjs/utils/Package.d.ts +14 -0
- package/lib/cjs/utils/Package.js +43 -2
- package/lib/cjs/utils/Package.js.map +1 -1
- package/lib/esm/index.d.ts +3 -0
- package/lib/esm/index.js +5 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/ontologies/linked-core.d.ts +3 -0
- package/lib/esm/ontologies/linked-core.js +10 -0
- package/lib/esm/ontologies/linked-core.js.map +1 -1
- package/lib/esm/ontologies/shacl.d.ts +7 -0
- package/lib/esm/ontologies/shacl.js +14 -0
- package/lib/esm/ontologies/shacl.js.map +1 -1
- package/lib/esm/shapes/List.d.ts +27 -8
- package/lib/esm/shapes/List.js +35 -28
- package/lib/esm/shapes/List.js.map +1 -1
- package/lib/esm/shapes/PathNode.d.ts +20 -0
- package/lib/esm/shapes/PathNode.js +78 -0
- package/lib/esm/shapes/PathNode.js.map +1 -0
- package/lib/esm/shapes/SHACL.d.ts +19 -0
- package/lib/esm/shapes/SHACL.js +3 -0
- package/lib/esm/shapes/SHACL.js.map +1 -1
- package/lib/esm/shapes/serializePathToNodeData.d.ts +24 -0
- package/lib/esm/shapes/serializePathToNodeData.js +48 -0
- package/lib/esm/shapes/serializePathToNodeData.js.map +1 -0
- package/lib/esm/shapes/syncShapes.d.ts +14 -0
- package/lib/esm/shapes/syncShapes.js +138 -0
- package/lib/esm/shapes/syncShapes.js.map +1 -0
- package/lib/esm/sparql/irToAlgebra.d.ts +10 -1
- package/lib/esm/sparql/irToAlgebra.js +147 -6
- package/lib/esm/sparql/irToAlgebra.js.map +1 -1
- package/lib/esm/utils/Package.d.ts +14 -0
- package/lib/esm/utils/Package.js +43 -2
- package/lib/esm/utils/Package.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { PathExpr } from '../paths/PropertyPathExpr.js';
|
|
2
|
+
import type { NodeReferenceValue } from '../utils/NodeReference.js';
|
|
3
|
+
import { List } from './List.js';
|
|
4
|
+
import { PathNode } from './PathNode.js';
|
|
5
|
+
/**
|
|
6
|
+
* Node-data the create pipeline accepts for a `sh:path` value: an IRI reference (simple path),
|
|
7
|
+
* an `rdf:List` (sequence / alternative members), or a `PathNode` operator node.
|
|
8
|
+
*/
|
|
9
|
+
export type PathNodeData = NodeReferenceValue | {
|
|
10
|
+
shape: typeof List | typeof PathNode;
|
|
11
|
+
__id?: string;
|
|
12
|
+
[k: string]: unknown;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Translate a `PathExpr` into create-pipeline node-data for `sh:path`.
|
|
16
|
+
*
|
|
17
|
+
* - simple `PathRef` → `{id}` (a shared predicate IRI; NOT owned)
|
|
18
|
+
* - sequence → an `rdf:List` of segments (via {@link rdfList})
|
|
19
|
+
* - inverse / alternative / cardinality → a `PathNode` operator node (alt's operand is an `rdf:List`)
|
|
20
|
+
*
|
|
21
|
+
* `base` seeds deterministic ids for the minted owned nodes (`{base}/inv`, `{base}/seq/0`, …) so a
|
|
22
|
+
* re-sync overwrites them in place; the owned subtree is cleaned by the containment cascade.
|
|
23
|
+
*/
|
|
24
|
+
export declare function serializePathToNodeData(path: PathExpr, base: string): PathNodeData;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serializePathToNodeData = serializePathToNodeData;
|
|
4
|
+
const PropertyPathExpr_js_1 = require("../paths/PropertyPathExpr.js");
|
|
5
|
+
const List_js_1 = require("./List.js");
|
|
6
|
+
const PathNode_js_1 = require("./PathNode.js");
|
|
7
|
+
/**
|
|
8
|
+
* Translate a `PathExpr` into create-pipeline node-data for `sh:path`.
|
|
9
|
+
*
|
|
10
|
+
* - simple `PathRef` → `{id}` (a shared predicate IRI; NOT owned)
|
|
11
|
+
* - sequence → an `rdf:List` of segments (via {@link rdfList})
|
|
12
|
+
* - inverse / alternative / cardinality → a `PathNode` operator node (alt's operand is an `rdf:List`)
|
|
13
|
+
*
|
|
14
|
+
* `base` seeds deterministic ids for the minted owned nodes (`{base}/inv`, `{base}/seq/0`, …) so a
|
|
15
|
+
* re-sync overwrites them in place; the owned subtree is cleaned by the containment cascade.
|
|
16
|
+
*/
|
|
17
|
+
function serializePathToNodeData(path, base) {
|
|
18
|
+
const serialize = (e, b) => {
|
|
19
|
+
if ((0, PropertyPathExpr_js_1.isPathRef)(e)) {
|
|
20
|
+
return { id: typeof e === 'string' ? e : e.id };
|
|
21
|
+
}
|
|
22
|
+
if ('seq' in e) {
|
|
23
|
+
return (0, List_js_1.rdfList)(e.seq.map((s, i) => serialize(s, `${b}/seq/${i}`)), { base: `${b}/seq` });
|
|
24
|
+
}
|
|
25
|
+
if ('inv' in e) {
|
|
26
|
+
return { shape: PathNode_js_1.PathNode, __id: `${b}/inv`, inversePath: serialize(e.inv, `${b}/inv`) };
|
|
27
|
+
}
|
|
28
|
+
if ('alt' in e) {
|
|
29
|
+
return {
|
|
30
|
+
shape: PathNode_js_1.PathNode,
|
|
31
|
+
__id: `${b}/alt`,
|
|
32
|
+
alternativePath: (0, List_js_1.rdfList)(e.alt.map((a, i) => serialize(a, `${b}/alt/${i}`)), { base: `${b}/alt` }),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if ('zeroOrMore' in e) {
|
|
36
|
+
return { shape: PathNode_js_1.PathNode, __id: `${b}/zom`, zeroOrMorePath: serialize(e.zeroOrMore, `${b}/zom`) };
|
|
37
|
+
}
|
|
38
|
+
if ('oneOrMore' in e) {
|
|
39
|
+
return { shape: PathNode_js_1.PathNode, __id: `${b}/oom`, oneOrMorePath: serialize(e.oneOrMore, `${b}/oom`) };
|
|
40
|
+
}
|
|
41
|
+
if ('zeroOrOne' in e) {
|
|
42
|
+
return { shape: PathNode_js_1.PathNode, __id: `${b}/zoo`, zeroOrOnePath: serialize(e.zeroOrOne, `${b}/zoo`) };
|
|
43
|
+
}
|
|
44
|
+
if ('negatedPropertySet' in e) {
|
|
45
|
+
throw new Error('negatedPropertySet cannot be serialized to SHACL sh:path — no SHACL representation.');
|
|
46
|
+
}
|
|
47
|
+
throw new Error(`Unknown PathExpr shape: ${JSON.stringify(e)}`);
|
|
48
|
+
};
|
|
49
|
+
return serialize(path, base);
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=serializePathToNodeData.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serializePathToNodeData.js","sourceRoot":"","sources":["../../../src/shapes/serializePathToNodeData.ts"],"names":[],"mappings":";;AA6BA,0DAyCC;AAhED,sEAAuD;AAEvD,uCAAwC;AACxC,+CAAuC;AAUvC;;;;;;;;;GASG;AACH,SAAgB,uBAAuB,CAAC,IAAc,EAAE,IAAY;IAClE,MAAM,SAAS,GAAG,CAAC,CAAW,EAAE,CAAS,EAAgB,EAAE;QACzD,IAAI,IAAA,+BAAS,EAAC,CAAC,CAAC,EAAE,CAAC;YACjB,OAAO,EAAC,EAAE,EAAE,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAC,CAAC;QAChD,CAAC;QACD,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACf,OAAO,IAAA,iBAAO,EACZ,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAClD,EAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAC,CACH,CAAC;QACpB,CAAC;QACD,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACf,OAAO,EAAC,KAAK,EAAE,sBAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,CAAC,MAAM,CAAC,EAAC,CAAC;QACxF,CAAC;QACD,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;YACf,OAAO;gBACL,KAAK,EAAE,sBAAQ;gBACf,IAAI,EAAE,GAAG,CAAC,MAAM;gBAChB,eAAe,EAAE,IAAA,iBAAO,EACtB,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,EAClD,EAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAC,CACnB;aACF,CAAC;QACJ,CAAC;QACD,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;YACtB,OAAO,EAAC,KAAK,EAAE,sBAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,EAAE,SAAS,CAAC,CAAC,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EAAC,CAAC;QAClG,CAAC;QACD,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;YACrB,OAAO,EAAC,KAAK,EAAE,sBAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EAAC,CAAC;QAChG,CAAC;QACD,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;YACrB,OAAO,EAAC,KAAK,EAAE,sBAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,EAAC,CAAC;QAChG,CAAC;QACD,IAAI,oBAAoB,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,qFAAqF,CACtF,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,2BAA2B,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC;IACF,OAAO,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan an idempotent sync of all code-registered (non-framework) NodeShapes into the store as
|
|
3
|
+
* SHACL data — the forward (code → graph) materialization of arch-05 "code is canonical".
|
|
4
|
+
*
|
|
5
|
+
* Returns built-but-unexecuted thunks; the caller controls execution and batching:
|
|
6
|
+
* ```ts
|
|
7
|
+
* await Promise.all((await syncShapes()).map((run) => run()));
|
|
8
|
+
* ```
|
|
9
|
+
* Each in-code shape's thunk runs `delete → create` in order (the delete cascade-cleans the old
|
|
10
|
+
* property shapes / list / path subtrees via the containment cascade, the create rebuilds them).
|
|
11
|
+
* Shapes present in the store but no longer in code are deleted as orphans (their owned subtree
|
|
12
|
+
* cascades too). Reads existing shape IRIs via the global query dispatch for orphan detection.
|
|
13
|
+
*/
|
|
14
|
+
export declare function syncShapes(): Promise<Array<() => Promise<void>>>;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.syncShapes = syncShapes;
|
|
13
|
+
/*
|
|
14
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
15
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
16
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
17
|
+
*/
|
|
18
|
+
const ShapeClass_js_1 = require("../utils/ShapeClass.js");
|
|
19
|
+
const SHACL_js_1 = require("./SHACL.js");
|
|
20
|
+
const DeleteBuilder_js_1 = require("../queries/DeleteBuilder.js");
|
|
21
|
+
const List_js_1 = require("./List.js");
|
|
22
|
+
const serializePathToNodeData_js_1 = require("./serializePathToNodeData.js");
|
|
23
|
+
/**
|
|
24
|
+
* The npm scope of framework shapes (NodeShape/PropertyShape/List/PathNode/Shape) that
|
|
25
|
+
* describe the meta-model itself and must never be synced as user data.
|
|
26
|
+
*/
|
|
27
|
+
const FRAMEWORK_PACKAGE = '@_linked/core';
|
|
28
|
+
/** Build the create-pipeline data object for a single PropertyShape (flattened under shapeIri). */
|
|
29
|
+
function buildPropertyShapeData(ps, shapeIri) {
|
|
30
|
+
const psIri = `${shapeIri}/${ps.label}`;
|
|
31
|
+
const d = {
|
|
32
|
+
__id: psIri,
|
|
33
|
+
path: (0, serializePathToNodeData_js_1.serializePathToNodeData)(ps.path, psIri),
|
|
34
|
+
};
|
|
35
|
+
if (ps.nodeKind)
|
|
36
|
+
d.nodeKind = ps.nodeKind;
|
|
37
|
+
if (ps.datatype)
|
|
38
|
+
d.datatype = ps.datatype;
|
|
39
|
+
if (typeof ps.minCount === 'number')
|
|
40
|
+
d.minCount = ps.minCount;
|
|
41
|
+
if (typeof ps.maxCount === 'number')
|
|
42
|
+
d.maxCount = ps.maxCount;
|
|
43
|
+
if (ps.name)
|
|
44
|
+
d.name = ps.name;
|
|
45
|
+
if (ps.description)
|
|
46
|
+
d.description = ps.description;
|
|
47
|
+
if (typeof ps.order === 'number')
|
|
48
|
+
d.order = ps.order;
|
|
49
|
+
if (ps.group)
|
|
50
|
+
d.group = ps.group;
|
|
51
|
+
if (ps.class)
|
|
52
|
+
d.class = ps.class;
|
|
53
|
+
if (ps.in)
|
|
54
|
+
d.in = (0, List_js_1.rdfList)(ps.in, { base: `${psIri}/in` });
|
|
55
|
+
if (ps.equalsConstraint)
|
|
56
|
+
d.equals = ps.equalsConstraint;
|
|
57
|
+
if (ps.disjoint)
|
|
58
|
+
d.disjoint = ps.disjoint;
|
|
59
|
+
if (ps.lessThan)
|
|
60
|
+
d.lessThan = ps.lessThan;
|
|
61
|
+
if (ps.lessThanOrEquals)
|
|
62
|
+
d.lessThanOrEquals = ps.lessThanOrEquals;
|
|
63
|
+
if (ps.hasValueConstraint !== undefined)
|
|
64
|
+
d.hasValue = ps.hasValueConstraint;
|
|
65
|
+
if (ps.valueShape)
|
|
66
|
+
d.valueShape = ps.valueShape;
|
|
67
|
+
if (ps.contains)
|
|
68
|
+
d.contains = true;
|
|
69
|
+
return d;
|
|
70
|
+
}
|
|
71
|
+
/** Build the create-pipeline data object for a NodeShape and its (flattened) property shapes. */
|
|
72
|
+
function buildNodeShapeData(nodeShape, shapeIri) {
|
|
73
|
+
const d = {};
|
|
74
|
+
if (nodeShape.targetClass)
|
|
75
|
+
d.targetClass = nodeShape.targetClass;
|
|
76
|
+
if (nodeShape.description)
|
|
77
|
+
d.description = nodeShape.description;
|
|
78
|
+
if (nodeShape.extends)
|
|
79
|
+
d.extends = nodeShape.extends;
|
|
80
|
+
if (nodeShape.dependent)
|
|
81
|
+
d.dependent = true;
|
|
82
|
+
if (nodeShape.closed !== undefined)
|
|
83
|
+
d.closed = nodeShape.closed;
|
|
84
|
+
if (nodeShape.ignoredProperties && nodeShape.ignoredProperties.length) {
|
|
85
|
+
d.ignoredProperties = nodeShape.ignoredProperties;
|
|
86
|
+
}
|
|
87
|
+
// Flatten: emit own + inherited property shapes (deduped by label) under this shape.
|
|
88
|
+
d.properties = nodeShape
|
|
89
|
+
.getUniquePropertyShapes()
|
|
90
|
+
.map((ps) => buildPropertyShapeData(ps, shapeIri));
|
|
91
|
+
return d;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Plan an idempotent sync of all code-registered (non-framework) NodeShapes into the store as
|
|
95
|
+
* SHACL data — the forward (code → graph) materialization of arch-05 "code is canonical".
|
|
96
|
+
*
|
|
97
|
+
* Returns built-but-unexecuted thunks; the caller controls execution and batching:
|
|
98
|
+
* ```ts
|
|
99
|
+
* await Promise.all((await syncShapes()).map((run) => run()));
|
|
100
|
+
* ```
|
|
101
|
+
* Each in-code shape's thunk runs `delete → create` in order (the delete cascade-cleans the old
|
|
102
|
+
* property shapes / list / path subtrees via the containment cascade, the create rebuilds them).
|
|
103
|
+
* Shapes present in the store but no longer in code are deleted as orphans (their owned subtree
|
|
104
|
+
* cascades too). Reads existing shape IRIs via the global query dispatch for orphan detection.
|
|
105
|
+
*/
|
|
106
|
+
function syncShapes() {
|
|
107
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
108
|
+
// 1. Enumerate code-registered user shapes (exclude framework/meta shapes).
|
|
109
|
+
const userShapes = [];
|
|
110
|
+
for (const [iri, shapeClass] of (0, ShapeClass_js_1.getAllShapeClasses)()) {
|
|
111
|
+
if (!(shapeClass === null || shapeClass === void 0 ? void 0 : shapeClass.shape))
|
|
112
|
+
continue;
|
|
113
|
+
const pkg = shapeClass.packageName;
|
|
114
|
+
// Skip framework/meta shapes: the core package, and the base `Shape` which is registered
|
|
115
|
+
// directly (no applyLinkedShape) and therefore carries no packageName.
|
|
116
|
+
if (!pkg || pkg === FRAMEWORK_PACKAGE)
|
|
117
|
+
continue;
|
|
118
|
+
userShapes.push({ iri, nodeShape: shapeClass.shape });
|
|
119
|
+
}
|
|
120
|
+
const localShapeIris = new Set(userShapes.map((s) => s.iri));
|
|
121
|
+
// 2. Identity-read existing NodeShape IRIs (ids only) for orphan detection.
|
|
122
|
+
const existingRows = (yield SHACL_js_1.NodeShape.select());
|
|
123
|
+
const existingShapeIris = new Set(existingRows.map((r) => r.id));
|
|
124
|
+
const thunks = [];
|
|
125
|
+
// 3. Per in-code shape: delete (cascade) then recreate.
|
|
126
|
+
for (const { iri, nodeShape } of userShapes) {
|
|
127
|
+
const data = buildNodeShapeData(nodeShape, iri);
|
|
128
|
+
thunks.push(() => DeleteBuilder_js_1.DeleteBuilder.from(SHACL_js_1.NodeShape, { id: iri }).exec()
|
|
129
|
+
.then(() => (SHACL_js_1.NodeShape.create(data).withId(iri).exec()))
|
|
130
|
+
.then(() => undefined));
|
|
131
|
+
}
|
|
132
|
+
// 4. Orphan shapes (in store, not in code) → delete (cascade cleans their owned subtree).
|
|
133
|
+
for (const iri of existingShapeIris) {
|
|
134
|
+
if (!localShapeIris.has(iri)) {
|
|
135
|
+
thunks.push(() => DeleteBuilder_js_1.DeleteBuilder.from(SHACL_js_1.NodeShape, { id: iri }).exec().then(() => undefined));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return thunks;
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=syncShapes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"syncShapes.js","sourceRoot":"","sources":["../../../src/shapes/syncShapes.ts"],"names":[],"mappings":";;;;;;;;;;;AA2EA,gCA+CC;AA1HD;;;;GAIG;AACH,0DAA0D;AAC1D,yCAAoD;AACpD,kEAA0D;AAC1D,uCAAkC;AAClC,6EAAqE;AAErE;;;GAGG;AACH,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAE1C,mGAAmG;AACnG,SAAS,sBAAsB,CAAC,EAAiB,EAAE,QAAgB;IACjE,MAAM,KAAK,GAAG,GAAG,QAAQ,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;IACxC,MAAM,CAAC,GAA4B;QACjC,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,IAAA,oDAAuB,EAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC;KAC9C,CAAC;IACF,IAAI,EAAE,CAAC,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC1C,IAAI,EAAE,CAAC,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC1C,IAAI,OAAO,EAAE,CAAC,QAAQ,KAAK,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC9D,IAAI,OAAO,EAAE,CAAC,QAAQ,KAAK,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC9D,IAAI,EAAE,CAAC,IAAI;QAAE,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,IAAI,CAAC;IAC9B,IAAI,EAAE,CAAC,WAAW;QAAE,CAAC,CAAC,WAAW,GAAG,EAAE,CAAC,WAAW,CAAC;IACnD,IAAI,OAAO,EAAE,CAAC,KAAK,KAAK,QAAQ;QAAE,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC;IACrD,IAAI,EAAE,CAAC,KAAK;QAAE,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC;IACjC,IAAI,EAAE,CAAC,KAAK;QAAE,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC;IACjC,IAAI,EAAE,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,GAAG,IAAA,iBAAO,EAAC,EAAE,CAAC,EAAE,EAAE,EAAC,IAAI,EAAE,GAAG,KAAK,KAAK,EAAC,CAAC,CAAC;IACxD,IAAI,EAAE,CAAC,gBAAgB;QAAE,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC,gBAAgB,CAAC;IACxD,IAAI,EAAE,CAAC,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC1C,IAAI,EAAE,CAAC,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC;IAC1C,IAAI,EAAE,CAAC,gBAAgB;QAAE,CAAC,CAAC,gBAAgB,GAAG,EAAE,CAAC,gBAAgB,CAAC;IAClE,IAAI,EAAE,CAAC,kBAAkB,KAAK,SAAS;QAAE,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC,kBAAkB,CAAC;IAC5E,IAAI,EAAE,CAAC,UAAU;QAAE,CAAC,CAAC,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC;IAChD,IAAI,EAAE,CAAC,QAAQ;QAAE,CAAC,CAAC,QAAQ,GAAG,IAAI,CAAC;IACnC,OAAO,CAAC,CAAC;AACX,CAAC;AAED,iGAAiG;AACjG,SAAS,kBAAkB,CAAC,SAAoB,EAAE,QAAgB;IAChE,MAAM,CAAC,GAA4B,EAAE,CAAC;IACtC,IAAI,SAAS,CAAC,WAAW;QAAE,CAAC,CAAC,WAAW,GAAG,SAAS,CAAC,WAAW,CAAC;IACjE,IAAI,SAAS,CAAC,WAAW;QAAE,CAAC,CAAC,WAAW,GAAG,SAAS,CAAC,WAAW,CAAC;IACjE,IAAI,SAAS,CAAC,OAAO;QAAE,CAAC,CAAC,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC;IACrD,IAAI,SAAS,CAAC,SAAS;QAAE,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC;IAC5C,IAAI,SAAS,CAAC,MAAM,KAAK,SAAS;QAAE,CAAC,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;IAChE,IAAI,SAAS,CAAC,iBAAiB,IAAI,SAAS,CAAC,iBAAiB,CAAC,MAAM,EAAE,CAAC;QACtE,CAAC,CAAC,iBAAiB,GAAG,SAAS,CAAC,iBAAiB,CAAC;IACpD,CAAC;IACD,qFAAqF;IACrF,CAAC,CAAC,UAAU,GAAG,SAAS;SACrB,uBAAuB,EAAE;SACzB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,sBAAsB,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;IACrD,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAsB,UAAU;;QAC9B,4EAA4E;QAC5E,MAAM,UAAU,GAA+C,EAAE,CAAC;QAClE,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,IAAA,kCAAkB,GAAE,EAAE,CAAC;YACrD,IAAI,CAAC,CAAA,UAAU,aAAV,UAAU,uBAAV,UAAU,CAAE,KAAK,CAAA;gBAAE,SAAS;YACjC,MAAM,GAAG,GAAI,UAAqC,CAAC,WAAW,CAAC;YAC/D,yFAAyF;YACzF,uEAAuE;YACvE,IAAI,CAAC,GAAG,IAAI,GAAG,KAAK,iBAAiB;gBAAE,SAAS;YAChD,UAAU,CAAC,IAAI,CAAC,EAAC,GAAG,EAAE,SAAS,EAAE,UAAU,CAAC,KAAK,EAAC,CAAC,CAAC;QACtD,CAAC;QACD,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAE7D,4EAA4E;QAC5E,MAAM,YAAY,GAAG,CAAC,MAAO,oBAE3B,CAAC,MAAM,EAAE,CAAwB,CAAC;QACpC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAEjE,MAAM,MAAM,GAA+B,EAAE,CAAC;QAE9C,wDAAwD;QACxD,KAAK,MAAM,EAAC,GAAG,EAAE,SAAS,EAAC,IAAI,UAAU,EAAE,CAAC;YAC1C,MAAM,IAAI,GAAG,kBAAkB,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;YAChD,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CACd,gCAAa,CAAC,IAAI,CAAC,oBAAS,EAAE,EAAC,EAAE,EAAE,GAAG,EAAC,CAAC,CAAC,IAAI,EAAuB;iBAClE,IAAI,CAAC,GAAG,EAAE,CACT,CAAE,oBAAS,CAAC,MAAM,CAAC,IAAa,CAAC,CAAC,MAAM,CAAC,GAAG,CAE1C,CAAC,IAAI,EAAE,CAAC,CACX;iBACA,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CACzB,CAAC;QACJ,CAAC;QAED,0FAA0F;QAC1F,KAAK,MAAM,GAAG,IAAI,iBAAiB,EAAE,CAAC;YACpC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC7B,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CACd,gCAAa,CAAC,IAAI,CAAC,oBAAS,EAAE,EAAC,EAAE,EAAE,GAAG,EAAC,CAAC,CAAC,IAAI,EAAuB,CAAC,IAAI,CACxE,GAAG,EAAE,CAAC,SAAS,CAChB,CACF,CAAC;YACJ,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;CAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IRSelectQuery, IRCreateMutation, IRUpdateMutation, IRDeleteMutation, IRDeleteAllMutation, IRDeleteWhereMutation, IRUpdateWhereMutation } from '../queries/IntermediateRepresentation.js';
|
|
2
|
-
import type { SparqlSelectPlan, SparqlInsertDataPlan, SparqlDeleteInsertPlan } from './SparqlAlgebra.js';
|
|
2
|
+
import type { SparqlSelectPlan, SparqlInsertDataPlan, SparqlDeleteInsertPlan, SparqlAlgebraNode, SparqlTriple, SparqlTerm } from './SparqlAlgebra.js';
|
|
3
3
|
import { type SparqlOptions } from './sparqlUtils.js';
|
|
4
4
|
/**
|
|
5
5
|
* Converts an IRSelectQuery to a SparqlSelectPlan.
|
|
@@ -13,6 +13,15 @@ export declare function createToAlgebra(query: IRCreateMutation, options?: Sparq
|
|
|
13
13
|
* Converts an IRUpdateMutation to a SparqlDeleteInsertPlan.
|
|
14
14
|
*/
|
|
15
15
|
export declare function updateToAlgebra(query: IRUpdateMutation, options?: SparqlOptions): SparqlDeleteInsertPlan;
|
|
16
|
+
/**
|
|
17
|
+
* Build DELETE patterns + WHERE OPTIONAL blocks that cascade-delete the owned subtree
|
|
18
|
+
* reachable from `rootTerm` via `contains` edges. `varPrefix` keeps generated variables
|
|
19
|
+
* unique across multiple roots in one query. Returns empty when nothing is owned.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildOwnedCascade(rootTerm: SparqlTerm, varPrefix: string): {
|
|
22
|
+
deletePatterns: SparqlTriple[];
|
|
23
|
+
whereOptionals: SparqlAlgebraNode[];
|
|
24
|
+
};
|
|
16
25
|
/**
|
|
17
26
|
* Converts an IRDeleteMutation to a SparqlDeleteInsertPlan (DELETE + WHERE).
|
|
18
27
|
*/
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.selectToAlgebra = selectToAlgebra;
|
|
4
4
|
exports.createToAlgebra = createToAlgebra;
|
|
5
5
|
exports.updateToAlgebra = updateToAlgebra;
|
|
6
|
+
exports.buildOwnedCascade = buildOwnedCascade;
|
|
6
7
|
exports.deleteToAlgebra = deleteToAlgebra;
|
|
7
8
|
exports.deleteAllToAlgebra = deleteAllToAlgebra;
|
|
8
9
|
exports.deleteWhereToAlgebra = deleteWhereToAlgebra;
|
|
@@ -487,10 +488,19 @@ function selectToAlgebra(query, _options) {
|
|
|
487
488
|
const requiredTraversalAliases = collectRequiredTraversalAliases(query, traversePatternMap);
|
|
488
489
|
const optionalTraversalAliases = new Set([...projectedTraversalAliases].filter((alias) => {
|
|
489
490
|
const pattern = traversePatternMap.get(alias);
|
|
491
|
+
// Lower projection-only traversals into OPTIONAL subtrees regardless of
|
|
492
|
+
// cardinality, so a parent with an empty relationship is preserved (with
|
|
493
|
+
// an empty array / null child) instead of being inner-joined away. The
|
|
494
|
+
// result grouper already collects multiple child bindings into an array.
|
|
495
|
+
//
|
|
496
|
+
// Excluded:
|
|
497
|
+
// - filtered traversals (`.where(...)`) — the filter makes the child required
|
|
498
|
+
// - aliases otherwise required by the query (e.g. used in a top-level filter)
|
|
499
|
+
// - paginated traversals — emitted separately as a sub-SELECT (section 5a),
|
|
500
|
+
// so lowering them here too would double-emit the traverse triple
|
|
490
501
|
return !!pattern &&
|
|
491
|
-
typeof pattern.maxCount === 'number' &&
|
|
492
|
-
pattern.maxCount <= 1 &&
|
|
493
502
|
!pattern.filter &&
|
|
503
|
+
!subSelectAliases.has(alias) &&
|
|
494
504
|
!requiredTraversalAliases.has(alias);
|
|
495
505
|
}));
|
|
496
506
|
const requiredTraverseTriples = traverseTriples.filter((triple) => !(triple.object.kind === 'variable' &&
|
|
@@ -1226,9 +1236,15 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1226
1236
|
const insertPatterns = [];
|
|
1227
1237
|
const oldValueTriples = [];
|
|
1228
1238
|
const extends_ = [];
|
|
1239
|
+
// Owned-subtree cascade: replacing a `contains` property must also
|
|
1240
|
+
// delete the old object's owned subtree, not just the one-hop edge.
|
|
1241
|
+
const { containsPreds: updContainsPreds } = collectContainment();
|
|
1242
|
+
const containsOldVars = [];
|
|
1243
|
+
const cascadeOptionals = [];
|
|
1229
1244
|
for (const field of data.fields) {
|
|
1230
1245
|
const propertyTerm = resolvePropertyPredicateTerm(field.property);
|
|
1231
1246
|
const suffix = propertySuffix(field.property);
|
|
1247
|
+
const isContainsField = propertyTerm.kind === 'iri' && updContainsPreds.includes(propertyTerm.value);
|
|
1232
1248
|
// Check for set modification ({add, remove})
|
|
1233
1249
|
if (field.value &&
|
|
1234
1250
|
typeof field.value === 'object' &&
|
|
@@ -1239,11 +1255,17 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1239
1255
|
('add' in field.value || 'remove' in field.value)) {
|
|
1240
1256
|
const setMod = field.value;
|
|
1241
1257
|
if (setMod.remove) {
|
|
1242
|
-
|
|
1258
|
+
setMod.remove.forEach((removeItem, ri) => {
|
|
1243
1259
|
const removeTerm = iriTerm(removeItem.id);
|
|
1244
1260
|
deletePatterns.push(tripleOf(subjectTerm, propertyTerm, removeTerm));
|
|
1245
1261
|
oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, removeTerm));
|
|
1246
|
-
|
|
1262
|
+
// For a `contains` set, removing a value also removes its owned subtree.
|
|
1263
|
+
if (isContainsField) {
|
|
1264
|
+
const cascade = buildOwnedCascade(removeTerm, `ucr_${suffix}_${ri}_`);
|
|
1265
|
+
deletePatterns.push(...cascade.deletePatterns);
|
|
1266
|
+
cascadeOptionals.push(...cascade.whereOptionals);
|
|
1267
|
+
}
|
|
1268
|
+
});
|
|
1247
1269
|
}
|
|
1248
1270
|
if (setMod.add) {
|
|
1249
1271
|
for (const addItem of setMod.add) {
|
|
@@ -1262,6 +1284,11 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1262
1284
|
}
|
|
1263
1285
|
continue;
|
|
1264
1286
|
}
|
|
1287
|
+
// Non-set-modification replace of a `contains` property: cascade the old value's
|
|
1288
|
+
// subtree (every branch below binds `old_${suffix}` in the WHERE).
|
|
1289
|
+
if (isContainsField) {
|
|
1290
|
+
containsOldVars.push(`old_${suffix}`);
|
|
1291
|
+
}
|
|
1265
1292
|
// Unset (undefined/null) — delete only
|
|
1266
1293
|
if (field.value === undefined || field.value === null) {
|
|
1267
1294
|
const oldVar = varTerm(`old_${suffix}`);
|
|
@@ -1343,7 +1370,19 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1343
1370
|
insertPatterns.push(tripleOf(subjectTerm, propertyTerm, term));
|
|
1344
1371
|
}
|
|
1345
1372
|
}
|
|
1346
|
-
|
|
1373
|
+
// Append cascade for each replaced `contains` property's old value.
|
|
1374
|
+
containsOldVars.forEach((oldVar, n) => {
|
|
1375
|
+
const cascade = buildOwnedCascade(varTerm(oldVar), `uc${n}_`);
|
|
1376
|
+
deletePatterns.push(...cascade.deletePatterns);
|
|
1377
|
+
cascadeOptionals.push(...cascade.whereOptionals);
|
|
1378
|
+
});
|
|
1379
|
+
return {
|
|
1380
|
+
deletePatterns,
|
|
1381
|
+
insertPatterns,
|
|
1382
|
+
oldValueTriples,
|
|
1383
|
+
extends: extends_,
|
|
1384
|
+
cascadeOptionals,
|
|
1385
|
+
};
|
|
1347
1386
|
}
|
|
1348
1387
|
/**
|
|
1349
1388
|
* Wraps old-value triples in OPTIONAL (LEFT JOIN) so UPDATE succeeds
|
|
@@ -1383,6 +1422,10 @@ function updateToAlgebra(query, options) {
|
|
|
1383
1422
|
};
|
|
1384
1423
|
}
|
|
1385
1424
|
}
|
|
1425
|
+
// Owned-subtree cascade OPTIONALs for replaced `contains` properties.
|
|
1426
|
+
for (const optional of result.cascadeOptionals) {
|
|
1427
|
+
whereAlgebra = { type: 'left_join', left: whereAlgebra, right: optional };
|
|
1428
|
+
}
|
|
1386
1429
|
// Add BIND expressions for computed fields
|
|
1387
1430
|
for (const ext of result.extends) {
|
|
1388
1431
|
whereAlgebra = {
|
|
@@ -1399,6 +1442,90 @@ function updateToAlgebra(query, options) {
|
|
|
1399
1442
|
whereAlgebra,
|
|
1400
1443
|
};
|
|
1401
1444
|
}
|
|
1445
|
+
// ---------------------------------------------------------------------------
|
|
1446
|
+
// Owned-subtree cascade
|
|
1447
|
+
//
|
|
1448
|
+
// Composition cleanup driven by two declarative flags:
|
|
1449
|
+
// - a property marked `contains` → the cascade FOLLOWS that edge,
|
|
1450
|
+
// - a shape marked `dependent` → its instances may be DELETED when reached.
|
|
1451
|
+
// Both are read from the live registry, so no vocabulary is hardcoded. From a root
|
|
1452
|
+
// node we follow `(c1|c2|…)+` over all contains predicates and, for each dependent
|
|
1453
|
+
// targetClass, wildcard-delete the reached typed nodes. Requiring an asserted
|
|
1454
|
+
// `?owned a <dependentType>` naturally excludes shared predicate IRIs (e.g. a simple
|
|
1455
|
+
// `sh:path`) and `rdf:nil` (never typed), so only owned structural nodes are removed.
|
|
1456
|
+
// ---------------------------------------------------------------------------
|
|
1457
|
+
/** True when the shape (or an ancestor) declares at least one `contains` property. */
|
|
1458
|
+
function shapeHasContainsProperty(shapeId) {
|
|
1459
|
+
var _a;
|
|
1460
|
+
const nodeShape = (_a = (0, ShapeClass_js_1.getShapeClass)(shapeId)) === null || _a === void 0 ? void 0 : _a.shape;
|
|
1461
|
+
if (!nodeShape || typeof nodeShape.getPropertyShapes !== 'function')
|
|
1462
|
+
return false;
|
|
1463
|
+
return nodeShape
|
|
1464
|
+
.getPropertyShapes(true)
|
|
1465
|
+
.some((ps) => ps.contains);
|
|
1466
|
+
}
|
|
1467
|
+
/** Gather contains-predicate IRIs and dependent targetClass IRIs from the registry. */
|
|
1468
|
+
function collectContainment() {
|
|
1469
|
+
var _a;
|
|
1470
|
+
const containsPreds = new Set();
|
|
1471
|
+
const dependentTypes = new Set();
|
|
1472
|
+
for (const [, shapeClass] of (0, ShapeClass_js_1.getAllShapeClasses)()) {
|
|
1473
|
+
const nodeShape = shapeClass === null || shapeClass === void 0 ? void 0 : shapeClass.shape;
|
|
1474
|
+
if (!nodeShape)
|
|
1475
|
+
continue;
|
|
1476
|
+
if (nodeShape.dependent && ((_a = nodeShape.targetClass) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
1477
|
+
dependentTypes.add(nodeShape.targetClass.id);
|
|
1478
|
+
}
|
|
1479
|
+
const propertyShapes = typeof nodeShape.getPropertyShapes === 'function'
|
|
1480
|
+
? nodeShape.getPropertyShapes(false)
|
|
1481
|
+
: [];
|
|
1482
|
+
for (const ps of propertyShapes) {
|
|
1483
|
+
if (ps.contains) {
|
|
1484
|
+
const predId = (0, normalizePropertyPath_js_1.getSimplePathId)(ps.path);
|
|
1485
|
+
if (predId)
|
|
1486
|
+
containsPreds.add(predId);
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
return { containsPreds: [...containsPreds], dependentTypes: [...dependentTypes] };
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Build DELETE patterns + WHERE OPTIONAL blocks that cascade-delete the owned subtree
|
|
1494
|
+
* reachable from `rootTerm` via `contains` edges. `varPrefix` keeps generated variables
|
|
1495
|
+
* unique across multiple roots in one query. Returns empty when nothing is owned.
|
|
1496
|
+
*/
|
|
1497
|
+
function buildOwnedCascade(rootTerm, varPrefix) {
|
|
1498
|
+
const { containsPreds, dependentTypes } = collectContainment();
|
|
1499
|
+
if (containsPreds.length === 0 || dependentTypes.length === 0) {
|
|
1500
|
+
return { deletePatterns: [], whereOptionals: [] };
|
|
1501
|
+
}
|
|
1502
|
+
// (c1|c2|…)+ — follow one-or-more contains edges from the root.
|
|
1503
|
+
const pathExpr = containsPreds.length === 1
|
|
1504
|
+
? { oneOrMore: { id: containsPreds[0] } }
|
|
1505
|
+
: { oneOrMore: { alt: containsPreds.map((id) => ({ id })) } };
|
|
1506
|
+
const pathTerm = {
|
|
1507
|
+
kind: 'path',
|
|
1508
|
+
value: (0, pathExprToSparql_js_1.pathExprToSparql)(pathExpr),
|
|
1509
|
+
uris: (0, pathExprToSparql_js_1.collectPathUris)(pathExpr),
|
|
1510
|
+
};
|
|
1511
|
+
const deletePatterns = [];
|
|
1512
|
+
const whereOptionals = [];
|
|
1513
|
+
dependentTypes.forEach((typeId, i) => {
|
|
1514
|
+
const owned = varTerm(`${varPrefix}own${i}`);
|
|
1515
|
+
const p = varTerm(`${varPrefix}op${i}`);
|
|
1516
|
+
const o = varTerm(`${varPrefix}ov${i}`);
|
|
1517
|
+
deletePatterns.push(tripleOf(owned, p, o));
|
|
1518
|
+
whereOptionals.push({
|
|
1519
|
+
type: 'bgp',
|
|
1520
|
+
triples: [
|
|
1521
|
+
tripleOf(rootTerm, pathTerm, owned),
|
|
1522
|
+
tripleOf(owned, iriTerm(RDF_TYPE), iriTerm(typeId)),
|
|
1523
|
+
tripleOf(owned, p, o),
|
|
1524
|
+
],
|
|
1525
|
+
});
|
|
1526
|
+
});
|
|
1527
|
+
return { deletePatterns, whereOptionals };
|
|
1528
|
+
}
|
|
1402
1529
|
/**
|
|
1403
1530
|
* Converts an IRDeleteMutation to a SparqlDeleteInsertPlan (DELETE + WHERE).
|
|
1404
1531
|
*/
|
|
@@ -1406,6 +1533,7 @@ function deleteToAlgebra(query, _options) {
|
|
|
1406
1533
|
const deletePatterns = [];
|
|
1407
1534
|
const requiredTriples = [];
|
|
1408
1535
|
const optionalTriples = [];
|
|
1536
|
+
const cascadeOptionals = [];
|
|
1409
1537
|
for (let i = 0; i < query.ids.length; i++) {
|
|
1410
1538
|
const subjectTerm = iriTerm(query.ids[i].id);
|
|
1411
1539
|
const idx = query.ids.length > 1 ? `_${i}` : '';
|
|
@@ -1418,8 +1546,15 @@ function deleteToAlgebra(query, _options) {
|
|
|
1418
1546
|
// object-wildcard is OPTIONAL (entity may have no incoming references)
|
|
1419
1547
|
requiredTriples.push(subjWild, typeGuard);
|
|
1420
1548
|
optionalTriples.push(objWild);
|
|
1549
|
+
// Cascade-delete the owned subtree — only for shapes that actually own something
|
|
1550
|
+
// (have a `contains` property); a plain entity delete is left untouched.
|
|
1551
|
+
if (shapeHasContainsProperty(query.shape)) {
|
|
1552
|
+
const cascade = buildOwnedCascade(subjectTerm, `c${idx}_`);
|
|
1553
|
+
deletePatterns.push(...cascade.deletePatterns);
|
|
1554
|
+
cascadeOptionals.push(...cascade.whereOptionals);
|
|
1555
|
+
}
|
|
1421
1556
|
}
|
|
1422
|
-
// Build WHERE algebra: required BGP + OPTIONAL for each object-wildcard
|
|
1557
|
+
// Build WHERE algebra: required BGP + OPTIONAL for each object-wildcard + cascade OPTIONALs
|
|
1423
1558
|
let whereAlgebra = { type: 'bgp', triples: requiredTriples };
|
|
1424
1559
|
for (const triple of optionalTriples) {
|
|
1425
1560
|
whereAlgebra = {
|
|
@@ -1428,6 +1563,9 @@ function deleteToAlgebra(query, _options) {
|
|
|
1428
1563
|
right: { type: 'bgp', triples: [triple] },
|
|
1429
1564
|
};
|
|
1430
1565
|
}
|
|
1566
|
+
for (const optional of cascadeOptionals) {
|
|
1567
|
+
whereAlgebra = { type: 'left_join', left: whereAlgebra, right: optional };
|
|
1568
|
+
}
|
|
1431
1569
|
return {
|
|
1432
1570
|
type: 'delete_insert',
|
|
1433
1571
|
deletePatterns,
|
|
@@ -1629,6 +1767,10 @@ function updateWhereToAlgebra(query, options) {
|
|
|
1629
1767
|
};
|
|
1630
1768
|
}
|
|
1631
1769
|
}
|
|
1770
|
+
// Owned-subtree cascade OPTIONALs for replaced `contains` properties.
|
|
1771
|
+
for (const optional of result.cascadeOptionals) {
|
|
1772
|
+
whereAlgebra = { type: 'left_join', left: whereAlgebra, right: optional };
|
|
1773
|
+
}
|
|
1632
1774
|
// Add BIND expressions for computed fields
|
|
1633
1775
|
for (const ext of result.extends) {
|
|
1634
1776
|
whereAlgebra = {
|