@_linked/core 2.31.0 → 2.33.0
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 +38 -0
- package/README.md +61 -61
- package/lib/esm/index.d.ts +1 -0
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/ontologies/linked-core.d.ts +1 -0
- package/lib/esm/ontologies/linked-core.js +8 -2
- package/lib/esm/ontologies/linked-core.js.map +1 -1
- package/lib/esm/ontologies/shacl.d.ts +3 -0
- package/lib/esm/ontologies/shacl.js +6 -0
- package/lib/esm/ontologies/shacl.js.map +1 -1
- package/lib/esm/shapes/List.class.d.ts +3 -3
- package/lib/esm/shapes/List.class.js +3 -3
- package/lib/esm/shapes/List.class.js.map +1 -1
- package/lib/esm/shapes/PathList.d.ts +5 -8
- package/lib/esm/shapes/PathList.js +6 -9
- package/lib/esm/shapes/PathList.js.map +1 -1
- package/lib/esm/shapes/PathNode.d.ts +1 -3
- package/lib/esm/shapes/PathNode.js +6 -8
- package/lib/esm/shapes/PathNode.js.map +1 -1
- package/lib/esm/shapes/SHACL.d.ts +15 -13
- package/lib/esm/shapes/SHACL.js +8 -0
- package/lib/esm/shapes/SHACL.js.map +1 -1
- package/lib/esm/shapes/nodeShapeData.d.ts +4 -1
- package/lib/esm/shapes/nodeShapeData.js.map +1 -1
- package/lib/esm/shapes/singleOwnerConstraint.d.ts +5 -7
- package/lib/esm/shapes/singleOwnerConstraint.js +65 -21
- package/lib/esm/shapes/singleOwnerConstraint.js.map +1 -1
- package/lib/esm/shapes/syncShapes.d.ts +54 -3
- package/lib/esm/shapes/syncShapes.js +141 -13
- package/lib/esm/shapes/syncShapes.js.map +1 -1
- package/lib/esm/sparql/irToAlgebra.d.ts +15 -38
- package/lib/esm/sparql/irToAlgebra.js +406 -391
- package/lib/esm/sparql/irToAlgebra.js.map +1 -1
- package/lib/esm/sparql/ownedLinkCheck.d.ts +6 -10
- package/lib/esm/sparql/ownedLinkCheck.js +10 -11
- package/lib/esm/sparql/ownedLinkCheck.js.map +1 -1
- package/lib/esm/utils/Package.d.ts +3 -2
- package/lib/esm/utils/Package.js +21 -8
- package/lib/esm/utils/Package.js.map +1 -1
- package/lib/esm/utils/sha256.d.ts +8 -0
- package/lib/esm/utils/sha256.js +80 -0
- package/lib/esm/utils/sha256.js.map +1 -0
- package/package.json +1 -1
|
@@ -6,12 +6,13 @@ import { rdf } from '../ontologies/rdf.js';
|
|
|
6
6
|
import { shacl } from '../ontologies/shacl.js';
|
|
7
7
|
import { xsd } from '../ontologies/xsd.js';
|
|
8
8
|
import { getSimplePathId } from '../paths/normalizePropertyPath.js';
|
|
9
|
-
import { getAllNodeShapes, getNodeShape, getRegistryVersion, getShapeClass, getSubShapes, getTargetClassId, } from '../utils/ShapeClass.js';
|
|
9
|
+
import { getAllNodeShapes, getNodeShape, getRegistryVersion, getShapeClass, getSubShapes, getTargetClassId, isSubShapeOf, } from '../utils/ShapeClass.js';
|
|
10
10
|
import { UnresolvedContextError } from '../queries/QueryContext.js';
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
12
|
// Constants
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
14
14
|
const RDF_TYPE = rdf.type.id;
|
|
15
|
+
const RDF_NIL = rdf.nil.id;
|
|
15
16
|
const XSD_DATETIME = xsd.dateTime.id;
|
|
16
17
|
const XSD_DATE = xsd.date.id;
|
|
17
18
|
const XSD_TIME = xsd.time.id;
|
|
@@ -1593,7 +1594,7 @@ function keptReferences(value) {
|
|
|
1593
1594
|
* Shared between updateToAlgebra (IRI subject) and updateWhereToAlgebra (variable subject).
|
|
1594
1595
|
*/
|
|
1595
1596
|
function processUpdateFields(data, subjectTerm, options) {
|
|
1596
|
-
var _a, _b
|
|
1597
|
+
var _a, _b;
|
|
1597
1598
|
const deletePatterns = [];
|
|
1598
1599
|
const insertPatterns = [];
|
|
1599
1600
|
const oldValueTriples = [];
|
|
@@ -1601,18 +1602,16 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1601
1602
|
// Owned cleanup on `contains` replace/remove: dropping a `contains` edge must delete
|
|
1602
1603
|
// the old object itself (its own triples) AND its owned subtree, not just unlink the
|
|
1603
1604
|
// one-hop edge — otherwise the old node orphans (backlog 032).
|
|
1604
|
-
const { containsPreds: updContainsPreds } = collectContainment();
|
|
1605
1605
|
const containsOldVars = [];
|
|
1606
1606
|
const cascadeOptionals = [];
|
|
1607
1607
|
for (const field of data.fields) {
|
|
1608
1608
|
const propertyTerm = resolvePropertyPredicateTerm(field.property);
|
|
1609
1609
|
const suffix = propertySuffix(field.property);
|
|
1610
|
-
const
|
|
1611
|
-
//
|
|
1612
|
-
//
|
|
1613
|
-
//
|
|
1614
|
-
const valueShapeId =
|
|
1615
|
-
const hasValueShape = !!valueShapeId;
|
|
1610
|
+
const fieldShape = findPropertyShapeById(field.property);
|
|
1611
|
+
// Ownership is the property's own: a `contains` property with a value shape and a simple
|
|
1612
|
+
// path (an IRI predicate term). A plain property on a predicate another shape declares
|
|
1613
|
+
// `contains` owns nothing.
|
|
1614
|
+
const valueShapeId = propertyTerm.kind === 'iri' && (fieldShape === null || fieldShape === void 0 ? void 0 : fieldShape.contains) ? (_a = fieldShape.valueShape) === null || _a === void 0 ? void 0 : _a.id : undefined;
|
|
1616
1615
|
// Check for set modification ({add, remove})
|
|
1617
1616
|
if (field.value &&
|
|
1618
1617
|
typeof field.value === 'object' &&
|
|
@@ -1625,7 +1624,7 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1625
1624
|
if (setMod.remove) {
|
|
1626
1625
|
// A value that is removed and added back in the same modification stays: its edge
|
|
1627
1626
|
// is deleted and re-inserted (a no-op), and the node itself is not touched.
|
|
1628
|
-
const readdedIds = new Set(((
|
|
1627
|
+
const readdedIds = new Set(((_b = setMod.add) !== null && _b !== void 0 ? _b : []).filter(isBareNodeReference).map((ref) => ref.id));
|
|
1629
1628
|
setMod.remove.forEach((removeItem, ri) => {
|
|
1630
1629
|
const removeId = removeItem.id;
|
|
1631
1630
|
const removeTerm = iriTerm(removeId);
|
|
@@ -1634,24 +1633,21 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1634
1633
|
oldValueTriples.push(edge);
|
|
1635
1634
|
// For a `contains` set, removing a value deletes the removed node itself
|
|
1636
1635
|
// (its own triples) and its owned subtree — exclusive ownership via the edge.
|
|
1637
|
-
if (
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
deletePatterns.push(...blank.deletePatterns);
|
|
1647
|
-
cascadeOptionals.push(blank.whereBlock);
|
|
1648
|
-
}
|
|
1636
|
+
if (valueShapeId && !readdedIds.has(removeId)) {
|
|
1637
|
+
const prefix = `ucr_${suffix}_${ri}_`;
|
|
1638
|
+
const self = buildOwnedSelfDelete(subjectTerm, propertyTerm, removeTerm, prefix);
|
|
1639
|
+
deletePatterns.push(...self.deletePatterns);
|
|
1640
|
+
cascadeOptionals.push(...self.whereOptionals);
|
|
1641
|
+
const blank = subjectBlankCascade(valueShapeId, removeTerm, [edge], `${prefix}vb_`);
|
|
1642
|
+
if (blank) {
|
|
1643
|
+
deletePatterns.push(...blank.deletePatterns);
|
|
1644
|
+
cascadeOptionals.push(blank.whereBlock);
|
|
1649
1645
|
}
|
|
1650
|
-
// The edge leads
|
|
1651
|
-
//
|
|
1652
|
-
const
|
|
1653
|
-
deletePatterns.push(...
|
|
1654
|
-
cascadeOptionals.push(...
|
|
1646
|
+
// The edge leads every branch: the removed node's subtree may only be deleted
|
|
1647
|
+
// while the node is still owned here.
|
|
1648
|
+
const owned = ownedSubtreeBlocks(valueShapeId, removeTerm, [edge], prefix);
|
|
1649
|
+
deletePatterns.push(...owned.deletePatterns);
|
|
1650
|
+
cascadeOptionals.push(...owned.whereBlocks);
|
|
1655
1651
|
}
|
|
1656
1652
|
});
|
|
1657
1653
|
}
|
|
@@ -1672,7 +1668,6 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1672
1668
|
}
|
|
1673
1669
|
// Replace / unset / overwrite of a blank-capable property: its old blank value and the
|
|
1674
1670
|
// blank nodes below it go (the branch binds `old_${suffix}` through the edge itself).
|
|
1675
|
-
const fieldShape = findPropertyShapeById(field.property);
|
|
1676
1671
|
if (isBlankCapable(fieldShape)) {
|
|
1677
1672
|
const blank = buildBlankCascade([tripleOf(subjectTerm, propertyTerm, varTerm(`old_${suffix}`))], varTerm(`old_${suffix}`), null, blankWalkPredicates([fieldShape]), `ub_${suffix}_`);
|
|
1678
1673
|
if (blank) {
|
|
@@ -1682,12 +1677,11 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1682
1677
|
}
|
|
1683
1678
|
// Non-set-modification replace of a `contains` property: cascade the old value's
|
|
1684
1679
|
// subtree (every branch below binds `old_${suffix}` in the WHERE).
|
|
1685
|
-
if (
|
|
1680
|
+
if (valueShapeId) {
|
|
1686
1681
|
containsOldVars.push({
|
|
1687
1682
|
oldVar: `old_${suffix}`,
|
|
1688
1683
|
propertyTerm,
|
|
1689
1684
|
keep: keptReferences(field.value),
|
|
1690
|
-
hasValueShape,
|
|
1691
1685
|
valueShapeId,
|
|
1692
1686
|
});
|
|
1693
1687
|
}
|
|
@@ -1772,27 +1766,24 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1772
1766
|
// (exclusive ownership via the edge — backlog 032) then cascade any owned descendants.
|
|
1773
1767
|
// Values re-sent as a bare `{id}` are kept: their blocks are filtered, so re-sending the
|
|
1774
1768
|
// current value is a no-op (the edge is deleted and re-inserted).
|
|
1775
|
-
containsOldVars.forEach(({ oldVar, propertyTerm, keep,
|
|
1769
|
+
containsOldVars.forEach(({ oldVar, propertyTerm, keep, valueShapeId }, n) => {
|
|
1776
1770
|
const oldTerm = varTerm(oldVar);
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
const cascade = buildOwnedCascade(oldTerm, `uc${n}_`, tripleOf(subjectTerm, propertyTerm, oldTerm), keep);
|
|
1794
|
-
deletePatterns.push(...cascade.deletePatterns);
|
|
1795
|
-
cascadeOptionals.push(...cascade.whereOptionals);
|
|
1771
|
+
const edge = tripleOf(subjectTerm, propertyTerm, oldTerm);
|
|
1772
|
+
const self = buildOwnedSelfDelete(subjectTerm, propertyTerm, oldTerm, `uc${n}_`, keep);
|
|
1773
|
+
deletePatterns.push(...self.deletePatterns);
|
|
1774
|
+
cascadeOptionals.push(...self.whereOptionals);
|
|
1775
|
+
// The old value's own blank values go with it.
|
|
1776
|
+
const blank = subjectBlankCascade(valueShapeId, oldTerm, [edge], `uc${n}_vb_`);
|
|
1777
|
+
if (blank) {
|
|
1778
|
+
deletePatterns.push(...blank.deletePatterns);
|
|
1779
|
+
cascadeOptionals.push(keepFilter(blank.whereBlock, oldTerm, keep));
|
|
1780
|
+
}
|
|
1781
|
+
// Every subtree branch binds `?old` itself through the owning edge, like the self-delete
|
|
1782
|
+
// block does. Without it an absent old value leaves `?old` unbound and the branch matches
|
|
1783
|
+
// the owned subtree of EVERY node in the graph.
|
|
1784
|
+
const owned = ownedSubtreeBlocks(valueShapeId, oldTerm, [edge], `uc${n}_`, keep);
|
|
1785
|
+
deletePatterns.push(...owned.deletePatterns);
|
|
1786
|
+
cascadeOptionals.push(...owned.whereBlocks);
|
|
1796
1787
|
});
|
|
1797
1788
|
return {
|
|
1798
1789
|
deletePatterns,
|
|
@@ -1986,212 +1977,6 @@ function buildUpdateWhere(base, result, traversalPatterns, subjectTerm) {
|
|
|
1986
1977
|
return whereAlgebra;
|
|
1987
1978
|
}
|
|
1988
1979
|
// ---------------------------------------------------------------------------
|
|
1989
|
-
// Owned-subtree cascade
|
|
1990
|
-
//
|
|
1991
|
-
// Composition cleanup driven by two declarative flags:
|
|
1992
|
-
// - a property marked `contains` → the cascade FOLLOWS that edge,
|
|
1993
|
-
// - a shape marked `dependent` → its instances may be DELETED when reached.
|
|
1994
|
-
// Both are read from the live registry, so no vocabulary is hardcoded. From a root
|
|
1995
|
-
// node we follow `(c1|c2|…)*` over all contains predicates (zero hops included, so a
|
|
1996
|
-
// dependent-typed root is itself a match) and, for each dependent targetClass,
|
|
1997
|
-
// wildcard-delete the reached typed nodes. Requiring an asserted `?owned a <dependentType>`
|
|
1998
|
-
// naturally excludes shared predicate IRIs (e.g. a simple `sh:path`) and `rdf:nil` (never
|
|
1999
|
-
// typed), so only owned structural nodes are removed.
|
|
2000
|
-
//
|
|
2001
|
-
// Whether a field owns its value is decided per predicate across the registry
|
|
2002
|
-
// (`collectContainment`): the replace and set-remove deletes, and the class check in
|
|
2003
|
-
// `ownedLinkCheck.ts`, apply that same rule. The subject deletes (by id and bulk) use the
|
|
2004
|
-
// deleted shape's own `contains` properties with a value shape for the immediate children.
|
|
2005
|
-
// ---------------------------------------------------------------------------
|
|
2006
|
-
/**
|
|
2007
|
-
* The simple-path predicates of the shape's own and inherited `contains` properties that
|
|
2008
|
-
* declare a value shape, deduplicated in declaration order. These are the properties
|
|
2009
|
-
* whose immediate values a subject delete removes.
|
|
2010
|
-
*/
|
|
2011
|
-
function ownedValueShapePredicates(shapeId) {
|
|
2012
|
-
var _a, _b;
|
|
2013
|
-
const nodeShape = (_b = (_a = getShapeClass(shapeId)) === null || _a === void 0 ? void 0 : _a.shape) !== null && _b !== void 0 ? _b : getNodeShape(shapeId);
|
|
2014
|
-
if (!nodeShape)
|
|
2015
|
-
return [];
|
|
2016
|
-
const predicates = new Set();
|
|
2017
|
-
for (const ps of getPropertyShapes(nodeShape, true)) {
|
|
2018
|
-
if (!ps.contains || !ps.valueShape)
|
|
2019
|
-
continue;
|
|
2020
|
-
const predicate = getSimplePathId(ps.path);
|
|
2021
|
-
if (predicate)
|
|
2022
|
-
predicates.add(predicate);
|
|
2023
|
-
}
|
|
2024
|
-
return [...predicates];
|
|
2025
|
-
}
|
|
2026
|
-
/**
|
|
2027
|
-
* The blank-node cascade of the immediately-owned children of `shapeId` on `predicate` (see
|
|
2028
|
-
* `ownedValueShapePredicates`): the blank values of every value shape its `contains` properties
|
|
2029
|
-
* on that predicate declare. `ownerTriples` bind the child.
|
|
2030
|
-
*/
|
|
2031
|
-
function ownedChildBlankCascade(shapeId, predicate, childTerm, ownerTriples, varPrefix) {
|
|
2032
|
-
var _a, _b, _c;
|
|
2033
|
-
const nodeShape = (_b = (_a = getShapeClass(shapeId)) === null || _a === void 0 ? void 0 : _a.shape) !== null && _b !== void 0 ? _b : getNodeShape(shapeId);
|
|
2034
|
-
if (!nodeShape)
|
|
2035
|
-
return [];
|
|
2036
|
-
const valueShapes = new Set();
|
|
2037
|
-
for (const ps of getPropertyShapes(nodeShape, true)) {
|
|
2038
|
-
if (ps.contains && ((_c = ps.valueShape) === null || _c === void 0 ? void 0 : _c.id) && getSimplePathId(ps.path) === predicate) {
|
|
2039
|
-
valueShapes.add(ps.valueShape.id);
|
|
2040
|
-
}
|
|
2041
|
-
}
|
|
2042
|
-
return [...valueShapes].flatMap((valueShapeId, i) => {
|
|
2043
|
-
const cascade = subjectBlankCascade(valueShapeId, childTerm, ownerTriples, `${varPrefix}${i}_`);
|
|
2044
|
-
return cascade ? [cascade] : [];
|
|
2045
|
-
});
|
|
2046
|
-
}
|
|
2047
|
-
/** True when the shape (or an ancestor) declares at least one `contains` property. */
|
|
2048
|
-
function shapeHasContainsProperty(shapeId) {
|
|
2049
|
-
var _a, _b;
|
|
2050
|
-
const nodeShape = (_b = (_a = getShapeClass(shapeId)) === null || _a === void 0 ? void 0 : _a.shape) !== null && _b !== void 0 ? _b : getNodeShape(shapeId);
|
|
2051
|
-
if (!nodeShape)
|
|
2052
|
-
return false;
|
|
2053
|
-
return getPropertyShapes(nodeShape, true)
|
|
2054
|
-
.some((ps) => ps.contains);
|
|
2055
|
-
}
|
|
2056
|
-
let containmentCache = null;
|
|
2057
|
-
let containmentCacheSize = -1;
|
|
2058
|
-
/** Gather contains-predicate IRIs and dependent targetClass IRIs from the registry. */
|
|
2059
|
-
export function collectContainment() {
|
|
2060
|
-
var _a, _b, _c;
|
|
2061
|
-
const version = getRegistryVersion();
|
|
2062
|
-
if (containmentCache && version === containmentCacheSize) {
|
|
2063
|
-
return containmentCache;
|
|
2064
|
-
}
|
|
2065
|
-
const containsPreds = new Set();
|
|
2066
|
-
const dependentTypes = new Set();
|
|
2067
|
-
const dependentBlankProps = new Map();
|
|
2068
|
-
// Per targetClass, the predicates some shape targeting it declares NOT blank-capable.
|
|
2069
|
-
const plainPredsByType = new Map();
|
|
2070
|
-
for (const nodeShape of getAllNodeShapes().values()) {
|
|
2071
|
-
if (!nodeShape)
|
|
2072
|
-
continue;
|
|
2073
|
-
if ((_a = nodeShape.targetClass) === null || _a === void 0 ? void 0 : _a.id) {
|
|
2074
|
-
const typeId = nodeShape.targetClass.id;
|
|
2075
|
-
for (const ps of getPropertyShapes(nodeShape, true)) {
|
|
2076
|
-
const predicate = getSimplePathId(ps.path);
|
|
2077
|
-
if (!predicate || isBlankCapable(ps))
|
|
2078
|
-
continue;
|
|
2079
|
-
if (!plainPredsByType.has(typeId))
|
|
2080
|
-
plainPredsByType.set(typeId, new Set());
|
|
2081
|
-
plainPredsByType.get(typeId).add(predicate);
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
if (nodeShape.dependent && ((_b = nodeShape.targetClass) === null || _b === void 0 ? void 0 : _b.id)) {
|
|
2085
|
-
const typeId = nodeShape.targetClass.id;
|
|
2086
|
-
dependentTypes.add(typeId);
|
|
2087
|
-
const blank = getPropertyShapes(nodeShape, true).filter(isBlankCapable);
|
|
2088
|
-
if (blank.length > 0) {
|
|
2089
|
-
dependentBlankProps.set(typeId, [...((_c = dependentBlankProps.get(typeId)) !== null && _c !== void 0 ? _c : []), ...blank]);
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
2092
|
-
const propertyShapes = getPropertyShapes(nodeShape, false);
|
|
2093
|
-
for (const ps of propertyShapes) {
|
|
2094
|
-
if (ps.contains) {
|
|
2095
|
-
const predId = getSimplePathId(ps.path);
|
|
2096
|
-
if (predId)
|
|
2097
|
-
containsPreds.add(predId);
|
|
2098
|
-
}
|
|
2099
|
-
}
|
|
2100
|
-
}
|
|
2101
|
-
// A typed node only says its class, not which of the shapes targeting it wrote it. When the
|
|
2102
|
-
// shapes disagree on a predicate (`PathList.first` is blank-capable, `List.first` is not, both
|
|
2103
|
-
// target `rdf:List`), the cascade does not follow it: a user list's blank `rdf:first` member
|
|
2104
|
-
// is content the list does not own.
|
|
2105
|
-
for (const [typeId, props] of dependentBlankProps) {
|
|
2106
|
-
const plain = plainPredsByType.get(typeId);
|
|
2107
|
-
if (!plain)
|
|
2108
|
-
continue;
|
|
2109
|
-
const kept = props.filter((ps) => {
|
|
2110
|
-
const predicate = getSimplePathId(ps.path);
|
|
2111
|
-
return !!predicate && !plain.has(predicate);
|
|
2112
|
-
});
|
|
2113
|
-
if (kept.length > 0)
|
|
2114
|
-
dependentBlankProps.set(typeId, kept);
|
|
2115
|
-
else
|
|
2116
|
-
dependentBlankProps.delete(typeId);
|
|
2117
|
-
}
|
|
2118
|
-
containmentCache = { containsPreds: [...containsPreds], dependentTypes: [...dependentTypes], dependentBlankProps };
|
|
2119
|
-
containmentCacheSize = version;
|
|
2120
|
-
return containmentCache;
|
|
2121
|
-
}
|
|
2122
|
-
let reachableCache = new Map();
|
|
2123
|
-
let reachableCacheVersion = -1;
|
|
2124
|
-
/**
|
|
2125
|
-
* The dependent targetClasses a subject delete of `shapeId` can reach through `contains`
|
|
2126
|
-
* edges, in `collectContainment` order. A subject delete emits one cascade block per type it
|
|
2127
|
-
* returns, and each block is a property-path closure per subject, so a shape that owns only
|
|
2128
|
-
* non-dependent values (every `Thing.image` owner) pays for none.
|
|
2129
|
-
*
|
|
2130
|
-
* The walk starts at the shape and its registered sub-shapes and follows every flattened
|
|
2131
|
-
* property on a `contains` predicate (the registry-wide predicate rule the cascade path uses)
|
|
2132
|
-
* to its value shape and that shape's sub-shapes. A property on a `contains` predicate with no
|
|
2133
|
-
* value shape is polymorphic (`sh:path`) and may reach anything, so it yields every dependent
|
|
2134
|
-
* type; that keeps the shape-sync delete of a NodeShape exact.
|
|
2135
|
-
*/
|
|
2136
|
-
function reachableDependentTypes(shapeId) {
|
|
2137
|
-
var _a, _b;
|
|
2138
|
-
const version = getRegistryVersion();
|
|
2139
|
-
if (version !== reachableCacheVersion) {
|
|
2140
|
-
reachableCache = new Map();
|
|
2141
|
-
reachableCacheVersion = version;
|
|
2142
|
-
}
|
|
2143
|
-
const cached = reachableCache.get(shapeId);
|
|
2144
|
-
if (cached)
|
|
2145
|
-
return cached;
|
|
2146
|
-
const { containsPreds, dependentTypes } = collectContainment();
|
|
2147
|
-
const shapeOf = (id) => { var _a, _b; return (_b = (_a = getShapeClass(id)) === null || _a === void 0 ? void 0 : _a.shape) !== null && _b !== void 0 ? _b : getNodeShape(id); };
|
|
2148
|
-
const reached = new Set();
|
|
2149
|
-
// Shapes whose instances an owned edge can lead to. The start shapes join only when an
|
|
2150
|
-
// edge leads back to them (a dependent shape that owns its own kind, like a list cell).
|
|
2151
|
-
const owned = new Set();
|
|
2152
|
-
const seen = new Set();
|
|
2153
|
-
const queue = [];
|
|
2154
|
-
const visit = (id, viaEdge) => {
|
|
2155
|
-
for (const sid of [id, ...getSubShapes(id).map((sub) => sub.id)]) {
|
|
2156
|
-
if (viaEdge)
|
|
2157
|
-
owned.add(sid);
|
|
2158
|
-
if (seen.has(sid))
|
|
2159
|
-
continue;
|
|
2160
|
-
seen.add(sid);
|
|
2161
|
-
queue.push(sid);
|
|
2162
|
-
}
|
|
2163
|
-
};
|
|
2164
|
-
visit(shapeId, false);
|
|
2165
|
-
let everything = false;
|
|
2166
|
-
while (queue.length > 0 && !everything) {
|
|
2167
|
-
const nodeShape = shapeOf(queue.shift());
|
|
2168
|
-
if (!nodeShape)
|
|
2169
|
-
continue;
|
|
2170
|
-
for (const ps of getPropertyShapes(nodeShape, true)) {
|
|
2171
|
-
const predicate = getSimplePathId(ps.path);
|
|
2172
|
-
if (!predicate || !containsPreds.includes(predicate))
|
|
2173
|
-
continue;
|
|
2174
|
-
if (!((_a = ps.valueShape) === null || _a === void 0 ? void 0 : _a.id)) {
|
|
2175
|
-
everything = true;
|
|
2176
|
-
break;
|
|
2177
|
-
}
|
|
2178
|
-
visit(ps.valueShape.id, true);
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
if (!everything) {
|
|
2182
|
-
for (const id of owned) {
|
|
2183
|
-
const nodeShape = shapeOf(id);
|
|
2184
|
-
const dependent = nodeShape === null || nodeShape === void 0 ? void 0 : nodeShape.dependent;
|
|
2185
|
-
if (dependent && ((_b = nodeShape === null || nodeShape === void 0 ? void 0 : nodeShape.targetClass) === null || _b === void 0 ? void 0 : _b.id)) {
|
|
2186
|
-
reached.add(nodeShape.targetClass.id);
|
|
2187
|
-
}
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
|
-
const result = everything ? dependentTypes : dependentTypes.filter((t) => reached.has(t));
|
|
2191
|
-
reachableCache.set(shapeId, result);
|
|
2192
|
-
return result;
|
|
2193
|
-
}
|
|
2194
|
-
// ---------------------------------------------------------------------------
|
|
2195
1980
|
// Blank-node cascade: blank values are deleted through the blank-capable properties
|
|
2196
1981
|
// (`isBlankCapable`) that hold them, never through `contains`.
|
|
2197
1982
|
// ---------------------------------------------------------------------------
|
|
@@ -2227,8 +2012,8 @@ let blankWalkCacheVersion = -1;
|
|
|
2227
2012
|
* the blank-capable properties of every shape the value (and, transitively, its blank values)
|
|
2228
2013
|
* can have — a property's value shape and its sub-shapes. A blank-capable property without a
|
|
2229
2014
|
* value shape is polymorphic (`sh:path`, the `PathNode` slots, `PathList.first`) and may hold
|
|
2230
|
-
* any shape, so it yields every blank-capable predicate in the registry
|
|
2231
|
-
*
|
|
2015
|
+
* any shape, so it yields every blank-capable predicate in the registry. Precision is not what
|
|
2016
|
+
* keeps the walk safe:
|
|
2232
2017
|
* `buildBlankCascade` refuses any node reached through an IRI.
|
|
2233
2018
|
*/
|
|
2234
2019
|
function blankWalkPredicates(start) {
|
|
@@ -2387,72 +2172,354 @@ function subjectBlankCascade(shapeId, subjectTerm, ownerTriples, varPrefix) {
|
|
|
2387
2172
|
return null;
|
|
2388
2173
|
return buildBlankCascade(ownerTriples, subjectTerm, simplePredicates(props), blankWalkPredicates(props), varPrefix);
|
|
2389
2174
|
}
|
|
2175
|
+
// ---------------------------------------------------------------------------
|
|
2176
|
+
// Owned-subtree walk
|
|
2177
|
+
//
|
|
2178
|
+
// A `contains` property (with a value shape and a simple path) owns its value: replacing,
|
|
2179
|
+
// unsetting or set-removing the value, or deleting the subject, deletes the value node, then
|
|
2180
|
+
// the values that node's shape owns, and so on down. Ownership is decided per property: the
|
|
2181
|
+
// walk starts at the root's shape and follows only the `contains` properties the reached
|
|
2182
|
+
// shapes declare (own and inherited), so a plain property on a predicate that another shape
|
|
2183
|
+
// declares `contains` is never followed. A registered sub-shape that adds `contains`
|
|
2184
|
+
// properties contributes hops guarded by its concrete class (`?n a <SubClass>`). Every node
|
|
2185
|
+
// the walk reaches loses its own triples and its blank values; `dependent` plays no part.
|
|
2186
|
+
//
|
|
2187
|
+
// Recursion: a shape that owns itself through an unguarded edge (`List.rest`) is followed with
|
|
2188
|
+
// a closure (`rdf:rest*`), since every such edge from one of its nodes is its own. Any other
|
|
2189
|
+
// recursion (a cycle through several shapes, a sub-shape's edge back into the walk) is unrolled
|
|
2190
|
+
// into typed hops, up to `MAX_OWNED_RECURSION` repeated shapes per path; deeper values are left.
|
|
2191
|
+
// ---------------------------------------------------------------------------
|
|
2192
|
+
/**
|
|
2193
|
+
* How many times one walk path may re-enter a shape it already passed through (a cycle through
|
|
2194
|
+
* several shapes, or a sub-shape's edge back into its own kind). Values below are not deleted.
|
|
2195
|
+
*/
|
|
2196
|
+
export const MAX_OWNED_RECURSION = 4;
|
|
2197
|
+
/** Above this many branches for one root shape, lowering warns once (see backlog 055). */
|
|
2198
|
+
export const OWNED_BRANCH_WARNING = 64;
|
|
2199
|
+
/** The simple-path predicate through which a property owns its value, or null when it does not. */
|
|
2200
|
+
function ownedPredicate(ps) {
|
|
2201
|
+
var _a, _b;
|
|
2202
|
+
if (!ps.contains || !((_a = ps.valueShape) === null || _a === void 0 ? void 0 : _a.id))
|
|
2203
|
+
return null;
|
|
2204
|
+
return (_b = getSimplePathId(ps.path)) !== null && _b !== void 0 ? _b : null;
|
|
2205
|
+
}
|
|
2206
|
+
let classHoldersCache = new Map();
|
|
2207
|
+
let classHoldersVersion = -1;
|
|
2208
|
+
/** Every registered shape id per `targetClass` (memoized per registry version). */
|
|
2209
|
+
function classHolders(classId) {
|
|
2210
|
+
var _a, _b, _c;
|
|
2211
|
+
const version = getRegistryVersion();
|
|
2212
|
+
if (version !== classHoldersVersion) {
|
|
2213
|
+
classHoldersCache = new Map();
|
|
2214
|
+
for (const nodeShape of getAllNodeShapes().values()) {
|
|
2215
|
+
const cls = (_a = nodeShape === null || nodeShape === void 0 ? void 0 : nodeShape.targetClass) === null || _a === void 0 ? void 0 : _a.id;
|
|
2216
|
+
if (!cls)
|
|
2217
|
+
continue;
|
|
2218
|
+
classHoldersCache.set(cls, [...((_b = classHoldersCache.get(cls)) !== null && _b !== void 0 ? _b : []), nodeShape.id]);
|
|
2219
|
+
}
|
|
2220
|
+
classHoldersVersion = version;
|
|
2221
|
+
}
|
|
2222
|
+
return (_c = classHoldersCache.get(classId)) !== null && _c !== void 0 ? _c : [];
|
|
2223
|
+
}
|
|
2390
2224
|
/**
|
|
2391
|
-
*
|
|
2392
|
-
*
|
|
2393
|
-
*
|
|
2225
|
+
* Why the added `contains` property `ps` of the sub-shape `sub` cannot be followed from nodes of
|
|
2226
|
+
* the sub-shape's class, or null when it can. The guard `?n a <C>` also matches the nodes of
|
|
2227
|
+
* every other shape targeting `C`, so it is refused when one of them is a super-shape of `sub`
|
|
2228
|
+
* (its own nodes would match) or declares the same predicate as a plain property (following it
|
|
2229
|
+
* would delete that shape's references). A holder that does not declare the predicate, or owns
|
|
2230
|
+
* it too, does not block it.
|
|
2231
|
+
*/
|
|
2232
|
+
function blockedGuard(sub, ps) {
|
|
2233
|
+
var _a;
|
|
2234
|
+
const cls = (_a = sub.targetClass) === null || _a === void 0 ? void 0 : _a.id;
|
|
2235
|
+
if (!cls)
|
|
2236
|
+
return 'has no targetClass';
|
|
2237
|
+
const predicate = getSimplePathId(ps.path);
|
|
2238
|
+
for (const id of classHolders(cls)) {
|
|
2239
|
+
if (id === sub.id || isSubShapeOf(id, sub.id))
|
|
2240
|
+
continue;
|
|
2241
|
+
if (isSubShapeOf(sub.id, id))
|
|
2242
|
+
return `shares its targetClass with its super-shape '${id}'`;
|
|
2243
|
+
const holder = shapeById(id);
|
|
2244
|
+
const plain = holder && getPropertyShapes(holder, true).some((q) => !q.contains && getSimplePathId(q.path) === predicate);
|
|
2245
|
+
if (plain)
|
|
2246
|
+
return `shares its targetClass with '${id}', which holds <${predicate}> as a plain property`;
|
|
2247
|
+
}
|
|
2248
|
+
return null;
|
|
2249
|
+
}
|
|
2250
|
+
const warnedUnguardable = new Set();
|
|
2251
|
+
/**
|
|
2252
|
+
* The owned edges of a shape: its own and inherited `contains` properties, then, per registered
|
|
2253
|
+
* sub-shape, the `contains` properties it adds, guarded by its `targetClass` (a node carries only
|
|
2254
|
+
* its concrete class's `rdf:type`, so each sub-shape lists its full extra set). An added property
|
|
2255
|
+
* whose guard cannot tell the sub-shape's nodes apart (`blockedGuard`) is not followed and is
|
|
2256
|
+
* warned once, unless the sub-shape is a profile or data-only copy of its direct super-shape (same
|
|
2257
|
+
* class or none, the property inherited from that super-shape), which is skipped silently: that
|
|
2258
|
+
* super-shape's entry covers it.
|
|
2259
|
+
*/
|
|
2260
|
+
function ownedEdges(shapeId) {
|
|
2261
|
+
var _a, _b, _c;
|
|
2262
|
+
const nodeShape = shapeById(shapeId);
|
|
2263
|
+
if (!nodeShape)
|
|
2264
|
+
return [];
|
|
2265
|
+
const edges = new Map();
|
|
2266
|
+
const add = (ps, guard) => {
|
|
2267
|
+
const predicate = ownedPredicate(ps);
|
|
2268
|
+
if (!predicate)
|
|
2269
|
+
return;
|
|
2270
|
+
const target = ps.valueShape.id;
|
|
2271
|
+
edges.set(`${guard !== null && guard !== void 0 ? guard : ''} ${predicate} ${target}`, { guard, predicate, target });
|
|
2272
|
+
};
|
|
2273
|
+
const own = getPropertyShapes(nodeShape, true);
|
|
2274
|
+
own.forEach((ps) => add(ps));
|
|
2275
|
+
const ownIds = new Set(own.map((ps) => ps.id));
|
|
2276
|
+
for (const sub of getSubShapes(shapeId)) {
|
|
2277
|
+
const extra = getPropertyShapes(sub, true).filter((ps) => !ownIds.has(ps.id) && ownedPredicate(ps));
|
|
2278
|
+
if (extra.length === 0)
|
|
2279
|
+
continue;
|
|
2280
|
+
const parent = ((_a = sub.extends) === null || _a === void 0 ? void 0 : _a.id) ? shapeById(sub.extends.id) : undefined;
|
|
2281
|
+
const inherited = new Set(parent ? getPropertyShapes(parent, true).map((ps) => ps.id) : []);
|
|
2282
|
+
for (const ps of extra) {
|
|
2283
|
+
const reason = blockedGuard(sub, ps);
|
|
2284
|
+
if (!reason) {
|
|
2285
|
+
add(ps, sub.targetClass.id);
|
|
2286
|
+
continue;
|
|
2287
|
+
}
|
|
2288
|
+
// A profile or data-only copy of its direct super-shape (same class, or none) is that
|
|
2289
|
+
// super-shape's node by type: the super-shape's own entry covers the property, unless
|
|
2290
|
+
// that super-shape is this walk's shape itself (no other entry covers it, so warn).
|
|
2291
|
+
const sameKind = !((_b = sub.targetClass) === null || _b === void 0 ? void 0 : _b.id) || sub.targetClass.id === ((_c = parent === null || parent === void 0 ? void 0 : parent.targetClass) === null || _c === void 0 ? void 0 : _c.id);
|
|
2292
|
+
if (sameKind && inherited.has(ps.id) && (parent === null || parent === void 0 ? void 0 : parent.id) !== shapeId)
|
|
2293
|
+
continue;
|
|
2294
|
+
const key = `${sub.id} ${ps.id}`;
|
|
2295
|
+
if (warnedUnguardable.has(key))
|
|
2296
|
+
continue;
|
|
2297
|
+
warnedUnguardable.add(key);
|
|
2298
|
+
console.warn(`[linked] Shape '${sub.id}' adds the contains property "${ps.label}" to '${shapeId}', but ` +
|
|
2299
|
+
`${reason}, so its nodes cannot be told apart by rdf:type: deleting through ` +
|
|
2300
|
+
`'${shapeId}' does not follow it. Give the shape a targetClass of its own.`);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
return [...edges.values()];
|
|
2304
|
+
}
|
|
2305
|
+
/** The classes a node of `shapeId` may carry: its targetClass and every registered sub-shape's. */
|
|
2306
|
+
function shapeClasses(shapeId) {
|
|
2307
|
+
var _a;
|
|
2308
|
+
const nodeShape = shapeById(shapeId);
|
|
2309
|
+
const classes = new Set();
|
|
2310
|
+
for (const shape of nodeShape ? [nodeShape, ...getSubShapes(shapeId)] : []) {
|
|
2311
|
+
if ((_a = shape.targetClass) === null || _a === void 0 ? void 0 : _a.id)
|
|
2312
|
+
classes.add(shape.targetClass.id);
|
|
2313
|
+
}
|
|
2314
|
+
return [...classes];
|
|
2315
|
+
}
|
|
2316
|
+
let ownedWalkCache = new Map();
|
|
2317
|
+
let ownedWalkCacheVersion = -1;
|
|
2318
|
+
const warnedBranches = new Set();
|
|
2319
|
+
/**
|
|
2320
|
+
* The owned subtree of a root shape, as hops (memoized per registry version; read-only).
|
|
2394
2321
|
*
|
|
2395
|
-
*
|
|
2396
|
-
*
|
|
2397
|
-
*
|
|
2398
|
-
*
|
|
2322
|
+
* One target per path from the root. A self-loop (`S` owns `S` through an unguarded edge) is a
|
|
2323
|
+
* closure, exact because every such edge from an `S` node is `S`'s own. Every other edge is an
|
|
2324
|
+
* explicit hop. A hop is typed when it is a sub-shape's (its class) or when it stays inside a
|
|
2325
|
+
* cycle of several shapes (the declaring shape's classes), and a path stops after
|
|
2326
|
+
* `MAX_OWNED_RECURSION` hops into a shape already on it.
|
|
2399
2327
|
*/
|
|
2400
|
-
|
|
2401
|
-
const
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2328
|
+
function ownedWalk(rootShapeId) {
|
|
2329
|
+
const version = getRegistryVersion();
|
|
2330
|
+
if (version !== ownedWalkCacheVersion) {
|
|
2331
|
+
ownedWalkCache = new Map();
|
|
2332
|
+
ownedWalkCacheVersion = version;
|
|
2333
|
+
}
|
|
2334
|
+
const cached = ownedWalkCache.get(rootShapeId);
|
|
2335
|
+
if (cached)
|
|
2336
|
+
return cached;
|
|
2337
|
+
const edgesOf = new Map();
|
|
2338
|
+
const pending = [rootShapeId];
|
|
2339
|
+
while (pending.length > 0) {
|
|
2340
|
+
const id = pending.pop();
|
|
2341
|
+
if (edgesOf.has(id))
|
|
2342
|
+
continue;
|
|
2343
|
+
const edges = ownedEdges(id);
|
|
2344
|
+
edgesOf.set(id, edges);
|
|
2345
|
+
for (const edge of edges)
|
|
2346
|
+
if (!edgesOf.has(edge.target))
|
|
2347
|
+
pending.push(edge.target);
|
|
2348
|
+
}
|
|
2349
|
+
const isSelfLoop = (from, e) => !e.guard && e.target === from;
|
|
2350
|
+
const closureOf = (id) => [
|
|
2351
|
+
...new Set(edgesOf.get(id).filter((e) => isSelfLoop(id, e)).map((e) => e.predicate)),
|
|
2352
|
+
];
|
|
2353
|
+
// Tarjan's strongly connected components, over the edges that are not closures.
|
|
2354
|
+
const index = new Map();
|
|
2355
|
+
const low = new Map();
|
|
2356
|
+
const onStack = new Set();
|
|
2357
|
+
const stack = [];
|
|
2358
|
+
const groupOf = new Map();
|
|
2359
|
+
const groupSize = [];
|
|
2360
|
+
const strong = (v) => {
|
|
2361
|
+
index.set(v, index.size);
|
|
2362
|
+
low.set(v, index.get(v));
|
|
2363
|
+
stack.push(v);
|
|
2364
|
+
onStack.add(v);
|
|
2365
|
+
for (const e of edgesOf.get(v)) {
|
|
2366
|
+
if (isSelfLoop(v, e))
|
|
2367
|
+
continue;
|
|
2368
|
+
const w = e.target;
|
|
2369
|
+
if (!index.has(w)) {
|
|
2370
|
+
strong(w);
|
|
2371
|
+
low.set(v, Math.min(low.get(v), low.get(w)));
|
|
2372
|
+
}
|
|
2373
|
+
else if (onStack.has(w)) {
|
|
2374
|
+
low.set(v, Math.min(low.get(v), index.get(w)));
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
if (low.get(v) !== index.get(v))
|
|
2378
|
+
return;
|
|
2379
|
+
let w;
|
|
2380
|
+
let size = 0;
|
|
2381
|
+
do {
|
|
2382
|
+
w = stack.pop();
|
|
2383
|
+
onStack.delete(w);
|
|
2384
|
+
groupOf.set(w, groupSize.length);
|
|
2385
|
+
size++;
|
|
2386
|
+
} while (w !== v);
|
|
2387
|
+
groupSize.push(size);
|
|
2388
|
+
};
|
|
2389
|
+
strong(rootShapeId);
|
|
2390
|
+
// A non-closure edge back to its own shape (a sub-shape's guarded edge) counts as a cycle too.
|
|
2391
|
+
const inCycle = (from, e) => e.target === from || (groupOf.get(from) === groupOf.get(e.target) && groupSize[groupOf.get(from)] > 1);
|
|
2392
|
+
const targets = [];
|
|
2393
|
+
const visit = (shape, hops, path, repeats) => {
|
|
2394
|
+
for (const edge of edgesOf.get(shape)) {
|
|
2395
|
+
if (isSelfLoop(shape, edge))
|
|
2396
|
+
continue;
|
|
2397
|
+
const repeat = path.includes(edge.target) ? 1 : 0;
|
|
2398
|
+
if (repeats + repeat > MAX_OWNED_RECURSION)
|
|
2399
|
+
continue;
|
|
2400
|
+
const typeClasses = edge.guard
|
|
2401
|
+
? [edge.guard]
|
|
2402
|
+
: inCycle(shape, edge)
|
|
2403
|
+
? shapeClasses(shape)
|
|
2404
|
+
: undefined;
|
|
2405
|
+
const hop = Object.assign(Object.assign({}, ((typeClasses === null || typeClasses === void 0 ? void 0 : typeClasses.length) ? { typeClasses } : {})), { predicate: edge.predicate, closure: closureOf(edge.target) });
|
|
2406
|
+
const next = [...hops, hop];
|
|
2407
|
+
targets.push({ hops: next, blankProps: blankCapableProperties(edge.target) });
|
|
2408
|
+
visit(edge.target, next, [...path, edge.target], repeats + repeat);
|
|
2409
|
+
}
|
|
2410
|
+
};
|
|
2411
|
+
visit(rootShapeId, [], [rootShapeId], 0);
|
|
2412
|
+
if (targets.length > OWNED_BRANCH_WARNING && !warnedBranches.has(rootShapeId)) {
|
|
2413
|
+
warnedBranches.add(rootShapeId);
|
|
2414
|
+
console.warn(`[linked] Deleting a '${rootShapeId}' walks ${targets.length} owned paths (more than ` +
|
|
2415
|
+
`${OWNED_BRANCH_WARNING}); each is one UNION branch of the delete.`);
|
|
2416
|
+
}
|
|
2417
|
+
const walk = {
|
|
2418
|
+
rootClosure: closureOf(rootShapeId),
|
|
2419
|
+
rootBlankProps: blankCapableProperties(rootShapeId),
|
|
2420
|
+
targets,
|
|
2421
|
+
};
|
|
2422
|
+
ownedWalkCache.set(rootShapeId, walk);
|
|
2423
|
+
return walk;
|
|
2424
|
+
}
|
|
2425
|
+
/** `FILTER(!sameTerm(?n, rdf:nil))`: the walk never deletes `rdf:nil`, which every list ends in. */
|
|
2426
|
+
function notNil(term) {
|
|
2427
|
+
if (term.kind !== 'variable')
|
|
2428
|
+
return null;
|
|
2429
|
+
return {
|
|
2430
|
+
kind: 'not_expr',
|
|
2431
|
+
inner: {
|
|
2432
|
+
kind: 'function_expr',
|
|
2433
|
+
name: 'sameTerm',
|
|
2434
|
+
args: [{ kind: 'variable_expr', name: term.name }, { kind: 'iri_expr', value: RDF_NIL }],
|
|
2435
|
+
},
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
/** `inner` filtered by every expression in `filters` (unchanged when there are none). */
|
|
2439
|
+
function filtered(inner, filters) {
|
|
2440
|
+
if (filters.length === 0)
|
|
2441
|
+
return inner;
|
|
2442
|
+
return {
|
|
2443
|
+
type: 'filter',
|
|
2444
|
+
expression: filters.length === 1 ? filters[0] : { kind: 'logical_expr', op: 'and', exprs: filters },
|
|
2445
|
+
inner,
|
|
2419
2446
|
};
|
|
2447
|
+
}
|
|
2448
|
+
/**
|
|
2449
|
+
* DELETE patterns and UNION branches that delete the owned subtree below `rootTerm`, a node of
|
|
2450
|
+
* `rootShapeId` (see `ownedWalk`). The root itself is the caller's: the subject wildcard of a
|
|
2451
|
+
* delete, or `buildOwnedSelfDelete` for a replaced value. Each branch deletes one node per
|
|
2452
|
+
* solution, its own triples and its blank values. Schematically (variable names simplified):
|
|
2453
|
+
*
|
|
2454
|
+
* ```sparql
|
|
2455
|
+
* …anchor… <root> (r…)* ?w . # the root shape's self-loop, if any
|
|
2456
|
+
* ?w a <SubClass> . # a typed hop only
|
|
2457
|
+
* ?w <c1> ?x0e . ?x0e (s…)* ?x0 . # closure only for a self-owning target shape
|
|
2458
|
+
* ?x0 ?x0p ?x0o . FILTER(!sameTerm(?x0, rdf:nil))
|
|
2459
|
+
* ```
|
|
2460
|
+
*
|
|
2461
|
+
* `anchor` leads every branch: the owning edge that binds a variable root (`<s> <p> ?old`), or a
|
|
2462
|
+
* bulk delete's type triple, so a branch evaluated on its own never runs with an unbound root.
|
|
2463
|
+
* `keep` filters every branch on the root (`FILTER(?old NOT IN (…))`).
|
|
2464
|
+
*/
|
|
2465
|
+
function ownedSubtreeBlocks(rootShapeId, rootTerm, anchor, varPrefix, keep) {
|
|
2466
|
+
const walk = ownedWalk(rootShapeId);
|
|
2420
2467
|
const deletePatterns = [];
|
|
2421
|
-
const
|
|
2422
|
-
|
|
2423
|
-
const
|
|
2424
|
-
const
|
|
2425
|
-
const
|
|
2426
|
-
deletePatterns.push(
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
triples: [
|
|
2430
|
-
...(anchor ? [anchor] : []),
|
|
2431
|
-
tripleOf(rootTerm, pathTerm, owned),
|
|
2432
|
-
tripleOf(owned, iriTerm(RDF_TYPE), iriTerm(typeId)),
|
|
2433
|
-
tripleOf(owned, p, o),
|
|
2434
|
-
],
|
|
2435
|
-
}, rootTerm, keep));
|
|
2436
|
-
});
|
|
2437
|
-
// The blank values of every dependent node deleted above, through the blank-capable
|
|
2438
|
-
// properties of its type's shapes (a property shape's `sh:path`). Blank nodes carry no type,
|
|
2439
|
-
// so the typed blocks cannot see them; see `buildBlankCascade`.
|
|
2440
|
-
dependentTypes.forEach((typeId, i) => {
|
|
2441
|
-
const props = containment.dependentBlankProps.get(typeId);
|
|
2442
|
-
if (!props)
|
|
2468
|
+
const whereBlocks = [];
|
|
2469
|
+
const emit = (reach, filters, node, blankProps, prefix) => {
|
|
2470
|
+
const nil = notNil(node);
|
|
2471
|
+
const all = nil ? [...filters, nil] : filters;
|
|
2472
|
+
const own = tripleOf(node, varTerm(`${prefix}p`), varTerm(`${prefix}o`));
|
|
2473
|
+
deletePatterns.push(own);
|
|
2474
|
+
whereBlocks.push(keepFilter(filtered({ type: 'bgp', triples: [...anchor, ...reach, own] }, all), rootTerm, keep));
|
|
2475
|
+
if (blankProps.length === 0)
|
|
2443
2476
|
return;
|
|
2444
|
-
const
|
|
2445
|
-
|
|
2446
|
-
...(anchor ? [anchor] : []),
|
|
2447
|
-
tripleOf(rootTerm, pathTerm, owner),
|
|
2448
|
-
tripleOf(owner, iriTerm(RDF_TYPE), iriTerm(typeId)),
|
|
2449
|
-
], owner, simplePredicates(props), blankWalkPredicates(props), `${varPrefix}b${i}_`);
|
|
2450
|
-
if (!cascade)
|
|
2477
|
+
const blank = buildBlankCascade([...anchor, ...reach], node, simplePredicates(blankProps), blankWalkPredicates(blankProps), `${prefix}b_`);
|
|
2478
|
+
if (!blank)
|
|
2451
2479
|
return;
|
|
2452
|
-
deletePatterns.push(...
|
|
2453
|
-
|
|
2480
|
+
deletePatterns.push(...blank.deletePatterns);
|
|
2481
|
+
whereBlocks.push(keepFilter(filtered(blank.whereBlock, all), rootTerm, keep));
|
|
2482
|
+
};
|
|
2483
|
+
let base = rootTerm;
|
|
2484
|
+
const baseReach = [];
|
|
2485
|
+
if (walk.rootClosure.length > 0) {
|
|
2486
|
+
// The other nodes of the root's own kind (the tail cells of a list).
|
|
2487
|
+
const others = varTerm(`${varPrefix}r`);
|
|
2488
|
+
emit([tripleOf(rootTerm, predicatePathTerm(walk.rootClosure, 'oneOrMore'), others)], [], others, walk.rootBlankProps, `${varPrefix}r`);
|
|
2489
|
+
if (walk.targets.length > 0) {
|
|
2490
|
+
base = varTerm(`${varPrefix}w`);
|
|
2491
|
+
baseReach.push(tripleOf(rootTerm, predicatePathTerm(walk.rootClosure, 'zeroOrMore'), base));
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
walk.targets.forEach((target, t) => {
|
|
2495
|
+
const reach = [...baseReach];
|
|
2496
|
+
const filters = [];
|
|
2497
|
+
let at = base;
|
|
2498
|
+
target.hops.forEach((hop, h) => {
|
|
2499
|
+
var _a;
|
|
2500
|
+
const name = `${varPrefix}${t}_${h}`;
|
|
2501
|
+
if (((_a = hop.typeClasses) === null || _a === void 0 ? void 0 : _a.length) === 1) {
|
|
2502
|
+
reach.push(tripleOf(at, iriTerm(RDF_TYPE), iriTerm(hop.typeClasses[0])));
|
|
2503
|
+
}
|
|
2504
|
+
else if (hop.typeClasses) {
|
|
2505
|
+
reach.push(tripleOf(at, iriTerm(RDF_TYPE), varTerm(`${name}t`)));
|
|
2506
|
+
filters.push({
|
|
2507
|
+
kind: 'in_expr',
|
|
2508
|
+
negated: false,
|
|
2509
|
+
value: { kind: 'variable_expr', name: `${name}t` },
|
|
2510
|
+
list: hop.typeClasses.map((value) => ({ kind: 'iri_expr', value })),
|
|
2511
|
+
});
|
|
2512
|
+
}
|
|
2513
|
+
const end = varTerm(hop.closure.length > 0 ? `${name}e` : name);
|
|
2514
|
+
reach.push(tripleOf(at, iriTerm(hop.predicate), end));
|
|
2515
|
+
if (hop.closure.length > 0) {
|
|
2516
|
+
reach.push(tripleOf(end, predicatePathTerm(hop.closure, 'zeroOrMore'), varTerm(name)));
|
|
2517
|
+
}
|
|
2518
|
+
at = varTerm(name);
|
|
2519
|
+
});
|
|
2520
|
+
emit(reach, filters, at, target.blankProps, `${varPrefix}${t}_`);
|
|
2454
2521
|
});
|
|
2455
|
-
return { deletePatterns,
|
|
2522
|
+
return { deletePatterns, whereBlocks };
|
|
2456
2523
|
}
|
|
2457
2524
|
/**
|
|
2458
2525
|
* Wrap an owned-cleanup block in `FILTER(?old NOT IN (<k1>, …))` so a value the mutation
|
|
@@ -2480,13 +2547,8 @@ function keepFilter(block, oldTerm, keep) {
|
|
|
2480
2547
|
* Build DELETE + WHERE OPTIONAL that removes the *old node's own* one-hop triples when a
|
|
2481
2548
|
* `contains` edge is replaced, unset, or set-removed. A `contains` edge asserts exclusive
|
|
2482
2549
|
* ownership, so overwriting it must delete the previously owned child fully — not merely
|
|
2483
|
-
* unlink it — or the child orphans (backlog 032).
|
|
2484
|
-
*
|
|
2485
|
-
* Owned descendants *below* the old node are cascaded separately by `buildOwnedCascade`.
|
|
2486
|
-
*
|
|
2487
|
-
* Callers use it only for a property that declares a value shape. A shape-less `contains`
|
|
2488
|
-
* slot (like `sh:path`) may hold a shared IRI, so its old value is left to
|
|
2489
|
-
* `buildOwnedCascade`, which deletes it only when it is dependent-typed.
|
|
2550
|
+
* unlink it — or the child orphans (backlog 032). Ownership via the edge is sufficient.
|
|
2551
|
+
* Owned descendants *below* the old node are deleted separately (`ownedSubtreeBlocks`).
|
|
2490
2552
|
*
|
|
2491
2553
|
* The owning edge `<subject> <prop> ?old` is re-asserted INSIDE the WHERE group so `?old`
|
|
2492
2554
|
* is bound there: when no old value exists the group matches nothing and the DELETE is a
|
|
@@ -2502,13 +2564,11 @@ export function buildOwnedSelfDelete(subjectTerm, propertyTerm, oldTerm, varPref
|
|
|
2502
2564
|
const p = varTerm(`${varPrefix}sp`);
|
|
2503
2565
|
const o = varTerm(`${varPrefix}so`);
|
|
2504
2566
|
const wildcard = tripleOf(oldTerm, p, o);
|
|
2567
|
+
const nil = notNil(oldTerm);
|
|
2505
2568
|
return {
|
|
2506
2569
|
deletePatterns: [wildcard],
|
|
2507
2570
|
whereOptionals: [
|
|
2508
|
-
keepFilter({
|
|
2509
|
-
type: 'bgp',
|
|
2510
|
-
triples: [tripleOf(subjectTerm, propertyTerm, oldTerm), wildcard],
|
|
2511
|
-
}, oldTerm, keep),
|
|
2571
|
+
keepFilter(filtered({ type: 'bgp', triples: [tripleOf(subjectTerm, propertyTerm, oldTerm), wildcard] }, nil ? [nil] : []), oldTerm, keep),
|
|
2512
2572
|
],
|
|
2513
2573
|
};
|
|
2514
2574
|
}
|
|
@@ -2516,19 +2576,18 @@ export function buildOwnedSelfDelete(subjectTerm, propertyTerm, oldTerm, varPref
|
|
|
2516
2576
|
* Converts an IRDeleteMutation to a SparqlDeleteInsertPlan (DELETE + WHERE).
|
|
2517
2577
|
*
|
|
2518
2578
|
* The WHERE is the type guard of every id, joined with ONE UNION of independent
|
|
2519
|
-
* blocks: per id its own triples, its incoming references
|
|
2520
|
-
*
|
|
2521
|
-
*
|
|
2522
|
-
*
|
|
2523
|
-
* (`reachableDependentTypes`; none for a shape that owns only non-dependent values). The
|
|
2579
|
+
* blocks: per id its own triples, its incoming references, its blank values and one branch
|
|
2580
|
+
* per owned path, plus that node's blank values (`ownedSubtreeBlocks`: the shape's own and
|
|
2581
|
+
* inherited `contains` properties, their value shapes' in turn, and the extra ones of
|
|
2582
|
+
* registered sub-shapes behind a type guard; none for a shape that owns nothing). The
|
|
2524
2583
|
* bulk deletes (`deleteAllToAlgebra`, `deleteWhereToAlgebra`) add the same owned blocks,
|
|
2525
2584
|
* rooted at the subject variable (see `bulkDeleteUnion`).
|
|
2526
2585
|
*
|
|
2527
2586
|
* The blocks must not be chained as OPTIONALs. A chain of OPTIONALs is a join, so
|
|
2528
2587
|
* the solutions multiply: own triples x incoming references x owned triples of each
|
|
2529
|
-
*
|
|
2588
|
+
* owned path. Deleting one shape description with the shape sync's cascade
|
|
2530
2589
|
* produced ~27,000 solutions for ~130 triples and took over 30 seconds on Fuseki,
|
|
2531
|
-
* which re-
|
|
2590
|
+
* which re-evaluated the former registry-wide cascade path for every left-hand row. A UNION
|
|
2532
2591
|
* yields each block's matches once, so the solutions add up instead, and it deletes
|
|
2533
2592
|
* exactly the same triples: a DELETE template triple whose variables a solution
|
|
2534
2593
|
* leaves unbound is skipped, as it was for an OPTIONAL that did not match.
|
|
@@ -2540,9 +2599,6 @@ export function deleteToAlgebra(query, _options) {
|
|
|
2540
2599
|
const deletePatterns = [];
|
|
2541
2600
|
const guards = [];
|
|
2542
2601
|
const blocks = [];
|
|
2543
|
-
const hasContains = shapeHasContainsProperty(query.shape);
|
|
2544
|
-
const ownedChildPredicates = hasContains ? ownedValueShapePredicates(query.shape) : [];
|
|
2545
|
-
const cascadeTypes = hasContains ? reachableDependentTypes(query.shape) : [];
|
|
2546
2602
|
for (let i = 0; i < query.ids.length; i++) {
|
|
2547
2603
|
const subjectTerm = iriTerm(query.ids[i].id);
|
|
2548
2604
|
const idx = query.ids.length > 1 ? `_${i}` : '';
|
|
@@ -2555,23 +2611,6 @@ export function deleteToAlgebra(query, _options) {
|
|
|
2555
2611
|
// incoming references (the entity may have none) are separate UNION blocks.
|
|
2556
2612
|
guards.push(typeGuard);
|
|
2557
2613
|
blocks.push({ type: 'bgp', triples: [subjWild] }, { type: 'bgp', triples: [objWild] });
|
|
2558
|
-
// The immediately-owned children: for every `contains` property with a value shape,
|
|
2559
|
-
// the child's own one-hop triples, as a replace or a set-remove deletes them. No type
|
|
2560
|
-
// guard and no reference check: the `contains` edge alone asserts ownership.
|
|
2561
|
-
ownedChildPredicates.forEach((predicate, n) => {
|
|
2562
|
-
const child = varTerm(`d${n}${idx}`);
|
|
2563
|
-
const childOwn = tripleOf(child, varTerm(`d${n}${idx}_p`), varTerm(`d${n}${idx}_o`));
|
|
2564
|
-
deletePatterns.push(childOwn);
|
|
2565
|
-
blocks.push({
|
|
2566
|
-
type: 'bgp',
|
|
2567
|
-
triples: [tripleOf(subjectTerm, iriTerm(predicate), child), childOwn],
|
|
2568
|
-
});
|
|
2569
|
-
// …and the child's own blank values.
|
|
2570
|
-
for (const blank of ownedChildBlankCascade(query.shape, predicate, child, [tripleOf(subjectTerm, iriTerm(predicate), child)], `d${n}${idx}_b`)) {
|
|
2571
|
-
deletePatterns.push(...blank.deletePatterns);
|
|
2572
|
-
blocks.push(blank.whereBlock);
|
|
2573
|
-
}
|
|
2574
|
-
});
|
|
2575
2614
|
// The subject's own blank values (through its blank-capable properties) and the blank
|
|
2576
2615
|
// nodes below them.
|
|
2577
2616
|
const blank = subjectBlankCascade(query.shape, subjectTerm, [], `b${idx}_`);
|
|
@@ -2579,13 +2618,10 @@ export function deleteToAlgebra(query, _options) {
|
|
|
2579
2618
|
deletePatterns.push(...blank.deletePatterns);
|
|
2580
2619
|
blocks.push(blank.whereBlock);
|
|
2581
2620
|
}
|
|
2582
|
-
//
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
deletePatterns.push(...cascade.deletePatterns);
|
|
2587
|
-
blocks.push(...cascade.whereOptionals);
|
|
2588
|
-
}
|
|
2621
|
+
// The owned subtree: every node the subject's `contains` properties reach (`ownedWalk`).
|
|
2622
|
+
const owned = ownedSubtreeBlocks(query.shape, subjectTerm, [], `c${idx}_`);
|
|
2623
|
+
deletePatterns.push(...owned.deletePatterns);
|
|
2624
|
+
blocks.push(...owned.whereBlocks);
|
|
2589
2625
|
}
|
|
2590
2626
|
const whereAlgebra = {
|
|
2591
2627
|
type: 'join',
|
|
@@ -2605,12 +2641,10 @@ export function deleteToAlgebra(query, _options) {
|
|
|
2605
2641
|
* clean up (the root wildcard then stays in the required BGP).
|
|
2606
2642
|
*
|
|
2607
2643
|
* - The blank-node cascade of the shape's blank-capable properties (`subjectBlankCascade`).
|
|
2608
|
-
* -
|
|
2609
|
-
*
|
|
2610
|
-
*
|
|
2611
|
-
*
|
|
2612
|
-
* is evaluated on its own from running `?a0 (c…)* ?own` with both ends unbound, which
|
|
2613
|
-
* matches every node in the graph.
|
|
2644
|
+
* - The same owned subtree as a delete by id (`ownedSubtreeBlocks`), rooted at the subject
|
|
2645
|
+
* variable. Each owned block is led by the subject's type triple. With a linear join it is
|
|
2646
|
+
* redundant; it keeps a branch that is evaluated on its own from running `?a0 <c> ?x` with
|
|
2647
|
+
* both ends unbound, which matches every edge in the graph.
|
|
2614
2648
|
*/
|
|
2615
2649
|
function bulkDeleteUnion(shapeId, subjectVar, rootWildcard, typeTriple, deletePatterns) {
|
|
2616
2650
|
const blocks = [];
|
|
@@ -2619,28 +2653,9 @@ function bulkDeleteUnion(shapeId, subjectVar, rootWildcard, typeTriple, deletePa
|
|
|
2619
2653
|
deletePatterns.push(...blank.deletePatterns);
|
|
2620
2654
|
blocks.push(blank.whereBlock);
|
|
2621
2655
|
}
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
const child = varTerm(`d${n}`);
|
|
2626
|
-
const childOwn = tripleOf(child, varTerm(`d${n}_p`), varTerm(`d${n}_o`));
|
|
2627
|
-
deletePatterns.push(childOwn);
|
|
2628
|
-
blocks.push({
|
|
2629
|
-
type: 'bgp',
|
|
2630
|
-
triples: [typeTriple, tripleOf(subject, iriTerm(predicate), child), childOwn],
|
|
2631
|
-
});
|
|
2632
|
-
for (const blank of ownedChildBlankCascade(shapeId, predicate, child, [typeTriple, tripleOf(subject, iriTerm(predicate), child)], `d${n}_b`)) {
|
|
2633
|
-
deletePatterns.push(...blank.deletePatterns);
|
|
2634
|
-
blocks.push(blank.whereBlock);
|
|
2635
|
-
}
|
|
2636
|
-
});
|
|
2637
|
-
const cascadeTypes = reachableDependentTypes(shapeId);
|
|
2638
|
-
if (cascadeTypes.length > 0) {
|
|
2639
|
-
const cascade = buildOwnedCascade(subject, 'c_', typeTriple, undefined, cascadeTypes);
|
|
2640
|
-
deletePatterns.push(...cascade.deletePatterns);
|
|
2641
|
-
blocks.push(...cascade.whereOptionals);
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2656
|
+
const owned = ownedSubtreeBlocks(shapeId, varTerm(subjectVar), [typeTriple], 'c_');
|
|
2657
|
+
deletePatterns.push(...owned.deletePatterns);
|
|
2658
|
+
blocks.push(...owned.whereBlocks);
|
|
2644
2659
|
if (blocks.length === 0)
|
|
2645
2660
|
return null;
|
|
2646
2661
|
return unionOf([{ type: 'bgp', triples: [rootWildcard] }, ...blocks]);
|
|
@@ -2650,7 +2665,7 @@ function bulkDeleteUnion(shapeId, subjectVar, rootWildcard, typeTriple, deletePa
|
|
|
2650
2665
|
*
|
|
2651
2666
|
* Generates DELETE { ?a0 ?p ?o . [blank-node and owned-cleanup wildcards] }
|
|
2652
2667
|
* WHERE { ?a0 a <Shape> . { ?a0 ?p ?o } UNION [one block per blank node,
|
|
2653
|
-
* owned
|
|
2668
|
+
* owned group] }
|
|
2654
2669
|
* (see `bulkDeleteUnion`). Unlike a delete by id, incoming references are not deleted.
|
|
2655
2670
|
*/
|
|
2656
2671
|
export function deleteAllToAlgebra(query, _options) {
|
|
@@ -2677,7 +2692,7 @@ export function deleteAllToAlgebra(query, _options) {
|
|
|
2677
2692
|
* Converts an IRDeleteWhereMutation to a SparqlDeleteInsertPlan.
|
|
2678
2693
|
*
|
|
2679
2694
|
* Like deleteAllToAlgebra but adds filter conditions from the where clause. The cleanup
|
|
2680
|
-
* UNION (blank nodes, owned
|
|
2695
|
+
* UNION (blank nodes, owned subtree) is joined with the filtered match,
|
|
2681
2696
|
* so only the matched subjects' children are deleted.
|
|
2682
2697
|
*/
|
|
2683
2698
|
export function deleteWhereToAlgebra(query, _options) {
|