@_linked/core 2.0.0 → 2.2.0-next.20260313111019

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 (119) hide show
  1. package/CHANGELOG.md +33 -171
  2. package/README.md +197 -45
  3. package/lib/cjs/expressions/Expr.d.ts +58 -0
  4. package/lib/cjs/expressions/Expr.js +217 -0
  5. package/lib/cjs/expressions/Expr.js.map +1 -0
  6. package/lib/cjs/expressions/ExpressionMethods.d.ts +81 -0
  7. package/lib/cjs/expressions/ExpressionMethods.js +3 -0
  8. package/lib/cjs/expressions/ExpressionMethods.js.map +1 -0
  9. package/lib/cjs/expressions/ExpressionNode.d.ts +95 -0
  10. package/lib/cjs/expressions/ExpressionNode.js +349 -0
  11. package/lib/cjs/expressions/ExpressionNode.js.map +1 -0
  12. package/lib/cjs/index.d.ts +4 -0
  13. package/lib/cjs/index.js +6 -1
  14. package/lib/cjs/index.js.map +1 -1
  15. package/lib/cjs/queries/DeleteBuilder.d.ts +17 -20
  16. package/lib/cjs/queries/DeleteBuilder.js +46 -19
  17. package/lib/cjs/queries/DeleteBuilder.js.map +1 -1
  18. package/lib/cjs/queries/DeleteQuery.d.ts +2 -2
  19. package/lib/cjs/queries/DeleteQuery.js.map +1 -1
  20. package/lib/cjs/queries/FieldSet.d.ts +3 -0
  21. package/lib/cjs/queries/FieldSet.js +16 -0
  22. package/lib/cjs/queries/FieldSet.js.map +1 -1
  23. package/lib/cjs/queries/IRCanonicalize.d.ts +10 -3
  24. package/lib/cjs/queries/IRCanonicalize.js +10 -1
  25. package/lib/cjs/queries/IRCanonicalize.js.map +1 -1
  26. package/lib/cjs/queries/IRDesugar.d.ts +30 -2
  27. package/lib/cjs/queries/IRDesugar.js +29 -9
  28. package/lib/cjs/queries/IRDesugar.js.map +1 -1
  29. package/lib/cjs/queries/IRLower.d.ts +19 -2
  30. package/lib/cjs/queries/IRLower.js +104 -20
  31. package/lib/cjs/queries/IRLower.js.map +1 -1
  32. package/lib/cjs/queries/IRMutation.d.ts +31 -1
  33. package/lib/cjs/queries/IRMutation.js +68 -15
  34. package/lib/cjs/queries/IRMutation.js.map +1 -1
  35. package/lib/cjs/queries/IntermediateRepresentation.d.ts +33 -4
  36. package/lib/cjs/queries/MutationQuery.js +16 -2
  37. package/lib/cjs/queries/MutationQuery.js.map +1 -1
  38. package/lib/cjs/queries/QueryBuilder.d.ts +14 -1
  39. package/lib/cjs/queries/QueryBuilder.js +59 -2
  40. package/lib/cjs/queries/QueryBuilder.js.map +1 -1
  41. package/lib/cjs/queries/QueryFactory.d.ts +2 -1
  42. package/lib/cjs/queries/QueryFactory.js.map +1 -1
  43. package/lib/cjs/queries/SelectQuery.d.ts +6 -2
  44. package/lib/cjs/queries/SelectQuery.js +51 -7
  45. package/lib/cjs/queries/SelectQuery.js.map +1 -1
  46. package/lib/cjs/queries/UpdateBuilder.d.ts +22 -13
  47. package/lib/cjs/queries/UpdateBuilder.js +60 -11
  48. package/lib/cjs/queries/UpdateBuilder.js.map +1 -1
  49. package/lib/cjs/queries/UpdateQuery.d.ts +2 -2
  50. package/lib/cjs/shapes/Shape.d.ts +8 -2
  51. package/lib/cjs/shapes/Shape.js +9 -13
  52. package/lib/cjs/shapes/Shape.js.map +1 -1
  53. package/lib/cjs/sparql/SparqlStore.js +15 -0
  54. package/lib/cjs/sparql/SparqlStore.js.map +1 -1
  55. package/lib/cjs/sparql/irToAlgebra.d.ts +34 -3
  56. package/lib/cjs/sparql/irToAlgebra.js +380 -31
  57. package/lib/cjs/sparql/irToAlgebra.js.map +1 -1
  58. package/lib/cjs/test-helpers/query-fixtures.d.ts +96 -208
  59. package/lib/cjs/test-helpers/query-fixtures.js +96 -19
  60. package/lib/cjs/test-helpers/query-fixtures.js.map +1 -1
  61. package/lib/esm/expressions/Expr.d.ts +58 -0
  62. package/lib/esm/expressions/Expr.js +214 -0
  63. package/lib/esm/expressions/Expr.js.map +1 -0
  64. package/lib/esm/expressions/ExpressionMethods.d.ts +81 -0
  65. package/lib/esm/expressions/ExpressionMethods.js +2 -0
  66. package/lib/esm/expressions/ExpressionMethods.js.map +1 -0
  67. package/lib/esm/expressions/ExpressionNode.d.ts +95 -0
  68. package/lib/esm/expressions/ExpressionNode.js +341 -0
  69. package/lib/esm/expressions/ExpressionNode.js.map +1 -0
  70. package/lib/esm/index.d.ts +4 -0
  71. package/lib/esm/index.js +3 -0
  72. package/lib/esm/index.js.map +1 -1
  73. package/lib/esm/queries/DeleteBuilder.d.ts +17 -20
  74. package/lib/esm/queries/DeleteBuilder.js +46 -19
  75. package/lib/esm/queries/DeleteBuilder.js.map +1 -1
  76. package/lib/esm/queries/DeleteQuery.d.ts +2 -2
  77. package/lib/esm/queries/DeleteQuery.js.map +1 -1
  78. package/lib/esm/queries/FieldSet.d.ts +3 -0
  79. package/lib/esm/queries/FieldSet.js +16 -0
  80. package/lib/esm/queries/FieldSet.js.map +1 -1
  81. package/lib/esm/queries/IRCanonicalize.d.ts +10 -3
  82. package/lib/esm/queries/IRCanonicalize.js +10 -1
  83. package/lib/esm/queries/IRCanonicalize.js.map +1 -1
  84. package/lib/esm/queries/IRDesugar.d.ts +30 -2
  85. package/lib/esm/queries/IRDesugar.js +21 -2
  86. package/lib/esm/queries/IRDesugar.js.map +1 -1
  87. package/lib/esm/queries/IRLower.d.ts +19 -2
  88. package/lib/esm/queries/IRLower.js +101 -19
  89. package/lib/esm/queries/IRLower.js.map +1 -1
  90. package/lib/esm/queries/IRMutation.d.ts +31 -1
  91. package/lib/esm/queries/IRMutation.js +63 -14
  92. package/lib/esm/queries/IRMutation.js.map +1 -1
  93. package/lib/esm/queries/IntermediateRepresentation.d.ts +33 -4
  94. package/lib/esm/queries/MutationQuery.js +16 -2
  95. package/lib/esm/queries/MutationQuery.js.map +1 -1
  96. package/lib/esm/queries/QueryBuilder.d.ts +14 -1
  97. package/lib/esm/queries/QueryBuilder.js +59 -2
  98. package/lib/esm/queries/QueryBuilder.js.map +1 -1
  99. package/lib/esm/queries/QueryFactory.d.ts +2 -1
  100. package/lib/esm/queries/QueryFactory.js.map +1 -1
  101. package/lib/esm/queries/SelectQuery.d.ts +6 -2
  102. package/lib/esm/queries/SelectQuery.js +51 -7
  103. package/lib/esm/queries/SelectQuery.js.map +1 -1
  104. package/lib/esm/queries/UpdateBuilder.d.ts +22 -13
  105. package/lib/esm/queries/UpdateBuilder.js +60 -11
  106. package/lib/esm/queries/UpdateBuilder.js.map +1 -1
  107. package/lib/esm/queries/UpdateQuery.d.ts +2 -2
  108. package/lib/esm/shapes/Shape.d.ts +8 -2
  109. package/lib/esm/shapes/Shape.js +9 -13
  110. package/lib/esm/shapes/Shape.js.map +1 -1
  111. package/lib/esm/sparql/SparqlStore.js +16 -1
  112. package/lib/esm/sparql/SparqlStore.js.map +1 -1
  113. package/lib/esm/sparql/irToAlgebra.d.ts +34 -3
  114. package/lib/esm/sparql/irToAlgebra.js +374 -31
  115. package/lib/esm/sparql/irToAlgebra.js.map +1 -1
  116. package/lib/esm/test-helpers/query-fixtures.d.ts +96 -208
  117. package/lib/esm/test-helpers/query-fixtures.js +96 -19
  118. package/lib/esm/test-helpers/query-fixtures.js.map +1 -1
  119. package/package.json +3 -3
