@_linked/core 1.0.0 → 1.2.0-next.20260302120536

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 (187) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +321 -43
  3. package/lib/cjs/index.js +4 -0
  4. package/lib/cjs/index.js.map +1 -1
  5. package/lib/cjs/interfaces/IQuadStore.d.ts +19 -7
  6. package/lib/cjs/queries/CreateQuery.d.ts +7 -8
  7. package/lib/cjs/queries/CreateQuery.js +4 -4
  8. package/lib/cjs/queries/CreateQuery.js.map +1 -1
  9. package/lib/cjs/queries/DeleteQuery.d.ts +7 -8
  10. package/lib/cjs/queries/DeleteQuery.js +4 -4
  11. package/lib/cjs/queries/DeleteQuery.js.map +1 -1
  12. package/lib/cjs/queries/IRAliasScope.d.ts +20 -0
  13. package/lib/cjs/queries/IRAliasScope.js +52 -0
  14. package/lib/cjs/queries/IRAliasScope.js.map +1 -0
  15. package/lib/cjs/queries/IRCanonicalize.d.ts +36 -0
  16. package/lib/cjs/queries/IRCanonicalize.js +121 -0
  17. package/lib/cjs/queries/IRCanonicalize.js.map +1 -0
  18. package/lib/cjs/queries/IRDesugar.d.ts +98 -0
  19. package/lib/cjs/queries/IRDesugar.js +244 -0
  20. package/lib/cjs/queries/IRDesugar.js.map +1 -0
  21. package/lib/cjs/queries/IRLower.d.ts +8 -0
  22. package/lib/cjs/queries/IRLower.js +272 -0
  23. package/lib/cjs/queries/IRLower.js.map +1 -0
  24. package/lib/cjs/queries/IRMutation.d.ts +23 -0
  25. package/lib/cjs/queries/IRMutation.js +77 -0
  26. package/lib/cjs/queries/IRMutation.js.map +1 -0
  27. package/lib/cjs/queries/IRPipeline.d.ts +8 -0
  28. package/lib/cjs/queries/IRPipeline.js +25 -0
  29. package/lib/cjs/queries/IRPipeline.js.map +1 -0
  30. package/lib/cjs/queries/IRProjection.d.ts +38 -0
  31. package/lib/cjs/queries/IRProjection.js +98 -0
  32. package/lib/cjs/queries/IRProjection.js.map +1 -0
  33. package/lib/cjs/queries/IntermediateRepresentation.d.ts +210 -0
  34. package/lib/cjs/queries/IntermediateRepresentation.js +3 -0
  35. package/lib/cjs/queries/IntermediateRepresentation.js.map +1 -0
  36. package/lib/cjs/queries/MutationQuery.js +9 -23
  37. package/lib/cjs/queries/MutationQuery.js.map +1 -1
  38. package/lib/cjs/queries/QueryFactory.d.ts +0 -2
  39. package/lib/cjs/queries/QueryFactory.js +0 -3
  40. package/lib/cjs/queries/QueryFactory.js.map +1 -1
  41. package/lib/cjs/queries/QueryParser.d.ts +6 -1
  42. package/lib/cjs/queries/QueryParser.js +14 -22
  43. package/lib/cjs/queries/QueryParser.js.map +1 -1
  44. package/lib/cjs/queries/SelectQuery.d.ts +18 -27
  45. package/lib/cjs/queries/SelectQuery.js +54 -45
  46. package/lib/cjs/queries/SelectQuery.js.map +1 -1
  47. package/lib/cjs/queries/UpdateQuery.d.ts +8 -9
  48. package/lib/cjs/queries/UpdateQuery.js +4 -4
  49. package/lib/cjs/queries/UpdateQuery.js.map +1 -1
  50. package/lib/cjs/shapes/SHACL.d.ts +1 -0
  51. package/lib/cjs/shapes/SHACL.js +82 -2
  52. package/lib/cjs/shapes/SHACL.js.map +1 -1
  53. package/lib/cjs/shapes/Shape.d.ts +11 -10
  54. package/lib/cjs/shapes/Shape.js +11 -5
  55. package/lib/cjs/shapes/Shape.js.map +1 -1
  56. package/lib/cjs/sparql/SparqlAlgebra.d.ts +158 -0
  57. package/lib/cjs/sparql/SparqlAlgebra.js +4 -0
  58. package/lib/cjs/sparql/SparqlAlgebra.js.map +1 -0
  59. package/lib/cjs/sparql/SparqlStore.d.ts +52 -0
  60. package/lib/cjs/sparql/SparqlStore.js +81 -0
  61. package/lib/cjs/sparql/SparqlStore.js.map +1 -0
  62. package/lib/cjs/sparql/algebraToString.d.ts +13 -0
  63. package/lib/cjs/sparql/algebraToString.js +298 -0
  64. package/lib/cjs/sparql/algebraToString.js.map +1 -0
  65. package/lib/cjs/sparql/index.d.ts +9 -0
  66. package/lib/cjs/sparql/index.js +40 -0
  67. package/lib/cjs/sparql/index.js.map +1 -0
  68. package/lib/cjs/sparql/irToAlgebra.d.ts +39 -0
  69. package/lib/cjs/sparql/irToAlgebra.js +927 -0
  70. package/lib/cjs/sparql/irToAlgebra.js.map +1 -0
  71. package/lib/cjs/sparql/resultMapping.d.ts +36 -0
  72. package/lib/cjs/sparql/resultMapping.js +501 -0
  73. package/lib/cjs/sparql/resultMapping.js.map +1 -0
  74. package/lib/cjs/sparql/sparqlUtils.d.ts +32 -0
  75. package/lib/cjs/sparql/sparqlUtils.js +89 -0
  76. package/lib/cjs/sparql/sparqlUtils.js.map +1 -0
  77. package/lib/cjs/test-helpers/FusekiStore.d.ts +29 -0
  78. package/lib/cjs/test-helpers/FusekiStore.js +82 -0
  79. package/lib/cjs/test-helpers/FusekiStore.js.map +1 -0
  80. package/lib/cjs/test-helpers/fuseki-test-store.d.ts +43 -0
  81. package/lib/cjs/test-helpers/fuseki-test-store.js +144 -0
  82. package/lib/cjs/test-helpers/fuseki-test-store.js.map +1 -0
  83. package/lib/cjs/test-helpers/query-capture-store.d.ts +5 -0
  84. package/lib/cjs/test-helpers/query-capture-store.js +59 -0
  85. package/lib/cjs/test-helpers/query-capture-store.js.map +1 -0
  86. package/lib/cjs/test-helpers/query-fixtures.d.ts +700 -117
  87. package/lib/cjs/test-helpers/query-fixtures.js +39 -1
  88. package/lib/cjs/test-helpers/query-fixtures.js.map +1 -1
  89. package/lib/cjs/utils/LinkedStorage.d.ts +7 -7
  90. package/lib/cjs/utils/LinkedStorage.js +4 -3
  91. package/lib/cjs/utils/LinkedStorage.js.map +1 -1
  92. package/lib/esm/index.js +4 -0
  93. package/lib/esm/index.js.map +1 -1
  94. package/lib/esm/interfaces/IQuadStore.d.ts +19 -7
  95. package/lib/esm/queries/CreateQuery.d.ts +7 -8
  96. package/lib/esm/queries/CreateQuery.js +4 -4
  97. package/lib/esm/queries/CreateQuery.js.map +1 -1
  98. package/lib/esm/queries/DeleteQuery.d.ts +7 -8
  99. package/lib/esm/queries/DeleteQuery.js +4 -4
  100. package/lib/esm/queries/DeleteQuery.js.map +1 -1
  101. package/lib/esm/queries/IRAliasScope.d.ts +20 -0
  102. package/lib/esm/queries/IRAliasScope.js +47 -0
  103. package/lib/esm/queries/IRAliasScope.js.map +1 -0
  104. package/lib/esm/queries/IRCanonicalize.d.ts +36 -0
  105. package/lib/esm/queries/IRCanonicalize.js +116 -0
  106. package/lib/esm/queries/IRCanonicalize.js.map +1 -0
  107. package/lib/esm/queries/IRDesugar.d.ts +98 -0
  108. package/lib/esm/queries/IRDesugar.js +240 -0
  109. package/lib/esm/queries/IRDesugar.js.map +1 -0
  110. package/lib/esm/queries/IRLower.d.ts +8 -0
  111. package/lib/esm/queries/IRLower.js +268 -0
  112. package/lib/esm/queries/IRLower.js.map +1 -0
  113. package/lib/esm/queries/IRMutation.d.ts +23 -0
  114. package/lib/esm/queries/IRMutation.js +71 -0
  115. package/lib/esm/queries/IRMutation.js.map +1 -0
  116. package/lib/esm/queries/IRPipeline.d.ts +8 -0
  117. package/lib/esm/queries/IRPipeline.js +21 -0
  118. package/lib/esm/queries/IRPipeline.js.map +1 -0
  119. package/lib/esm/queries/IRProjection.d.ts +38 -0
  120. package/lib/esm/queries/IRProjection.js +92 -0
  121. package/lib/esm/queries/IRProjection.js.map +1 -0
  122. package/lib/esm/queries/IntermediateRepresentation.d.ts +210 -0
  123. package/lib/esm/queries/IntermediateRepresentation.js +2 -0
  124. package/lib/esm/queries/IntermediateRepresentation.js.map +1 -0
  125. package/lib/esm/queries/MutationQuery.js +9 -23
  126. package/lib/esm/queries/MutationQuery.js.map +1 -1
  127. package/lib/esm/queries/QueryFactory.d.ts +0 -2
  128. package/lib/esm/queries/QueryFactory.js +0 -3
  129. package/lib/esm/queries/QueryFactory.js.map +1 -1
  130. package/lib/esm/queries/QueryParser.d.ts +6 -1
  131. package/lib/esm/queries/QueryParser.js +14 -23
  132. package/lib/esm/queries/QueryParser.js.map +1 -1
  133. package/lib/esm/queries/SelectQuery.d.ts +18 -27
  134. package/lib/esm/queries/SelectQuery.js +54 -45
  135. package/lib/esm/queries/SelectQuery.js.map +1 -1
  136. package/lib/esm/queries/UpdateQuery.d.ts +8 -9
  137. package/lib/esm/queries/UpdateQuery.js +4 -4
  138. package/lib/esm/queries/UpdateQuery.js.map +1 -1
  139. package/lib/esm/shapes/SHACL.d.ts +1 -0
  140. package/lib/esm/shapes/SHACL.js +82 -2
  141. package/lib/esm/shapes/SHACL.js.map +1 -1
  142. package/lib/esm/shapes/Shape.d.ts +11 -10
  143. package/lib/esm/shapes/Shape.js +11 -5
  144. package/lib/esm/shapes/Shape.js.map +1 -1
  145. package/lib/esm/sparql/SparqlAlgebra.d.ts +158 -0
  146. package/lib/esm/sparql/SparqlAlgebra.js +3 -0
  147. package/lib/esm/sparql/SparqlAlgebra.js.map +1 -0
  148. package/lib/esm/sparql/SparqlStore.d.ts +52 -0
  149. package/lib/esm/sparql/SparqlStore.js +77 -0
  150. package/lib/esm/sparql/SparqlStore.js.map +1 -0
  151. package/lib/esm/sparql/algebraToString.d.ts +13 -0
  152. package/lib/esm/sparql/algebraToString.js +289 -0
  153. package/lib/esm/sparql/algebraToString.js.map +1 -0
  154. package/lib/esm/sparql/index.d.ts +9 -0
  155. package/lib/esm/sparql/index.js +13 -0
  156. package/lib/esm/sparql/index.js.map +1 -0
  157. package/lib/esm/sparql/irToAlgebra.d.ts +39 -0
  158. package/lib/esm/sparql/irToAlgebra.js +917 -0
  159. package/lib/esm/sparql/irToAlgebra.js.map +1 -0
  160. package/lib/esm/sparql/resultMapping.d.ts +36 -0
  161. package/lib/esm/sparql/resultMapping.js +496 -0
  162. package/lib/esm/sparql/resultMapping.js.map +1 -0
  163. package/lib/esm/sparql/sparqlUtils.d.ts +32 -0
  164. package/lib/esm/sparql/sparqlUtils.js +82 -0
  165. package/lib/esm/sparql/sparqlUtils.js.map +1 -0
  166. package/lib/esm/test-helpers/FusekiStore.d.ts +29 -0
  167. package/lib/esm/test-helpers/FusekiStore.js +78 -0
  168. package/lib/esm/test-helpers/FusekiStore.js.map +1 -0
  169. package/lib/esm/test-helpers/fuseki-test-store.d.ts +43 -0
  170. package/lib/esm/test-helpers/fuseki-test-store.js +135 -0
  171. package/lib/esm/test-helpers/fuseki-test-store.js.map +1 -0
  172. package/lib/esm/test-helpers/query-capture-store.d.ts +5 -0
  173. package/lib/esm/test-helpers/query-capture-store.js +55 -0
  174. package/lib/esm/test-helpers/query-capture-store.js.map +1 -0
  175. package/lib/esm/test-helpers/query-fixtures.d.ts +700 -117
  176. package/lib/esm/test-helpers/query-fixtures.js +38 -0
  177. package/lib/esm/test-helpers/query-fixtures.js.map +1 -1
  178. package/lib/esm/utils/LinkedStorage.d.ts +7 -7
  179. package/lib/esm/utils/LinkedStorage.js +4 -3
  180. package/lib/esm/utils/LinkedStorage.js.map +1 -1
  181. package/package.json +7 -3
  182. package/lib/cjs/interfaces/IQueryParser.d.ts +0 -13
  183. package/lib/cjs/interfaces/IQueryParser.js +0 -10
  184. package/lib/cjs/interfaces/IQueryParser.js.map +0 -1
  185. package/lib/esm/interfaces/IQueryParser.d.ts +0 -13
  186. package/lib/esm/interfaces/IQueryParser.js +0 -7
  187. package/lib/esm/interfaces/IQueryParser.js.map +0 -1
