@_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.
- package/CHANGELOG.md +33 -171
- package/README.md +197 -45
- package/lib/cjs/expressions/Expr.d.ts +58 -0
- package/lib/cjs/expressions/Expr.js +217 -0
- package/lib/cjs/expressions/Expr.js.map +1 -0
- package/lib/cjs/expressions/ExpressionMethods.d.ts +81 -0
- package/lib/cjs/expressions/ExpressionMethods.js +3 -0
- package/lib/cjs/expressions/ExpressionMethods.js.map +1 -0
- package/lib/cjs/expressions/ExpressionNode.d.ts +95 -0
- package/lib/cjs/expressions/ExpressionNode.js +349 -0
- package/lib/cjs/expressions/ExpressionNode.js.map +1 -0
- package/lib/cjs/index.d.ts +4 -0
- package/lib/cjs/index.js +6 -1
- package/lib/cjs/index.js.map +1 -1
- package/lib/cjs/queries/DeleteBuilder.d.ts +17 -20
- package/lib/cjs/queries/DeleteBuilder.js +46 -19
- package/lib/cjs/queries/DeleteBuilder.js.map +1 -1
- package/lib/cjs/queries/DeleteQuery.d.ts +2 -2
- package/lib/cjs/queries/DeleteQuery.js.map +1 -1
- package/lib/cjs/queries/FieldSet.d.ts +3 -0
- package/lib/cjs/queries/FieldSet.js +16 -0
- package/lib/cjs/queries/FieldSet.js.map +1 -1
- package/lib/cjs/queries/IRCanonicalize.d.ts +10 -3
- package/lib/cjs/queries/IRCanonicalize.js +10 -1
- package/lib/cjs/queries/IRCanonicalize.js.map +1 -1
- package/lib/cjs/queries/IRDesugar.d.ts +30 -2
- package/lib/cjs/queries/IRDesugar.js +29 -9
- package/lib/cjs/queries/IRDesugar.js.map +1 -1
- package/lib/cjs/queries/IRLower.d.ts +19 -2
- package/lib/cjs/queries/IRLower.js +104 -20
- package/lib/cjs/queries/IRLower.js.map +1 -1
- package/lib/cjs/queries/IRMutation.d.ts +31 -1
- package/lib/cjs/queries/IRMutation.js +68 -15
- package/lib/cjs/queries/IRMutation.js.map +1 -1
- package/lib/cjs/queries/IntermediateRepresentation.d.ts +33 -4
- package/lib/cjs/queries/MutationQuery.js +16 -2
- package/lib/cjs/queries/MutationQuery.js.map +1 -1
- package/lib/cjs/queries/QueryBuilder.d.ts +14 -1
- package/lib/cjs/queries/QueryBuilder.js +59 -2
- package/lib/cjs/queries/QueryBuilder.js.map +1 -1
- package/lib/cjs/queries/QueryFactory.d.ts +2 -1
- package/lib/cjs/queries/QueryFactory.js.map +1 -1
- package/lib/cjs/queries/SelectQuery.d.ts +6 -2
- package/lib/cjs/queries/SelectQuery.js +51 -7
- package/lib/cjs/queries/SelectQuery.js.map +1 -1
- package/lib/cjs/queries/UpdateBuilder.d.ts +22 -13
- package/lib/cjs/queries/UpdateBuilder.js +60 -11
- package/lib/cjs/queries/UpdateBuilder.js.map +1 -1
- package/lib/cjs/queries/UpdateQuery.d.ts +2 -2
- package/lib/cjs/shapes/Shape.d.ts +8 -2
- package/lib/cjs/shapes/Shape.js +9 -13
- package/lib/cjs/shapes/Shape.js.map +1 -1
- package/lib/cjs/sparql/SparqlStore.js +15 -0
- package/lib/cjs/sparql/SparqlStore.js.map +1 -1
- package/lib/cjs/sparql/irToAlgebra.d.ts +34 -3
- package/lib/cjs/sparql/irToAlgebra.js +380 -31
- package/lib/cjs/sparql/irToAlgebra.js.map +1 -1
- package/lib/cjs/test-helpers/query-fixtures.d.ts +96 -208
- package/lib/cjs/test-helpers/query-fixtures.js +96 -19
- package/lib/cjs/test-helpers/query-fixtures.js.map +1 -1
- package/lib/esm/expressions/Expr.d.ts +58 -0
- package/lib/esm/expressions/Expr.js +214 -0
- package/lib/esm/expressions/Expr.js.map +1 -0
- package/lib/esm/expressions/ExpressionMethods.d.ts +81 -0
- package/lib/esm/expressions/ExpressionMethods.js +2 -0
- package/lib/esm/expressions/ExpressionMethods.js.map +1 -0
- package/lib/esm/expressions/ExpressionNode.d.ts +95 -0
- package/lib/esm/expressions/ExpressionNode.js +341 -0
- package/lib/esm/expressions/ExpressionNode.js.map +1 -0
- package/lib/esm/index.d.ts +4 -0
- package/lib/esm/index.js +3 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/queries/DeleteBuilder.d.ts +17 -20
- package/lib/esm/queries/DeleteBuilder.js +46 -19
- package/lib/esm/queries/DeleteBuilder.js.map +1 -1
- package/lib/esm/queries/DeleteQuery.d.ts +2 -2
- package/lib/esm/queries/DeleteQuery.js.map +1 -1
- package/lib/esm/queries/FieldSet.d.ts +3 -0
- package/lib/esm/queries/FieldSet.js +16 -0
- package/lib/esm/queries/FieldSet.js.map +1 -1
- package/lib/esm/queries/IRCanonicalize.d.ts +10 -3
- package/lib/esm/queries/IRCanonicalize.js +10 -1
- package/lib/esm/queries/IRCanonicalize.js.map +1 -1
- package/lib/esm/queries/IRDesugar.d.ts +30 -2
- package/lib/esm/queries/IRDesugar.js +21 -2
- package/lib/esm/queries/IRDesugar.js.map +1 -1
- package/lib/esm/queries/IRLower.d.ts +19 -2
- package/lib/esm/queries/IRLower.js +101 -19
- package/lib/esm/queries/IRLower.js.map +1 -1
- package/lib/esm/queries/IRMutation.d.ts +31 -1
- package/lib/esm/queries/IRMutation.js +63 -14
- package/lib/esm/queries/IRMutation.js.map +1 -1
- package/lib/esm/queries/IntermediateRepresentation.d.ts +33 -4
- package/lib/esm/queries/MutationQuery.js +16 -2
- package/lib/esm/queries/MutationQuery.js.map +1 -1
- package/lib/esm/queries/QueryBuilder.d.ts +14 -1
- package/lib/esm/queries/QueryBuilder.js +59 -2
- package/lib/esm/queries/QueryBuilder.js.map +1 -1
- package/lib/esm/queries/QueryFactory.d.ts +2 -1
- package/lib/esm/queries/QueryFactory.js.map +1 -1
- package/lib/esm/queries/SelectQuery.d.ts +6 -2
- package/lib/esm/queries/SelectQuery.js +51 -7
- package/lib/esm/queries/SelectQuery.js.map +1 -1
- package/lib/esm/queries/UpdateBuilder.d.ts +22 -13
- package/lib/esm/queries/UpdateBuilder.js +60 -11
- package/lib/esm/queries/UpdateBuilder.js.map +1 -1
- package/lib/esm/queries/UpdateQuery.d.ts +2 -2
- package/lib/esm/shapes/Shape.d.ts +8 -2
- package/lib/esm/shapes/Shape.js +9 -13
- package/lib/esm/shapes/Shape.js.map +1 -1
- package/lib/esm/sparql/SparqlStore.js +16 -1
- package/lib/esm/sparql/SparqlStore.js.map +1 -1
- package/lib/esm/sparql/irToAlgebra.d.ts +34 -3
- package/lib/esm/sparql/irToAlgebra.js +374 -31
- package/lib/esm/sparql/irToAlgebra.js.map +1 -1
- package/lib/esm/test-helpers/query-fixtures.d.ts +96 -208
- package/lib/esm/test-helpers/query-fixtures.js +96 -19
- package/lib/esm/test-helpers/query-fixtures.js.map +1 -1
- 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
|
-
*
|
|
782
|
+
* Processes IRNodeData fields into DELETE/INSERT/WHERE triples.
|
|
783
|
+
* Shared between updateToAlgebra (IRI subject) and updateWhereToAlgebra (variable subject).
|
|
728
784
|
*/
|
|
729
|
-
|
|
730
|
-
const subjectTerm = iriTerm(query.id);
|
|
785
|
+
function processUpdateFields(data, subjectTerm, options) {
|
|
731
786
|
const deletePatterns = [];
|
|
732
787
|
const insertPatterns = [];
|
|
733
|
-
const
|
|
734
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
-
|
|
826
|
-
|
|
918
|
+
for (const triple of oldValueTriples) {
|
|
919
|
+
algebra = {
|
|
827
920
|
type: 'left_join',
|
|
828
|
-
left:
|
|
829
|
-
right: { type: 'bgp', triples:
|
|
921
|
+
left: algebra,
|
|
922
|
+
right: { type: 'bgp', triples: [triple] },
|
|
830
923
|
};
|
|
831
924
|
}
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
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: [
|
|
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
|