@@ -1,5 +1,23 @@
1
1
  import { generateEntityUri } from './sparqlUtils.js';
2
2
  import { selectPlanToSparql, insertDataPlanToSparql, deleteInsertPlanToSparql, } from './algebraToString.js';
3
+ // Lazy-loaded to avoid circular dependency:
4
+ // irToAlgebra → ShapeClass → Shape → CreateBuilder → … → SHACL → Shape (circular)
5
+ let _getShapeClass;
6
+ function lazyGetShapeClass(id) {
7
+ if (!_getShapeClass) {
8
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
9
+ _getShapeClass = require('../utils/ShapeClass.js').getShapeClass;
10
+ }
11
+ return _getShapeClass(id);
12
+ }
13
+ let _shacl;
14
+ function lazyShacl() {
15
+ if (!_shacl) {
16
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
17
+ _shacl = require('../ontologies/shacl.js').shacl;
18
+ }
19
+ return _shacl;
20
+ }
3
21
  import { rdf } from '../ontologies/rdf.js';
4
22
  import { xsd } from '../ontologies/xsd.js';
5
23
  // ---------------------------------------------------------------------------
@@ -43,6 +61,16 @@ function propertySuffix(propertyUri) {
43
61
  function sanitizeVarName(name) {
44
62
  return name.replace(/[^A-Za-z0-9_]/g, '_');
45
63
  }
64
+ const IR_EXPRESSION_KINDS = new Set([
65
+ 'literal_expr', 'property_expr', 'binary_expr', 'logical_expr',
66
+ 'not_expr', 'function_expr', 'aggregate_expr', 'reference_expr',
67
+ 'alias_expr', 'context_property_expr', 'exists_expr',
68
+ ]);
69
+ function isIRExpression(value) {
70
+ return !!value && typeof value === 'object' && 'kind' in value &&
71
+ typeof value.kind === 'string' &&
72
+ IR_EXPRESSION_KINDS.has(value.kind);
73
+ }
46
74
  /**
47
75
  * Wrap a single node in a LeftJoin, making `right` optional relative to `left`.
48
76
  */
@@ -228,6 +256,23 @@ export function selectToAlgebra(query, _options) {
228
256
  };
229
257
  }