@@ -0,0 +1,927 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.selectToAlgebra = selectToAlgebra;
4
+ exports.createToAlgebra = createToAlgebra;
5
+ exports.updateToAlgebra = updateToAlgebra;
6
+ exports.deleteToAlgebra = deleteToAlgebra;
7
+ exports.selectToSparql = selectToSparql;
8
+ exports.createToSparql = createToSparql;
9
+ exports.updateToSparql = updateToSparql;
10
+ exports.deleteToSparql = deleteToSparql;
11
+ const sparqlUtils_js_1 = require("./sparqlUtils.js");
12
+ const algebraToString_js_1 = require("./algebraToString.js");
13
+ const rdf_js_1 = require("../ontologies/rdf.js");
14
+ const xsd_js_1 = require("../ontologies/xsd.js");
15
+ // ---------------------------------------------------------------------------
16
+ // Constants
17
+ // ---------------------------------------------------------------------------
18
+ const RDF_TYPE = rdf_js_1.rdf.type.id;
19
+ const XSD_DATETIME = xsd_js_1.xsd.dateTime.id;
20
+ const XSD_BOOLEAN = xsd_js_1.xsd.boolean.id;
21
+ const XSD_INTEGER = xsd_js_1.xsd.integer.id;
22
+ const XSD_DOUBLE = xsd_js_1.xsd.double.id;
23
+ // ---------------------------------------------------------------------------
24
+ // Helpers
25
+ // ---------------------------------------------------------------------------
26
+ function iriTerm(value) {
27
+ return { kind: 'iri', value };
28
+ }
29
+ function varTerm(name) {
30
+ return { kind: 'variable', name };
31
+ }
32
+ function literalTerm(value, datatype) {
33
+ if (datatype) {
34
+ return { kind: 'literal', value, datatype };
35
+ }
36
+ return { kind: 'literal', value };
37
+ }
38
+ function tripleOf(subject, predicate, object) {
39
+ return { subject, predicate, object };
40
+ }
41
+ /** Produce variable name suffix from the last segment of a property URI. */
42
+ function propertySuffix(propertyUri) {
43
+ const hashIdx = propertyUri.lastIndexOf('#');
44
+ if (hashIdx >= 0)
45
+ return propertyUri.substring(hashIdx + 1);
46
+ const slashIdx = propertyUri.lastIndexOf('/');
47
+ return slashIdx >= 0 ? propertyUri.substring(slashIdx + 1) : propertyUri;
48
+ }
49
+ /**
50
+ * Sanitize a string so it's valid in a SPARQL variable name.
51
+ * Replaces any non-alphanumeric/underscore characters with underscores.
52
+ */
53
+ function sanitizeVarName(name) {
54
+ return name.replace(/[^A-Za-z0-9_]/g, '_');
55
+ }
56
+ /**
57
+ * Wrap a single node in a LeftJoin, making `right` optional relative to `left`.
58
+ */
59
+ function wrapOptional(left, right) {
60
+ return { type: 'left_join', left, right };
61
+ }
62
+ /**
63
+ * Join two algebra nodes. If left is null, returns right.
64
+ */
65
+ function joinNodes(left, right) {
66
+ if (!left)
67
+ return right;
68
+ return { type: 'join', left, right };
69
+ }
70
+ // ---------------------------------------------------------------------------
71
+ // Pattern helpers
72
+ // ---------------------------------------------------------------------------
73
+ /**
74
+ * Recursively collects all traversal alias target variables from IR patterns.
75
+ * Used to ensure traversal aliases appear in the SELECT projection for result grouping.
76
+ */
77
+ function collectTraversalAliases(patterns) {
78
+ const aliases = [];
79
+ for (const p of patterns) {
80
+ if (p.kind === 'traverse') {
81
+ aliases.push(p.to);
82
+ }
83
+ else if (p.kind === 'join') {
84
+ aliases.push(...collectTraversalAliases(p.patterns));
85
+ }
86
+ else if (p.kind === 'optional') {
87
+ aliases.push(...collectTraversalAliases([p.pattern]));
88
+ }
89
+ else if (p.kind === 'union') {
90
+ for (const branch of p.branches) {
91
+ aliases.push(...collectTraversalAliases([branch]));
92
+ }
93
+ }
94
+ }
95
+ return aliases;
96
+ }
97
+ // ---------------------------------------------------------------------------
98
+ // Variable Registry
99
+ // ---------------------------------------------------------------------------
100
+ /**
101
+ * Maps (alias, property) → SPARQL variable name.
102
+ * Used to deduplicate variables across traverse and property_expr nodes.
103
+ */
104
+ class VariableRegistry {
105
+ constructor() {
106
+ this.map = new Map();
107
+ this.usedVarNames = new Set();
108
+ }
109
+ key(alias, property) {
110
+ return `${alias}::${property}`;
111
+ }
112
+ has(alias, property) {
113
+ return this.map.has(this.key(alias, property));
114
+ }
115
+ get(alias, property) {
116
+ return this.map.get(this.key(alias, property));
117
+ }
118
+ set(alias, property, variable) {
119
+ this.map.set(this.key(alias, property), variable);
120
+ this.usedVarNames.add(variable);
121
+ }
122
+ getOrCreate(alias, property) {
123
+ const existing = this.get(alias, property);
124
+ if (existing)
125
+ return existing;
126
+ const suffix = propertySuffix(property);
127
+ let varName = `${sanitizeVarName(alias)}_${suffix}`;
128
+ // Deduplicate: if varName is already used by a different (alias, property),
129
+ // append a counter to ensure unique SPARQL variable names
130
+ let counter = 2;
131
+ while (this.usedVarNames.has(varName)) {
132
+ varName = `${sanitizeVarName(alias)}_${suffix}_${counter}`;
133
+ counter++;
134
+ }
135
+ this.set(alias, property, varName);
136
+ return varName;
137
+ }
138
+ }
139
+ // ---------------------------------------------------------------------------
140
+ // Aggregate detection
141
+ // ---------------------------------------------------------------------------
142
+ /**
143
+ * Checks whether a SparqlExpression tree contains an aggregate sub-expression.
144
+ * Used to route aggregate-containing filters to HAVING instead of FILTER.
145
+ */
146
+ function containsAggregate(expr) {
147
+ switch (expr.kind) {
148
+ case 'aggregate_expr':
149
+ return true;
150
+ case 'binary_expr':
151
+ return containsAggregate(expr.left) || containsAggregate(expr.right);
152
+ case 'logical_expr':
153
+ return expr.exprs.some(containsAggregate);
154
+ case 'not_expr':
155
+ return containsAggregate(expr.inner);
156
+ case 'function_expr':
157
+ return expr.args.some(containsAggregate);
158
+ default:
159
+ return false;
160
+ }
161
+ }
162
+ // ---------------------------------------------------------------------------
163
+ // Select conversion
164
+ // ---------------------------------------------------------------------------
165
+ /**
166
+ * Converts an IRSelectQuery to a SparqlSelectPlan.
167
+ */
168
+ function selectToAlgebra(query, _options) {
169
+ const registry = new VariableRegistry();
170
+ // Track property triples that need to be added as OPTIONAL
171
+ const optionalPropertyTriples = [];
172
+ // Track filtered traversals (inline where) — these get their own OPTIONAL blocks
173
+ const filteredTraverseBlocks = [];
174
+ // 1. Root shape scan → BGP with type triple
175
+ const rootAlias = query.root.alias;
176
+ const shapeUri = query.root.shape;
177
+ const typeTriple = tripleOf(varTerm(rootAlias), iriTerm(RDF_TYPE), iriTerm(shapeUri));
178
+ const requiredTriples = [typeTriple];
179
+ // Track traverse triples (required pattern)
180
+ const traverseTriples = [];
181
+ // 2. Process patterns → traverse triples, populate variable registry
182
+ for (const pattern of query.patterns) {
183
+ processPattern(pattern, registry, traverseTriples, optionalPropertyTriples, filteredTraverseBlocks);
184
+ }
185
+ // 3. Process projection expressions, where clause, orderBy expressions
186
+ // to discover any additional property_expr references
187
+ for (const item of query.projection) {
188
+ processExpressionForProperties(item.expression, registry, optionalPropertyTriples);
189
+ }
190
+ if (query.where) {
191
+ processExpressionForProperties(query.where, registry, optionalPropertyTriples);
192
+ }
193
+ if (query.orderBy) {
194
+ for (const orderItem of query.orderBy) {
195
+ processExpressionForProperties(orderItem.expression, registry, optionalPropertyTriples);
196
+ }
197
+ }
198
+ // 4. Build the algebra tree
199
+ // - Start with the required BGP (type triple + traverse triples)
200
+ // - Wrap each optional property triple in a LeftJoin
201
+ const requiredBgp = {
202
+ type: 'bgp',
203
+ triples: [...requiredTriples, ...traverseTriples],
204
+ };
205
+ let algebra = requiredBgp;
206
+ // 4b. Build filtered OPTIONAL blocks for inline where traversals.
207
+ // Each block contains: traverse triple + filter property triples + FILTER.
208
+ // Property triples referenced by the filter are co-located inside the OPTIONAL
209
+ // so that the filter can reference them.
210
+ for (const block of filteredTraverseBlocks) {
211
+ const filterPropertyTriples = [];
212
+ processExpressionForProperties(block.filter, registry, filterPropertyTriples);
213
+ const filterExpr = convertExpression(block.filter, registry, filterPropertyTriples);
214
+ const blockTriples = [block.traverseTriple, ...filterPropertyTriples];
215
+ const blockBgp = { type: 'bgp', triples: blockTriples };
216
+ const filteredBlock = { type: 'filter', expression: filterExpr, inner: blockBgp };
217
+ algebra = wrapOptional(algebra, filteredBlock);
218
+ }
219
+ // Wrap each optional property triple in its own OPTIONAL (LeftJoin)
220
+ for (const propTriple of optionalPropertyTriples) {
221
+ algebra = wrapOptional(algebra, {
222
+ type: 'bgp',
223
+ triples: [propTriple],
224
+ });
225
+ }
226
+ // 5. Where clause → Filter wrapping (or HAVING if aggregate-containing)
227
+ let havingExpr;
228
+ if (query.where) {
229
+ const filterExpr = convertExpression(query.where, registry, optionalPropertyTriples);
230
+ if (containsAggregate(filterExpr)) {
231
+ havingExpr = filterExpr;
232
+ }
233
+ else {
234
+ algebra = {
235
+ type: 'filter',
236
+ expression: filterExpr,
237
+ inner: algebra,
238
+ };
239
+ }
240
+ }
241
+ // 6. SubjectId → Filter
242
+ if (query.subjectId) {
243
+ const subjectFilter = {
244
+ kind: 'binary_expr',
245
+ op: '=',
246
+ left: { kind: 'variable_expr', name: rootAlias },
247
+ right: { kind: 'iri_expr', value: query.subjectId },
248
+ };
249
+ algebra = {
250
+ type: 'filter',
251
+ expression: subjectFilter,
252
+ inner: algebra,
253
+ };
254
+ }
255
+ // 7. Build projection
256
+ const projection = [];
257
+ const aggregates = [];
258
+ let hasAggregates = false;
259
+ // Always include root alias as first projection variable
260
+ projection.push({ kind: 'variable', name: rootAlias });
261
+ // Collect traversal aliases upfront to detect aggregate alias collisions
262
+ const traversalAliasSet = new Set(collectTraversalAliases(query.patterns));
263
+ // Track traversal aliases consumed by aggregate renames (should not be
264
+ // re-projected as plain variables, which would alter GROUP BY semantics)
265
+ const aggregateRenamedAliases = new Set();
266
+ for (const item of query.projection) {
267
+ const sparqlExpr = convertExpression(item.expression, registry, optionalPropertyTriples);
268
+ if (sparqlExpr.kind === 'aggregate_expr') {
269
+ hasAggregates = true;
270
+ // Avoid collision: if aggregate alias matches a traversal alias,
271
+ // rename it so SPARQL doesn't produce duplicate variable bindings
272
+ let aggAlias = item.alias;
273
+ if (traversalAliasSet.has(aggAlias)) {
274
+ aggregateRenamedAliases.add(aggAlias);
275
+ aggAlias = `${aggAlias}_agg`;
276
+ // Update resultMap so result mapping uses the renamed alias
277
+ for (const rm of query.resultMap) {
278
+ if (rm.alias === item.alias)
279
+ rm.alias = aggAlias;
280
+ }
281
+ }
282
+ projection.push({
283
+ kind: 'aggregate',
284
+ expression: sparqlExpr,
285
+ alias: aggAlias,
286
+ });
287
+ aggregates.push({
288
+ variable: aggAlias,
289
+ aggregate: sparqlExpr,
290
+ });
291
+ }
292
+ else {
293
+ // For property_expr, the variable is the resolved name from registry
294
+ const varName = resolveExpressionVariable(item.expression, registry);
295
+ if (varName && varName !== rootAlias) {
296
+ projection.push({ kind: 'variable', name: varName });
297
+ }
298
+ else if (!varName) {
299
+ // Non-variable expression (binary_expr, function_expr, etc.)
300
+ // → project as (expr AS ?alias)
301
+ projection.push({ kind: 'expression', expression: sparqlExpr, alias: item.alias });
302
+ }
303
+ }
304
+ }
305
+ // 7b. Include traversal aliases needed for result grouping
306
+ // When nested results are projected (e.g. p.friends.name), the result
307
+ // mapping needs the traversal alias variable (?a1) in the bindings to
308
+ // group nested rows by entity. Without this, mapNestedRows() can't
309
+ // identify which nested fields belong to which traversed entity.
310
+ const projectedNames = new Set();
311
+ for (const p of projection) {
312
+ if (p.kind === 'variable')
313
+ projectedNames.add(p.name);
314
+ else if (p.kind === 'aggregate' || p.kind === 'expression')
315
+ projectedNames.add(p.alias);
316
+ }
317
+ for (const alias of collectTraversalAliases(query.patterns)) {
318
+ if (!projectedNames.has(alias) && !aggregateRenamedAliases.has(alias)) {
319
+ projection.push({ kind: 'variable', name: alias });
320
+ projectedNames.add(alias);
321
+ }
322
+ }
323
+ // 8. GROUP BY inference
324
+ let groupBy;
325
+ if (havingExpr) {
326
+ hasAggregates = true;
327
+ }
328
+ if (hasAggregates) {
329
+ // All non-aggregate projected variables become GROUP BY targets
330
+ groupBy = projection
331
+ .filter((p) => p.kind === 'variable')
332
+ .map((p) => p.name);
333
+ }
334
+ // 9. OrderBy
335
+ let orderBy;
336
+ if (query.orderBy) {
337
+ orderBy = query.orderBy.map((item) => ({
338
+ expression: convertExpression(item.expression, registry, optionalPropertyTriples),
339
+ direction: item.direction,
340
+ }));
341
+ }
342
+ return {
343
+ type: 'select',
344
+ algebra,
345
+ projection,
346
+ distinct: !hasAggregates ? true : undefined,
347
+ orderBy,
348
+ limit: query.limit,
349
+ offset: query.offset,
350
+ groupBy,
351
+ having: havingExpr,
352
+ aggregates: aggregates.length > 0 ? aggregates : undefined,
353
+ };
354
+ }
355
+ // ---------------------------------------------------------------------------
356
+ // Pattern processing
357
+ // ---------------------------------------------------------------------------
358
+ function processPattern(pattern, registry, traverseTriples, optionalPropertyTriples, filteredTraverseBlocks) {
359
+ switch (pattern.kind) {
360
+ case 'shape_scan':
361
+ // Additional shape scans (non-root) are handled as type triples
362
+ // but this case is rare — root is handled separately
363
+ break;
364
+ case 'traverse': {
365
+ // Register the traverse variable: (from, property) → to
366
+ registry.set(pattern.from, pattern.property, pattern.to);
367
+ // Add traverse triple to required pattern (or filtered block if inline where)
368
+ const triple = tripleOf(varTerm(pattern.from), iriTerm(pattern.property), varTerm(pattern.to));
369
+ if (pattern.filter && filteredTraverseBlocks) {
370
+ filteredTraverseBlocks.push({
371
+ traverseTriple: triple,
372
+ filter: pattern.filter,
373
+ toAlias: pattern.to,
374
+ });
375
+ }
376
+ else {
377
+ traverseTriples.push(triple);
378
+ }
379
+ break;
380
+ }
381
+ case 'join': {
382
+ for (const sub of pattern.patterns) {
383
+ processPattern(sub, registry, traverseTriples, optionalPropertyTriples, filteredTraverseBlocks);
384
+ }
385
+ break;
386
+ }
387
+ case 'optional': {
388
+ // Optional patterns — process inner patterns but keep them optional
389
+ processPattern(pattern.pattern, registry, traverseTriples, optionalPropertyTriples, filteredTraverseBlocks);
390
+ break;
391
+ }
392
+ case 'union': {
393
+ for (const branch of pattern.branches) {
394
+ processPattern(branch, registry, traverseTriples, optionalPropertyTriples, filteredTraverseBlocks);
395
+ }
396
+ break;
397
+ }
398
+ case 'exists': {
399
+ processPattern(pattern.pattern, registry, traverseTriples, optionalPropertyTriples, filteredTraverseBlocks);
400
+ break;
401
+ }
402
+ }
403
+ }
404
+ // ---------------------------------------------------------------------------
405
+ // Expression processing — discover property_expr references
406
+ // ---------------------------------------------------------------------------
407
+ function processExpressionForProperties(expr, registry, optionalPropertyTriples) {
408
+ switch (expr.kind) {
409
+ case 'property_expr': {
410
+ if (!registry.has(expr.sourceAlias, expr.property)) {
411
+ // Create a new OPTIONAL triple for this property
412
+ const varName = registry.getOrCreate(expr.sourceAlias, expr.property);
413
+ optionalPropertyTriples.push(tripleOf(varTerm(expr.sourceAlias), iriTerm(expr.property), varTerm(varName)));
414
+ }
415
+ break;
416
+ }
417
+ case 'binary_expr':
418
+ processExpressionForProperties(expr.left, registry, optionalPropertyTriples);
419
+ processExpressionForProperties(expr.right, registry, optionalPropertyTriples);
420
+ break;
421
+ case 'logical_expr':
422
+ for (const sub of expr.expressions) {
423
+ processExpressionForProperties(sub, registry, optionalPropertyTriples);
424
+ }
425
+ break;
426
+ case 'not_expr':
427
+ processExpressionForProperties(expr.expression, registry, optionalPropertyTriples);
428
+ break;
429
+ case 'function_expr':
430
+ for (const arg of expr.args) {
431
+ processExpressionForProperties(arg, registry, optionalPropertyTriples);
432
+ }
433
+ break;
434
+ case 'aggregate_expr':
435
+ for (const arg of expr.args) {
436
+ processExpressionForProperties(arg, registry, optionalPropertyTriples);
437
+ }
438
+ break;
439
+ case 'exists_expr':
440
+ // exists_expr in IR has pattern + filter
441
+ // Process the filter for property references
442
+ if (expr.filter) {
443
+ processExpressionForProperties(expr.filter, registry, optionalPropertyTriples);
444
+ }
445
+ break;
446
+ case 'context_property_expr': {
447
+ // Context entity property — emit a triple with fixed IRI as subject.
448
+ // Use raw IRI as registry key to avoid collision between IRIs that
449
+ // sanitize to the same string (e.g. ctx-1 vs ctx_1).
450
+ const ctxKey = `__ctx__${expr.contextIri}`;
451
+ if (!registry.has(ctxKey, expr.property)) {
452
+ const varName = registry.getOrCreate(ctxKey, expr.property);
453
+ optionalPropertyTriples.push(tripleOf(iriTerm(expr.contextIri), iriTerm(expr.property), varTerm(varName)));
454
+ }
455
+ break;
456
+ }
457
+ case 'literal_expr':
458
+ case 'reference_expr':
459
+ case 'alias_expr':
460
+ // No property references to discover
461
+ break;
462
+ }
463
+ }
464
+ // ---------------------------------------------------------------------------
465
+ // Expression conversion
466
+ // ---------------------------------------------------------------------------
467
+ function convertExpression(expr, registry, optionalPropertyTriples) {
468
+ switch (expr.kind) {
469
+ case 'literal_expr': {
470
+ const value = expr.value;
471
+ if (value === null || value === undefined) {
472
+ return { kind: 'literal_expr', value: '' };
473
+ }
474
+ if (typeof value === 'boolean') {
475
+ return {
476
+ kind: 'literal_expr',
477
+ value: String(value),
478
+ datatype: XSD_BOOLEAN,
479
+ };
480
+ }
481
+ if (typeof value === 'number') {
482
+ if (Number.isInteger(value)) {
483
+ return {
484
+ kind: 'literal_expr',
485
+ value: String(value),
486
+ datatype: XSD_INTEGER,
487
+ };
488
+ }
489
+ return {
490
+ kind: 'literal_expr',
491
+ value: String(value),
492
+ datatype: XSD_DOUBLE,
493
+ };
494
+ }
495
+ return { kind: 'literal_expr', value: String(value) };
496
+ }
497
+ case 'reference_expr':
498
+ return { kind: 'iri_expr', value: expr.value };
499
+ case 'alias_expr':
500
+ return { kind: 'variable_expr', name: expr.alias };
501
+ case 'context_property_expr': {
502
+ const ctxKey = `__ctx__${expr.contextIri}`;
503
+ const ctxVarName = registry.getOrCreate(ctxKey, expr.property);
504
+ return { kind: 'variable_expr', name: ctxVarName };
505
+ }
506
+ case 'property_expr': {
507
+ const varName = registry.getOrCreate(expr.sourceAlias, expr.property);
508
+ return { kind: 'variable_expr', name: varName };
509
+ }
510
+ case 'binary_expr':
511
+ return {
512
+ kind: 'binary_expr',
513
+ op: expr.operator,
514
+ left: convertExpression(expr.left, registry, optionalPropertyTriples),
515
+ right: convertExpression(expr.right, registry, optionalPropertyTriples),
516
+ };
517
+ case 'logical_expr':
518
+ return {
519
+ kind: 'logical_expr',
520
+ op: expr.operator,
521
+ exprs: expr.expressions.map((e) => convertExpression(e, registry, optionalPropertyTriples)),
522
+ };
523
+ case 'not_expr':
524
+ return {
525
+ kind: 'not_expr',
526
+ inner: convertExpression(expr.expression, registry, optionalPropertyTriples),
527
+ };
528
+ case 'function_expr':
529
+ return {
530
+ kind: 'function_expr',
531
+ name: expr.name,
532
+ args: expr.args.map((a) => convertExpression(a, registry, optionalPropertyTriples)),
533
+ };
534
+ case 'aggregate_expr':
535
+ return {
536
+ kind: 'aggregate_expr',
537
+ name: expr.name,
538
+ args: expr.args.map((a) => convertExpression(a, registry, optionalPropertyTriples)),
539
+ };
540
+ case 'exists_expr': {
541
+ // Convert exists expression with inner pattern + filter
542
+ const innerAlgebra = convertExistsPattern(expr.pattern, registry);
543
+ if (expr.filter) {
544
+ const filterExpr = convertExpression(expr.filter, registry, optionalPropertyTriples);
545
+ // Wrap the inner pattern with a filter
546
+ const filteredInner = {
547
+ type: 'filter',
548
+ expression: filterExpr,
549
+ inner: innerAlgebra,
550
+ };
551
+ return {
552
+ kind: 'exists_expr',
553
+ pattern: filteredInner,
554
+ negated: false,
555
+ };
556
+ }
557
+ return {
558
+ kind: 'exists_expr',
559
+ pattern: innerAlgebra,
560
+ negated: false,
561
+ };
562
+ }
563
+ default:
564
+ throw new Error(`Unknown IR expression kind: ${expr.kind}`);
565
+ }
566
+ }
567
+ /**
568
+ * Convert an exists pattern (from exists_expr) into an algebra node.
569
+ * Recursively handles all IR graph pattern kinds.
570
+ */
571
+ function convertExistsPattern(pattern, registry) {
572
+ switch (pattern.kind) {
573
+ case 'traverse': {
574
+ const triple = tripleOf(varTerm(pattern.from), iriTerm(pattern.property), varTerm(pattern.to));
575
+ return { type: 'bgp', triples: [triple] };
576
+ }
577
+ case 'join': {
578
+ let result = null;
579
+ for (const sub of pattern.patterns) {
580
+ const subNode = convertExistsPattern(sub, registry);
581
+ result = result ? joinNodes(result, subNode) : subNode;
582
+ }
583
+ return result || { type: 'bgp', triples: [] };
584
+ }
585
+ case 'shape_scan': {
586
+ return {
587
+ type: 'bgp',
588
+ triples: [
589
+ tripleOf(varTerm(pattern.alias), iriTerm(RDF_TYPE), iriTerm(pattern.shape)),
590
+ ],
591
+ };
592
+ }
593
+ case 'optional': {
594
+ const inner = convertExistsPattern(pattern.pattern, registry);
595
+ return wrapOptional({ type: 'bgp', triples: [] }, inner);
596
+ }
597
+ case 'union': {
598
+ let result = null;
599
+ for (const branch of pattern.branches) {
600
+ const branchNode = convertExistsPattern(branch, registry);
601
+ if (!result) {
602
+ result = branchNode;
603
+ }
604
+ else {
605
+ result = { type: 'union', left: result, right: branchNode };
606
+ }
607
+ }
608
+ return result || { type: 'bgp', triples: [] };
609
+ }
610
+ case 'exists': {
611
+ return convertExistsPattern(pattern.pattern, registry);
612
+ }
613
+ default:
614
+ throw new Error(`Unsupported pattern kind in EXISTS: ${pattern.kind}`);
615
+ }
616
+ }
617
+ /**
618
+ * Resolve what variable name an IR expression ultimately refers to.
619
+ */
620
+ function resolveExpressionVariable(expr, registry) {
621
+ switch (expr.kind) {
622
+ case 'alias_expr':
623
+ return expr.alias;
624
+ case 'property_expr':
625
+ return registry.getOrCreate(expr.sourceAlias, expr.property);
626
+ default:
627
+ return null;
628
+ }
629
+ }
630
+ // ---------------------------------------------------------------------------
631
+ // Mutation conversions
632
+ // ---------------------------------------------------------------------------
633
+ /**
634
+ * Convert a field value to one or more SparqlTerm objects for triple objects.
635
+ */
636
+ function fieldValueToTerms(value, options) {
637
+ if (value === null || value === undefined) {
638
+ return [];
639
+ }
640
+ if (typeof value === 'string') {
641
+ return [literalTerm(value)];
642
+ }
643
+ if (typeof value === 'number') {
644
+ if (Number.isInteger(value)) {
645
+ return [literalTerm(String(value), XSD_INTEGER)];
646
+ }
647
+ return [literalTerm(String(value), XSD_DOUBLE)];
648
+ }
649
+ if (typeof value === 'boolean') {
650
+ return [literalTerm(String(value), XSD_BOOLEAN)];
651
+ }
652
+ if (value instanceof Date) {
653
+ return [literalTerm(value.toISOString(), XSD_DATETIME)];
654
+ }
655
+ // NodeReferenceValue
656
+ if (typeof value === 'object' && 'id' in value && !('shape' in value) && !('fields' in value)) {
657
+ return [iriTerm(value.id)];
658
+ }
659
+ // IRNodeData — should not produce a term directly (handled by nested create)
660
+ if (typeof value === 'object' && 'shape' in value && 'fields' in value) {
661
+ return []; // Handled separately
662
+ }
663
+ // Array
664
+ if (Array.isArray(value)) {
665
+ const terms = [];
666
+ for (const item of value) {
667
+ terms.push(...fieldValueToTerms(item, options));
668
+ }
669
+ return terms;
670
+ }
671
+ return [];
672
+ }
673
+ /**
674
+ * Recursively generate triples for an IRNodeData (used in create and nested creates).
675
+ * Returns the URI used for this node and all generated triples.
676
+ */
677
+ function generateNodeDataTriples(data, options) {
678
+ const uri = data.id || (0, sparqlUtils_js_1.generateEntityUri)(data.shape, options);
679
+ const triples = [];
680
+ const subjectTerm = iriTerm(uri);
681
+ // Type triple
682
+ triples.push(tripleOf(subjectTerm, iriTerm(RDF_TYPE), iriTerm(data.shape)));
683
+ // Field triples
684
+ for (const field of data.fields) {
685
+ const propertyTerm = iriTerm(field.property);
686
+ if (field.value === null || field.value === undefined) {
687
+ continue;
688
+ }
689
+ // Handle arrays (including mixed arrays of references and nested creates)
690
+ if (Array.isArray(field.value)) {
691
+ for (const item of field.value) {
692
+ if (item && typeof item === 'object' && 'shape' in item && 'fields' in item) {
693
+ // Nested create
694
+ const nested = generateNodeDataTriples(item, options);
695
+ triples.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
696
+ triples.push(...nested.triples);
697
+ }
698
+ else {
699
+ const terms = fieldValueToTerms(item, options);
700
+ for (const term of terms) {
701
+ triples.push(tripleOf(subjectTerm, propertyTerm, term));
702
+ }
703
+ }
704
+ }
705
+ continue;
706
+ }
707
+ // Handle nested IRNodeData
708
+ if (typeof field.value === 'object' && 'shape' in field.value && 'fields' in field.value) {
709
+ const nested = generateNodeDataTriples(field.value, options);
710
+ triples.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
711
+ triples.push(...nested.triples);
712
+ continue;
713
+ }
714
+ // Simple values
715
+ const terms = fieldValueToTerms(field.value, options);
716
+ for (const term of terms) {
717
+ triples.push(tripleOf(subjectTerm, propertyTerm, term));
718
+ }
719
+ }
720
+ return { uri, triples };
721
+ }
722
+ /**
723
+ * Converts an IRCreateMutation to a SparqlInsertDataPlan.
724
+ */
725
+ function createToAlgebra(query, options) {
726
+ const { triples } = generateNodeDataTriples(query.data, options);
727
+ return {
728
+ type: 'insert_data',
729
+ triples,
730
+ };
731
+ }
732
+ /**
733
+ * Converts an IRUpdateMutation to a SparqlDeleteInsertPlan.
734
+ */
735
+ function updateToAlgebra(query, options) {
736
+ const subjectTerm = iriTerm(query.id);
737
+ const deletePatterns = [];
738
+ const insertPatterns = [];
739
+ const whereTriples = [];
740
+ for (const field of query.data.fields) {
741
+ const propertyTerm = iriTerm(field.property);
742
+ const suffix = propertySuffix(field.property);
743
+ // Check for set modification ({add, remove})
744
+ if (field.value &&
745
+ typeof field.value === 'object' &&
746
+ !Array.isArray(field.value) &&
747
+ !(field.value instanceof Date) &&
748
+ !('id' in field.value) &&
749
+ !('shape' in field.value) &&
750
+ ('add' in field.value || 'remove' in field.value)) {
751
+ const setMod = field.value;
752
+ // Remove specific values
753
+ if (setMod.remove) {
754
+ for (const removeItem of setMod.remove) {
755
+ const removeTerm = iriTerm(removeItem.id);
756
+ deletePatterns.push(tripleOf(subjectTerm, propertyTerm, removeTerm));
757
+ whereTriples.push(tripleOf(subjectTerm, propertyTerm, removeTerm));
758
+ }
759
+ }
760
+ // Add new values
761
+ if (setMod.add) {
762
+ for (const addItem of setMod.add) {
763
+ if (addItem && typeof addItem === 'object' && 'shape' in addItem && 'fields' in addItem) {
764
+ // Nested create in add
765
+ const nested = generateNodeDataTriples(addItem, options);
766
+ insertPatterns.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
767
+ insertPatterns.push(...nested.triples);
768
+ }
769
+ else {
770
+ const terms = fieldValueToTerms(addItem, options);
771
+ for (const term of terms) {
772
+ insertPatterns.push(tripleOf(subjectTerm, propertyTerm, term));
773
+ }
774
+ }
775
+ }
776
+ }
777
+ continue;
778
+ }
779
+ // Unset (undefined/null) — delete only
780
+ if (field.value === undefined || field.value === null) {
781
+ const oldVar = varTerm(`old_${suffix}`);
782
+ deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
783
+ whereTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
784
+ continue;
785
+ }
786
+ // Array overwrite — delete old values + insert new ones
787
+ if (Array.isArray(field.value)) {
788
+ const oldVar = varTerm(`old_${suffix}`);
789
+ deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
790
+ whereTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
791
+ for (const item of field.value) {
792
+ if (item && typeof item === 'object' && 'shape' in item && 'fields' in item) {
793
+ const nested = generateNodeDataTriples(item, options);
794
+ insertPatterns.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
795
+ insertPatterns.push(...nested.triples);
796
+ }
797
+ else {
798
+ const terms = fieldValueToTerms(item, options);
799
+ for (const term of terms) {
800
+ insertPatterns.push(tripleOf(subjectTerm, propertyTerm, term));
801
+ }
802
+ }
803
+ }
804
+ continue;
805
+ }
806
+ // Nested create (single object field)
807
+ if (typeof field.value === 'object' && 'shape' in field.value && 'fields' in field.value) {
808
+ const oldVar = varTerm(`old_${suffix}`);
809
+ deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
810
+ whereTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
811
+ const nested = generateNodeDataTriples(field.value, options);
812
+ insertPatterns.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
813
+ insertPatterns.push(...nested.triples);
814
+ continue;
815
+ }
816
+ // Simple value update — delete old + insert new
817
+ const oldVar = varTerm(`old_${suffix}`);
818
+ deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
819
+ whereTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
820
+ const terms = fieldValueToTerms(field.value, options);
821
+ for (const term of terms) {
822
+ insertPatterns.push(tripleOf(subjectTerm, propertyTerm, term));
823
+ }
824
+ }
825
+ // Wrap WHERE triples in OPTIONAL so UPDATE succeeds even when the old
826
+ // value doesn't exist (e.g. setting bestFriend when none was set before).
827
+ let whereAlgebra;
828
+ if (whereTriples.length === 0) {
829
+ whereAlgebra = { type: 'bgp', triples: [] };
830
+ }
831
+ else if (whereTriples.length === 1) {
832
+ whereAlgebra = {
833
+ type: 'left_join',
834
+ left: { type: 'bgp', triples: [] },
835
+ right: { type: 'bgp', triples: whereTriples },
836
+ };
837
+ }
838
+ else {
839
+ // Wrap each triple in its own OPTIONAL for independent matching
840
+ whereAlgebra = { type: 'bgp', triples: [] };
841
+ for (const triple of whereTriples) {
842
+ whereAlgebra = {
843
+ type: 'left_join',
844
+ left: whereAlgebra,
845
+ right: { type: 'bgp', triples: [triple] },
846
+ };
847
+ }
848
+ }
849
+ return {
850
+ type: 'delete_insert',
851
+ deletePatterns,
852
+ insertPatterns,
853
+ whereAlgebra,
854
+ };
855
+ }
856
+ /**
857
+ * Converts an IRDeleteMutation to a SparqlDeleteInsertPlan (DELETE + WHERE).
858
+ */
859
+ function deleteToAlgebra(query, _options) {
860
+ const deletePatterns = [];
861
+ const requiredTriples = [];
862
+ const optionalTriples = [];
863
+ for (let i = 0; i < query.ids.length; i++) {
864
+ const subjectTerm = iriTerm(query.ids[i].id);
865
+ const idx = query.ids.length > 1 ? `_${i}` : '';
866
+ const subjWild = tripleOf(subjectTerm, varTerm(`p${idx}`), varTerm(`o${idx}`));
867
+ const objWild = tripleOf(varTerm(`s${idx}`), varTerm(`p2${idx}`), subjectTerm);
868
+ const typeGuard = tripleOf(subjectTerm, iriTerm(RDF_TYPE), iriTerm(query.shape));
869
+ // DELETE block: all patterns (subject-wildcard, object-wildcard, type)
870
+ deletePatterns.push(subjWild, objWild, typeGuard);
871
+ // WHERE block: subject-wildcard and type guard are required;
872
+ // object-wildcard is OPTIONAL (entity may have no incoming references)
873
+ requiredTriples.push(subjWild, typeGuard);
874
+ optionalTriples.push(objWild);
875
+ }
876
+ // Build WHERE algebra: required BGP + OPTIONAL for each object-wildcard
877
+ let whereAlgebra = { type: 'bgp', triples: requiredTriples };
878
+ for (const triple of optionalTriples) {
879
+ whereAlgebra = {
880
+ type: 'left_join',
881
+ left: whereAlgebra,
882
+ right: { type: 'bgp', triples: [triple] },
883
+ };
884
+ }
885
+ return {
886
+ type: 'delete_insert',
887
+ deletePatterns,
888
+ insertPatterns: [],
889
+ whereAlgebra,
890
+ };
891
+ }
892
+ // ---------------------------------------------------------------------------
893
+ // Convenience wrappers: IR → algebra → SPARQL string in one call
894
+ // ---------------------------------------------------------------------------
895
+ /**
896
+ * Converts an IRSelectQuery to a SPARQL string.
897
+ * Stub: will be implemented when algebraToString is available.
898
+ */
899
+ function selectToSparql(query, options) {
900
+ const plan = selectToAlgebra(query, options);
901
+ return (0, algebraToString_js_1.selectPlanToSparql)(plan, options);
902
+ }
903
+ /**
904
+ * Converts an IRCreateMutation to a SPARQL string.
905
+ * Stub: will be implemented when algebraToString is available.
906
+ */
907
+ function createToSparql(query, options) {
908
+ const plan = createToAlgebra(query, options);
909
+ return (0, algebraToString_js_1.insertDataPlanToSparql)(plan, options);
910
+ }
911
+ /**
912
+ * Converts an IRUpdateMutation to a SPARQL string.
913
+ * Stub: will be implemented when algebraToString is available.
914
+ */
915
+ function updateToSparql(query, options) {
916
+ const plan = updateToAlgebra(query, options);
917
+ return (0, algebraToString_js_1.deleteInsertPlanToSparql)(plan, options);
918
+ }
919
+ /**
920
+ * Converts an IRDeleteMutation to a SPARQL string.
921
+ * Stub: will be implemented when algebraToString is available.
922
+ */
923
+ function deleteToSparql(query, options) {
924
+ const plan = deleteToAlgebra(query, options);
925
+ return (0, algebraToString_js_1.deleteInsertPlanToSparql)(plan, options);
926
+ }
927
+ //# sourceMappingURL=irToAlgebra.js.map