@_linked/core 2.12.0 → 2.13.0-next.20260706164859

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.
Files changed (246) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/lib/esm/queries/IRPipeline.d.ts +7 -1
  3. package/lib/esm/queries/IRPipeline.js +20 -6
  4. package/lib/esm/queries/IRPipeline.js.map +1 -1
  5. package/lib/esm/queries/queryDispatch.js +12 -2
  6. package/lib/esm/queries/queryDispatch.js.map +1 -1
  7. package/package.json +10 -13
  8. package/lib/cjs/collections/CoreSet.d.ts +0 -47
  9. package/lib/cjs/collections/CoreSet.js +0 -136
  10. package/lib/cjs/collections/CoreSet.js.map +0 -1
  11. package/lib/cjs/collections/ShapeSet.d.ts +0 -6
  12. package/lib/cjs/collections/ShapeSet.js +0 -20
  13. package/lib/cjs/collections/ShapeSet.js.map +0 -1
  14. package/lib/cjs/expressions/Expr.d.ts +0 -29
  15. package/lib/cjs/expressions/Expr.js +0 -80
  16. package/lib/cjs/expressions/Expr.js.map +0 -1
  17. package/lib/cjs/expressions/ExpressionMethods.d.ts +0 -85
  18. package/lib/cjs/expressions/ExpressionMethods.js +0 -3
  19. package/lib/cjs/expressions/ExpressionMethods.js.map +0 -1
  20. package/lib/cjs/expressions/ExpressionNode.d.ts +0 -144
  21. package/lib/cjs/expressions/ExpressionNode.js +0 -432
  22. package/lib/cjs/expressions/ExpressionNode.js.map +0 -1
  23. package/lib/cjs/index.d.ts +0 -29
  24. package/lib/cjs/index.js +0 -65
  25. package/lib/cjs/index.js.map +0 -1
  26. package/lib/cjs/interfaces/ICoreIterable.d.ts +0 -14
  27. package/lib/cjs/interfaces/ICoreIterable.js +0 -3
  28. package/lib/cjs/interfaces/ICoreIterable.js.map +0 -1
  29. package/lib/cjs/interfaces/IDataset.d.ts +0 -26
  30. package/lib/cjs/interfaces/IDataset.js +0 -3
  31. package/lib/cjs/interfaces/IDataset.js.map +0 -1
  32. package/lib/cjs/interfaces/IFileStore.d.ts +0 -16
  33. package/lib/cjs/interfaces/IFileStore.js +0 -3
  34. package/lib/cjs/interfaces/IFileStore.js.map +0 -1
  35. package/lib/cjs/ontologies/linked-core.d.ts +0 -15
  36. package/lib/cjs/ontologies/linked-core.js +0 -38
  37. package/lib/cjs/ontologies/linked-core.js.map +0 -1
  38. package/lib/cjs/ontologies/npm.d.ts +0 -6
  39. package/lib/cjs/ontologies/npm.js +0 -15
  40. package/lib/cjs/ontologies/npm.js.map +0 -1
  41. package/lib/cjs/ontologies/owl.d.ts +0 -14
  42. package/lib/cjs/ontologies/owl.js +0 -21
  43. package/lib/cjs/ontologies/owl.js.map +0 -1
  44. package/lib/cjs/ontologies/rdf.d.ts +0 -13
  45. package/lib/cjs/ontologies/rdf.js +0 -27
  46. package/lib/cjs/ontologies/rdf.js.map +0 -1
  47. package/lib/cjs/ontologies/rdfs.d.ts +0 -16
  48. package/lib/cjs/ontologies/rdfs.js +0 -33
  49. package/lib/cjs/ontologies/rdfs.js.map +0 -1
  50. package/lib/cjs/ontologies/shacl.d.ts +0 -103
  51. package/lib/cjs/ontologies/shacl.js +0 -152
  52. package/lib/cjs/ontologies/shacl.js.map +0 -1
  53. package/lib/cjs/ontologies/xsd.d.ts +0 -19
  54. package/lib/cjs/ontologies/xsd.js +0 -41
  55. package/lib/cjs/ontologies/xsd.js.map +0 -1
  56. package/lib/cjs/package.d.ts +0 -4
  57. package/lib/cjs/package.js +0 -6
  58. package/lib/cjs/package.js.map +0 -1
  59. package/lib/cjs/package.json +0 -9
  60. package/lib/cjs/paths/PropertyPathExpr.d.ts +0 -38
  61. package/lib/cjs/paths/PropertyPathExpr.js +0 -221
  62. package/lib/cjs/paths/PropertyPathExpr.js.map +0 -1
  63. package/lib/cjs/paths/normalizePropertyPath.d.ts +0 -23
  64. package/lib/cjs/paths/normalizePropertyPath.js +0 -75
  65. package/lib/cjs/paths/normalizePropertyPath.js.map +0 -1
  66. package/lib/cjs/paths/pathExprToSparql.d.ts +0 -14
  67. package/lib/cjs/paths/pathExprToSparql.js +0 -136
  68. package/lib/cjs/paths/pathExprToSparql.js.map +0 -1
  69. package/lib/cjs/queries/ContextRef.d.ts +0 -32
  70. package/lib/cjs/queries/ContextRef.js +0 -69
  71. package/lib/cjs/queries/ContextRef.js.map +0 -1
  72. package/lib/cjs/queries/CreateBuilder.d.ts +0 -59
  73. package/lib/cjs/queries/CreateBuilder.js +0 -138
  74. package/lib/cjs/queries/CreateBuilder.js.map +0 -1
  75. package/lib/cjs/queries/CreateQuery.d.ts +0 -20
  76. package/lib/cjs/queries/CreateQuery.js +0 -3
  77. package/lib/cjs/queries/CreateQuery.js.map +0 -1
  78. package/lib/cjs/queries/DeleteBuilder.d.ts +0 -56
  79. package/lib/cjs/queries/DeleteBuilder.js +0 -172
  80. package/lib/cjs/queries/DeleteBuilder.js.map +0 -1
  81. package/lib/cjs/queries/DeleteQuery.d.ts +0 -37
  82. package/lib/cjs/queries/DeleteQuery.js +0 -3
  83. package/lib/cjs/queries/DeleteQuery.js.map +0 -1
  84. package/lib/cjs/queries/DslJsonExpression.d.ts +0 -68
  85. package/lib/cjs/queries/DslJsonExpression.js +0 -427
  86. package/lib/cjs/queries/DslJsonExpression.js.map +0 -1
  87. package/lib/cjs/queries/FieldSet.d.ts +0 -310
  88. package/lib/cjs/queries/FieldSet.js +0 -802
  89. package/lib/cjs/queries/FieldSet.js.map +0 -1
  90. package/lib/cjs/queries/IRAliasScope.d.ts +0 -20
  91. package/lib/cjs/queries/IRAliasScope.js +0 -52
  92. package/lib/cjs/queries/IRAliasScope.js.map +0 -1
  93. package/lib/cjs/queries/IRCanonicalize.d.ts +0 -19
  94. package/lib/cjs/queries/IRCanonicalize.js +0 -19
  95. package/lib/cjs/queries/IRCanonicalize.js.map +0 -1
  96. package/lib/cjs/queries/IRDesugar.d.ts +0 -123
  97. package/lib/cjs/queries/IRDesugar.js +0 -261
  98. package/lib/cjs/queries/IRDesugar.js.map +0 -1
  99. package/lib/cjs/queries/IRLower.d.ts +0 -25
  100. package/lib/cjs/queries/IRLower.js +0 -398
  101. package/lib/cjs/queries/IRLower.js.map +0 -1
  102. package/lib/cjs/queries/IRMutation.d.ts +0 -53
  103. package/lib/cjs/queries/IRMutation.js +0 -130
  104. package/lib/cjs/queries/IRMutation.js.map +0 -1
  105. package/lib/cjs/queries/IRPipeline.d.ts +0 -8
  106. package/lib/cjs/queries/IRPipeline.js +0 -25
  107. package/lib/cjs/queries/IRPipeline.js.map +0 -1
  108. package/lib/cjs/queries/IRProjection.d.ts +0 -39
  109. package/lib/cjs/queries/IRProjection.js +0 -105
  110. package/lib/cjs/queries/IRProjection.js.map +0 -1
  111. package/lib/cjs/queries/IntermediateRepresentation.d.ts +0 -282
  112. package/lib/cjs/queries/IntermediateRepresentation.js +0 -3
  113. package/lib/cjs/queries/IntermediateRepresentation.js.map +0 -1
  114. package/lib/cjs/queries/MutationQuery.d.ts +0 -50
  115. package/lib/cjs/queries/MutationQuery.js +0 -334
  116. package/lib/cjs/queries/MutationQuery.js.map +0 -1
  117. package/lib/cjs/queries/MutationSerialization.d.ts +0 -102
  118. package/lib/cjs/queries/MutationSerialization.js +0 -207
  119. package/lib/cjs/queries/MutationSerialization.js.map +0 -1
  120. package/lib/cjs/queries/MutationThenable.d.ts +0 -18
  121. package/lib/cjs/queries/MutationThenable.js +0 -32
  122. package/lib/cjs/queries/MutationThenable.js.map +0 -1
  123. package/lib/cjs/queries/PropertyPath.d.ts +0 -38
  124. package/lib/cjs/queries/PropertyPath.js +0 -82
  125. package/lib/cjs/queries/PropertyPath.js.map +0 -1
  126. package/lib/cjs/queries/ProxiedPathBuilder.d.ts +0 -14
  127. package/lib/cjs/queries/ProxiedPathBuilder.js +0 -29
  128. package/lib/cjs/queries/ProxiedPathBuilder.js.map +0 -1
  129. package/lib/cjs/queries/QueryBuilder.d.ts +0 -193
  130. package/lib/cjs/queries/QueryBuilder.js +0 -519
  131. package/lib/cjs/queries/QueryBuilder.js.map +0 -1
  132. package/lib/cjs/queries/QueryBuilderSerialization.d.ts +0 -31
  133. package/lib/cjs/queries/QueryBuilderSerialization.js +0 -68
  134. package/lib/cjs/queries/QueryBuilderSerialization.js.map +0 -1
  135. package/lib/cjs/queries/QueryContext.d.ts +0 -50
  136. package/lib/cjs/queries/QueryContext.js +0 -147
  137. package/lib/cjs/queries/QueryContext.js.map +0 -1
  138. package/lib/cjs/queries/QueryFactory.d.ts +0 -80
  139. package/lib/cjs/queries/QueryFactory.js +0 -21
  140. package/lib/cjs/queries/QueryFactory.js.map +0 -1
  141. package/lib/cjs/queries/SelectQuery.d.ts +0 -302
  142. package/lib/cjs/queries/SelectQuery.js +0 -761
  143. package/lib/cjs/queries/SelectQuery.js.map +0 -1
  144. package/lib/cjs/queries/UpdateBuilder.d.ts +0 -69
  145. package/lib/cjs/queries/UpdateBuilder.js +0 -199
  146. package/lib/cjs/queries/UpdateBuilder.js.map +0 -1
  147. package/lib/cjs/queries/UpdateQuery.d.ts +0 -18
  148. package/lib/cjs/queries/UpdateQuery.js +0 -3
  149. package/lib/cjs/queries/UpdateQuery.js.map +0 -1
  150. package/lib/cjs/queries/fromJSON.d.ts +0 -16
  151. package/lib/cjs/queries/fromJSON.js +0 -41
  152. package/lib/cjs/queries/fromJSON.js.map +0 -1
  153. package/lib/cjs/queries/lower.d.ts +0 -28
  154. package/lib/cjs/queries/lower.js +0 -114
  155. package/lib/cjs/queries/lower.js.map +0 -1
  156. package/lib/cjs/queries/lowerMutationJSON.d.ts +0 -25
  157. package/lib/cjs/queries/lowerMutationJSON.js +0 -173
  158. package/lib/cjs/queries/lowerMutationJSON.js.map +0 -1
  159. package/lib/cjs/queries/mutationLowerSpec.d.ts +0 -32
  160. package/lib/cjs/queries/mutationLowerSpec.js +0 -8
  161. package/lib/cjs/queries/mutationLowerSpec.js.map +0 -1
  162. package/lib/cjs/queries/queryDispatch.d.ts +0 -33
  163. package/lib/cjs/queries/queryDispatch.js +0 -33
  164. package/lib/cjs/queries/queryDispatch.js.map +0 -1
  165. package/lib/cjs/queries/resolveShape.d.ts +0 -10
  166. package/lib/cjs/queries/resolveShape.js +0 -23
  167. package/lib/cjs/queries/resolveShape.js.map +0 -1
  168. package/lib/cjs/queries/wireVersion.d.ts +0 -7
  169. package/lib/cjs/queries/wireVersion.js +0 -25
  170. package/lib/cjs/queries/wireVersion.js.map +0 -1
  171. package/lib/cjs/shapes/List.d.ts +0 -35
  172. package/lib/cjs/shapes/List.js +0 -72
  173. package/lib/cjs/shapes/List.js.map +0 -1
  174. package/lib/cjs/shapes/PathNode.d.ts +0 -20
  175. package/lib/cjs/shapes/PathNode.js +0 -68
  176. package/lib/cjs/shapes/PathNode.js.map +0 -1
  177. package/lib/cjs/shapes/SHACL.d.ts +0 -300
  178. package/lib/cjs/shapes/SHACL.js +0 -612
  179. package/lib/cjs/shapes/SHACL.js.map +0 -1
  180. package/lib/cjs/shapes/Shape.d.ts +0 -74
  181. package/lib/cjs/shapes/Shape.js +0 -124
  182. package/lib/cjs/shapes/Shape.js.map +0 -1
  183. package/lib/cjs/shapes/serializePathToNodeData.d.ts +0 -24
  184. package/lib/cjs/shapes/serializePathToNodeData.js +0 -67
  185. package/lib/cjs/shapes/serializePathToNodeData.js.map +0 -1
  186. package/lib/cjs/shapes/syncShapes.d.ts +0 -38
  187. package/lib/cjs/shapes/syncShapes.js +0 -214
  188. package/lib/cjs/shapes/syncShapes.js.map +0 -1
  189. package/lib/cjs/sparql/SparqlAlgebra.d.ts +0 -190
  190. package/lib/cjs/sparql/SparqlAlgebra.js +0 -4
  191. package/lib/cjs/sparql/SparqlAlgebra.js.map +0 -1
  192. package/lib/cjs/sparql/SparqlDataset.d.ts +0 -60
  193. package/lib/cjs/sparql/SparqlDataset.js +0 -117
  194. package/lib/cjs/sparql/SparqlDataset.js.map +0 -1
  195. package/lib/cjs/sparql/algebraToString.d.ts +0 -13
  196. package/lib/cjs/sparql/algebraToString.js +0 -385
  197. package/lib/cjs/sparql/algebraToString.js.map +0 -1
  198. package/lib/cjs/sparql/index.d.ts +0 -9
  199. package/lib/cjs/sparql/index.js +0 -41
  200. package/lib/cjs/sparql/index.js.map +0 -1
  201. package/lib/cjs/sparql/irToAlgebra.d.ts +0 -79
  202. package/lib/cjs/sparql/irToAlgebra.js +0 -2042
  203. package/lib/cjs/sparql/irToAlgebra.js.map +0 -1
  204. package/lib/cjs/sparql/resultMapping.d.ts +0 -36
  205. package/lib/cjs/sparql/resultMapping.js +0 -563
  206. package/lib/cjs/sparql/resultMapping.js.map +0 -1
  207. package/lib/cjs/sparql/sparqlUtils.d.ts +0 -53
  208. package/lib/cjs/sparql/sparqlUtils.js +0 -154
  209. package/lib/cjs/sparql/sparqlUtils.js.map +0 -1
  210. package/lib/cjs/utils/LinkedErrorLogging.d.ts +0 -13
  211. package/lib/cjs/utils/LinkedErrorLogging.js +0 -22
  212. package/lib/cjs/utils/LinkedErrorLogging.js.map +0 -1
  213. package/lib/cjs/utils/LinkedFileStorage.d.ts +0 -23
  214. package/lib/cjs/utils/LinkedFileStorage.js +0 -62
  215. package/lib/cjs/utils/LinkedFileStorage.js.map +0 -1
  216. package/lib/cjs/utils/LinkedStorage.d.ts +0 -40
  217. package/lib/cjs/utils/LinkedStorage.js +0 -148
  218. package/lib/cjs/utils/LinkedStorage.js.map +0 -1
  219. package/lib/cjs/utils/NameSpace.d.ts +0 -2
  220. package/lib/cjs/utils/NameSpace.js +0 -8
  221. package/lib/cjs/utils/NameSpace.js.map +0 -1
  222. package/lib/cjs/utils/NodeReference.d.ts +0 -30
  223. package/lib/cjs/utils/NodeReference.js +0 -54
  224. package/lib/cjs/utils/NodeReference.js.map +0 -1
  225. package/lib/cjs/utils/Package.d.ts +0 -153
  226. package/lib/cjs/utils/Package.js +0 -419
  227. package/lib/cjs/utils/Package.js.map +0 -1
  228. package/lib/cjs/utils/Prefix.d.ts +0 -22
  229. package/lib/cjs/utils/Prefix.js +0 -104
  230. package/lib/cjs/utils/Prefix.js.map +0 -1
  231. package/lib/cjs/utils/ShapeClass.d.ts +0 -30
  232. package/lib/cjs/utils/ShapeClass.js +0 -180
  233. package/lib/cjs/utils/ShapeClass.js.map +0 -1
  234. package/lib/cjs/utils/URI.d.ts +0 -18
  235. package/lib/cjs/utils/URI.js +0 -42
  236. package/lib/cjs/utils/URI.js.map +0 -1
  237. package/lib/cjs/utils/cached.d.ts +0 -14
  238. package/lib/cjs/utils/cached.js +0 -71
  239. package/lib/cjs/utils/cached.js.map +0 -1
  240. package/lib/cjs/utils/loadStores.d.ts +0 -34
  241. package/lib/cjs/utils/loadStores.js +0 -105
  242. package/lib/cjs/utils/loadStores.js.map +0 -1
  243. package/lib/cjs/utils/parseDatasetsConfig.d.ts +0 -49
  244. package/lib/cjs/utils/parseDatasetsConfig.js +0 -110
  245. package/lib/cjs/utils/parseDatasetsConfig.js.map +0 -1
  246. package/lib/esm/package.json +0 -9
