@_linked/core 2.28.1 → 2.28.3
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.
|
@@ -1641,7 +1641,10 @@ function processUpdateFields(data, subjectTerm, options) {
|
|
|
1641
1641
|
const self = buildOwnedSelfDelete(subjectTerm, propertyTerm, oldTerm, `uc${n}_`);
|
|
1642
1642
|
deletePatterns.push(...self.deletePatterns);
|
|
1643
1643
|
cascadeOptionals.push(...self.whereOptionals);
|
|
1644
|
-
|
|
1644
|
+
// Every cascade block binds `?old` itself through the owning edge, like the
|
|
1645
|
+
// self-delete block does. Without it an absent old value leaves `?old` unbound and
|
|
1646
|
+
// the block matches the owned subtree of EVERY node in the graph.
|
|
1647
|
+
const cascade = buildOwnedCascade(oldTerm, `uc${n}_`, tripleOf(subjectTerm, propertyTerm, oldTerm));
|
|
1645
1648
|
deletePatterns.push(...cascade.deletePatterns);
|
|
1646
1649
|
cascadeOptionals.push(...cascade.whereOptionals);
|
|
1647
1650
|
});
|
|
@@ -1699,21 +1702,80 @@ export function upsertToAlgebra(query, options) {
|
|
|
1699
1702
|
* update path has always emitted.
|
|
1700
1703
|
*/
|
|
1701
1704
|
function buildDeleteInsertPlan(query, options, opts) {
|
|
1702
|
-
var _a, _b, _c;
|
|
1703
1705
|
const subjectTerm = iriTerm(query.id);
|
|
1704
1706
|
const result = processUpdateFields(query.data, subjectTerm, options);
|
|
1707
|
+
const whereAlgebra = buildUpdateWhere({ type: 'bgp', triples: [] }, result, query.traversalPatterns, subjectTerm);
|
|
1708
|
+
// The type triple leads the INSERT, mirroring `create`'s triple order.
|
|
1709
|
+
const insertPatterns = (opts === null || opts === void 0 ? void 0 : opts.ensureType)
|
|
1710
|
+
? [
|
|
1711
|
+
tripleOf(subjectTerm, iriTerm(RDF_TYPE), iriTerm(opts.ensureType)),
|
|
1712
|
+
...result.insertPatterns,
|
|
1713
|
+
]
|
|
1714
|
+
: result.insertPatterns;
|
|
1715
|
+
return {
|
|
1716
|
+
type: 'delete_insert',
|
|
1717
|
+
deletePatterns: result.deletePatterns,
|
|
1718
|
+
insertPatterns,
|
|
1719
|
+
whereAlgebra,
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
/** Left-nested UNION of `blocks` (at least one). */
|
|
1723
|
+
function unionOf(blocks) {
|
|
1724
|
+
let union = blocks[0];
|
|
1725
|
+
for (let i = 1; i < blocks.length; i++) {
|
|
1726
|
+
union = { type: 'union', left: union, right: blocks[i] };
|
|
1727
|
+
}
|
|
1728
|
+
return union;
|
|
1729
|
+
}
|
|
1730
|
+
/** Every variable name an expression mentions, including inside EXISTS patterns. */
|
|
1731
|
+
function collectVariableNames(value, into) {
|
|
1732
|
+
if (Array.isArray(value)) {
|
|
1733
|
+
for (const item of value)
|
|
1734
|
+
collectVariableNames(item, into);
|
|
1735
|
+
}
|
|
1736
|
+
else if (value && typeof value === 'object') {
|
|
1737
|
+
const node = value;
|
|
1738
|
+
if ((node.kind === 'variable_expr' || node.kind === 'variable') && typeof node.name === 'string') {
|
|
1739
|
+
into.add(node.name);
|
|
1740
|
+
}
|
|
1741
|
+
if (node.kind === 'bound_expr' && typeof node.variable === 'string') {
|
|
1742
|
+
into.add(node.variable);
|
|
1743
|
+
}
|
|
1744
|
+
for (const child of Object.values(value))
|
|
1745
|
+
collectVariableNames(child, into);
|
|
1746
|
+
}
|
|
1747
|
+
return into;
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* The WHERE shared by `update`, `upsert` and `updateWhere`, on top of `base` (empty for
|
|
1751
|
+
* an id-targeted update; the type guard and filter for an update-where).
|
|
1752
|
+
*
|
|
1753
|
+
* The old values and owned-cleanup blocks are independent of each other, so they are
|
|
1754
|
+
* joined as ONE OPTIONAL over a UNION, never as a chain of OPTIONALs. A chain is a join:
|
|
1755
|
+
* the old values of two multi-valued properties, or an owned node's own triples and its
|
|
1756
|
+
* owned subtree, multiply into one solution per combination. A UNION yields each block's
|
|
1757
|
+
* matches once, so the solutions add up, and the DELETE removes exactly the same triples
|
|
1758
|
+
* (a template triple a solution leaves unbound is skipped). The OPTIONAL keeps one
|
|
1759
|
+
* solution when nothing matches, so the INSERT still happens.
|
|
1760
|
+
*
|
|
1761
|
+
* Old values that a computed field's BIND reads stay in `base` as before, together with
|
|
1762
|
+
* the traversals they need, because the BIND needs them bound in the same solution. With
|
|
1763
|
+
* a single independent block the output is the one OPTIONAL it always was.
|
|
1764
|
+
*/
|
|
1765
|
+
function buildUpdateWhere(base, result, traversalPatterns, subjectTerm) {
|
|
1766
|
+
var _a, _b;
|
|
1705
1767
|
// Partition old-value/optional triples into subject-anchored ones and those
|
|
1706
1768
|
// anchored on a traversal target variable (e.g. `?a1 <name> ?a1_name` from a
|
|
1707
1769
|
// `p.bestFriend.name.ucase()` expression). Traversal-anchored triples MUST be
|
|
1708
1770
|
// nested inside the same OPTIONAL group as their traversal edge — otherwise,
|
|
1709
1771
|
// when the subject has no such edge, the leaf variable is unbound and the
|
|
1710
1772
|
// property triple matches every entity in the graph (data-corruption bug).
|
|
1711
|
-
const travTos = new Set((
|
|
1773
|
+
const travTos = new Set((traversalPatterns !== null && traversalPatterns !== void 0 ? traversalPatterns : []).map((t) => t.to));
|
|
1712
1774
|
const travAnchoredByTo = new Map();
|
|
1713
1775
|
const subjectAnchored = [];
|
|
1714
1776
|
for (const triple of result.oldValueTriples) {
|
|
1715
1777
|
if (triple.subject.kind === 'variable' && travTos.has(triple.subject.name)) {
|
|
1716
|
-
const list = (
|
|
1778
|
+
const list = (_a = travAnchoredByTo.get(triple.subject.name)) !== null && _a !== void 0 ? _a : [];
|
|
1717
1779
|
list.push(triple);
|
|
1718
1780
|
travAnchoredByTo.set(triple.subject.name, list);
|
|
1719
1781
|
}
|
|
@@ -1721,36 +1783,50 @@ function buildDeleteInsertPlan(query, options, opts) {
|
|
|
1721
1783
|
subjectAnchored.push(triple);
|
|
1722
1784
|
}
|
|
1723
1785
|
}
|
|
1724
|
-
|
|
1786
|
+
// Old values a BIND reads must share its solution; the rest are independent blocks.
|
|
1787
|
+
const readByBinds = new Set();
|
|
1788
|
+
for (const ext of result.extends)
|
|
1789
|
+
collectVariableNames(ext.expression, readByBinds);
|
|
1790
|
+
const correlated = [];
|
|
1791
|
+
const independent = [];
|
|
1792
|
+
for (const triple of subjectAnchored) {
|
|
1793
|
+
const vars = collectVariableNames([triple.subject, triple.predicate, triple.object], new Set());
|
|
1794
|
+
if ([...vars].some((v) => readByBinds.has(v))) {
|
|
1795
|
+
correlated.push(triple);
|
|
1796
|
+
}
|
|
1797
|
+
else {
|
|
1798
|
+
independent.push({ type: 'bgp', triples: [triple] });
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
let whereAlgebra = wrapOldValueOptionals(base, correlated);
|
|
1725
1802
|
// Add traversal OPTIONAL patterns (for multi-segment expression refs). The
|
|
1726
1803
|
// traversal edge and its dependent leaf property triples share one OPTIONAL
|
|
1727
1804
|
// group so the leaf variable is scoped to the traversal target. These come
|
|
1728
1805
|
// BEFORE expression BINDs since the BINDs reference the traversal variables.
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
};
|
|
1747
|
-
}
|
|
1748
|
-
whereAlgebra = { type: 'left_join', left: whereAlgebra, right: travNode };
|
|
1806
|
+
for (const trav of traversalPatterns !== null && traversalPatterns !== void 0 ? traversalPatterns : []) {
|
|
1807
|
+
const fromTerm = trav.from === '__mutation_subject__' ? subjectTerm : varTerm(trav.from);
|
|
1808
|
+
const traversalTriple = tripleOf(fromTerm,
|
|
1809
|
+
// The declared `sh:path`, like every other predicate — not the property
|
|
1810
|
+
// shape's own IRI, which identifies the description of the property.
|
|
1811
|
+
resolvePropertyPredicateTerm(trav.property), varTerm(trav.to));
|
|
1812
|
+
// The traversal edge binds the target var; each dependent leaf property is
|
|
1813
|
+
// a nested OPTIONAL *within* that scope, so a missing optional property
|
|
1814
|
+
// (e.g. a bestFriend with no hobby) doesn't drop the whole group, while an
|
|
1815
|
+
// absent edge still leaves every leaf var unbound (no cross-entity match).
|
|
1816
|
+
let travNode = { type: 'bgp', triples: [traversalTriple] };
|
|
1817
|
+
for (const leaf of (_b = travAnchoredByTo.get(trav.to)) !== null && _b !== void 0 ? _b : []) {
|
|
1818
|
+
travNode = {
|
|
1819
|
+
type: 'left_join',
|
|
1820
|
+
left: travNode,
|
|
1821
|
+
right: { type: 'bgp', triples: [leaf] },
|
|
1822
|
+
};
|
|
1749
1823
|
}
|
|
1824
|
+
whereAlgebra = { type: 'left_join', left: whereAlgebra, right: travNode };
|
|
1750
1825
|
}
|
|
1751
|
-
//
|
|
1752
|
-
|
|
1753
|
-
|
|
1826
|
+
// Independent old values + owned-subtree cleanup for `contains` replace/remove.
|
|
1827
|
+
const blocks = [...independent, ...result.cascadeOptionals];
|
|
1828
|
+
if (blocks.length > 0) {
|
|
1829
|
+
whereAlgebra = { type: 'left_join', left: whereAlgebra, right: unionOf(blocks) };
|
|
1754
1830
|
}
|
|
1755
1831
|
// Add BIND expressions for computed fields
|
|
1756
1832
|
for (const ext of result.extends) {
|
|
@@ -1761,19 +1837,7 @@ function buildDeleteInsertPlan(query, options, opts) {
|
|
|
1761
1837
|
expression: ext.expression,
|
|
1762
1838
|
};
|
|
1763
1839
|
}
|
|
1764
|
-
|
|
1765
|
-
const insertPatterns = (opts === null || opts === void 0 ? void 0 : opts.ensureType)
|
|
1766
|
-
? [
|
|
1767
|
-
tripleOf(subjectTerm, iriTerm(RDF_TYPE), iriTerm(opts.ensureType)),
|
|
1768
|
-
...result.insertPatterns,
|
|
1769
|
-
]
|
|
1770
|
-
: result.insertPatterns;
|
|
1771
|
-
return {
|
|
1772
|
-
type: 'delete_insert',
|
|
1773
|
-
deletePatterns: result.deletePatterns,
|
|
1774
|
-
insertPatterns,
|
|
1775
|
-
whereAlgebra,
|
|
1776
|
-
};
|
|
1840
|
+
return whereAlgebra;
|
|
1777
1841
|
}
|
|
1778
1842
|
// ---------------------------------------------------------------------------
|
|
1779
1843
|
// Owned-subtree cascade
|
|
@@ -1833,8 +1897,11 @@ function collectContainment() {
|
|
|
1833
1897
|
* Build DELETE patterns + WHERE OPTIONAL blocks that cascade-delete the owned subtree
|
|
1834
1898
|
* reachable from `rootTerm` via `contains` edges. `varPrefix` keeps generated variables
|
|
1835
1899
|
* unique across multiple roots in one query. Returns empty when nothing is owned.
|
|
1900
|
+
*
|
|
1901
|
+
* `anchor`, when given, leads every block: the triple that binds a variable root, so
|
|
1902
|
+
* each block stays correct on its own (as a UNION branch) when no such root exists.
|
|
1836
1903
|
*/
|
|
1837
|
-
export function buildOwnedCascade(rootTerm, varPrefix) {
|
|
1904
|
+
export function buildOwnedCascade(rootTerm, varPrefix, anchor) {
|
|
1838
1905
|
const { containsPreds, dependentTypes } = collectContainment();
|
|
1839
1906
|
if (containsPreds.length === 0 || dependentTypes.length === 0) {
|
|
1840
1907
|
return { deletePatterns: [], whereOptionals: [] };
|
|
@@ -1858,6 +1925,7 @@ export function buildOwnedCascade(rootTerm, varPrefix) {
|
|
|
1858
1925
|
whereOptionals.push({
|
|
1859
1926
|
type: 'bgp',
|
|
1860
1927
|
triples: [
|
|
1928
|
+
...(anchor ? [anchor] : []),
|
|
1861
1929
|
tripleOf(rootTerm, pathTerm, owned),
|
|
1862
1930
|
tripleOf(owned, iriTerm(RDF_TYPE), iriTerm(typeId)),
|
|
1863
1931
|
tripleOf(owned, p, o),
|
|
@@ -1895,12 +1963,27 @@ export function buildOwnedSelfDelete(subjectTerm, propertyTerm, oldTerm, varPref
|
|
|
1895
1963
|
}
|
|
1896
1964
|
/**
|
|
1897
1965
|
* Converts an IRDeleteMutation to a SparqlDeleteInsertPlan (DELETE + WHERE).
|
|
1966
|
+
*
|
|
1967
|
+
* The WHERE is the type guard of every id, joined with ONE UNION of independent
|
|
1968
|
+
* blocks: per id its own triples, its incoming references and, for a shape that
|
|
1969
|
+
* owns something, one block per dependent type of its owned subtree.
|
|
1970
|
+
*
|
|
1971
|
+
* The blocks must not be chained as OPTIONALs. A chain of OPTIONALs is a join, so
|
|
1972
|
+
* the solutions multiply: own triples x incoming references x owned triples of each
|
|
1973
|
+
* dependent type. Deleting one shape description with the shape sync's cascade
|
|
1974
|
+
* produced ~27,000 solutions for ~130 triples and took over 30 seconds on Fuseki,
|
|
1975
|
+
* which re-evaluates the cascade property path for every left-hand row. A UNION
|
|
1976
|
+
* yields each block's matches once, so the solutions add up instead, and it deletes
|
|
1977
|
+
* exactly the same triples: a DELETE template triple whose variables a solution
|
|
1978
|
+
* leaves unbound is skipped, as it was for an OPTIONAL that did not match.
|
|
1979
|
+
*
|
|
1980
|
+
* The guards stay required for every id, as before: when any id is not of the
|
|
1981
|
+
* shape, the WHERE has no solution and nothing is deleted.
|
|
1898
1982
|
*/
|
|
1899
1983
|
export function deleteToAlgebra(query, _options) {
|
|
1900
1984
|
const deletePatterns = [];
|
|
1901
|
-
const
|
|
1902
|
-
const
|
|
1903
|
-
const cascadeOptionals = [];
|
|
1985
|
+
const guards = [];
|
|
1986
|
+
const blocks = [];
|
|
1904
1987
|
for (let i = 0; i < query.ids.length; i++) {
|
|
1905
1988
|
const subjectTerm = iriTerm(query.ids[i].id);
|
|
1906
1989
|
const idx = query.ids.length > 1 ? `_${i}` : '';
|
|
@@ -1909,30 +1992,23 @@ export function deleteToAlgebra(query, _options) {
|
|
|
1909
1992
|
const typeGuard = tripleOf(subjectTerm, iriTerm(RDF_TYPE), iriTerm(resolveShapeScanIri(query.shape)));
|
|
1910
1993
|
// DELETE block: all patterns (subject-wildcard, object-wildcard, type)
|
|
1911
1994
|
deletePatterns.push(subjWild, objWild, typeGuard);
|
|
1912
|
-
// WHERE
|
|
1913
|
-
//
|
|
1914
|
-
|
|
1915
|
-
|
|
1995
|
+
// WHERE: the type guard is required; the subject's own triples and its
|
|
1996
|
+
// incoming references (the entity may have none) are separate UNION blocks.
|
|
1997
|
+
guards.push(typeGuard);
|
|
1998
|
+
blocks.push({ type: 'bgp', triples: [subjWild] }, { type: 'bgp', triples: [objWild] });
|
|
1916
1999
|
// Cascade-delete the owned subtree — only for shapes that actually own something
|
|
1917
2000
|
// (have a `contains` property); a plain entity delete is left untouched.
|
|
1918
2001
|
if (shapeHasContainsProperty(query.shape)) {
|
|
1919
2002
|
const cascade = buildOwnedCascade(subjectTerm, `c${idx}_`);
|
|
1920
2003
|
deletePatterns.push(...cascade.deletePatterns);
|
|
1921
|
-
|
|
2004
|
+
blocks.push(...cascade.whereOptionals);
|
|
1922
2005
|
}
|
|
1923
2006
|
}
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
left: whereAlgebra,
|
|
1930
|
-
right: { type: 'bgp', triples: [triple] },
|
|
1931
|
-
};
|
|
1932
|
-
}
|
|
1933
|
-
for (const optional of cascadeOptionals) {
|
|
1934
|
-
whereAlgebra = { type: 'left_join', left: whereAlgebra, right: optional };
|
|
1935
|
-
}
|
|
2007
|
+
const whereAlgebra = {
|
|
2008
|
+
type: 'join',
|
|
2009
|
+
left: { type: 'bgp', triples: guards },
|
|
2010
|
+
right: unionOf(blocks),
|
|
2011
|
+
};
|
|
1936
2012
|
return {
|
|
1937
2013
|
type: 'delete_insert',
|
|
1938
2014
|
deletePatterns,
|
|
@@ -1956,63 +2032,63 @@ function isBlankNodeProperty(prop) {
|
|
|
1956
2032
|
return nk === shacl.BlankNode.id || nk === shacl.BlankNodeOrIRI.id;
|
|
1957
2033
|
}
|
|
1958
2034
|
/**
|
|
1959
|
-
*
|
|
2035
|
+
* Builds DELETE patterns + one WHERE block per blank node reachable from `parentVar`
|
|
2036
|
+
* through blank-node-typed properties, recursing into each property's valueShape for
|
|
2037
|
+
* nested blank nodes (e.g. Person → Address (blank) → GeoPoint (blank)).
|
|
1960
2038
|
*
|
|
1961
|
-
*
|
|
1962
|
-
* -
|
|
1963
|
-
*
|
|
2039
|
+
* - DELETE: `?bnN ?pN ?oN .` (wildcard all triples on the blank node)
|
|
2040
|
+
* - WHERE block: the path of edges from the root down to `?bnN`, `?bnN ?pN ?oN`, and
|
|
2041
|
+
* `FILTER(isBlank(…))` for every blank node on that path.
|
|
1964
2042
|
*
|
|
1965
|
-
*
|
|
1966
|
-
*
|
|
2043
|
+
* Each block carries its own path from the root, so the blocks are independent of one
|
|
2044
|
+
* another and are joined with a UNION by the caller. Nested as OPTIONALs, a node's
|
|
2045
|
+
* triples multiplied with every blank node below and beside it.
|
|
1967
2046
|
*/
|
|
1968
|
-
function
|
|
2047
|
+
function blankNodeBlocks(shapeId, parentVar, path, counter, deletePatterns, blocks) {
|
|
1969
2048
|
var _a, _b, _c;
|
|
1970
2049
|
const nodeShape = (_b = (_a = getShapeClass(shapeId)) === null || _a === void 0 ? void 0 : _a.shape) !== null && _b !== void 0 ? _b : getNodeShape(shapeId);
|
|
1971
2050
|
if (!nodeShape)
|
|
1972
|
-
return
|
|
1973
|
-
let optionals = null;
|
|
2051
|
+
return;
|
|
1974
2052
|
const props = getPropertyShapes(nodeShape, true);
|
|
1975
2053
|
for (const prop of props) {
|
|
1976
2054
|
if (!isBlankNodeProperty(prop))
|
|
1977
2055
|
continue;
|
|
1978
|
-
const
|
|
1979
|
-
const
|
|
1980
|
-
const
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
name: 'isBlank',
|
|
1989
|
-
args: [{ kind: 'variable_expr', name: bnVar }],
|
|
1990
|
-
};
|
|
1991
|
-
// ?bnVar ?pN ?oN
|
|
1992
|
-
const wildcardTriple = tripleOf(varTerm(bnVar), varTerm(pVar), varTerm(oVar));
|
|
1993
|
-
// Build inner pattern: traverse + filter + wildcard
|
|
1994
|
-
let innerPattern = {
|
|
1995
|
-
type: 'bgp',
|
|
1996
|
-
triples: [traverseTriple, wildcardTriple],
|
|
2056
|
+
const n = counter.next++;
|
|
2057
|
+
const bnVar = `bn${n}`;
|
|
2058
|
+
const wildcardTriple = tripleOf(varTerm(bnVar), varTerm(`p${n}`), varTerm(`o${n}`));
|
|
2059
|
+
deletePatterns.push(wildcardTriple);
|
|
2060
|
+
const here = {
|
|
2061
|
+
triples: [
|
|
2062
|
+
...path.triples,
|
|
2063
|
+
tripleOf(varTerm(parentVar), resolvePropertyPredicateTerm(prop.id), varTerm(bnVar)),
|
|
2064
|
+
],
|
|
2065
|
+
bnVars: [...path.bnVars, bnVar],
|
|
1997
2066
|
};
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
// Wrap in OPTIONAL (left_join)
|
|
2007
|
-
if (optionals) {
|
|
2008
|
-
optionals = { type: 'left_join', left: optionals, right: innerPattern };
|
|
2067
|
+
let block = { type: 'bgp', triples: [...here.triples, wildcardTriple] };
|
|
2068
|
+
for (const v of here.bnVars) {
|
|
2069
|
+
block = {
|
|
2070
|
+
type: 'filter',
|
|
2071
|
+
expression: { kind: 'function_expr', name: 'isBlank', args: [{ kind: 'variable_expr', name: v }] },
|
|
2072
|
+
inner: block,
|
|
2073
|
+
};
|
|
2009
2074
|
}
|
|
2010
|
-
|
|
2011
|
-
|
|
2075
|
+
blocks.push(block);
|
|
2076
|
+
if ((_c = prop.valueShape) === null || _c === void 0 ? void 0 : _c.id) {
|
|
2077
|
+
blankNodeBlocks(prop.valueShape.id, bnVar, here, counter, deletePatterns, blocks);
|
|
2012
2078
|
}
|
|
2013
|
-
depth++;
|
|
2014
2079
|
}
|
|
2015
|
-
|
|
2080
|
+
}
|
|
2081
|
+
/**
|
|
2082
|
+
* The blank-node cleanup blocks of a bulk delete, already joined with the root's own
|
|
2083
|
+
* triples as ONE UNION so their matches add up instead of multiplying; `null` when the
|
|
2084
|
+
* shape has no blank-node property (the root wildcard then stays in the required BGP).
|
|
2085
|
+
*/
|
|
2086
|
+
function bulkDeleteUnion(shapeId, subjectVar, rootWildcard, deletePatterns) {
|
|
2087
|
+
const blocks = [];
|
|
2088
|
+
blankNodeBlocks(shapeId, subjectVar, { triples: [], bnVars: [] }, { next: 1 }, deletePatterns, blocks);
|
|
2089
|
+
if (blocks.length === 0)
|
|
2090
|
+
return null;
|
|
2091
|
+
return unionOf([{ type: 'bgp', triples: [rootWildcard] }, ...blocks]);
|
|
2016
2092
|
}
|
|
2017
2093
|
/**
|
|
2018
2094
|
* Converts an IRDeleteAllMutation to a SparqlDeleteInsertPlan.
|
|
@@ -2026,15 +2102,13 @@ export function deleteAllToAlgebra(query, _options) {
|
|
|
2026
2102
|
const deletePatterns = [
|
|
2027
2103
|
tripleOf(varTerm(subjectVar), varTerm('p'), varTerm('o')),
|
|
2028
2104
|
];
|
|
2029
|
-
// WHERE: type triple
|
|
2105
|
+
// WHERE: type triple, then the root wildcard and the blank-node cleanup
|
|
2030
2106
|
const typeTriple = tripleOf(varTerm(subjectVar), iriTerm(RDF_TYPE), iriTerm(resolveShapeScanIri(query.shape)));
|
|
2031
2107
|
const rootWildcard = tripleOf(varTerm(subjectVar), varTerm('p'), varTerm('o'));
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
whereAlgebra = { type: 'left_join', left: whereAlgebra, right: blankNodeOptional };
|
|
2037
|
-
}
|
|
2108
|
+
const union = bulkDeleteUnion(query.shape, subjectVar, rootWildcard, deletePatterns);
|
|
2109
|
+
const whereAlgebra = union
|
|
2110
|
+
? { type: 'join', left: { type: 'bgp', triples: [typeTriple] }, right: union }
|
|
2111
|
+
: { type: 'bgp', triples: [typeTriple, rootWildcard] };
|
|
2038
2112
|
return {
|
|
2039
2113
|
type: 'delete_insert',
|
|
2040
2114
|
deletePatterns,
|
|
@@ -2054,10 +2128,14 @@ export function deleteWhereToAlgebra(query, _options) {
|
|
|
2054
2128
|
const deletePatterns = [
|
|
2055
2129
|
tripleOf(varTerm(subjectVar), varTerm('p'), varTerm('o')),
|
|
2056
2130
|
];
|
|
2057
|
-
// WHERE: type triple + root wildcard
|
|
2131
|
+
// WHERE: type triple + the where clause; the root wildcard and blank-node cleanup follow
|
|
2058
2132
|
const typeTriple = tripleOf(varTerm(subjectVar), iriTerm(RDF_TYPE), iriTerm(resolveShapeScanIri(query.shape)));
|
|
2059
2133
|
const rootWildcard = tripleOf(varTerm(subjectVar), varTerm('p'), varTerm('o'));
|
|
2060
|
-
|
|
2134
|
+
const union = bulkDeleteUnion(query.shape, subjectVar, rootWildcard, deletePatterns);
|
|
2135
|
+
let whereAlgebra = {
|
|
2136
|
+
type: 'bgp',
|
|
2137
|
+
triples: union ? [typeTriple] : [typeTriple, rootWildcard],
|
|
2138
|
+
};
|
|
2061
2139
|
// Process where patterns (traversals from the where clause)
|
|
2062
2140
|
const traverseTriples = [];
|
|
2063
2141
|
const optionalPropertyTriples = [];
|
|
@@ -2077,10 +2155,8 @@ export function deleteWhereToAlgebra(query, _options) {
|
|
|
2077
2155
|
// Convert and add filter expression
|
|
2078
2156
|
const filterExpr = convertExpression(query.where, registry, []);
|
|
2079
2157
|
whereAlgebra = { type: 'filter', expression: filterExpr, inner: whereAlgebra };
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
if (blankNodeOptional) {
|
|
2083
|
-
whereAlgebra = { type: 'left_join', left: whereAlgebra, right: blankNodeOptional };
|
|
2158
|
+
if (union) {
|
|
2159
|
+
whereAlgebra = { type: 'join', left: whereAlgebra, right: union };
|
|
2084
2160
|
}
|
|
2085
2161
|
return {
|
|
2086
2162
|
type: 'delete_insert',
|
|
@@ -2097,7 +2173,6 @@ export function deleteWhereToAlgebra(query, _options) {
|
|
|
2097
2173
|
* filter conditions from the where clause.
|
|
2098
2174
|
*/
|
|
2099
2175
|
export function updateWhereToAlgebra(query, options) {
|
|
2100
|
-
var _a, _b, _c;
|
|
2101
2176
|
const subjectTerm = varTerm('a0');
|
|
2102
2177
|
const result = processUpdateFields(query.data, subjectTerm, options);
|
|
2103
2178
|
// WHERE: type triple is always required
|
|
@@ -2122,62 +2197,11 @@ export function updateWhereToAlgebra(query, options) {
|
|
|
2122
2197
|
whereAlgebra = { type: 'filter', expression: filterExpr, inner: whereAlgebra };
|
|
2123
2198
|
}
|
|
2124
2199
|
// Old-value triples anchored on a traversal *target* belong INSIDE that
|
|
2125
|
-
// traversal's OPTIONAL group, not beside it
|
|
2126
|
-
//
|
|
2127
|
-
//
|
|
2128
|
-
//
|
|
2129
|
-
|
|
2130
|
-
// removes rows. `updateToAlgebra` performs the same split; this path did not.
|
|
2131
|
-
const travTos = new Set(((_a = query.traversalPatterns) !== null && _a !== void 0 ? _a : []).map((t) => t.to));
|
|
2132
|
-
const travAnchoredByTo = new Map();
|
|
2133
|
-
const subjectAnchored = [];
|
|
2134
|
-
for (const triple of result.oldValueTriples) {
|
|
2135
|
-
if (triple.subject.kind === 'variable' && travTos.has(triple.subject.name)) {
|
|
2136
|
-
const list = (_b = travAnchoredByTo.get(triple.subject.name)) !== null && _b !== void 0 ? _b : [];
|
|
2137
|
-
list.push(triple);
|
|
2138
|
-
travAnchoredByTo.set(triple.subject.name, list);
|
|
2139
|
-
}
|
|
2140
|
-
else {
|
|
2141
|
-
subjectAnchored.push(triple);
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
whereAlgebra = wrapOldValueOptionals(whereAlgebra, subjectAnchored);
|
|
2145
|
-
// Add traversal OPTIONAL patterns (for multi-segment expression refs)
|
|
2146
|
-
// These must come BEFORE expression BINDs since the BINDs reference traversal variables.
|
|
2147
|
-
if (query.traversalPatterns) {
|
|
2148
|
-
for (const trav of query.traversalPatterns) {
|
|
2149
|
-
const fromTerm = trav.from === '__mutation_subject__' ? varTerm('a0') : varTerm(trav.from);
|
|
2150
|
-
const traversalTriple = tripleOf(fromTerm,
|
|
2151
|
-
// The declared `sh:path`, like every other predicate — not the property
|
|
2152
|
-
// shape's own IRI, which identifies the description of the property.
|
|
2153
|
-
resolvePropertyPredicateTerm(trav.property), varTerm(trav.to));
|
|
2154
|
-
// The edge binds the target variable; each dependent leaf property is a
|
|
2155
|
-
// nested OPTIONAL within that scope, so a missing leaf does not drop the
|
|
2156
|
-
// group while an absent edge leaves every leaf variable unbound.
|
|
2157
|
-
let travNode = { type: 'bgp', triples: [traversalTriple] };
|
|
2158
|
-
for (const leaf of (_c = travAnchoredByTo.get(trav.to)) !== null && _c !== void 0 ? _c : []) {
|
|
2159
|
-
travNode = {
|
|
2160
|
-
type: 'left_join',
|
|
2161
|
-
left: travNode,
|
|
2162
|
-
right: { type: 'bgp', triples: [leaf] },
|
|
2163
|
-
};
|
|
2164
|
-
}
|
|
2165
|
-
whereAlgebra = { type: 'left_join', left: whereAlgebra, right: travNode };
|
|
2166
|
-
}
|
|
2167
|
-
}
|
|
2168
|
-
// Owned-subtree cascade OPTIONALs for replaced `contains` properties.
|
|
2169
|
-
for (const optional of result.cascadeOptionals) {
|
|
2170
|
-
whereAlgebra = { type: 'left_join', left: whereAlgebra, right: optional };
|
|
2171
|
-
}
|
|
2172
|
-
// Add BIND expressions for computed fields
|
|
2173
|
-
for (const ext of result.extends) {
|
|
2174
|
-
whereAlgebra = {
|
|
2175
|
-
type: 'extend',
|
|
2176
|
-
inner: whereAlgebra,
|
|
2177
|
-
variable: ext.variable,
|
|
2178
|
-
expression: ext.expression,
|
|
2179
|
-
};
|
|
2180
|
-
}
|
|
2200
|
+
// traversal's OPTIONAL group, not beside it: beside it, the leaf's subject variable
|
|
2201
|
+
// is introduced by an OPTIONAL that shares no variable with anything to its left — a
|
|
2202
|
+
// cartesian product over every node carrying that predicate. `buildUpdateWhere` does
|
|
2203
|
+
// that split for both update paths.
|
|
2204
|
+
whereAlgebra = buildUpdateWhere(whereAlgebra, result, query.traversalPatterns, subjectTerm);
|
|
2181
2205
|
return {
|
|
2182
2206
|
type: 'delete_insert',
|
|
2183
2207
|
deletePatterns: result.deletePatterns,
|