230
258
  }
259
+ // 5b. MINUS patterns — wrap algebra in SparqlMinus for each minus pattern
260
+ for (const pattern of query.patterns) {
261
+ if (pattern.kind === 'minus') {
262
+ let minusAlgebra = convertExistsPattern(pattern.pattern, registry);
263
+ if (pattern.filter) {
264
+ const minusPropertyTriples = [];
265
+ processExpressionForProperties(pattern.filter, registry, minusPropertyTriples);
266
+ // Add property triples into the MINUS block
267
+ if (minusPropertyTriples.length > 0) {
268
+ minusAlgebra = joinNodes(minusAlgebra, { type: 'bgp', triples: minusPropertyTriples });
269
+ }
270
+ const filterExpr = convertExpression(pattern.filter, registry, minusPropertyTriples);
271
+ minusAlgebra = { type: 'filter', expression: filterExpr, inner: minusAlgebra };
272
+ }
273
+ algebra = { type: 'minus', left: algebra, right: minusAlgebra };
274
+ }
275
+ }
231
276
  // 6. SubjectId → Filter / SubjectIds → VALUES
232
277
  if (query.subjectIds && query.subjectIds.length > 0) {
233
278
  // Multiple subjects: use VALUES clause for efficient filtering
@@ -393,6 +438,10 @@ function processPattern(pattern, registry, traverseTriples, optionalPropertyTrip
393
438
  processPattern(pattern.pattern, registry, traverseTriples, optionalPropertyTriples, filteredTraverseBlocks);
394
439
  break;
395
440
  }
441
+ case 'minus': {
442
+ // MINUS patterns are handled separately in selectToAlgebra — skip in processPattern.
443
+ break;
444
+ }
396
445
  }
397
446
  }
398
447
  // ---------------------------------------------------------------------------
@@ -604,6 +653,9 @@ function convertExistsPattern(pattern, registry) {
604
653
  case 'exists': {
605
654
  return convertExistsPattern(pattern.pattern, registry);
606
655
  }
656
+ case 'minus': {
657
+ return convertExistsPattern(pattern.pattern, registry);
658
+ }
607
659
  default:
608
660
  throw new Error(`Unsupported pattern kind in EXISTS: ${pattern.kind}`);
609
661
  }
@@ -723,15 +775,19 @@ export function createToAlgebra(query, options) {
723
775
  triples,
724
776
  };
725
777
  }