@@ -1,2042 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.selectToAlgebra = selectToAlgebra;
4
- exports.createToAlgebra = createToAlgebra;
5
- exports.updateToAlgebra = updateToAlgebra;
6
- exports.buildOwnedCascade = buildOwnedCascade;
7
- exports.deleteToAlgebra = deleteToAlgebra;
8
- exports.deleteAllToAlgebra = deleteAllToAlgebra;
9
- exports.deleteWhereToAlgebra = deleteWhereToAlgebra;
10
- exports.updateWhereToAlgebra = updateWhereToAlgebra;
11
- exports.selectToSparql = selectToSparql;
12
- exports.createToSparql = createToSparql;
13
- exports.updateToSparql = updateToSparql;
14
- exports.deleteToSparql = deleteToSparql;
15
- exports.deleteAllToSparql = deleteAllToSparql;
16
- exports.deleteWhereToSparql = deleteWhereToSparql;
17
- exports.updateWhereToSparql = updateWhereToSparql;
18
- const pathExprToSparql_js_1 = require("../paths/pathExprToSparql.js");
19
- const sparqlUtils_js_1 = require("./sparqlUtils.js");
20
- const algebraToString_js_1 = require("./algebraToString.js");
21
- const rdf_js_1 = require("../ontologies/rdf.js");
22
- const shacl_js_1 = require("../ontologies/shacl.js");
23
- const xsd_js_1 = require("../ontologies/xsd.js");
24
- const normalizePropertyPath_js_1 = require("../paths/normalizePropertyPath.js");
25
- const ShapeClass_js_1 = require("../utils/ShapeClass.js");
26
- const QueryContext_js_1 = require("../queries/QueryContext.js");
27
- // ---------------------------------------------------------------------------
28
- // Constants
29
- // ---------------------------------------------------------------------------
30
- const RDF_TYPE = rdf_js_1.rdf.type.id;
31
- const XSD_DATETIME = xsd_js_1.xsd.dateTime.id;
32
- const XSD_BOOLEAN = xsd_js_1.xsd.boolean.id;
33
- const XSD_INTEGER = xsd_js_1.xsd.integer.id;
34
- const XSD_DOUBLE = xsd_js_1.xsd.double.id;
35
- // ---------------------------------------------------------------------------
36
- // Helpers
37
- // ---------------------------------------------------------------------------
38
- function iriTerm(value) {
39
- return { kind: 'iri', value };
40
- }
41
- function varTerm(name) {
42
- return { kind: 'variable', name };
43
- }
44
- function literalTerm(value, datatype) {
45
- if (datatype) {
46
- return { kind: 'literal', value, datatype };
47
- }
48
- return { kind: 'literal', value };
49
- }
50
- function tripleOf(subject, predicate, object) {
51
- return { subject, predicate, object };
52
- }
53
- function shouldResolveShapeOrPropertyId(resolvedId) {
54
- return !!resolvedId && !resolvedId.startsWith('linked://tmp/');
55
- }
56
- function resolveShapeScanIri(shapeId) {
57
- var _a;
58
- const shapeClass = (0, ShapeClass_js_1.getShapeClass)(shapeId);
59
- const targetClassId = (_a = shapeClass === null || shapeClass === void 0 ? void 0 : shapeClass.targetClass) === null || _a === void 0 ? void 0 : _a.id;
60
- return shouldResolveShapeOrPropertyId(targetClassId) ? targetClassId : shapeId;
61
- }
62
- /**
63
- * Resolve a SHACL property-shape id to the predicate term used in a triple.
64
- *
65
- * - Simple single-IRI path → `{kind:'iri'}` (byte-for-byte unchanged behaviour).
66
- * - Structured path (sequence / inverse / alternative / …) → `{kind:'path'}` built from the property
67
- * shape's `sh:path`, reusing the same `pathExprToSparql` / `collectPathUris` machinery as the
68
- * inline-`pathExpr` branches. Without this, structured named-property paths collapsed to a shadow IRI
69
- * that matched nothing.
70
- * - No matching shape / unresolvable → `{kind:'iri'}` of the property id itself (unchanged shadow fallback).
71
- */
72
- // Memoizes resolved predicate terms across the many call sites that resolve the
73
- // same property. Guarded by the shape-registry size so it self-invalidates when
74
- // new shapes register (e.g. across test-file module instances). Only successful
75
- // resolutions are cached — a not-found fallback is never stored, so a predicate
76
- // resolved before its shape registers can still resolve correctly afterwards.
77
- const predicateTermCache = new Map();
78
- let predicateTermCacheSize = -1;
79
- function resolvePropertyPredicateTerm(propertyId) {
80
- var _a, _b, _c;
81
- const shapeClasses = (0, ShapeClass_js_1.getAllShapeClasses)();
82
- if (shapeClasses.size !== predicateTermCacheSize) {
83
- predicateTermCache.clear();
84
- predicateTermCacheSize = shapeClasses.size;
85
- }
86
- const cached = predicateTermCache.get(propertyId);
87
- if (cached)
88
- return cached;
89
- for (const shapeClass of shapeClasses.values()) {
90
- const propertyShape = (_c = (_b = (_a = shapeClass.shape) === null || _a === void 0 ? void 0 : _a.getPropertyShapes) === null || _b === void 0 ? void 0 : _b.call(_a, true)) === null || _c === void 0 ? void 0 : _c.find((prop) => prop.id === propertyId);
91
- if (!propertyShape)
92
- continue;
93
- const simplePathId = (0, normalizePropertyPath_js_1.getSimplePathId)(propertyShape.path);
94
- let term;
95
- if (simplePathId !== null) {
96
- // Simple single-IRI path: resolve to the IRI, or fall back to the property id
97
- // (e.g. unresolvable `linked://tmp/` ids) — unchanged from before.
98
- term = iriTerm(shouldResolveShapeOrPropertyId(simplePathId) ? simplePathId : propertyId);
99
- }
100
- else {
101
- // Structured sh:path — emit a property-path predicate instead of a shadow IRI.
102
- term = {
103
- kind: 'path',
104
- value: (0, pathExprToSparql_js_1.pathExprToSparql)(propertyShape.path),
105
- uris: (0, pathExprToSparql_js_1.collectPathUris)(propertyShape.path),
106
- };
107
- }
108
- predicateTermCache.set(propertyId, term);
109
- return term;
110
- }
111
- return iriTerm(propertyId);
112
- }
113
- /**
114
- * Resolve the predicate term for a traversal/property node: a structured
115
- * property-path when an explicit `pathExpr` is present, otherwise the
116
- * registry-resolved predicate for `property`. Centralizes the invariant that
117
- * these two branches stay in lockstep across every triple-building site.
118
- */
119
- function buildPredicateTerm(spec) {
120
- return spec.pathExpr
121
- ? {
122
- kind: 'path',
123
- value: (0, pathExprToSparql_js_1.pathExprToSparql)(spec.pathExpr),
124
- uris: (0, pathExprToSparql_js_1.collectPathUris)(spec.pathExpr),
125
- }
126
- : resolvePropertyPredicateTerm(spec.property);
127
- }
128
- /** Produce variable name suffix from the last segment of a property URI. */
129
- function propertySuffix(propertyUri) {
130
- const hashIdx = propertyUri.lastIndexOf('#');
131
- if (hashIdx >= 0)
132
- return propertyUri.substring(hashIdx + 1);
133
- const slashIdx = propertyUri.lastIndexOf('/');
134
- return slashIdx >= 0 ? propertyUri.substring(slashIdx + 1) : propertyUri;
135
- }
136
- /**
137
- * Sanitize a string so it's valid in a SPARQL variable name.
138
- * Replaces any non-alphanumeric/underscore characters with underscores.
139
- */
140
- function sanitizeVarName(name) {
141
- return name.replace(/[^A-Za-z0-9_]/g, '_');
142
- }
143
- const IR_EXPRESSION_KINDS = new Set([
144
- 'literal_expr', 'property_expr', 'binary_expr', 'logical_expr',
145
- 'not_expr', 'function_expr', 'aggregate_expr', 'reference_expr',
146
- 'alias_expr', 'context_property_expr', 'exists_expr', 'in_expr',
147
- ]);
148
- function isIRExpression(value) {
149
- return !!value && typeof value === 'object' && 'kind' in value &&
150
- typeof value.kind === 'string' &&
151
- IR_EXPRESSION_KINDS.has(value.kind);
152
- }
153
- /**
154
- * Wrap a single node in a LeftJoin, making `right` optional relative to `left`.
155
- */
156
- function wrapOptional(left, right) {
157
- return { type: 'left_join', left, right };
158
- }
159
- /**
160
- * Join two algebra nodes. If left is null, returns right.
161
- */
162
- function joinNodes(left, right) {
163
- if (!left)
164
- return right;
165
- return { type: 'join', left, right };
166
- }
167
- function bindingKey(alias, property) {
168
- return `${alias}::${property}`;
169
- }
170
- function contextAliasKey(contextIri) {
171
- return `__ctx__${contextIri}`;
172
- }
173
- /**
174
- * Defense-in-depth: a `reference_expr`/`context_property_expr` must arrive here with its
175
- * IRI already resolved (lowering's `resolveContextRefs` fills it from `contextName`). If it
176
- * didn't — an unresolved `{@ctx}` reaching the algebra through some path that bypassed
177
- * resolution — fail with a clear `UnresolvedContextError` instead of an opaque
178
- * `undefined.substring` crash deeper in URI formatting.
179
- */
180
- function resolvedContextIri(iri, contextName) {
181
- if (iri === undefined) {
182
- throw new QueryContext_js_1.UnresolvedContextError(contextName !== null && contextName !== void 0 ? contextName : '<unknown>');
183
- }
184
- return iri;
185
- }
186
- function mergeKeySets(...sets) {
187
- const merged = new Set();
188
- for (const set of sets) {
189
- for (const key of set) {
190
- merged.add(key);
191
- }
192
- }
193
- return merged;
194
- }
195
- function intersectKeySets(sets) {
196
- if (sets.length === 0) {
197
- return new Set();
198
- }
199
- const [first, ...rest] = sets;
200
- const intersection = new Set(first);
201
- for (const value of intersection) {
202
- if (!rest.every((set) => set.has(value))) {
203
- intersection.delete(value);
204
- }
205
- }
206
- return intersection;
207
- }
208
- // ---------------------------------------------------------------------------
209
- // Pattern helpers
210
- // ---------------------------------------------------------------------------
211
- /**
212
- * Recursively collects all traversal alias target variables from IR patterns.
213
- * Used to ensure traversal aliases appear in the SELECT projection for result grouping.
214
- */
215
- function collectTraversalAliases(patterns) {
216
- const aliases = [];
217
- for (const p of patterns) {
218
- if (p.kind === 'traverse') {
219
- aliases.push(p.to);
220
- }
221
- else if (p.kind === 'join') {
222
- aliases.push(...collectTraversalAliases(p.patterns));
223
- }
224
- else if (p.kind === 'optional') {
225
- aliases.push(...collectTraversalAliases([p.pattern]));
226
- }
227
- else if (p.kind === 'union') {
228
- for (const branch of p.branches) {
229
- aliases.push(...collectTraversalAliases([branch]));
230
- }
231
- }
232
- }
233
- return aliases;
234
- }
235
- function buildTraverseTriple(pattern) {
236
- const predicate = buildPredicateTerm(pattern);
237
- return tripleOf(varTerm(pattern.from), predicate, varTerm(pattern.to));
238
- }
239
- function collectTraversePatternsInOrder(patterns, out = []) {
240
- for (const pattern of patterns) {
241
- switch (pattern.kind) {
242
- case 'traverse':
243
- out.push(pattern);
244
- break;
245
- case 'join':
246
- collectTraversePatternsInOrder(pattern.patterns, out);
247
- break;
248
- case 'optional':
249
- case 'exists':
250
- collectTraversePatternsInOrder([pattern.pattern], out);
251
- break;
252
- case 'union':
253
- for (const branch of pattern.branches) {
254
- collectTraversePatternsInOrder([branch], out);
255
- }
256
- break;
257
- case 'shape_scan':
258
- case 'minus':
259
- break;
260
- }
261
- }
262
- return out;
263
- }
264
- function collectDirectProjectionAliases(query, rootAlias) {
265
- const aliases = new Set();
266
- for (const item of query.projection) {
267
- if (item.expression.kind === 'property_expr') {
268
- if (item.expression.sourceAlias !== rootAlias) {
269
- aliases.add(item.expression.sourceAlias);
270
- }
271
- }
272
- else if (item.expression.kind === 'alias_expr') {
273
- if (item.expression.alias !== rootAlias) {
274
- aliases.add(item.expression.alias);
275
- }
276
- }
277
- }
278
- return aliases;
279
- }
280
- function collectExpressionAliases(expr, aliases) {
281
- switch (expr.kind) {
282
- case 'property_expr':
283
- aliases.add(expr.sourceAlias);
284
- break;
285
- case 'alias_expr':
286
- aliases.add(expr.alias);
287
- break;
288
- case 'binary_expr':
289
- collectExpressionAliases(expr.left, aliases);
290
- collectExpressionAliases(expr.right, aliases);
291
- break;
292
- case 'in_expr':
293
- collectExpressionAliases(expr.value, aliases);
294
- for (const el of expr.source.list)
295
- collectExpressionAliases(el, aliases);
296
- break;
297
- case 'logical_expr':
298
- for (const sub of expr.expressions) {
299
- collectExpressionAliases(sub, aliases);
300
- }
301
- break;
302
- case 'not_expr':
303
- collectExpressionAliases(expr.expression, aliases);
304
- break;
305
- case 'function_expr':
306
- case 'aggregate_expr':
307
- for (const arg of expr.args) {
308
- collectExpressionAliases(arg, aliases);
309
- }
310
- break;
311
- case 'exists_expr':
312
- if (expr.filter) {
313
- collectExpressionAliases(expr.filter, aliases);
314
- }
315
- break;
316
- case 'literal_expr':
317
- case 'reference_expr':
318
- case 'context_property_expr':
319
- break;
320
- }
321
- }
322
- function markAliasAndAncestors(alias, traversePatternMap, target) {
323
- var _a;
324
- let currentAlias = alias;
325
- while (currentAlias && traversePatternMap.has(currentAlias)) {
326
- if (target.has(currentAlias)) {
327
- return;
328
- }
329
- target.add(currentAlias);
330
- currentAlias = (_a = traversePatternMap.get(currentAlias)) === null || _a === void 0 ? void 0 : _a.from;
331
- }
332
- }
333
- function collectRequiredTraversalAliases(query, traversePatternMap) {
334
- const directAliases = new Set();
335
- if (query.where) {
336
- collectExpressionAliases(query.where, directAliases);
337
- }
338
- if (query.orderBy) {
339
- for (const item of query.orderBy) {
340
- collectExpressionAliases(item.expression, directAliases);
341
- }
342
- }
343
- for (const pattern of traversePatternMap.values()) {
344
- if (pattern.filter) {
345
- directAliases.add(pattern.to);
346
- }
347
- }
348
- const requiredAliases = new Set();
349
- for (const alias of directAliases) {
350
- markAliasAndAncestors(alias, traversePatternMap, requiredAliases);
351
- }
352
- return requiredAliases;
353
- }
354
- function buildOptionalTraversalSubtree(alias, traversePatternMap, childOptionalAliasesByParent, propertyTriplesByAlias) {
355
- var _a, _b;
356
- const pattern = traversePatternMap.get(alias);
357
- if (!pattern) {
358
- throw new Error(`Missing traverse pattern for alias "${alias}"`);
359
- }
360
- let subtree = {
361
- type: 'bgp',
362
- triples: [buildTraverseTriple(pattern)],
363
- };
364
- for (const propTriple of (_a = propertyTriplesByAlias.get(alias)) !== null && _a !== void 0 ? _a : []) {
365
- subtree = wrapOptional(subtree, {
366
- type: 'bgp',
367
- triples: [propTriple],
368
- });
369
- }
370
- for (const childAlias of (_b = childOptionalAliasesByParent.get(alias)) !== null && _b !== void 0 ? _b : []) {
371
- subtree = wrapOptional(subtree, buildOptionalTraversalSubtree(childAlias, traversePatternMap, childOptionalAliasesByParent, propertyTriplesByAlias));
372
- }
373
- return subtree;
374
- }
375
- // ---------------------------------------------------------------------------
376
- // Variable Registry
377
- // ---------------------------------------------------------------------------
378
- /**
379
- * Maps (alias, property) → SPARQL variable name.
380
- * Used to deduplicate variables across traverse and property_expr nodes.
381
- */
382
- class VariableRegistry {
383
- constructor() {
384
- this.map = new Map();
385
- this.usedVarNames = new Set();
386
- }
387
- key(alias, property) {
388
- return bindingKey(alias, property);
389
- }
390
- has(alias, property) {
391
- return this.map.has(this.key(alias, property));
392
- }
393
- get(alias, property) {
394
- return this.map.get(this.key(alias, property));
395
- }
396
- set(alias, property, variable) {
397
- this.map.set(this.key(alias, property), variable);
398
- this.usedVarNames.add(variable);
399
- }
400
- getOrCreate(alias, property) {
401
- const existing = this.get(alias, property);
402
- if (existing)
403
- return existing;
404
- const suffix = propertySuffix(property);
405
- let varName = `${sanitizeVarName(alias)}_${suffix}`;
406
- // Deduplicate: if varName is already used by a different (alias, property),
407
- // append a counter to ensure unique SPARQL variable names
408
- let counter = 2;
409
- while (this.usedVarNames.has(varName)) {
410
- varName = `${sanitizeVarName(alias)}_${suffix}_${counter}`;
411
- counter++;
412
- }
413
- this.set(alias, property, varName);
414
- return varName;
415
- }
416
- }
417
- // ---------------------------------------------------------------------------
418
- // Aggregate detection
419
- // ---------------------------------------------------------------------------
420
- /**
421
- * Checks whether a SparqlExpression tree contains an aggregate sub-expression.
422
- * Used to route aggregate-containing filters to HAVING instead of FILTER.
423
- */
424
- function containsAggregate(expr) {
425
- switch (expr.kind) {
426
- case 'aggregate_expr':
427
- return true;
428
- case 'in_expr':
429
- return containsAggregate(expr.value) || expr.list.some(containsAggregate);
430
- case 'binary_expr':
431
- return containsAggregate(expr.left) || containsAggregate(expr.right);
432
- case 'logical_expr':
433
- return expr.exprs.some(containsAggregate);
434
- case 'not_expr':
435
- return containsAggregate(expr.inner);
436
- case 'function_expr':
437
- return expr.args.some(containsAggregate);
438
- default:
439
- return false;
440
- }
441
- }
442
- // ---------------------------------------------------------------------------
443
- // Select conversion
444
- // ---------------------------------------------------------------------------
445
- /**
446
- * Converts an IRSelectQuery to a SparqlSelectPlan.
447
- */
448
- function selectToAlgebra(query, _options) {
449
- var _a, _b, _c, _d, _e, _f, _g, _h;
450
- const registry = new VariableRegistry();
451
- // Promote bindings only when the top-level WHERE would reject rows without
452
- // them. This keeps human-like SPARQL for null-rejecting filters without
453
- // over-constraining OR cases that can still match through other branches.
454
- const requiredPropertyKeys = query.where
455
- ? collectRequiredBindingKeys(query.where)
456
- : new Set();
457
- const requiredPropertyTriples = [];
458
- // Track property triples that need to be added as OPTIONAL
459
- const optionalPropertyTriples = [];
460
- // Track filtered traversals (inline where) — these get their own OPTIONAL blocks
461
- const filteredTraverseBlocks = [];
462
- // 1. Root shape scan → BGP with type triple
463
- if (!(query === null || query === void 0 ? void 0 : query.root)) {
464
- throw new Error('selectToAlgebra: query.root is undefined. The query IR is missing its root shape scan. ' +
465
- 'This usually means the query was built with a null/undefined subject (e.g. getQueryContext returned null).');
466
- }
467
- const rootAlias = query.root.alias;
468
- const shapeUri = query.root.shape;
469
- const typeTriple = tripleOf(varTerm(rootAlias), iriTerm(RDF_TYPE), iriTerm(resolveShapeScanIri(shapeUri)));
470
- const requiredTriples = [typeTriple];
471
- // Track traverse triples (required pattern)
472
- const traverseTriples = [];
473
- const traversePatternsInOrder = collectTraversePatternsInOrder(query.patterns);
474
- const traversePatternMap = new Map(traversePatternsInOrder.map((pattern) => [pattern.to, pattern]));
475
- // ── Nested-select inner LIMIT/OFFSET (Option A: single-subject only) ──────
476
- // A nested select on a related collection may carry an inner LIMIT/OFFSET.
477
- // A plain SPARQL sub-SELECT is uncorrelated, so its LIMIT bounds GLOBALLY.
478
- // That only equals per-parent windowing when the outer query targets exactly
479
- // ONE root subject — then we inline that subject into the sub-SELECT.
480
- const paginatedTraversePatterns = traversePatternsInOrder.filter((p) => typeof p.innerLimit === 'number' ||
481
- typeof p.innerOffset === 'number' ||
482
- (p.innerOrderBy && p.innerOrderBy.length > 0));
483
- const singleSubjectIri = (_a = query.subjectId) !== null && _a !== void 0 ? _a : (query.subjectIds && query.subjectIds.length === 1
484
- ? query.subjectIds[0]
485
- : undefined);
486
- if (paginatedTraversePatterns.length > 0 && !singleSubjectIri) {
487
- throw new Error('Inner LIMIT/OFFSET on a nested select is only supported when the outer ' +
488
- 'query targets a single subject; multi-parent per-group pagination is not ' +
489
- 'yet implemented.');
490
- }
491
- // Only root→child traversals can be inlined against the single subject.
492
- // A paginated traversal deeper than root→child has a parent collection that is
493
- // itself multi-valued (effectively multi-parent), so a sub-SELECT LIMIT there
494
- // would bound globally, not per-parent. Reject it loudly rather than silently
495
- // dropping the inner limit.
496
- const deepPaginated = paginatedTraversePatterns.filter((p) => p.from !== rootAlias);
497
- if (deepPaginated.length > 0) {
498
- throw new Error('Inner LIMIT/OFFSET on a nested select is only supported on a direct nested ' +
499
- 'select of the single root subject; pagination on a deeper (grandchild) ' +
500
- 'collection is multi-parent and not yet implemented.');
501
- }
502
- const subSelectAliases = new Set(paginatedTraversePatterns.map((p) => p.to));
503
- // 2. Process patterns → traverse triples, populate variable registry.
504
- // Skip sub-SELECT-wrapped traversals here — they are emitted separately
505
- // (their child variable is still registered so projection/optionals work).
506
- for (const pattern of query.patterns) {
507
- processPattern(pattern, registry, traverseTriples, optionalPropertyTriples, filteredTraverseBlocks, subSelectAliases);
508
- }
509
- // 3. Pre-register filter property references BEFORE processing projections.
510
- // This ensures that property triples needed by inline where filters are
511
- // co-located inside the filtered OPTIONAL block, not in separate OPTIONALs.
512
- const filterPropertyTriplesMap = new Map();
513
- filteredTraverseBlocks.forEach((block, idx) => {
514
- const filterPropertyTriples = [];
515
- processExpressionForProperties(block.filter, registry, filterPropertyTriples);
516
- filterPropertyTriplesMap.set(idx, filterPropertyTriples);
517
- });
518
- // 4. Process projection expressions, where clause, orderBy expressions
519
- // to discover any additional property_expr references.
520
- // Properties already registered by inline filters (above) will be skipped.
521
- for (const item of query.projection) {
522
- processExpressionForProperties(item.expression, registry, optionalPropertyTriples, requiredPropertyTriples, requiredPropertyKeys);
523
- }
524
- if (query.where) {
525
- processExpressionForProperties(query.where, registry, optionalPropertyTriples, requiredPropertyTriples, requiredPropertyKeys);
526
- }
527
- if (query.orderBy) {
528
- for (const orderItem of query.orderBy) {
529
- processExpressionForProperties(orderItem.expression, registry, optionalPropertyTriples, requiredPropertyTriples, requiredPropertyKeys);
530
- }
531
- }
532
- const projectedTraversalAliases = new Set();
533
- const directProjectionAliases = collectDirectProjectionAliases(query, rootAlias);
534
- for (const alias of directProjectionAliases) {
535
- markAliasAndAncestors(alias, traversePatternMap, projectedTraversalAliases);
536
- }
537
- const requiredTraversalAliases = collectRequiredTraversalAliases(query, traversePatternMap);
538
- const optionalTraversalAliases = new Set([...projectedTraversalAliases].filter((alias) => {
539
- const pattern = traversePatternMap.get(alias);
540
- // Lower projection-only traversals into OPTIONAL subtrees regardless of
541
- // cardinality, so a parent with an empty relationship is preserved (with
542
- // an empty array / null child) instead of being inner-joined away. The
543
- // result grouper already collects multiple child bindings into an array.
544
- //
545
- // Excluded:
546
- // - filtered traversals (`.where(...)`) — the filter makes the child required
547
- // - aliases otherwise required by the query (e.g. used in a top-level filter)
548
- // - paginated traversals — emitted separately as a sub-SELECT (section 5a),
549
- // so lowering them here too would double-emit the traverse triple
550
- return !!pattern &&
551
- !pattern.filter &&
552
- !subSelectAliases.has(alias) &&
553
- !requiredTraversalAliases.has(alias);
554
- }));
555
- const requiredTraverseTriples = traverseTriples.filter((triple) => !(triple.object.kind === 'variable' &&
556
- optionalTraversalAliases.has(triple.object.name)));
557
- const nestedOptionalPropertyTriplesByAlias = new Map();
558
- const topLevelOptionalPropertyTriples = [];
559
- // Property triples whose subject is a sub-SELECT child alias must be nested
560
- // INSIDE the sub-SELECT's OPTIONAL block (keyed by alias), so that ?childVar
561
- // stays scoped — otherwise an empty window leaves ?childVar unbound and the
562
- // outer property OPTIONAL leaks across the whole graph.
563
- const subSelectChildPropertyTriplesByAlias = new Map();
564
- // Same scoping rule for filtered traversals (`.where(...)`): anything anchored
565
- // on the filtered alias must live inside its filtered OPTIONAL block — when
566
- // the filter matches nothing, the alias is unbound and a top-level OPTIONAL
567
- // would range over the whole graph.
568
- const filteredTraverseAliases = new Set(filteredTraverseBlocks.map((b) => b.toAlias));
569
- const filteredChildPropertyTriplesByAlias = new Map();
570
- for (const propTriple of optionalPropertyTriples) {
571
- if (propTriple.subject.kind === 'variable' &&
572
- subSelectAliases.has(propTriple.subject.name)) {
573
- const triples = (_b = subSelectChildPropertyTriplesByAlias.get(propTriple.subject.name)) !== null && _b !== void 0 ? _b : [];
574
- triples.push(propTriple);
575
- subSelectChildPropertyTriplesByAlias.set(propTriple.subject.name, triples);
576
- }
577
- else if (propTriple.subject.kind === 'variable' &&
578
- filteredTraverseAliases.has(propTriple.subject.name)) {
579
- const triples = (_c = filteredChildPropertyTriplesByAlias.get(propTriple.subject.name)) !== null && _c !== void 0 ? _c : [];
580
- triples.push(propTriple);
581
- filteredChildPropertyTriplesByAlias.set(propTriple.subject.name, triples);
582
- }
583
- else if (propTriple.subject.kind === 'variable' &&
584
- optionalTraversalAliases.has(propTriple.subject.name)) {
585
- const triples = (_d = nestedOptionalPropertyTriplesByAlias.get(propTriple.subject.name)) !== null && _d !== void 0 ? _d : [];
586
- triples.push(propTriple);
587
- nestedOptionalPropertyTriplesByAlias.set(propTriple.subject.name, triples);
588
- }
589
- else {
590
- topLevelOptionalPropertyTriples.push(propTriple);
591
- }
592
- }
593
- const childOptionalAliasesByParent = new Map();
594
- for (const pattern of traversePatternsInOrder) {
595
- if (!optionalTraversalAliases.has(pattern.to))
596
- continue;
597
- const siblings = (_e = childOptionalAliasesByParent.get(pattern.from)) !== null && _e !== void 0 ? _e : [];
598
- siblings.push(pattern.to);
599
- childOptionalAliasesByParent.set(pattern.from, siblings);
600
- }
601
- const rootOptionalTraversalAliases = traversePatternsInOrder
602
- .filter((pattern) => optionalTraversalAliases.has(pattern.to) &&
603
- !optionalTraversalAliases.has(pattern.from) &&
604
- // Children of a filtered traversal nest inside its filtered block (5b)
605
- !filteredTraverseAliases.has(pattern.from))
606
- .map((pattern) => pattern.to);
607
- // 5. Build the algebra tree
608
- // - Start with the required BGP (type triple + traverse triples)
609
- // - Wrap each optional property triple in a LeftJoin
610
- const requiredBgp = {
611
- type: 'bgp',
612
- triples: [...requiredTriples, ...requiredTraverseTriples, ...requiredPropertyTriples],
613
- };
614
- let algebra = requiredBgp;
615
- // 5a. Nested-select inner LIMIT/OFFSET → sub-SELECT (single-subject only).
616
- // Wrap each root→child paginated traversal in a sub-SELECT that inlines
617
- // the single subject, projecting only the child variable. The child's own
618
- // property triples stay OUTSIDE (as OPTIONALs), joined on the child var —
619
- // so the flat-row structure and result mapping are unchanged.
620
- // Every pattern here is a root→child paginated traversal (the deep-pagination
621
- // guard above rejected anything else), so each maps 1:1 to a sub-SELECT.
622
- for (const pattern of paginatedTraversePatterns) {
623
- const predicate = buildPredicateTerm(pattern);
624
- const innerTriple = tripleOf(iriTerm(singleSubjectIri), predicate, varTerm(pattern.to));
625
- // Default ORDER BY ?childVar for a deterministic window, unless the nested
626
- // select supplied its own ordering. When it did, each ordered property must
627
- // also be bound inside the sub-SELECT so the ORDER BY variable resolves.
628
- let orderBy;
629
- let innerNode = { type: 'bgp', triples: [innerTriple] };
630
- if (pattern.innerOrderBy && pattern.innerOrderBy.length > 0) {
631
- orderBy = pattern.innerOrderBy.map((o) => {
632
- const orderVar = registry.getOrCreate(pattern.to, o.property);
633
- innerNode = joinNodes(innerNode, {
634
- type: 'bgp',
635
- triples: [tripleOf(varTerm(pattern.to), resolvePropertyPredicateTerm(o.property), varTerm(orderVar))],
636
- });
637
- return {
638
- expression: { kind: 'variable_expr', name: orderVar },
639
- direction: o.direction,
640
- };
641
- });
642
- }
643
- else {
644
- orderBy = [{
645
- expression: { kind: 'variable_expr', name: pattern.to },
646
- direction: 'ASC',
647
- }];
648
- }
649
- const subSelect = Object.assign(Object.assign({ type: 'subselect', projection: [pattern.to], inner: innerNode, orderBy }, (typeof pattern.innerLimit === 'number' ? { limit: pattern.innerLimit } : {})), (typeof pattern.innerOffset === 'number' ? { offset: pattern.innerOffset } : {}));
650
- // Nest the child's own property triples (e.g. ?a1 <name> ?a1_name) INSIDE
651
- // this block, each as its own OPTIONAL, so they only bind when the window
652
- // produced a ?childVar. This keeps the flat-row projection unchanged.
653
- let childBlock = subSelect;
654
- for (const propTriple of (_f = subSelectChildPropertyTriplesByAlias.get(pattern.to)) !== null && _f !== void 0 ? _f : []) {
655
- childBlock = wrapOptional(childBlock, { type: 'bgp', triples: [propTriple] });
656
- }
657
- // OPTIONAL so a parent with an empty (or fully-windowed-out) child set is
658
- // still returned — the sub-SELECT yields no ?a1 binding, but the parent row
659
- // survives (mirrors the plain nested-traverse OPTIONAL behaviour).
660
- algebra = wrapOptional(algebra, childBlock);
661
- }
662
- // 5b. Build filtered OPTIONAL blocks for inline where traversals.
663
- // Each block contains: traverse triple + OPTIONAL property triples + FILTER.
664
- // Filter property triples are nested as OPTIONALs so that OR filters work
665
- // even when some entities lack certain properties.
666
- // First pass: build each filtered block's inner group and filter expression.
667
- const filteredBlockInners = [];
668
- const filteredBlockExprs = [];
669
- const filteredBlockIdxByAlias = new Map(filteredTraverseBlocks.map((block, i) => [block.toAlias, i]));
670
- for (let i = 0; i < filteredTraverseBlocks.length; i++) {
671
- const block = filteredTraverseBlocks[i];
672
- const filterPropertyTriples = filterPropertyTriplesMap.get(i) || [];
673
- const filterExpr = convertExpression(block.filter, registry, filterPropertyTriples);
674
- // Start with the traverse triple as the required BGP
675
- let blockInner = { type: 'bgp', triples: [block.traverseTriple] };
676
- // Wrap each filter property triple in its own nested OPTIONAL
677
- for (const propTriple of filterPropertyTriples) {
678
- blockInner = wrapOptional(blockInner, { type: 'bgp', triples: [propTriple] });
679
- }
680
- // Projected property triples on the filtered alias that aren't part of the
681
- // filter (e.g. `.where(name=...).select(pp => [pp.hobby])`)
682
- for (const propTriple of (_g = filteredChildPropertyTriplesByAlias.get(block.toAlias)) !== null && _g !== void 0 ? _g : []) {
683
- blockInner = wrapOptional(blockInner, { type: 'bgp', triples: [propTriple] });
684
- }
685
- // Optional child traversal subtrees (nested sub-selects below the filtered
686
- // alias) — kept inside the block so an empty filter match leaves them unbound
687
- for (const childAlias of (_h = childOptionalAliasesByParent.get(block.toAlias)) !== null && _h !== void 0 ? _h : []) {
688
- blockInner = wrapOptional(blockInner, buildOptionalTraversalSubtree(childAlias, traversePatternMap, childOptionalAliasesByParent, nestedOptionalPropertyTriplesByAlias));
689
- }
690
- filteredBlockInners.push(blockInner);
691
- filteredBlockExprs.push(filterExpr);
692
- }
693
- // Second pass, children first (blocks are created parent-before-child):
694
- // finish each block and either nest it inside its parent's filtered block —
695
- // a filtered sub-select inside another filtered sub-select must stay scoped
696
- // to the parent alias, or an empty parent match leaves the alias unbound and
697
- // the block cross-products over the whole graph — or attach it at top level.
698
- const rootFilteredBlocks = [];
699
- for (let i = filteredTraverseBlocks.length - 1; i >= 0; i--) {
700
- const block = filteredTraverseBlocks[i];
701
- const finished = {
702
- type: 'filter',
703
- expression: filteredBlockExprs[i],
704
- inner: filteredBlockInners[i],
705
- };
706
- const subject = block.traverseTriple.subject;
707
- const parentIdx = subject.kind === 'variable'
708
- ? filteredBlockIdxByAlias.get(subject.name)
709
- : undefined;
710
- if (parentIdx !== undefined && parentIdx !== i) {
711
- filteredBlockInners[parentIdx] = wrapOptional(filteredBlockInners[parentIdx], finished);
712
- }
713
- else {
714
- rootFilteredBlocks.unshift(finished);
715
- }
716
- }
717
- for (const filteredBlock of rootFilteredBlocks) {
718
- algebra = wrapOptional(algebra, filteredBlock);
719
- }
720
- for (const alias of rootOptionalTraversalAliases) {
721
- algebra = wrapOptional(algebra, buildOptionalTraversalSubtree(alias, traversePatternMap, childOptionalAliasesByParent, nestedOptionalPropertyTriplesByAlias));
722
- }
723
- // Wrap each optional property triple in its own OPTIONAL (LeftJoin)
724
- for (const propTriple of topLevelOptionalPropertyTriples) {
725
- algebra = wrapOptional(algebra, {
726
- type: 'bgp',
727
- triples: [propTriple],
728
- });
729
- }
730
- // 5. Where clause → Filter wrapping (or HAVING if aggregate-containing)
731
- let havingExpr;
732
- if (query.where) {
733
- const filterExpr = convertExpression(query.where, registry, optionalPropertyTriples);
734
- if (containsAggregate(filterExpr)) {
735
- havingExpr = filterExpr;
736
- }
737
- else {
738
- algebra = {
739
- type: 'filter',
740
- expression: filterExpr,
741
- inner: algebra,
742
- };
743
- }
744
- }
745
- // 5b. MINUS patterns — wrap algebra in SparqlMinus for each minus pattern
746
- for (const pattern of query.patterns) {
747
- if (pattern.kind === 'minus') {
748
- let minusAlgebra = convertExistsPattern(pattern.pattern, registry);
749
- if (pattern.filter) {
750
- const minusPropertyTriples = [];
751
- processExpressionForProperties(pattern.filter, registry, minusPropertyTriples);
752
- // Add property triples into the MINUS block
753
- if (minusPropertyTriples.length > 0) {
754
- minusAlgebra = joinNodes(minusAlgebra, { type: 'bgp', triples: minusPropertyTriples });
755
- }
756
- const filterExpr = convertExpression(pattern.filter, registry, minusPropertyTriples);
757
- minusAlgebra = { type: 'filter', expression: filterExpr, inner: minusAlgebra };
758
- }
759
- algebra = { type: 'minus', left: algebra, right: minusAlgebra };
760
- }
761
- }
762
- // 6. SubjectId → Filter / SubjectIds → VALUES
763
- if (query.subjectIds && query.subjectIds.length > 0) {
764
- // Multiple subjects: use VALUES clause for efficient filtering
765
- algebra = joinNodes({ type: 'values', variable: rootAlias, iris: query.subjectIds }, algebra);
766
- }
767
- else if (query.subjectId) {
768
- const subjectFilter = {
769
- kind: 'binary_expr',
770
- op: '=',
771
- left: { kind: 'variable_expr', name: rootAlias },
772
- right: { kind: 'iri_expr', value: query.subjectId },
773
- };
774
- algebra = {
775
- type: 'filter',
776
- expression: subjectFilter,
777
- inner: algebra,
778
- };
779
- }
780
- // 7. Build projection
781
- const projection = [];
782
- const aggregates = [];
783
- let hasAggregates = false;
784
- // Always include root alias as first projection variable
785
- projection.push({ kind: 'variable', name: rootAlias });
786
- // Collect traversal aliases upfront to detect aggregate alias collisions
787
- const traversalAliasSet = new Set(collectTraversalAliases(query.patterns));
788
- // Track traversal aliases consumed by aggregate renames (should not be
789
- // re-projected as plain variables, which would alter GROUP BY semantics)
790
- const aggregateRenamedAliases = new Set();
791
- for (const item of query.projection) {
792
- const sparqlExpr = convertExpression(item.expression, registry, optionalPropertyTriples);
793
- if (sparqlExpr.kind === 'aggregate_expr') {
794
- hasAggregates = true;
795
- // Avoid collision: if aggregate alias matches a traversal alias,
796
- // rename it so SPARQL doesn't produce duplicate variable bindings
797
- let aggAlias = item.alias;
798
- if (traversalAliasSet.has(aggAlias)) {
799
- aggregateRenamedAliases.add(aggAlias);
800
- aggAlias = `${aggAlias}_agg`;
801
- // Update resultMap so result mapping uses the renamed alias
802
- for (const rm of query.resultMap) {
803
- if (rm.alias === item.alias)
804
- rm.alias = aggAlias;
805
- }
806
- }
807
- projection.push({
808
- kind: 'aggregate',
809
- expression: sparqlExpr,
810
- alias: aggAlias,
811
- });
812
- aggregates.push({
813
- variable: aggAlias,
814
- aggregate: sparqlExpr,
815
- });
816
- }
817
- else {
818
- // For property_expr, the variable is the resolved name from registry
819
- const varName = resolveExpressionVariable(item.expression, registry);
820
- if (varName && varName !== rootAlias) {
821
- projection.push({ kind: 'variable', name: varName });
822
- }
823
- else if (!varName) {
824
- // Non-variable expression (binary_expr, function_expr, etc.)
825
- // → project as (expr AS ?alias)
826
- let exprAlias = item.alias;
827
- if (traversalAliasSet.has(exprAlias)) {
828
- // The output alias collides with a traversal target variable — e.g. a
829
- // computed expression over a traversed path lowers to
830
- // (UCASE(?a1_name) AS ?a1), and ?a1 is already bound by the traverse
831
- // triple. Rename the output so SPARQL doesn't reuse an in-scope var.
832
- exprAlias = `${exprAlias}_expr`;
833
- for (const rm of query.resultMap) {
834
- if (rm.alias === item.alias)
835
- rm.alias = exprAlias;
836
- }
837
- }
838
- projection.push({ kind: 'expression', expression: sparqlExpr, alias: exprAlias });
839
- }
840
- }
841
- }
842
- // 7b. Include traversal aliases needed for result grouping
843
- // When nested results are projected (e.g. p.friends.name), the result
844
- // mapping needs the traversal alias variable (?a1) in the bindings to
845
- // group nested rows by entity. Without this, mapNestedRows() can't
846
- // identify which nested fields belong to which traversed entity.
847
- const projectedNames = new Set();
848
- for (const p of projection) {
849
- if (p.kind === 'variable')
850
- projectedNames.add(p.name);
851
- else if (p.kind === 'aggregate' || p.kind === 'expression')
852
- projectedNames.add(p.alias);
853
- }
854
- for (const alias of collectTraversalAliases(query.patterns)) {
855
- if (!projectedNames.has(alias) && !aggregateRenamedAliases.has(alias)) {
856
- projection.push({ kind: 'variable', name: alias });
857
- projectedNames.add(alias);
858
- }
859
- }
860
- // 8. GROUP BY inference
861
- let groupBy;
862
- if (havingExpr) {
863
- hasAggregates = true;
864
- }
865
- if (hasAggregates) {
866
- // All non-aggregate projected variables become GROUP BY targets
867
- groupBy = projection
868
- .filter((p) => p.kind === 'variable')
869
- .map((p) => p.name);
870
- }
871
- // 9. OrderBy
872
- let orderBy;
873
- if (query.orderBy) {
874
- orderBy = query.orderBy.map((item) => ({
875
- expression: convertExpression(item.expression, registry, optionalPropertyTriples),
876
- direction: item.direction,
877
- }));
878
- }
879
- // `.one()` lowers to limit=1 + singleResult, but LIMIT bounds ROWS, not
880
- // entities. When the query yields multiple rows per root entity (traversals
881
- // or plural properties), a row-level LIMIT would truncate the entity's own
882
- // nested data — omit it and let the result mapper pick the single entity.
883
- const multiRowPerEntity = query.patterns.some((p) => p.kind === 'traverse') ||
884
- query.projection.some((item) => item.expression.kind === 'property_expr' &&
885
- !(typeof item.expression.maxCount === 'number' && item.expression.maxCount <= 1));
886
- const limit = query.singleResult && query.limit === 1 && multiRowPerEntity
887
- ? undefined
888
- : query.limit;
889
- return {
890
- type: 'select',
891
- algebra,
892
- projection,
893
- distinct: !hasAggregates ? true : undefined,
894
- orderBy,
895
- limit,
896
- offset: query.offset,
897
- groupBy,
898
- having: havingExpr,
899
- aggregates: aggregates.length > 0 ? aggregates : undefined,
900
- };
901
- }
902
- // ---------------------------------------------------------------------------
903
- // Pattern processing
904
- // ---------------------------------------------------------------------------
905
- function processPattern(pattern, registry, traverseTriples, optionalPropertyTriples, filteredTraverseBlocks, subSelectAliases) {
906
- switch (pattern.kind) {
907
- case 'shape_scan':
908
- // Additional shape scans (non-root) are handled as type triples
909
- // but this case is rare — root is handled separately
910
- break;
911
- case 'traverse': {
912
- // Register the traverse variable: (from, property) → to
913
- registry.set(pattern.from, pattern.property, pattern.to);
914
- // Traversals wrapped in a sub-SELECT (inner LIMIT/OFFSET) are emitted
915
- // separately — don't add their triple to the required BGP here.
916
- if (subSelectAliases === null || subSelectAliases === void 0 ? void 0 : subSelectAliases.has(pattern.to)) {
917
- break;
918
- }
919
- // Add traverse triple to required pattern (or filtered block if inline where)
920
- const triple = buildTraverseTriple(pattern);
921
- if (pattern.filter && filteredTraverseBlocks) {
922
- filteredTraverseBlocks.push({
923
- traverseTriple: triple,
924
- filter: pattern.filter,
925
- toAlias: pattern.to,
926
- });
927
- }
928
- else {
929
- traverseTriples.push(triple);
930
- }
931
- break;
932
- }
933
- case 'join': {
934
- for (const sub of pattern.patterns) {
935
- processPattern(sub, registry, traverseTriples, optionalPropertyTriples, filteredTraverseBlocks, subSelectAliases);
936
- }
937
- break;
938
- }
939
- case 'optional': {
940
- // Optional patterns — process inner patterns but keep them optional
941
- processPattern(pattern.pattern, registry, traverseTriples, optionalPropertyTriples, filteredTraverseBlocks, subSelectAliases);
942
- break;
943
- }
944
- case 'union': {
945
- for (const branch of pattern.branches) {
946
- processPattern(branch, registry, traverseTriples, optionalPropertyTriples, filteredTraverseBlocks, subSelectAliases);
947
- }
948
- break;
949
- }
950
- case 'exists': {
951
- processPattern(pattern.pattern, registry, traverseTriples, optionalPropertyTriples, filteredTraverseBlocks, subSelectAliases);
952
- break;
953
- }
954
- case 'minus': {
955
- // MINUS patterns are handled separately in selectToAlgebra — skip in processPattern.
956
- break;
957
- }
958
- }
959
- }
960
- // ---------------------------------------------------------------------------
961
- // Expression processing — discover property_expr references
962
- // ---------------------------------------------------------------------------
963
- function processExpressionForProperties(expr, registry, optionalPropertyTriples, requiredPropertyTriples = [], requiredPropertyKeys = new Set()) {
964
- switch (expr.kind) {
965
- case 'property_expr': {
966
- if (!registry.has(expr.sourceAlias, expr.property)) {
967
- const varName = registry.getOrCreate(expr.sourceAlias, expr.property);
968
- const predicate = buildPredicateTerm(expr);
969
- const triple = tripleOf(varTerm(expr.sourceAlias), predicate, varTerm(varName));
970
- const triples = requiredPropertyKeys.has(bindingKey(expr.sourceAlias, expr.property))
971
- ? requiredPropertyTriples
972
- : optionalPropertyTriples;
973
- triples.push(triple);
974
- }
975
- break;
976
- }
977
- case 'binary_expr':
978
- processExpressionForProperties(expr.left, registry, optionalPropertyTriples, requiredPropertyTriples, requiredPropertyKeys);
979
- processExpressionForProperties(expr.right, registry, optionalPropertyTriples, requiredPropertyTriples, requiredPropertyKeys);
980
- break;
981
- case 'in_expr':
982
- // The tested value binds a property; list elements are constants.
983
- processExpressionForProperties(expr.value, registry, optionalPropertyTriples, requiredPropertyTriples, requiredPropertyKeys);
984
- break;
985
- case 'logical_expr':
986
- for (const sub of expr.expressions) {
987
- processExpressionForProperties(sub, registry, optionalPropertyTriples, requiredPropertyTriples, requiredPropertyKeys);
988
- }
989
- break;
990
- case 'not_expr':
991
- processExpressionForProperties(expr.expression, registry, optionalPropertyTriples, requiredPropertyTriples, requiredPropertyKeys);
992
- break;
993
- case 'function_expr':
994
- for (const arg of expr.args) {
995
- processExpressionForProperties(arg, registry, optionalPropertyTriples, requiredPropertyTriples, requiredPropertyKeys);
996
- }
997
- break;
998
- case 'aggregate_expr':
999
- for (const arg of expr.args) {
1000
- processExpressionForProperties(arg, registry, optionalPropertyTriples, requiredPropertyTriples, requiredPropertyKeys);
1001
- }
1002
- break;
1003
- case 'exists_expr':
1004
- // exists_expr filter properties belong INSIDE the EXISTS block, not in
1005
- // the outer scope. Do NOT register them here — convertExpression's
1006
- // exists_expr handler will collect and emit them locally.
1007
- break;
1008
- case 'context_property_expr': {
1009
- // Context entity property — emit a triple with fixed IRI as subject.
1010
- // Use raw IRI as registry key to avoid collision between IRIs that
1011
- // sanitize to the same string (e.g. ctx-1 vs ctx_1).
1012
- const contextIri = resolvedContextIri(expr.contextIri, expr.contextName);
1013
- const ctxKey = contextAliasKey(contextIri);
1014
- if (!registry.has(ctxKey, expr.property)) {
1015
- const varName = registry.getOrCreate(ctxKey, expr.property);
1016
- const triple = tripleOf(iriTerm(contextIri), resolvePropertyPredicateTerm(expr.property), varTerm(varName));
1017
- const triples = requiredPropertyKeys.has(bindingKey(ctxKey, expr.property))
1018
- ? requiredPropertyTriples
1019
- : optionalPropertyTriples;
1020
- triples.push(triple);
1021
- }
1022
- break;
1023
- }
1024
- case 'literal_expr':
1025
- case 'reference_expr':
1026
- case 'alias_expr':
1027
- // No property references to discover
1028
- break;
1029
- }
1030
- }
1031
- /**
1032
- * Compute which bindings are mandatory for a top-level FILTER to keep a row.
1033
- * AND makes either side required; OR only keeps bindings required by every branch.
1034
- */
1035
- function collectRequiredBindingKeys(expr) {
1036
- switch (expr.kind) {
1037
- case 'property_expr':
1038
- return new Set([bindingKey(expr.sourceAlias, expr.property)]);
1039
- case 'context_property_expr':
1040
- return new Set([bindingKey(contextAliasKey(resolvedContextIri(expr.contextIri, expr.contextName)), expr.property)]);
1041
- case 'in_expr':
1042
- // The tested value must be bound; list elements are constants.
1043
- return collectRequiredBindingKeys(expr.value);
1044
- case 'binary_expr':
1045
- return mergeKeySets(collectRequiredBindingKeys(expr.left), collectRequiredBindingKeys(expr.right));
1046
- case 'function_expr': {
1047
- const fn = expr.name.toUpperCase();
1048
- // BOUND explicitly tests boundness — forcing its argument into a
1049
- // required (inner-join) pattern would make !BOUND unsatisfiable.
1050
- // COALESCE is unbound-tolerant by design — requiring its arguments
1051
- // would make the fallback unreachable.
1052
- if (fn === 'BOUND' || fn === 'COALESCE') {
1053
- return new Set();
1054
- }
1055
- // IF: an unbound variable in the condition errors the row out either
1056
- // way, so the condition may keep its requirements — but the then/else
1057
- // branches must stay optional (the untaken branch may reference a
1058
- // property the entity doesn't have).
1059
- if (fn === 'IF') {
1060
- return expr.args.length > 0
1061
- ? collectRequiredBindingKeys(expr.args[0])
1062
- : new Set();
1063
- }
1064
- return mergeKeySets(...expr.args.map((arg) => collectRequiredBindingKeys(arg)));
1065
- }
1066
- case 'not_expr':
1067
- return collectRequiredBindingKeys(expr.expression);
1068
- case 'logical_expr': {
1069
- const childSets = expr.expressions.map((sub) => collectRequiredBindingKeys(sub));
1070
- if (expr.operator === 'and') {
1071
- return mergeKeySets(...childSets);
1072
- }
1073
- return intersectKeySets(childSets);
1074
- }
1075
- case 'aggregate_expr':
1076
- case 'exists_expr':
1077
- case 'literal_expr':
1078
- case 'reference_expr':
1079
- case 'alias_expr':
1080
- return new Set();
1081
- }
1082
- }
1083
- // ---------------------------------------------------------------------------
1084
- // Expression conversion
1085
- // ---------------------------------------------------------------------------
1086
- function convertExpression(expr, registry, optionalPropertyTriples) {
1087
- switch (expr.kind) {
1088
- case 'literal_expr': {
1089
- const value = expr.value;
1090
- if (value === null || value === undefined) {
1091
- return { kind: 'literal_expr', value: '' };
1092
- }
1093
- if (typeof value === 'boolean') {
1094
- return {
1095
- kind: 'literal_expr',
1096
- value: String(value),
1097
- datatype: XSD_BOOLEAN,
1098
- };
1099
- }
1100
- if (typeof value === 'number') {
1101
- if (Number.isInteger(value)) {
1102
- return {
1103
- kind: 'literal_expr',
1104
- value: String(value),
1105
- datatype: XSD_INTEGER,
1106
- };
1107
- }
1108
- return {
1109
- kind: 'literal_expr',
1110
- value: String(value),
1111
- datatype: XSD_DOUBLE,
1112
- };
1113
- }
1114
- return { kind: 'literal_expr', value: String(value) };
1115
- }
1116
- case 'reference_expr':
1117
- return { kind: 'iri_expr', value: resolvedContextIri(expr.value, expr.contextName) };
1118
- case 'alias_expr':
1119
- return { kind: 'variable_expr', name: expr.alias };
1120
- case 'context_property_expr': {
1121
- const ctxKey = `__ctx__${resolvedContextIri(expr.contextIri, expr.contextName)}`;
1122
- const ctxVarName = registry.getOrCreate(ctxKey, expr.property);
1123
- return { kind: 'variable_expr', name: ctxVarName };
1124
- }
1125
- case 'property_expr': {
1126
- const varName = registry.getOrCreate(expr.sourceAlias, expr.property);
1127
- return { kind: 'variable_expr', name: varName };
1128
- }
1129
- case 'in_expr':
1130
- return {
1131
- kind: 'in_expr',
1132
- negated: expr.negated,
1133
- value: convertExpression(expr.value, registry, optionalPropertyTriples),
1134
- list: expr.source.list.map((e) => convertExpression(e, registry, optionalPropertyTriples)),
1135
- };
1136
- case 'binary_expr':
1137
- return {
1138
- kind: 'binary_expr',
1139
- op: expr.operator,
1140
- left: convertExpression(expr.left, registry, optionalPropertyTriples),
1141
- right: convertExpression(expr.right, registry, optionalPropertyTriples),
1142
- };
1143
- case 'logical_expr':
1144
- return {
1145
- kind: 'logical_expr',
1146
- op: expr.operator,
1147
- exprs: expr.expressions.map((e) => convertExpression(e, registry, optionalPropertyTriples)),
1148
- };
1149
- case 'not_expr':
1150
- return {
1151
- kind: 'not_expr',
1152
- inner: convertExpression(expr.expression, registry, optionalPropertyTriples),
1153
- };
1154
- case 'function_expr':
1155
- return {
1156
- kind: 'function_expr',
1157
- name: expr.name,
1158
- args: expr.args.map((a) => convertExpression(a, registry, optionalPropertyTriples)),
1159
- };
1160
- case 'aggregate_expr':
1161
- return {
1162
- kind: 'aggregate_expr',
1163
- name: expr.name,
1164
- args: expr.args.map((a) => convertExpression(a, registry, optionalPropertyTriples)),
1165
- };
1166
- case 'exists_expr': {
1167
- // Convert exists expression with inner pattern + filter.
1168
- // Filter property triples must live INSIDE the EXISTS block
1169
- // (not in the outer scope), so we collect them locally.
1170
- let innerAlgebra = convertExistsPattern(expr.pattern, registry);
1171
- if (expr.filter) {
1172
- // First, discover and register filter property references,
1173
- // collecting their triples into a local array (NOT the outer scope).
1174
- const existsPropertyTriples = [];
1175
- processExpressionForProperties(expr.filter, registry, existsPropertyTriples);
1176
- // Now convert the filter expression (variables are registered above).
1177
- const filterExpr = convertExpression(expr.filter, registry, existsPropertyTriples);
1178
- // Add filter property triples inside the EXISTS
1179
- for (const propTriple of existsPropertyTriples) {
1180
- innerAlgebra = joinNodes(innerAlgebra, { type: 'bgp', triples: [propTriple] });
1181
- }
1182
- // Wrap the inner pattern with a filter
1183
- const filteredInner = {
1184
- type: 'filter',
1185
- expression: filterExpr,
1186
- inner: innerAlgebra,
1187
- };
1188
- return {
1189
- kind: 'exists_expr',
1190
- pattern: filteredInner,
1191
- negated: false,
1192
- };
1193
- }
1194
- return {
1195
- kind: 'exists_expr',
1196
- pattern: innerAlgebra,
1197
- negated: false,
1198
- };
1199
- }
1200
- default:
1201
- throw new Error(`Unknown IR expression kind: ${expr.kind}`);
1202
- }
1203
- }
1204
- /**
1205
- * Convert an exists pattern (from exists_expr) into an algebra node.
1206
- * Recursively handles all IR graph pattern kinds.
1207
- */
1208
- function convertExistsPattern(pattern, registry) {
1209
- switch (pattern.kind) {
1210
- case 'traverse': {
1211
- const existsPredicate = buildPredicateTerm(pattern);
1212
- const triple = tripleOf(varTerm(pattern.from), existsPredicate, varTerm(pattern.to));
1213
- return { type: 'bgp', triples: [triple] };
1214
- }
1215
- case 'join': {
1216
- let result = null;
1217
- for (const sub of pattern.patterns) {
1218
- const subNode = convertExistsPattern(sub, registry);
1219
- result = result ? joinNodes(result, subNode) : subNode;
1220
- }
1221
- return result || { type: 'bgp', triples: [] };
1222
- }
1223
- case 'shape_scan': {
1224
- return {
1225
- type: 'bgp',
1226
- triples: [
1227
- tripleOf(varTerm(pattern.alias), iriTerm(RDF_TYPE), iriTerm(resolveShapeScanIri(pattern.shape))),
1228
- ],
1229
- };
1230
- }
1231
- case 'optional': {
1232
- const inner = convertExistsPattern(pattern.pattern, registry);
1233
- return wrapOptional({ type: 'bgp', triples: [] }, inner);
1234
- }
1235
- case 'union': {
1236
- let result = null;
1237
- for (const branch of pattern.branches) {
1238
- const branchNode = convertExistsPattern(branch, registry);
1239
- if (!result) {
1240
- result = branchNode;
1241
- }
1242
- else {
1243
- result = { type: 'union', left: result, right: branchNode };
1244
- }
1245
- }
1246
- return result || { type: 'bgp', triples: [] };
1247
- }
1248
- case 'exists': {
1249
- return convertExistsPattern(pattern.pattern, registry);
1250
- }
1251
- case 'minus': {
1252
- return convertExistsPattern(pattern.pattern, registry);
1253
- }
1254
- default:
1255
- throw new Error(`Unsupported pattern kind in EXISTS: ${pattern.kind}`);
1256
- }
1257
- }
1258
- /**
1259
- * Resolve what variable name an IR expression ultimately refers to.
1260
- */
1261
- function resolveExpressionVariable(expr, registry) {
1262
- switch (expr.kind) {
1263
- case 'alias_expr':
1264
- return expr.alias;
1265
- case 'property_expr':
1266
- return registry.getOrCreate(expr.sourceAlias, expr.property);
1267
- default:
1268
- return null;
1269
- }
1270
- }
1271
- // ---------------------------------------------------------------------------
1272
- // Mutation conversions
1273
- // ---------------------------------------------------------------------------
1274
- /**
1275
- * Convert a field value to one or more SparqlTerm objects for triple objects.
1276
- */
1277
- function fieldValueToTerms(value, options) {
1278
- if (value === null || value === undefined) {
1279
- return [];
1280
- }
1281
- if (typeof value === 'string') {
1282
- return [literalTerm(value)];
1283
- }
1284
- if (typeof value === 'number') {
1285
- if (Number.isInteger(value)) {
1286
- return [literalTerm(String(value), XSD_INTEGER)];
1287
- }
1288
- return [literalTerm(String(value), XSD_DOUBLE)];
1289
- }
1290
- if (typeof value === 'boolean') {
1291
- return [literalTerm(String(value), XSD_BOOLEAN)];
1292
- }
1293
- if (value instanceof Date) {
1294
- return [literalTerm(value.toISOString(), XSD_DATETIME)];
1295
- }
1296
- // Computed/expression value in create: create lowers to INSERT DATA (ground
1297
- // triples, no WHERE), so an expression can't be evaluated here. Fail loudly
1298
- // instead of silently emitting no triple (report 021 §3, G4).
1299
- if (value &&
1300
- typeof value === 'object' &&
1301
- (isIRExpression(value) || 'ir' in value)) {
1302
- throw new Error('Computed/expression values are not supported in create (INSERT DATA has no WHERE clause to evaluate them). Use a literal value, or use update.');
1303
- }
1304
- // NodeReferenceValue
1305
- if (typeof value === 'object' && 'id' in value && !('shape' in value) && !('fields' in value)) {
1306
- return [iriTerm(value.id)];
1307
- }
1308
- // IRNodeData — should not produce a term directly (handled by nested create)
1309
- if (typeof value === 'object' && 'shape' in value && 'fields' in value) {
1310
- return []; // Handled separately
1311
- }
1312
- // Array
1313
- if (Array.isArray(value)) {
1314
- const terms = [];
1315
- for (const item of value) {
1316
- terms.push(...fieldValueToTerms(item, options));
1317
- }
1318
- return terms;
1319
- }
1320
- return [];
1321
- }
1322
- /**
1323
- * Recursively generate triples for an IRNodeData (used in create and nested creates).
1324
- * Returns the URI used for this node and all generated triples.
1325
- */
1326
- function generateNodeDataTriples(data, options) {
1327
- const uri = data.id || (0, sparqlUtils_js_1.generateEntityUri)(data.shape, options);
1328
- const triples = [];
1329
- const subjectTerm = iriTerm(uri);
1330
- // Type triple — resolve the SHACL NodeShape id in the IR to the ontology
1331
- // targetClass URI (sibling of PR #77's SELECT-side resolveShapeScanIri).
1332
- // Mutations were not covered by #77; see mutation-uri-fidelity.test.ts.
1333
- triples.push(tripleOf(subjectTerm, iriTerm(RDF_TYPE), iriTerm(resolveShapeScanIri(data.shape))));
1334
- // Field triples
1335
- for (const field of data.fields) {
1336
- // Resolve the SHACL PropertyShape id to its declared `path` URI.
1337
- const propertyTerm = resolvePropertyPredicateTerm(field.property);
1338
- if (field.value === null || field.value === undefined) {
1339
- continue;
1340
- }
1341
- // Handle arrays (including mixed arrays of references and nested creates)
1342
- if (Array.isArray(field.value)) {
1343
- for (const item of field.value) {
1344
- if (item && typeof item === 'object' && 'shape' in item && 'fields' in item) {
1345
- // Nested create
1346
- const nested = generateNodeDataTriples(item, options);
1347
- triples.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
1348
- triples.push(...nested.triples);
1349
- }
1350
- else {
1351
- const terms = fieldValueToTerms(item, options);
1352
- for (const term of terms) {
1353
- triples.push(tripleOf(subjectTerm, propertyTerm, term));
1354
- }
1355
- }
1356
- }
1357
- continue;
1358
- }
1359
- // Handle nested IRNodeData
1360
- if (typeof field.value === 'object' && 'shape' in field.value && 'fields' in field.value) {
1361
- const nested = generateNodeDataTriples(field.value, options);
1362
- triples.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
1363
- triples.push(...nested.triples);
1364
- continue;
1365
- }
1366
- // Simple values
1367
- const terms = fieldValueToTerms(field.value, options);
1368
- for (const term of terms) {
1369
- triples.push(tripleOf(subjectTerm, propertyTerm, term));
1370
- }
1371
- }
1372
- return { uri, triples };
1373
- }
1374
- /**
1375
- * Converts an IRCreateMutation to a SparqlInsertDataPlan.
1376
- */
1377
- function createToAlgebra(query, options) {
1378
- const { triples } = generateNodeDataTriples(query.data, options);
1379
- return {
1380
- type: 'insert_data',
1381
- triples,
1382
- };
1383
- }
1384
- // ---------------------------------------------------------------------------
1385
- // Shared update field processing
1386
- // ---------------------------------------------------------------------------
1387
- /**
1388
- * Processes IRNodeData fields into DELETE/INSERT/WHERE triples.
1389
- * Shared between updateToAlgebra (IRI subject) and updateWhereToAlgebra (variable subject).
1390
- */
1391
- function processUpdateFields(data, subjectTerm, options) {
1392
- const deletePatterns = [];
1393
- const insertPatterns = [];
1394
- const oldValueTriples = [];
1395
- const extends_ = [];
1396
- // Owned-subtree cascade: replacing a `contains` property must also
1397
- // delete the old object's owned subtree, not just the one-hop edge.
1398
- const { containsPreds: updContainsPreds } = collectContainment();
1399
- const containsOldVars = [];
1400
- const cascadeOptionals = [];
1401
- for (const field of data.fields) {
1402
- const propertyTerm = resolvePropertyPredicateTerm(field.property);
1403
- const suffix = propertySuffix(field.property);
1404
- const isContainsField = propertyTerm.kind === 'iri' && updContainsPreds.includes(propertyTerm.value);
1405
- // Check for set modification ({add, remove})
1406
- if (field.value &&
1407
- typeof field.value === 'object' &&
1408
- !Array.isArray(field.value) &&
1409
- !(field.value instanceof Date) &&
1410
- !('id' in field.value) &&
1411
- !('shape' in field.value) &&
1412
- ('add' in field.value || 'remove' in field.value)) {
1413
- const setMod = field.value;
1414
- if (setMod.remove) {
1415
- setMod.remove.forEach((removeItem, ri) => {
1416
- const removeTerm = iriTerm(removeItem.id);
1417
- deletePatterns.push(tripleOf(subjectTerm, propertyTerm, removeTerm));
1418
- oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, removeTerm));
1419
- // For a `contains` set, removing a value also removes its owned subtree.
1420
- if (isContainsField) {
1421
- const cascade = buildOwnedCascade(removeTerm, `ucr_${suffix}_${ri}_`);
1422
- deletePatterns.push(...cascade.deletePatterns);
1423
- cascadeOptionals.push(...cascade.whereOptionals);
1424
- }
1425
- });
1426
- }
1427
- if (setMod.add) {
1428
- for (const addItem of setMod.add) {
1429
- if (addItem && typeof addItem === 'object' && 'shape' in addItem && 'fields' in addItem) {
1430
- const nested = generateNodeDataTriples(addItem, options);
1431
- insertPatterns.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
1432
- insertPatterns.push(...nested.triples);
1433
- }
1434
- else {
1435
- const terms = fieldValueToTerms(addItem, options);
1436
- for (const term of terms) {
1437
- insertPatterns.push(tripleOf(subjectTerm, propertyTerm, term));
1438
- }
1439
- }
1440
- }
1441
- }
1442
- continue;
1443
- }
1444
- // Non-set-modification replace of a `contains` property: cascade the old value's
1445
- // subtree (every branch below binds `old_${suffix}` in the WHERE).
1446
- if (isContainsField) {
1447
- containsOldVars.push(`old_${suffix}`);
1448
- }
1449
- // Unset (undefined/null) — delete only
1450
- if (field.value === undefined || field.value === null) {
1451
- const oldVar = varTerm(`old_${suffix}`);
1452
- deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
1453
- oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
1454
- continue;
1455
- }
1456
- // Array overwrite — delete old values + insert new ones
1457
- if (Array.isArray(field.value)) {
1458
- const oldVar = varTerm(`old_${suffix}`);
1459
- deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
1460
- oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
1461
- for (const item of field.value) {
1462
- if (item && typeof item === 'object' && 'shape' in item && 'fields' in item) {
1463
- const nested = generateNodeDataTriples(item, options);
1464
- insertPatterns.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
1465
- insertPatterns.push(...nested.triples);
1466
- }
1467
- else {
1468
- const terms = fieldValueToTerms(item, options);
1469
- for (const term of terms) {
1470
- insertPatterns.push(tripleOf(subjectTerm, propertyTerm, term));
1471
- }
1472
- }
1473
- }
1474
- continue;
1475
- }
1476
- // Nested create (single object field)
1477
- if (typeof field.value === 'object' && 'shape' in field.value && 'fields' in field.value) {
1478
- const oldVar = varTerm(`old_${suffix}`);
1479
- deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
1480
- oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
1481
- const nested = generateNodeDataTriples(field.value, options);
1482
- insertPatterns.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
1483
- insertPatterns.push(...nested.triples);
1484
- continue;
1485
- }
1486
- // IRExpression — computed value update (e.g. p.age.plus(1))
1487
- if (isIRExpression(field.value)) {
1488
- const expr = field.value;
1489
- const oldVar = varTerm(`old_${suffix}`);
1490
- const computedVarName = `computed_${suffix}`;
1491
- const computedVar = varTerm(computedVarName);
1492
- // DELETE old value
1493
- deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
1494
- // WHERE: OPTIONAL for old value
1495
- oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
1496
- // Discover additional property references in the expression and add OPTIONAL triples
1497
- const registry = new VariableRegistry();
1498
- const mutationSubjectAlias = '__mutation_subject__';
1499
- // Pre-register the subject variable mapping for the field being updated
1500
- registry.set(mutationSubjectAlias, field.property, `old_${suffix}`);
1501
- const additionalOptionals = [];
1502
- processExpressionForProperties(expr, registry, additionalOptionals);
1503
- // Add any additional property OPTIONAL triples (for refs to other properties)
1504
- for (const triple of additionalOptionals) {
1505
- // Rewrite the subject from the placeholder variable to the actual subject term
1506
- if (triple.subject.kind === 'variable' && triple.subject.name === mutationSubjectAlias) {
1507
- oldValueTriples.push(tripleOf(subjectTerm, triple.predicate, triple.object));
1508
- }
1509
- else {
1510
- oldValueTriples.push(triple);
1511
- }
1512
- }
1513
- // Convert IRExpression to SparqlExpression
1514
- const sparqlExpr = convertExpression(expr, registry, additionalOptionals);
1515
- // BIND computed expression
1516
- extends_.push({ variable: computedVarName, expression: sparqlExpr });
1517
- // INSERT computed value
1518
- insertPatterns.push(tripleOf(subjectTerm, propertyTerm, computedVar));
1519
- continue;
1520
- }
1521
- // Simple value update — delete old + insert new
1522
- const oldVar = varTerm(`old_${suffix}`);
1523
- deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
1524
- oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
1525
- const terms = fieldValueToTerms(field.value, options);
1526
- for (const term of terms) {
1527
- insertPatterns.push(tripleOf(subjectTerm, propertyTerm, term));
1528
- }
1529
- }
1530
- // Append cascade for each replaced `contains` property's old value.
1531
- containsOldVars.forEach((oldVar, n) => {
1532
- const cascade = buildOwnedCascade(varTerm(oldVar), `uc${n}_`);
1533
- deletePatterns.push(...cascade.deletePatterns);
1534
- cascadeOptionals.push(...cascade.whereOptionals);
1535
- });
1536
- return {
1537
- deletePatterns,
1538
- insertPatterns,
1539
- oldValueTriples,
1540
- extends: extends_,
1541
- cascadeOptionals,
1542
- };
1543
- }
1544
- /**
1545
- * Wraps old-value triples in OPTIONAL (LEFT JOIN) so UPDATE succeeds
1546
- * even when the old value doesn't exist.
1547
- */
1548
- function wrapOldValueOptionals(base, oldValueTriples) {
1549
- let algebra = base;
1550
- if (oldValueTriples.length === 0) {
1551
- return algebra;
1552
- }
1553
- for (const triple of oldValueTriples) {
1554
- algebra = {
1555
- type: 'left_join',
1556
- left: algebra,
1557
- right: { type: 'bgp', triples: [triple] },
1558
- };
1559
- }
1560
- return algebra;
1561
- }
1562
- /**
1563
- * Converts an IRUpdateMutation to a SparqlDeleteInsertPlan.
1564
- */
1565
- function updateToAlgebra(query, options) {
1566
- var _a, _b, _c;
1567
- const subjectTerm = iriTerm(query.id);
1568
- const result = processUpdateFields(query.data, subjectTerm, options);
1569
- // Partition old-value/optional triples into subject-anchored ones and those
1570
- // anchored on a traversal target variable (e.g. `?a1 <name> ?a1_name` from a
1571
- // `p.bestFriend.name.ucase()` expression). Traversal-anchored triples MUST be
1572
- // nested inside the same OPTIONAL group as their traversal edge — otherwise,
1573
- // when the subject has no such edge, the leaf variable is unbound and the
1574
- // property triple matches every entity in the graph (data-corruption bug).
1575
- const travTos = new Set(((_a = query.traversalPatterns) !== null && _a !== void 0 ? _a : []).map((t) => t.to));
1576
- const travAnchoredByTo = new Map();
1577
- const subjectAnchored = [];
1578
- for (const triple of result.oldValueTriples) {
1579
- if (triple.subject.kind === 'variable' && travTos.has(triple.subject.name)) {
1580
- const list = (_b = travAnchoredByTo.get(triple.subject.name)) !== null && _b !== void 0 ? _b : [];
1581
- list.push(triple);
1582
- travAnchoredByTo.set(triple.subject.name, list);
1583
- }
1584
- else {
1585
- subjectAnchored.push(triple);
1586
- }
1587
- }
1588
- let whereAlgebra = wrapOldValueOptionals({ type: 'bgp', triples: [] }, subjectAnchored);
1589
- // Add traversal OPTIONAL patterns (for multi-segment expression refs). The
1590
- // traversal edge and its dependent leaf property triples share one OPTIONAL
1591
- // group so the leaf variable is scoped to the traversal target. These come
1592
- // BEFORE expression BINDs since the BINDs reference the traversal variables.
1593
- if (query.traversalPatterns) {
1594
- for (const trav of query.traversalPatterns) {
1595
- const fromTerm = trav.from === '__mutation_subject__' ? subjectTerm : varTerm(trav.from);
1596
- const traversalTriple = tripleOf(fromTerm, iriTerm(trav.property), varTerm(trav.to));
1597
- // The traversal edge binds the target var; each dependent leaf property is
1598
- // a nested OPTIONAL *within* that scope, so a missing optional property
1599
- // (e.g. a bestFriend with no hobby) doesn't drop the whole group, while an
1600
- // absent edge still leaves every leaf var unbound (no cross-entity match).
1601
- let travNode = { type: 'bgp', triples: [traversalTriple] };
1602
- for (const leaf of (_c = travAnchoredByTo.get(trav.to)) !== null && _c !== void 0 ? _c : []) {
1603
- travNode = {
1604
- type: 'left_join',
1605
- left: travNode,
1606
- right: { type: 'bgp', triples: [leaf] },
1607
- };
1608
- }
1609
- whereAlgebra = { type: 'left_join', left: whereAlgebra, right: travNode };
1610
- }
1611
- }
1612
- // Owned-subtree cascade OPTIONALs for replaced `contains` properties.
1613
- for (const optional of result.cascadeOptionals) {
1614
- whereAlgebra = { type: 'left_join', left: whereAlgebra, right: optional };
1615
- }
1616
- // Add BIND expressions for computed fields
1617
- for (const ext of result.extends) {
1618
- whereAlgebra = {
1619
- type: 'extend',
1620
- inner: whereAlgebra,
1621
- variable: ext.variable,
1622
- expression: ext.expression,
1623
- };
1624
- }
1625
- return {
1626
- type: 'delete_insert',
1627
- deletePatterns: result.deletePatterns,
1628
- insertPatterns: result.insertPatterns,
1629
- whereAlgebra,
1630
- };
1631
- }
1632
- // ---------------------------------------------------------------------------
1633
- // Owned-subtree cascade
1634
- //
1635
- // Composition cleanup driven by two declarative flags:
1636
- // - a property marked `contains` → the cascade FOLLOWS that edge,
1637
- // - a shape marked `dependent` → its instances may be DELETED when reached.
1638
- // Both are read from the live registry, so no vocabulary is hardcoded. From a root
1639
- // node we follow `(c1|c2|…)+` over all contains predicates and, for each dependent
1640
- // targetClass, wildcard-delete the reached typed nodes. Requiring an asserted
1641
- // `?owned a <dependentType>` naturally excludes shared predicate IRIs (e.g. a simple
1642
- // `sh:path`) and `rdf:nil` (never typed), so only owned structural nodes are removed.
1643
- // ---------------------------------------------------------------------------
1644
- /** True when the shape (or an ancestor) declares at least one `contains` property. */
1645
- function shapeHasContainsProperty(shapeId) {
1646
- var _a;
1647
- const nodeShape = (_a = (0, ShapeClass_js_1.getShapeClass)(shapeId)) === null || _a === void 0 ? void 0 : _a.shape;
1648
- if (!nodeShape || typeof nodeShape.getPropertyShapes !== 'function')
1649
- return false;
1650
- return nodeShape
1651
- .getPropertyShapes(true)
1652
- .some((ps) => ps.contains);
1653
- }
1654
- // Memoized registry scan for cascade lowering — called once per update/delete and
1655
- // again per owned-cascade item inside loops. Guarded by registry size (same rationale
1656
- // as predicateTermCache). Callers treat the result as read-only.
1657
- let containmentCache = null;
1658
- let containmentCacheSize = -1;
1659
- /** Gather contains-predicate IRIs and dependent targetClass IRIs from the registry. */
1660
- function collectContainment() {
1661
- var _a;
1662
- const shapeClasses = (0, ShapeClass_js_1.getAllShapeClasses)();
1663
- if (containmentCache && shapeClasses.size === containmentCacheSize) {
1664
- return containmentCache;
1665
- }
1666
- const containsPreds = new Set();
1667
- const dependentTypes = new Set();
1668
- for (const [, shapeClass] of shapeClasses) {
1669
- const nodeShape = shapeClass === null || shapeClass === void 0 ? void 0 : shapeClass.shape;
1670
- if (!nodeShape)
1671
- continue;
1672
- if (nodeShape.dependent && ((_a = nodeShape.targetClass) === null || _a === void 0 ? void 0 : _a.id)) {
1673
- dependentTypes.add(nodeShape.targetClass.id);
1674
- }
1675
- const propertyShapes = typeof nodeShape.getPropertyShapes === 'function'
1676
- ? nodeShape.getPropertyShapes(false)
1677
- : [];
1678
- for (const ps of propertyShapes) {
1679
- if (ps.contains) {
1680
- const predId = (0, normalizePropertyPath_js_1.getSimplePathId)(ps.path);
1681
- if (predId)
1682
- containsPreds.add(predId);
1683
- }
1684
- }
1685
- }
1686
- containmentCache = { containsPreds: [...containsPreds], dependentTypes: [...dependentTypes] };
1687
- containmentCacheSize = shapeClasses.size;
1688
- return containmentCache;
1689
- }
1690
- /**
1691
- * Build DELETE patterns + WHERE OPTIONAL blocks that cascade-delete the owned subtree
1692
- * reachable from `rootTerm` via `contains` edges. `varPrefix` keeps generated variables
1693
- * unique across multiple roots in one query. Returns empty when nothing is owned.
1694
- */
1695
- function buildOwnedCascade(rootTerm, varPrefix) {
1696
- const { containsPreds, dependentTypes } = collectContainment();
1697
- if (containsPreds.length === 0 || dependentTypes.length === 0) {
1698
- return { deletePatterns: [], whereOptionals: [] };
1699
- }
1700
- // (c1|c2|…)+ — follow one-or-more contains edges from the root.
1701
- const pathExpr = containsPreds.length === 1
1702
- ? { oneOrMore: { id: containsPreds[0] } }
1703
- : { oneOrMore: { alt: containsPreds.map((id) => ({ id })) } };
1704
- const pathTerm = {
1705
- kind: 'path',
1706
- value: (0, pathExprToSparql_js_1.pathExprToSparql)(pathExpr),
1707
- uris: (0, pathExprToSparql_js_1.collectPathUris)(pathExpr),
1708
- };
1709
- const deletePatterns = [];
1710
- const whereOptionals = [];
1711
- dependentTypes.forEach((typeId, i) => {
1712
- const owned = varTerm(`${varPrefix}own${i}`);
1713
- const p = varTerm(`${varPrefix}op${i}`);
1714
- const o = varTerm(`${varPrefix}ov${i}`);
1715
- deletePatterns.push(tripleOf(owned, p, o));
1716
- whereOptionals.push({
1717
- type: 'bgp',
1718
- triples: [
1719
- tripleOf(rootTerm, pathTerm, owned),
1720
- tripleOf(owned, iriTerm(RDF_TYPE), iriTerm(typeId)),
1721
- tripleOf(owned, p, o),
1722
- ],
1723
- });
1724
- });
1725
- return { deletePatterns, whereOptionals };
1726
- }
1727
- /**
1728
- * Converts an IRDeleteMutation to a SparqlDeleteInsertPlan (DELETE + WHERE).
1729
- */
1730
- function deleteToAlgebra(query, _options) {
1731
- const deletePatterns = [];
1732
- const requiredTriples = [];
1733
- const optionalTriples = [];
1734
- const cascadeOptionals = [];
1735
- for (let i = 0; i < query.ids.length; i++) {
1736
- const subjectTerm = iriTerm(query.ids[i].id);
1737
- const idx = query.ids.length > 1 ? `_${i}` : '';
1738
- const subjWild = tripleOf(subjectTerm, varTerm(`p${idx}`), varTerm(`o${idx}`));
1739
- const objWild = tripleOf(varTerm(`s${idx}`), varTerm(`p2${idx}`), subjectTerm);
1740
- const typeGuard = tripleOf(subjectTerm, iriTerm(RDF_TYPE), iriTerm(resolveShapeScanIri(query.shape)));
1741
- // DELETE block: all patterns (subject-wildcard, object-wildcard, type)
1742
- deletePatterns.push(subjWild, objWild, typeGuard);
1743
- // WHERE block: subject-wildcard and type guard are required;
1744
- // object-wildcard is OPTIONAL (entity may have no incoming references)
1745
- requiredTriples.push(subjWild, typeGuard);
1746
- optionalTriples.push(objWild);
1747
- // Cascade-delete the owned subtree — only for shapes that actually own something
1748
- // (have a `contains` property); a plain entity delete is left untouched.
1749
- if (shapeHasContainsProperty(query.shape)) {
1750
- const cascade = buildOwnedCascade(subjectTerm, `c${idx}_`);
1751
- deletePatterns.push(...cascade.deletePatterns);
1752
- cascadeOptionals.push(...cascade.whereOptionals);
1753
- }
1754
- }
1755
- // Build WHERE algebra: required BGP + OPTIONAL for each object-wildcard + cascade OPTIONALs
1756
- let whereAlgebra = { type: 'bgp', triples: requiredTriples };
1757
- for (const triple of optionalTriples) {
1758
- whereAlgebra = {
1759
- type: 'left_join',
1760
- left: whereAlgebra,
1761
- right: { type: 'bgp', triples: [triple] },
1762
- };
1763
- }
1764
- for (const optional of cascadeOptionals) {
1765
- whereAlgebra = { type: 'left_join', left: whereAlgebra, right: optional };
1766
- }
1767
- return {
1768
- type: 'delete_insert',
1769
- deletePatterns,
1770
- insertPatterns: [],
1771
- whereAlgebra,
1772
- };
1773
- }
1774
- // ---------------------------------------------------------------------------
1775
- // Blank node tree walking for schema-aware delete cleanup
1776
- // ---------------------------------------------------------------------------
1777
- /**
1778
- * Checks whether a PropertyShape points to blank nodes (sh:BlankNode or
1779
- * sh:BlankNodeOrIRI). Returns true when the property's range *may* include
1780
- * blank node values that should be cleaned up on delete.
1781
- */
1782
- function isBlankNodeProperty(prop) {
1783
- var _a;
1784
- const nk = (_a = prop.nodeKind) === null || _a === void 0 ? void 0 : _a.id;
1785
- if (!nk)
1786
- return false;
1787
- return nk === shacl_js_1.shacl.BlankNode.id || nk === shacl_js_1.shacl.BlankNodeOrIRI.id;
1788
- }
1789
- /**
1790
- * Recursively builds DELETE + WHERE patterns for blank-node-typed properties.
1791
- *
1792
- * For each blank-node property on the shape:
1793
- * - DELETE: `?bnVar ?pN ?oN .` (wildcard all triples on the blank node)
1794
- * - WHERE: `OPTIONAL { ?parent <property> ?bnVar . FILTER(isBlank(?bnVar)) . ?bnVar ?pN ?oN . }`
1795
- *
1796
- * Recurses into the property's valueShape to handle nested blank nodes
1797
- * (e.g. Person → Address (blank) → GeoPoint (blank)).
1798
- */
1799
- function walkBlankNodeTree(shapeId, parentVar, depth, deletePatterns) {
1800
- var _a;
1801
- const shapeClass = (0, ShapeClass_js_1.getShapeClass)(shapeId);
1802
- if (!(shapeClass === null || shapeClass === void 0 ? void 0 : shapeClass.shape))
1803
- return null;
1804
- let optionals = null;
1805
- const props = shapeClass.shape.getPropertyShapes(true);
1806
- for (const prop of props) {
1807
- if (!isBlankNodeProperty(prop))
1808
- continue;
1809
- const bnVar = `bn${depth}`;
1810
- const pVar = `p${depth}`;
1811
- const oVar = `o${depth}`;
1812
- // DELETE pattern: wildcard all triples on the blank node
1813
- deletePatterns.push(tripleOf(varTerm(bnVar), varTerm(pVar), varTerm(oVar)));
1814
- // WHERE: parent --<property>--> ?bnVar
1815
- const traverseTriple = tripleOf(varTerm(parentVar), resolvePropertyPredicateTerm(prop.id), varTerm(bnVar));
1816
- // FILTER(isBlank(?bnVar))
1817
- const isBlankFilter = {
1818
- kind: 'function_expr',
1819
- name: 'isBlank',
1820
- args: [{ kind: 'variable_expr', name: bnVar }],
1821
- };
1822
- // ?bnVar ?pN ?oN
1823
- const wildcardTriple = tripleOf(varTerm(bnVar), varTerm(pVar), varTerm(oVar));
1824
- // Build inner pattern: traverse + filter + wildcard
1825
- let innerPattern = {
1826
- type: 'bgp',
1827
- triples: [traverseTriple, wildcardTriple],
1828
- };
1829
- innerPattern = { type: 'filter', expression: isBlankFilter, inner: innerPattern };
1830
- // Recurse into valueShape for nested blank nodes
1831
- if ((_a = prop.valueShape) === null || _a === void 0 ? void 0 : _a.id) {
1832
- const nestedOptional = walkBlankNodeTree(prop.valueShape.id, bnVar, depth + 1, deletePatterns);
1833
- if (nestedOptional) {
1834
- innerPattern = { type: 'left_join', left: innerPattern, right: nestedOptional };
1835
- }
1836
- }
1837
- // Wrap in OPTIONAL (left_join)
1838
- if (optionals) {
1839
- optionals = { type: 'left_join', left: optionals, right: innerPattern };
1840
- }
1841
- else {
1842
- optionals = innerPattern;
1843
- }
1844
- depth++;
1845
- }
1846
- return optionals;
1847
- }
1848
- /**
1849
- * Converts an IRDeleteAllMutation to a SparqlDeleteInsertPlan.
1850
- *
1851
- * Generates DELETE { ?a0 ?p ?o . [blank node wildcards] }
1852
- * WHERE { ?a0 a <Shape> . ?a0 ?p ?o . OPTIONAL { [blank node traversals] } }
1853
- */
1854
- function deleteAllToAlgebra(query, _options) {
1855
- const subjectVar = 'a0';
1856
- // DELETE patterns: root wildcard
1857
- const deletePatterns = [
1858
- tripleOf(varTerm(subjectVar), varTerm('p'), varTerm('o')),
1859
- ];
1860
- // WHERE: type triple + root wildcard
1861
- const typeTriple = tripleOf(varTerm(subjectVar), iriTerm(RDF_TYPE), iriTerm(resolveShapeScanIri(query.shape)));
1862
- const rootWildcard = tripleOf(varTerm(subjectVar), varTerm('p'), varTerm('o'));
1863
- let whereAlgebra = { type: 'bgp', triples: [typeTriple, rootWildcard] };
1864
- // Walk blank node tree for cleanup
1865
- const blankNodeOptional = walkBlankNodeTree(query.shape, subjectVar, 1, deletePatterns);
1866
- if (blankNodeOptional) {
1867
- whereAlgebra = { type: 'left_join', left: whereAlgebra, right: blankNodeOptional };
1868
- }
1869
- return {
1870
- type: 'delete_insert',
1871
- deletePatterns,
1872
- insertPatterns: [],
1873
- whereAlgebra,
1874
- };
1875
- }
1876
- /**
1877
- * Converts an IRDeleteWhereMutation to a SparqlDeleteInsertPlan.
1878
- *
1879
- * Like deleteAllToAlgebra but adds filter conditions from the where clause.
1880
- */
1881
- function deleteWhereToAlgebra(query, _options) {
1882
- const subjectVar = 'a0';
1883
- const registry = new VariableRegistry();
1884
- // DELETE patterns: root wildcard
1885
- const deletePatterns = [
1886
- tripleOf(varTerm(subjectVar), varTerm('p'), varTerm('o')),
1887
- ];
1888
- // WHERE: type triple + root wildcard
1889
- const typeTriple = tripleOf(varTerm(subjectVar), iriTerm(RDF_TYPE), iriTerm(resolveShapeScanIri(query.shape)));
1890
- const rootWildcard = tripleOf(varTerm(subjectVar), varTerm('p'), varTerm('o'));
1891
- let whereAlgebra = { type: 'bgp', triples: [typeTriple, rootWildcard] };
1892
- // Process where patterns (traversals from the where clause)
1893
- const traverseTriples = [];
1894
- const optionalPropertyTriples = [];
1895
- for (const pattern of query.wherePatterns) {
1896
- processPattern(pattern, registry, traverseTriples, optionalPropertyTriples);
1897
- }
1898
- // Add traverse triples to required BGP
1899
- if (traverseTriples.length > 0) {
1900
- whereAlgebra = joinNodes(whereAlgebra, { type: 'bgp', triples: traverseTriples });
1901
- }
1902
- // Process expression to discover property triples
1903
- processExpressionForProperties(query.where, registry, optionalPropertyTriples);
1904
- // Add optional property triples
1905
- for (const triple of optionalPropertyTriples) {
1906
- whereAlgebra = joinNodes(whereAlgebra, { type: 'bgp', triples: [triple] });
1907
- }
1908
- // Convert and add filter expression
1909
- const filterExpr = convertExpression(query.where, registry, []);
1910
- whereAlgebra = { type: 'filter', expression: filterExpr, inner: whereAlgebra };
1911
- // Walk blank node tree for cleanup
1912
- const blankNodeOptional = walkBlankNodeTree(query.shape, subjectVar, 1, deletePatterns);
1913
- if (blankNodeOptional) {
1914
- whereAlgebra = { type: 'left_join', left: whereAlgebra, right: blankNodeOptional };
1915
- }
1916
- return {
1917
- type: 'delete_insert',
1918
- deletePatterns,
1919
- insertPatterns: [],
1920
- whereAlgebra,
1921
- };
1922
- }
1923
- /**
1924
- * Converts an IRUpdateWhereMutation to a SparqlDeleteInsertPlan.
1925
- *
1926
- * Like updateToAlgebra but uses a variable subject (?a0) instead of a
1927
- * hardcoded entity IRI, adds a type triple, and optionally includes
1928
- * filter conditions from the where clause.
1929
- */
1930
- function updateWhereToAlgebra(query, options) {
1931
- const subjectTerm = varTerm('a0');
1932
- const result = processUpdateFields(query.data, subjectTerm, options);
1933
- // WHERE: type triple is always required
1934
- const typeTriple = tripleOf(subjectTerm, iriTerm(RDF_TYPE), iriTerm(resolveShapeScanIri(query.data.shape)));
1935
- let whereAlgebra = { type: 'bgp', triples: [typeTriple] };
1936
- // Process where filter conditions (if any)
1937
- if (query.where && query.wherePatterns) {
1938
- const registry = new VariableRegistry();
1939
- const traverseTriples = [];
1940
- const optionalPropertyTriples = [];
1941
- for (const pattern of query.wherePatterns) {
1942
- processPattern(pattern, registry, traverseTriples, optionalPropertyTriples);
1943
- }
1944
- if (traverseTriples.length > 0) {
1945
- whereAlgebra = joinNodes(whereAlgebra, { type: 'bgp', triples: traverseTriples });
1946
- }
1947
- processExpressionForProperties(query.where, registry, optionalPropertyTriples);
1948
- for (const triple of optionalPropertyTriples) {
1949
- whereAlgebra = joinNodes(whereAlgebra, { type: 'bgp', triples: [triple] });
1950
- }
1951
- const filterExpr = convertExpression(query.where, registry, []);
1952
- whereAlgebra = { type: 'filter', expression: filterExpr, inner: whereAlgebra };
1953
- }
1954
- whereAlgebra = wrapOldValueOptionals(whereAlgebra, result.oldValueTriples);
1955
- // Add traversal OPTIONAL patterns (for multi-segment expression refs)
1956
- // These must come BEFORE expression BINDs since the BINDs reference traversal variables.
1957
- if (query.traversalPatterns) {
1958
- for (const trav of query.traversalPatterns) {
1959
- const fromTerm = trav.from === '__mutation_subject__' ? varTerm('a0') : varTerm(trav.from);
1960
- const traversalTriple = tripleOf(fromTerm, iriTerm(trav.property), varTerm(trav.to));
1961
- whereAlgebra = {
1962
- type: 'left_join',
1963
- left: whereAlgebra,
1964
- right: { type: 'bgp', triples: [traversalTriple] },
1965
- };
1966
- }
1967
- }
1968
- // Owned-subtree cascade OPTIONALs for replaced `contains` properties.
1969
- for (const optional of result.cascadeOptionals) {
1970
- whereAlgebra = { type: 'left_join', left: whereAlgebra, right: optional };
1971
- }
1972
- // Add BIND expressions for computed fields
1973
- for (const ext of result.extends) {
1974
- whereAlgebra = {
1975
- type: 'extend',
1976
- inner: whereAlgebra,
1977
- variable: ext.variable,
1978
- expression: ext.expression,
1979
- };
1980
- }
1981
- return {
1982
- type: 'delete_insert',
1983
- deletePatterns: result.deletePatterns,
1984
- insertPatterns: result.insertPatterns,
1985
- whereAlgebra,
1986
- };
1987
- }
1988
- // ---------------------------------------------------------------------------
1989
- // Convenience wrappers: IR → algebra → SPARQL string in one call
1990
- // ---------------------------------------------------------------------------
1991
- /**
1992
- * Converts an IRSelectQuery to a SPARQL string.
1993
- */
1994
- function selectToSparql(query, options) {
1995
- const plan = selectToAlgebra(query, options);
1996
- return (0, algebraToString_js_1.selectPlanToSparql)(plan, options);
1997
- }
1998
- /**
1999
- * Converts an IRCreateMutation to a SPARQL string.
2000
- * Stub: will be implemented when algebraToString is available.
2001
- */
2002
- function createToSparql(query, options) {
2003
- const plan = createToAlgebra(query, options);
2004
- return (0, algebraToString_js_1.insertDataPlanToSparql)(plan, options);
2005
- }
2006
- /**
2007
- * Converts an IRUpdateMutation to a SPARQL string.
2008
- * Stub: will be implemented when algebraToString is available.
2009
- */
2010
- function updateToSparql(query, options) {
2011
- const plan = updateToAlgebra(query, options);
2012
- return (0, algebraToString_js_1.deleteInsertPlanToSparql)(plan, options);
2013
- }
2014
- /**
2015
- * Converts an IRDeleteMutation to a SPARQL string.
2016
- */
2017
- function deleteToSparql(query, options) {
2018
- const plan = deleteToAlgebra(query, options);
2019
- return (0, algebraToString_js_1.deleteInsertPlanToSparql)(plan, options);
2020
- }
2021
- /**
2022
- * Converts an IRDeleteAllMutation to a SPARQL string.
2023
- */
2024
- function deleteAllToSparql(query, options) {
2025
- const plan = deleteAllToAlgebra(query, options);
2026
- return (0, algebraToString_js_1.deleteInsertPlanToSparql)(plan, options);
2027
- }
2028
- /**
2029
- * Converts an IRDeleteWhereMutation to a SPARQL string.
2030
- */
2031
- function deleteWhereToSparql(query, options) {
2032
- const plan = deleteWhereToAlgebra(query, options);
2033
- return (0, algebraToString_js_1.deleteInsertPlanToSparql)(plan, options);
2034
- }
2035
- /**
2036
- * Converts an IRUpdateWhereMutation to a SPARQL string.
2037
- */
2038
- function updateWhereToSparql(query, options) {
2039
- const plan = updateWhereToAlgebra(query, options);
2040
- return (0, algebraToString_js_1.deleteInsertPlanToSparql)(plan, options);
2041
- }
2042
- //# sourceMappingURL=irToAlgebra.js.map