778
+ // ---------------------------------------------------------------------------
779
+ // Shared update field processing
780
+ // ---------------------------------------------------------------------------
726
781
  /**
727
- * Converts an IRUpdateMutation to a SparqlDeleteInsertPlan.
782
+ * Processes IRNodeData fields into DELETE/INSERT/WHERE triples.
783
+ * Shared between updateToAlgebra (IRI subject) and updateWhereToAlgebra (variable subject).
728
784
  */
729
- export function updateToAlgebra(query, options) {
730
- const subjectTerm = iriTerm(query.id);
785
+ function processUpdateFields(data, subjectTerm, options) {
731
786
  const deletePatterns = [];
732
787
  const insertPatterns = [];
733
- const whereTriples = [];
734
- for (const field of query.data.fields) {
788
+ const oldValueTriples = [];
789
+ const extends_ = [];
790
+ for (const field of data.fields) {
735
791
  const propertyTerm = iriTerm(field.property);
736
792
  const suffix = propertySuffix(field.property);
737
793
  // Check for set modification ({add, remove})
@@ -743,19 +799,16 @@ export function updateToAlgebra(query, options) {
743
799
  !('shape' in field.value) &&
744
800
  ('add' in field.value || 'remove' in field.value)) {
745
801
  const setMod = field.value;
746
- // Remove specific values
747
802
  if (setMod.remove) {
748
803
  for (const removeItem of setMod.remove) {
749
804
  const removeTerm = iriTerm(removeItem.id);
750
805
  deletePatterns.push(tripleOf(subjectTerm, propertyTerm, removeTerm));
751
- whereTriples.push(tripleOf(subjectTerm, propertyTerm, removeTerm));
806
+ oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, removeTerm));
752
807
  }
753
808
  }
754
- // Add new values
755
809
  if (setMod.add) {
756
810
  for (const addItem of setMod.add) {
757
811
  if (addItem && typeof addItem === 'object' && 'shape' in addItem && 'fields' in addItem) {
758
- // Nested create in add
759
812
  const nested = generateNodeDataTriples(addItem, options);
760
813
  insertPatterns.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
761
814
  insertPatterns.push(...nested.triples);
@@ -774,14 +827,14 @@ export function updateToAlgebra(query, options) {
774
827
  if (field.value === undefined || field.value === null) {
775
828
  const oldVar = varTerm(`old_${suffix}`);
776
829
  deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
777
- whereTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
830
+ oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
778
831
  continue;
779
832
  }
780
833
  // Array overwrite — delete old values + insert new ones
781
834
  if (Array.isArray(field.value)) {
782
835
  const oldVar = varTerm(`old_${suffix}`);
783
836
  deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
784
- whereTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
837
+ oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
785
838
  for (const item of field.value) {
786
839
  if (item && typeof item === 'object' && 'shape' in item && 'fields' in item) {
787
840
  const nested = generateNodeDataTriples(item, options);
@@ -801,49 +854,109 @@ export function updateToAlgebra(query, options) {
801
854
  if (typeof field.value === 'object' && 'shape' in field.value && 'fields' in field.value) {
802
855
  const oldVar = varTerm(`old_${suffix}`);
803
856
  deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
804
- whereTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
857
+ oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
805
858
  const nested = generateNodeDataTriples(field.value, options);
806
859
  insertPatterns.push(tripleOf(subjectTerm, propertyTerm, iriTerm(nested.uri)));
807
860
  insertPatterns.push(...nested.triples);
808
861
  continue;
809
862
  }
863
+ // IRExpression — computed value update (e.g. p.age.plus(1))
864
+ if (isIRExpression(field.value)) {
865
+ const expr = field.value;
866
+ const oldVar = varTerm(`old_${suffix}`);
867
+ const computedVarName = `computed_${suffix}`;
868
+ const computedVar = varTerm(computedVarName);
869
+ // DELETE old value
870
+ deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
871
+ // WHERE: OPTIONAL for old value
872
+ oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
873
+ // Discover additional property references in the expression and add OPTIONAL triples
874
+ const registry = new VariableRegistry();
875
+ const mutationSubjectAlias = '__mutation_subject__';
876
+ // Pre-register the subject variable mapping for the field being updated
877
+ registry.set(mutationSubjectAlias, field.property, `old_${suffix}`);
878
+ const additionalOptionals = [];
879
+ processExpressionForProperties(expr, registry, additionalOptionals);
880
+ // Add any additional property OPTIONAL triples (for refs to other properties)
881
+ for (const triple of additionalOptionals) {
882
+ // Rewrite the subject from the placeholder variable to the actual subject term
883
+ if (triple.subject.kind === 'variable' && triple.subject.name === mutationSubjectAlias) {
884
+ oldValueTriples.push(tripleOf(subjectTerm, triple.predicate, triple.object));
885
+ }
886
+ else {
887
+ oldValueTriples.push(triple);
888
+ }
889
+ }
890
+ // Convert IRExpression to SparqlExpression
891
+ const sparqlExpr = convertExpression(expr, registry, additionalOptionals);
892
+ // BIND computed expression
893
+ extends_.push({ variable: computedVarName, expression: sparqlExpr });
894
+ // INSERT computed value
895
+ insertPatterns.push(tripleOf(subjectTerm, propertyTerm, computedVar));
896
+ continue;
897
+ }
810
898
  // Simple value update — delete old + insert new
811
899
  const oldVar = varTerm(`old_${suffix}`);
812
900
  deletePatterns.push(tripleOf(subjectTerm, propertyTerm, oldVar));
813
- whereTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
901
+ oldValueTriples.push(tripleOf(subjectTerm, propertyTerm, oldVar));
814
902
  const terms = fieldValueToTerms(field.value, options);
815
903
  for (const term of terms) {
816
904
  insertPatterns.push(tripleOf(subjectTerm, propertyTerm, term));
817
905
  }
818
906
  }
819
- // Wrap WHERE triples in OPTIONAL so UPDATE succeeds even when the old
820
- // value doesn't exist (e.g. setting bestFriend when none was set before).
821
- let whereAlgebra;
822
- if (whereTriples.length === 0) {
823
- whereAlgebra = { type: 'bgp', triples: [] };
907
+ return { deletePatterns, insertPatterns, oldValueTriples, extends: extends_ };
908
+ }
909
+ /**
910
+ * Wraps old-value triples in OPTIONAL (LEFT JOIN) so UPDATE succeeds
911
+ * even when the old value doesn't exist.
912
+ */
913
+ function wrapOldValueOptionals(base, oldValueTriples) {
914
+ let algebra = base;
915
+ if (oldValueTriples.length === 0) {
916
+ return algebra;
824
917
  }
825
- else if (whereTriples.length === 1) {
826
- whereAlgebra = {
918
+ for (const triple of oldValueTriples) {
919
+ algebra = {
827
920
  type: 'left_join',
828
- left: { type: 'bgp', triples: [] },
829
- right: { type: 'bgp', triples: whereTriples },
921
+ left: algebra,
922
+ right: { type: 'bgp', triples: [triple] },
830
923
  };
831
924
  }
832
- else {
833
- // Wrap each triple in its own OPTIONAL for independent matching
834
- whereAlgebra = { type: 'bgp', triples: [] };
835
- for (const triple of whereTriples) {
925
+ return algebra;
926
+ }
927
+ /**
928
+ * Converts an IRUpdateMutation to a SparqlDeleteInsertPlan.
929
+ */
930
+ export function updateToAlgebra(query, options) {
931
+ const subjectTerm = iriTerm(query.id);
932
+ const result = processUpdateFields(query.data, subjectTerm, options);
933
+ let whereAlgebra = wrapOldValueOptionals({ type: 'bgp', triples: [] }, result.oldValueTriples);
934
+ // Add traversal OPTIONAL patterns (for multi-segment expression refs)
935
+ // These must come BEFORE expression BINDs since the BINDs reference traversal variables.
936
+ if (query.traversalPatterns) {
937
+ for (const trav of query.traversalPatterns) {
938
+ const fromTerm = trav.from === '__mutation_subject__' ? subjectTerm : varTerm(trav.from);
939
+ const traversalTriple = tripleOf(fromTerm, iriTerm(trav.property), varTerm(trav.to));
836
940
  whereAlgebra = {
837
941
  type: 'left_join',
838
942
  left: whereAlgebra,
839
- right: { type: 'bgp', triples: [triple] },
943
+ right: { type: 'bgp', triples: [traversalTriple] },
840
944
  };
841
945
  }
842
946
  }
947
+ // Add BIND expressions for computed fields
948
+ for (const ext of result.extends) {
949
+ whereAlgebra = {
950
+ type: 'extend',
951
+ inner: whereAlgebra,
952
+ variable: ext.variable,
953
+ expression: ext.expression,
954
+ };
955
+ }
843
956
  return {
844
957
  type: 'delete_insert',
845
- deletePatterns,
846
- insertPatterns,
958
+ deletePatterns: result.deletePatterns,
959
+ insertPatterns: result.insertPatterns,
847
960
  whereAlgebra,
848
961
  };
849
962
  }
@@ -884,11 +997,221 @@ export function deleteToAlgebra(query, _options) {
884
997
  };
885
998
  }
886
999
  // ---------------------------------------------------------------------------
1000
+ // Blank node tree walking for schema-aware delete cleanup
1001
+ // ---------------------------------------------------------------------------
1002
+ /**
1003
+ * Checks whether a PropertyShape points to blank nodes (sh:BlankNode or
1004
+ * sh:BlankNodeOrIRI). Returns true when the property's range *may* include
1005
+ * blank node values that should be cleaned up on delete.
1006
+ */
1007
+ function isBlankNodeProperty(prop) {
1008
+ var _a;
1009
+ const nk = (_a = prop.nodeKind) === null || _a === void 0 ? void 0 : _a.id;
1010
+ if (!nk)
1011
+ return false;
1012
+ const s = lazyShacl();
1013
+ return nk === s.BlankNode.id || nk === s.BlankNodeOrIRI.id;
1014
+ }
1015
+ /**
1016
+ * Recursively builds DELETE + WHERE patterns for blank-node-typed properties.
1017
+ *
1018
+ * For each blank-node property on the shape:
1019
+ * - DELETE: `?bnVar ?pN ?oN .` (wildcard all triples on the blank node)
1020
+ * - WHERE: `OPTIONAL { ?parent <property> ?bnVar . FILTER(isBlank(?bnVar)) . ?bnVar ?pN ?oN . }`
1021
+ *
1022
+ * Recurses into the property's valueShape to handle nested blank nodes
1023
+ * (e.g. Person → Address (blank) → GeoPoint (blank)).
1024
+ */
1025
+ function walkBlankNodeTree(shapeId, parentVar, depth, deletePatterns) {
1026
+ var _a;
1027
+ const shapeClass = lazyGetShapeClass(shapeId);
1028
+ if (!(shapeClass === null || shapeClass === void 0 ? void 0 : shapeClass.shape))
1029
+ return null;
1030
+ let optionals = null;
1031
+ const props = shapeClass.shape.getPropertyShapes(true);
1032
+ for (const prop of props) {
1033
+ if (!isBlankNodeProperty(prop))
1034
+ continue;
1035
+ const bnVar = `bn${depth}`;
1036
+ const pVar = `p${depth}`;
1037
+ const oVar = `o${depth}`;
1038
+ // DELETE pattern: wildcard all triples on the blank node
1039
+ deletePatterns.push(tripleOf(varTerm(bnVar), varTerm(pVar), varTerm(oVar)));
1040
+ // WHERE: parent --<property>--> ?bnVar
1041
+ const traverseTriple = tripleOf(varTerm(parentVar), iriTerm(prop.path[0].id), varTerm(bnVar));
1042
+ // FILTER(isBlank(?bnVar))
1043
+ const isBlankFilter = {
1044
+ kind: 'function_expr',
1045
+ name: 'isBlank',
1046
+ args: [{ kind: 'variable_expr', name: bnVar }],
1047
+ };
1048
+ // ?bnVar ?pN ?oN
1049
+ const wildcardTriple = tripleOf(varTerm(bnVar), varTerm(pVar), varTerm(oVar));
1050
+ // Build inner pattern: traverse + filter + wildcard
1051
+ let innerPattern = {
1052
+ type: 'bgp',
1053
+ triples: [traverseTriple, wildcardTriple],
1054
+ };
1055
+ innerPattern = { type: 'filter', expression: isBlankFilter, inner: innerPattern };
1056
+ // Recurse into valueShape for nested blank nodes
1057
+ if ((_a = prop.valueShape) === null || _a === void 0 ? void 0 : _a.id) {
1058
+ const nestedOptional = walkBlankNodeTree(prop.valueShape.id, bnVar, depth + 1, deletePatterns);
1059
+ if (nestedOptional) {
1060
+ innerPattern = { type: 'left_join', left: innerPattern, right: nestedOptional };
1061
+ }
1062
+ }
1063
+ // Wrap in OPTIONAL (left_join)
1064
+ if (optionals) {
1065
+ optionals = { type: 'left_join', left: optionals, right: innerPattern };
1066
+ }
1067
+ else {
1068
+ optionals = innerPattern;
1069
+ }
1070
+ depth++;
1071
+ }
1072
+ return optionals;
1073
+ }
1074
+ /**
1075
+ * Converts an IRDeleteAllMutation to a SparqlDeleteInsertPlan.
1076
+ *
1077
+ * Generates DELETE { ?a0 ?p ?o . [blank node wildcards] }
1078
+ * WHERE { ?a0 a <Shape> . ?a0 ?p ?o . OPTIONAL { [blank node traversals] } }
1079
+ */
1080
+ export function deleteAllToAlgebra(query, _options) {
1081
+ const subjectVar = 'a0';
1082
+ // DELETE patterns: root wildcard
1083
+ const deletePatterns = [
1084
+ tripleOf(varTerm(subjectVar), varTerm('p'), varTerm('o')),
1085
+ ];
1086
+ // WHERE: type triple + root wildcard
1087
+ const typeTriple = tripleOf(varTerm(subjectVar), iriTerm(RDF_TYPE), iriTerm(query.shape));
1088
+ const rootWildcard = tripleOf(varTerm(subjectVar), varTerm('p'), varTerm('o'));
1089
+ let whereAlgebra = { type: 'bgp', triples: [typeTriple, rootWildcard] };
1090
+ // Walk blank node tree for cleanup
1091
+ const blankNodeOptional = walkBlankNodeTree(query.shape, subjectVar, 1, deletePatterns);
1092
+ if (blankNodeOptional) {
1093
+ whereAlgebra = { type: 'left_join', left: whereAlgebra, right: blankNodeOptional };
1094
+ }
1095
+ return {
1096
+ type: 'delete_insert',
1097
+ deletePatterns,
1098
+ insertPatterns: [],
1099
+ whereAlgebra,
1100
+ };
1101
+ }
1102
+ /**
1103
+ * Converts an IRDeleteWhereMutation to a SparqlDeleteInsertPlan.
1104
+ *
1105
+ * Like deleteAllToAlgebra but adds filter conditions from the where clause.
1106
+ */
1107
+ export function deleteWhereToAlgebra(query, _options) {
1108
+ const subjectVar = 'a0';
1109
+ const registry = new VariableRegistry();
1110
+ // DELETE patterns: root wildcard
1111
+ const deletePatterns = [
1112
+ tripleOf(varTerm(subjectVar), varTerm('p'), varTerm('o')),
1113
+ ];
1114
+ // WHERE: type triple + root wildcard
1115
+ const typeTriple = tripleOf(varTerm(subjectVar), iriTerm(RDF_TYPE), iriTerm(query.shape));
1116
+ const rootWildcard = tripleOf(varTerm(subjectVar), varTerm('p'), varTerm('o'));
1117
+ let whereAlgebra = { type: 'bgp', triples: [typeTriple, rootWildcard] };
1118
+ // Process where patterns (traversals from the where clause)
1119
+ const traverseTriples = [];
1120
+ const optionalPropertyTriples = [];
1121
+ for (const pattern of query.wherePatterns) {
1122
+ processPattern(pattern, registry, traverseTriples, optionalPropertyTriples);
1123
+ }
1124
+ // Add traverse triples to required BGP
1125
+ if (traverseTriples.length > 0) {
1126
+ whereAlgebra = joinNodes(whereAlgebra, { type: 'bgp', triples: traverseTriples });
1127
+ }
1128
+ // Process expression to discover property triples
1129
+ processExpressionForProperties(query.where, registry, optionalPropertyTriples);
1130
+ // Add optional property triples
1131
+ for (const triple of optionalPropertyTriples) {
1132
+ whereAlgebra = joinNodes(whereAlgebra, { type: 'bgp', triples: [triple] });
1133
+ }
1134
+ // Convert and add filter expression
1135
+ const filterExpr = convertExpression(query.where, registry, []);
1136
+ whereAlgebra = { type: 'filter', expression: filterExpr, inner: whereAlgebra };
1137
+ // Walk blank node tree for cleanup
1138
+ const blankNodeOptional = walkBlankNodeTree(query.shape, subjectVar, 1, deletePatterns);
1139
+ if (blankNodeOptional) {
1140
+ whereAlgebra = { type: 'left_join', left: whereAlgebra, right: blankNodeOptional };
1141
+ }
1142
+ return {
1143
+ type: 'delete_insert',
1144
+ deletePatterns,
1145
+ insertPatterns: [],
1146
+ whereAlgebra,
1147
+ };
1148
+ }
1149
+ /**
1150
+ * Converts an IRUpdateWhereMutation to a SparqlDeleteInsertPlan.
1151
+ *
1152
+ * Like updateToAlgebra but uses a variable subject (?a0) instead of a
1153
+ * hardcoded entity IRI, adds a type triple, and optionally includes
1154
+ * filter conditions from the where clause.
1155
+ */
1156
+ export function updateWhereToAlgebra(query, options) {
1157
+ const subjectTerm = varTerm('a0');
1158
+ const result = processUpdateFields(query.data, subjectTerm, options);
1159
+ // WHERE: type triple is always required
1160
+ const typeTriple = tripleOf(subjectTerm, iriTerm(RDF_TYPE), iriTerm(query.data.shape));
1161
+ let whereAlgebra = { type: 'bgp', triples: [typeTriple] };
1162
+ // Process where filter conditions (if any)
1163
+ if (query.where && query.wherePatterns) {
1164
+ const registry = new VariableRegistry();
1165
+ const traverseTriples = [];
1166
+ const optionalPropertyTriples = [];
1167
+ for (const pattern of query.wherePatterns) {
1168
+ processPattern(pattern, registry, traverseTriples, optionalPropertyTriples);
1169
+ }
1170
+ if (traverseTriples.length > 0) {
1171
+ whereAlgebra = joinNodes(whereAlgebra, { type: 'bgp', triples: traverseTriples });
1172
+ }
1173
+ processExpressionForProperties(query.where, registry, optionalPropertyTriples);
1174
+ for (const triple of optionalPropertyTriples) {
1175
+ whereAlgebra = joinNodes(whereAlgebra, { type: 'bgp', triples: [triple] });
1176
+ }
1177
+ const filterExpr = convertExpression(query.where, registry, []);
1178
+ whereAlgebra = { type: 'filter', expression: filterExpr, inner: whereAlgebra };
1179
+ }
1180
+ whereAlgebra = wrapOldValueOptionals(whereAlgebra, result.oldValueTriples);
1181
+ // Add traversal OPTIONAL patterns (for multi-segment expression refs)
1182
+ // These must come BEFORE expression BINDs since the BINDs reference traversal variables.
1183
+ if (query.traversalPatterns) {
1184
+ for (const trav of query.traversalPatterns) {
1185
+ const fromTerm = trav.from === '__mutation_subject__' ? varTerm('a0') : varTerm(trav.from);
1186
+ const traversalTriple = tripleOf(fromTerm, iriTerm(trav.property), varTerm(trav.to));
1187
+ whereAlgebra = {
1188
+ type: 'left_join',
1189
+ left: whereAlgebra,
1190
+ right: { type: 'bgp', triples: [traversalTriple] },
1191
+ };
1192
+ }
1193
+ }
1194
+ // Add BIND expressions for computed fields
1195
+ for (const ext of result.extends) {
1196
+ whereAlgebra = {
1197
+ type: 'extend',
1198
+ inner: whereAlgebra,
1199
+ variable: ext.variable,
1200
+ expression: ext.expression,
1201
+ };
1202
+ }
1203
+ return {
1204
+ type: 'delete_insert',
1205
+ deletePatterns: result.deletePatterns,
1206
+ insertPatterns: result.insertPatterns,
1207
+ whereAlgebra,
1208
+ };
1209
+ }
1210
+ // ---------------------------------------------------------------------------
887
1211
  // Convenience wrappers: IR → algebra → SPARQL string in one call
888
1212
  // ---------------------------------------------------------------------------
889
1213
  /**
890
1214
  * Converts an IRSelectQuery to a SPARQL string.
891
- * Stub: will be implemented when algebraToString is available.
892
1215
  */
893
1216
  export function selectToSparql(query, options) {
894
1217
  const plan = selectToAlgebra(query, options);
@@ -912,10 +1235,30 @@ export function updateToSparql(query, options) {
912
1235
  }
913
1236
  /**
914
1237
  * Converts an IRDeleteMutation to a SPARQL string.
915
- * Stub: will be implemented when algebraToString is available.
916
1238
  */
917
1239
  export function deleteToSparql(query, options) {
918
1240
  const plan = deleteToAlgebra(query, options);
919
1241
  return deleteInsertPlanToSparql(plan, options);
920
1242
  }
1243
+ /**
1244
+ * Converts an IRDeleteAllMutation to a SPARQL string.
1245
+ */
1246
+ export function deleteAllToSparql(query, options) {
1247
+ const plan = deleteAllToAlgebra(query, options);
1248
+ return deleteInsertPlanToSparql(plan, options);
1249
+ }
1250
+ /**
1251
+ * Converts an IRDeleteWhereMutation to a SPARQL string.
1252
+ */
1253
+ export function deleteWhereToSparql(query, options) {
1254
+ const plan = deleteWhereToAlgebra(query, options);
1255
+ return deleteInsertPlanToSparql(plan, options);
1256
+ }
1257
+ /**
1258
+ * Converts an IRUpdateWhereMutation to a SPARQL string.
1259
+ */
1260
+ export function updateWhereToSparql(query, options) {
1261
+ const plan = updateWhereToAlgebra(query, options);
1262
+ return deleteInsertPlanToSparql(plan, options);
1263
+ }
921
1264
  //# sourceMappingURL=irToAlgebra.js.map