@oml/owl 0.7.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/README.md +46 -0
- package/out/index.d.ts +10 -0
- package/out/index.js +12 -0
- package/out/index.js.map +1 -0
- package/out/owl/owl-abox.d.ts +79 -0
- package/out/owl/owl-abox.js +765 -0
- package/out/owl/owl-abox.js.map +1 -0
- package/out/owl/owl-imports.d.ts +9 -0
- package/out/owl/owl-imports.js +102 -0
- package/out/owl/owl-imports.js.map +1 -0
- package/out/owl/owl-interfaces.d.ts +121 -0
- package/out/owl/owl-interfaces.js +3 -0
- package/out/owl/owl-interfaces.js.map +1 -0
- package/out/owl/owl-mapper.d.ts +80 -0
- package/out/owl/owl-mapper.js +1217 -0
- package/out/owl/owl-mapper.js.map +1 -0
- package/out/owl/owl-service.d.ts +65 -0
- package/out/owl/owl-service.js +552 -0
- package/out/owl/owl-service.js.map +1 -0
- package/out/owl/owl-shacl.d.ts +28 -0
- package/out/owl/owl-shacl.js +337 -0
- package/out/owl/owl-shacl.js.map +1 -0
- package/out/owl/owl-sparql.d.ts +71 -0
- package/out/owl/owl-sparql.js +260 -0
- package/out/owl/owl-sparql.js.map +1 -0
- package/out/owl/owl-store.d.ts +32 -0
- package/out/owl/owl-store.js +142 -0
- package/out/owl/owl-store.js.map +1 -0
- package/out/owl/owl-tbox.d.ts +98 -0
- package/out/owl/owl-tbox.js +575 -0
- package/out/owl/owl-tbox.js.map +1 -0
- package/out/owl-module.d.ts +15 -0
- package/out/owl-module.js +22 -0
- package/out/owl-module.js.map +1 -0
- package/package.json +52 -0
- package/src/index.ts +12 -0
- package/src/owl/owl-abox.ts +930 -0
- package/src/owl/owl-imports.ts +108 -0
- package/src/owl/owl-interfaces.ts +145 -0
- package/src/owl/owl-mapper.ts +1510 -0
- package/src/owl/owl-service.ts +642 -0
- package/src/owl/owl-shacl.ts +400 -0
- package/src/owl/owl-sparql.ts +317 -0
- package/src/owl/owl-store.ts +173 -0
- package/src/owl/owl-tbox.ts +727 -0
- package/src/owl-module.ts +52 -0
|
@@ -0,0 +1,930 @@
|
|
|
1
|
+
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
|
+
|
|
3
|
+
import { DataFactory } from 'n3';
|
|
4
|
+
import type { NamedNode, Quad, Store, Term } from 'n3';
|
|
5
|
+
import type { OwlStore } from './owl-interfaces.js';
|
|
6
|
+
import type { RuleAtom, RuleTermPattern, TBoxIndex } from './owl-tbox.js';
|
|
7
|
+
|
|
8
|
+
const { namedNode, quad } = DataFactory;
|
|
9
|
+
|
|
10
|
+
const RDF_TYPE = namedNode('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
|
|
11
|
+
|
|
12
|
+
export interface ChainingResult {
|
|
13
|
+
complete: boolean;
|
|
14
|
+
iterations: number;
|
|
15
|
+
newQuadsCount: number;
|
|
16
|
+
validationWarnings: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface Fact {
|
|
20
|
+
subject: Term;
|
|
21
|
+
predicate: NamedNode;
|
|
22
|
+
object: Term;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ScopeData {
|
|
26
|
+
ownEntailmentGraph: NamedNode;
|
|
27
|
+
ownBaseFacts: Fact[];
|
|
28
|
+
baseKeys: Set<string>;
|
|
29
|
+
importEntKeys: Set<string>;
|
|
30
|
+
ownEntKeys: Set<string>;
|
|
31
|
+
facts: Map<string, Fact>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface WorkingIndex {
|
|
35
|
+
facts: Map<string, Fact>;
|
|
36
|
+
typesBySubject: Map<string, Set<string>>;
|
|
37
|
+
subjectsByType: Map<string, Set<string>>;
|
|
38
|
+
edgeFactsBySubjectProperty: Map<string, Fact[]>;
|
|
39
|
+
edgeFactsByProperty: Map<string, Fact[]>;
|
|
40
|
+
subjectsByPropertyObject: Map<string, Set<string>>;
|
|
41
|
+
termByKey: Map<string, Term>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class ABoxEntailmentCache {
|
|
45
|
+
private readonly state = new Map<string, 'clean' | 'dirty' | 'absent'>();
|
|
46
|
+
private readonly deltaPlus = new Map<string, Map<string, Quad>>();
|
|
47
|
+
private readonly deltaMinus = new Map<string, Map<string, Quad>>();
|
|
48
|
+
private readonly validationWarnings = new Map<string, string[]>();
|
|
49
|
+
|
|
50
|
+
markDirty(modelUri: string): void {
|
|
51
|
+
this.state.set(modelUri, 'dirty');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
markDirtyWithDelta(modelUri: string, asserted: Quad[], retracted: Quad[]): void {
|
|
55
|
+
if (asserted.length > 0) {
|
|
56
|
+
const plus = this.deltaPlus.get(modelUri) ?? new Map<string, Quad>();
|
|
57
|
+
for (const q of asserted) {
|
|
58
|
+
plus.set(this.quadKey(q), q);
|
|
59
|
+
}
|
|
60
|
+
this.deltaPlus.set(modelUri, plus);
|
|
61
|
+
}
|
|
62
|
+
if (retracted.length > 0) {
|
|
63
|
+
const minus = this.deltaMinus.get(modelUri) ?? new Map<string, Quad>();
|
|
64
|
+
for (const q of retracted) {
|
|
65
|
+
minus.set(this.quadKey(q), q);
|
|
66
|
+
}
|
|
67
|
+
this.deltaMinus.set(modelUri, minus);
|
|
68
|
+
}
|
|
69
|
+
this.markDirty(modelUri);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
markClean(modelUri: string): void {
|
|
73
|
+
this.state.set(modelUri, 'clean');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
markDirtyAll(modelUris: string[]): void {
|
|
77
|
+
for (const modelUri of modelUris) {
|
|
78
|
+
this.markDirty(modelUri);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
isDirty(modelUri: string): boolean {
|
|
83
|
+
return this.state.get(modelUri) === 'dirty';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
isAbsent(modelUri: string): boolean {
|
|
87
|
+
const value = this.state.get(modelUri);
|
|
88
|
+
return value === undefined || value === 'absent';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
consumeDelta(modelUri: string): { plus: Quad[]; minus: Quad[] } {
|
|
92
|
+
const plus = [...(this.deltaPlus.get(modelUri)?.values() ?? [])];
|
|
93
|
+
const minus = [...(this.deltaMinus.get(modelUri)?.values() ?? [])];
|
|
94
|
+
this.deltaPlus.delete(modelUri);
|
|
95
|
+
this.deltaMinus.delete(modelUri);
|
|
96
|
+
return { plus, minus };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getValidationWarnings(modelUri: string): string[] {
|
|
100
|
+
return this.validationWarnings.get(modelUri) ?? [];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
setValidationWarnings(modelUri: string, warnings: string[]): void {
|
|
104
|
+
this.validationWarnings.set(modelUri, [...new Set(warnings)]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
invalidate(modelUri: string): void {
|
|
108
|
+
this.state.delete(modelUri);
|
|
109
|
+
this.deltaPlus.delete(modelUri);
|
|
110
|
+
this.deltaMinus.delete(modelUri);
|
|
111
|
+
this.validationWarnings.delete(modelUri);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private quadKey(q: Quad): string {
|
|
115
|
+
return `${q.subject.id}|${q.predicate.id}|${q.object.id}|${q.graph.id}`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export class ABoxChainer {
|
|
120
|
+
constructor(private readonly reasoningStore: OwlStore) {}
|
|
121
|
+
|
|
122
|
+
chain(
|
|
123
|
+
modelUri: string,
|
|
124
|
+
tboxIndex: TBoxIndex,
|
|
125
|
+
readModelUris: ReadonlyArray<string>,
|
|
126
|
+
deltaPlus: Quad[],
|
|
127
|
+
deltaMinus: Quad[],
|
|
128
|
+
): ChainingResult {
|
|
129
|
+
const scope = this.resolveScope(modelUri, readModelUris);
|
|
130
|
+
const store = this.reasoningStore.getStore();
|
|
131
|
+
const index = this.buildIndex(scope.facts);
|
|
132
|
+
|
|
133
|
+
const plusFacts = deltaPlus.map((q) => this.asFact(q));
|
|
134
|
+
const minusFacts = deltaMinus.map((q) => this.asFact(q));
|
|
135
|
+
const fullRecompute = plusFacts.length === 0 && minusFacts.length === 0;
|
|
136
|
+
|
|
137
|
+
let iterations = 0;
|
|
138
|
+
let newQuadsCount = 0;
|
|
139
|
+
|
|
140
|
+
if (fullRecompute) {
|
|
141
|
+
for (const key of [...scope.ownEntKeys]) {
|
|
142
|
+
const fact = scope.facts.get(key);
|
|
143
|
+
if (fact) {
|
|
144
|
+
this.removeOwnEntailedFact(scope, index, store, fact);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const over = fullRecompute ? [] : this.overdelete(scope, index, tboxIndex, minusFacts, store);
|
|
150
|
+
const seed = fullRecompute ? scope.ownBaseFacts : [...plusFacts, ...over];
|
|
151
|
+
|
|
152
|
+
const result = this.materializeAdd(scope, index, tboxIndex, seed, store);
|
|
153
|
+
iterations += result.iterations;
|
|
154
|
+
newQuadsCount += result.newQuads;
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
complete: true,
|
|
158
|
+
iterations,
|
|
159
|
+
newQuadsCount,
|
|
160
|
+
validationWarnings: this.collectValidationWarnings(index, tboxIndex),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private overdelete(
|
|
165
|
+
scope: ScopeData,
|
|
166
|
+
index: WorkingIndex,
|
|
167
|
+
tbox: TBoxIndex,
|
|
168
|
+
minusFacts: Fact[],
|
|
169
|
+
store: Store,
|
|
170
|
+
): Fact[] {
|
|
171
|
+
const queue = [...minusFacts];
|
|
172
|
+
const overByKey = new Map<string, Fact>();
|
|
173
|
+
|
|
174
|
+
while (queue.length > 0) {
|
|
175
|
+
const current = queue.shift();
|
|
176
|
+
if (!current) {
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
for (const head of this.reverseAnchoredHeads(current, index, tbox)) {
|
|
180
|
+
const key = this.factKey(head);
|
|
181
|
+
if (!scope.ownEntKeys.has(key)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
this.removeOwnEntailedFact(scope, index, store, head);
|
|
185
|
+
overByKey.set(key, head);
|
|
186
|
+
queue.push(head);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return [...overByKey.values()];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private materializeAdd(
|
|
194
|
+
scope: ScopeData,
|
|
195
|
+
index: WorkingIndex,
|
|
196
|
+
tbox: TBoxIndex,
|
|
197
|
+
seedFacts: Fact[],
|
|
198
|
+
store: Store,
|
|
199
|
+
): { iterations: number; newQuads: number } {
|
|
200
|
+
const queue: Fact[] = [];
|
|
201
|
+
const queued = new Set<string>();
|
|
202
|
+
let iterations = 0;
|
|
203
|
+
let newQuads = 0;
|
|
204
|
+
|
|
205
|
+
const enqueue = (fact: Fact) => {
|
|
206
|
+
const key = this.factKey(fact);
|
|
207
|
+
if (queued.has(key)) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
queue.push(fact);
|
|
211
|
+
queued.add(key);
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
for (const seed of seedFacts) {
|
|
215
|
+
const key = this.factKey(seed);
|
|
216
|
+
if (scope.baseKeys.has(key)) {
|
|
217
|
+
enqueue(seed);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (!this.canInsertEntailedFact(scope, seed) || !this.hasSupport(seed, index, tbox)) {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
this.insertOwnEntailedFact(scope, index, store, seed);
|
|
224
|
+
newQuads += 1;
|
|
225
|
+
enqueue(seed);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
while (queue.length > 0) {
|
|
229
|
+
const fact = queue.shift();
|
|
230
|
+
if (!fact) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
iterations += 1;
|
|
234
|
+
for (const head of this.forwardRules(fact, index, tbox)) {
|
|
235
|
+
if (!this.canInsertEntailedFact(scope, head) || !this.hasSupport(head, index, tbox)) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
this.insertOwnEntailedFact(scope, index, store, head);
|
|
239
|
+
newQuads += 1;
|
|
240
|
+
enqueue(head);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { iterations, newQuads };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
private forwardRules(fact: Fact, index: WorkingIndex, tbox: TBoxIndex): Fact[] {
|
|
248
|
+
const heads: Fact[] = [];
|
|
249
|
+
|
|
250
|
+
if (this.isTypeFact(fact)) {
|
|
251
|
+
for (const superClass of this.getSuperClasses(tbox, fact.object.value)) {
|
|
252
|
+
if (superClass !== fact.object.value) {
|
|
253
|
+
heads.push(this.typeFact(fact.subject, superClass));
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const p = fact.predicate.value;
|
|
259
|
+
const x = fact.subject;
|
|
260
|
+
const y = fact.object;
|
|
261
|
+
|
|
262
|
+
for (const q of this.getSuperProperties(tbox, p)) {
|
|
263
|
+
if (q !== p) {
|
|
264
|
+
heads.push(this.edgeFact(x, q, y));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
for (const d of this.getDomains(tbox, p)) {
|
|
269
|
+
heads.push(this.typeFact(x, d));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (y.termType !== 'Literal') {
|
|
273
|
+
for (const c of this.getObjectRanges(tbox, p)) {
|
|
274
|
+
heads.push(this.typeFact(y, c));
|
|
275
|
+
}
|
|
276
|
+
for (const inverse of this.getInverses(tbox, p)) {
|
|
277
|
+
heads.push(this.edgeFact(y, inverse, x));
|
|
278
|
+
}
|
|
279
|
+
if (this.isSymmetricProperty(tbox, p)) {
|
|
280
|
+
heads.push(this.edgeFact(y, p, x));
|
|
281
|
+
}
|
|
282
|
+
if (this.isTransitiveProperty(tbox, p)) {
|
|
283
|
+
for (const next of this.getEdgeFacts(index, y, p)) {
|
|
284
|
+
heads.push(this.edgeFact(x, p, next.object));
|
|
285
|
+
}
|
|
286
|
+
for (const prev of this.getIncomingEdgesByProperty(index, x, p)) {
|
|
287
|
+
heads.push(this.edgeFact(prev.subject, p, y));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
heads.push(...this.applyForwardRulesTriggeredByFact(fact, index, tbox));
|
|
293
|
+
|
|
294
|
+
return this.uniqueFacts(heads);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private reverseAnchoredHeads(fact: Fact, index: WorkingIndex, tbox: TBoxIndex): Fact[] {
|
|
298
|
+
return this.forwardRules(fact, index, tbox);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
private hasSupport(fact: Fact, index: WorkingIndex, tbox: TBoxIndex): boolean {
|
|
302
|
+
if (index.facts.has(this.factKey(fact))) {
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (this.isTypeFact(fact)) {
|
|
307
|
+
const types = this.getTypes(index, fact.subject);
|
|
308
|
+
for (const type of types) {
|
|
309
|
+
if (type === fact.object.value || this.getSuperClasses(tbox, type).includes(fact.object.value)) {
|
|
310
|
+
return true;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
for (const edge of this.getOutgoingEdges(index, fact.subject)) {
|
|
315
|
+
if (this.getDomains(tbox, edge.predicate.value).includes(fact.object.value)) {
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
for (const edge of this.getIncomingEdges(index, fact.subject)) {
|
|
321
|
+
if (edge.object.termType !== 'Literal' && this.getObjectRanges(tbox, edge.predicate.value).includes(fact.object.value)) {
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (this.hasForwardRuleSupport(fact, index, tbox)) {
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const x = fact.subject;
|
|
334
|
+
const p = fact.predicate.value;
|
|
335
|
+
const y = fact.object;
|
|
336
|
+
|
|
337
|
+
for (const edge of this.getOutgoingEdges(index, x)) {
|
|
338
|
+
if (edge.object.id === y.id && this.getSuperProperties(tbox, edge.predicate.value).includes(p)) {
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (y.termType !== 'Literal') {
|
|
344
|
+
for (const edge of this.getOutgoingEdges(index, y)) {
|
|
345
|
+
if (edge.object.id === x.id && this.getInverses(tbox, edge.predicate.value).includes(p)) {
|
|
346
|
+
return true;
|
|
347
|
+
}
|
|
348
|
+
if (edge.object.id === x.id && edge.predicate.value === p && this.isSymmetricProperty(tbox, p)) {
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (this.isTransitiveProperty(tbox, p) && this.hasTransitiveSupport(index, x, p, y)) {
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (this.hasForwardRuleSupport(fact, index, tbox)) {
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
private hasTransitiveSupport(index: WorkingIndex, subject: Term, property: string, object: Term): boolean {
|
|
366
|
+
for (const left of this.getEdgeFacts(index, subject, property)) {
|
|
367
|
+
if (left.object.id === object.id || left.object.termType === 'Literal') {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
for (const right of this.getEdgeFacts(index, left.object, property)) {
|
|
371
|
+
if (right.object.id === object.id) {
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private applyForwardRulesTriggeredByFact(fact: Fact, index: WorkingIndex, tbox: TBoxIndex): Fact[] {
|
|
380
|
+
const heads: Fact[] = [];
|
|
381
|
+
for (const rule of tbox.forwardRules) {
|
|
382
|
+
rule.body.forEach((atom, anchorIndex) => {
|
|
383
|
+
const bindings = this.matchAtomToFact(atom, fact, new Map<string, Term>());
|
|
384
|
+
if (!bindings) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
for (const resolved of this.resolveRuleBody(rule.body, index, bindings, anchorIndex)) {
|
|
388
|
+
for (const head of rule.head) {
|
|
389
|
+
const inferred = this.instantiateRuleAtom(head, resolved);
|
|
390
|
+
if (inferred) {
|
|
391
|
+
heads.push(inferred);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
return this.uniqueFacts(heads);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private hasForwardRuleSupport(fact: Fact, index: WorkingIndex, tbox: TBoxIndex): boolean {
|
|
401
|
+
for (const rule of tbox.forwardRules) {
|
|
402
|
+
for (const head of rule.head) {
|
|
403
|
+
const bindings = this.matchAtomToFact(head, fact, new Map<string, Term>());
|
|
404
|
+
if (!bindings) {
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
const resolved = this.resolveRuleBody(rule.body, index, bindings, -1);
|
|
408
|
+
if (resolved.length > 0) {
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private resolveRuleBody(
|
|
417
|
+
body: RuleAtom[],
|
|
418
|
+
index: WorkingIndex,
|
|
419
|
+
bindings: Map<string, Term>,
|
|
420
|
+
satisfiedIndex: number,
|
|
421
|
+
): Array<Map<string, Term>> {
|
|
422
|
+
const pending = body.filter((_atom, index) => index !== satisfiedIndex);
|
|
423
|
+
return this.resolvePendingAtoms(pending, index, bindings);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
private resolvePendingAtoms(
|
|
427
|
+
pending: RuleAtom[],
|
|
428
|
+
index: WorkingIndex,
|
|
429
|
+
bindings: Map<string, Term>,
|
|
430
|
+
): Array<Map<string, Term>> {
|
|
431
|
+
if (pending.length === 0) {
|
|
432
|
+
return [bindings];
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const [current, ...rest] = pending;
|
|
436
|
+
const matches = this.findMatchesForAtom(current, index, bindings);
|
|
437
|
+
const resolved: Array<Map<string, Term>> = [];
|
|
438
|
+
for (const nextBindings of matches) {
|
|
439
|
+
resolved.push(...this.resolvePendingAtoms(rest, index, nextBindings));
|
|
440
|
+
}
|
|
441
|
+
return resolved;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private findMatchesForAtom(atom: RuleAtom, index: WorkingIndex, bindings: Map<string, Term>): Array<Map<string, Term>> {
|
|
445
|
+
const matches: Array<Map<string, Term>> = [];
|
|
446
|
+
for (const fact of this.candidateFacts(atom, index, bindings)) {
|
|
447
|
+
const nextBindings = this.matchAtomToFact(atom, fact, bindings);
|
|
448
|
+
if (nextBindings) {
|
|
449
|
+
matches.push(nextBindings);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return matches;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
private candidateFacts(atom: RuleAtom, index: WorkingIndex, bindings: Map<string, Term>): Fact[] {
|
|
456
|
+
if (atom.kind === 'class') {
|
|
457
|
+
const bound = this.resolvePatternTerm(atom.argument, bindings);
|
|
458
|
+
if (bound) {
|
|
459
|
+
return [...this.getTypes(index, bound)].map((classIri) => this.typeFact(bound, classIri));
|
|
460
|
+
}
|
|
461
|
+
const subjects = index.subjectsByType.get(atom.classIri) ?? new Set<string>();
|
|
462
|
+
return [...subjects]
|
|
463
|
+
.map((subjectKey) => index.termByKey.get(subjectKey))
|
|
464
|
+
.filter((term): term is Term => Boolean(term))
|
|
465
|
+
.map((subject) => this.typeFact(subject, atom.classIri));
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const left = this.resolvePatternTerm(atom.argument1, bindings);
|
|
469
|
+
const right = this.resolvePatternTerm(atom.argument2, bindings);
|
|
470
|
+
if (left) {
|
|
471
|
+
return this.getEdgeFacts(index, left, atom.propertyIri);
|
|
472
|
+
}
|
|
473
|
+
if (right) {
|
|
474
|
+
return this.getIncomingEdgesByProperty(index, right, atom.propertyIri);
|
|
475
|
+
}
|
|
476
|
+
return index.edgeFactsByProperty.get(atom.propertyIri) ?? [];
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private matchAtomToFact(atom: RuleAtom, fact: Fact, bindings: Map<string, Term>): Map<string, Term> | undefined {
|
|
480
|
+
if (atom.kind === 'class') {
|
|
481
|
+
if (!this.isTypeFact(fact) || fact.object.value !== atom.classIri) {
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
return this.matchPattern(atom.argument, fact.subject, bindings);
|
|
485
|
+
}
|
|
486
|
+
if (fact.predicate.value !== atom.propertyIri) {
|
|
487
|
+
return undefined;
|
|
488
|
+
}
|
|
489
|
+
const afterLeft = this.matchPattern(atom.argument1, fact.subject, bindings);
|
|
490
|
+
if (!afterLeft) {
|
|
491
|
+
return undefined;
|
|
492
|
+
}
|
|
493
|
+
return this.matchPattern(atom.argument2, fact.object, afterLeft);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
private matchPattern(pattern: RuleTermPattern, term: Term, bindings: Map<string, Term>): Map<string, Term> | undefined {
|
|
497
|
+
if (pattern.kind === 'variable') {
|
|
498
|
+
const existing = bindings.get(pattern.name);
|
|
499
|
+
if (existing) {
|
|
500
|
+
return this.sameTerm(existing, term) ? bindings : undefined;
|
|
501
|
+
}
|
|
502
|
+
const next = new Map(bindings);
|
|
503
|
+
next.set(pattern.name, term);
|
|
504
|
+
return next;
|
|
505
|
+
}
|
|
506
|
+
if (pattern.kind === 'named') {
|
|
507
|
+
return term.termType === 'NamedNode' && term.value === pattern.iri ? bindings : undefined;
|
|
508
|
+
}
|
|
509
|
+
if (term.termType !== 'Literal') {
|
|
510
|
+
return undefined;
|
|
511
|
+
}
|
|
512
|
+
return term.value === pattern.value
|
|
513
|
+
&& term.datatype.value === pattern.datatype
|
|
514
|
+
&& term.language === pattern.language
|
|
515
|
+
? bindings
|
|
516
|
+
: undefined;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
private resolvePatternTerm(pattern: RuleTermPattern, bindings: Map<string, Term>): Term | undefined {
|
|
520
|
+
if (pattern.kind === 'variable') {
|
|
521
|
+
return bindings.get(pattern.name);
|
|
522
|
+
}
|
|
523
|
+
if (pattern.kind === 'named') {
|
|
524
|
+
return namedNode(pattern.iri);
|
|
525
|
+
}
|
|
526
|
+
return DataFactory.literal(pattern.value, pattern.language || namedNode(pattern.datatype));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private instantiateRuleAtom(atom: RuleAtom, bindings: Map<string, Term>): Fact | undefined {
|
|
530
|
+
if (atom.kind === 'class') {
|
|
531
|
+
const subject = this.resolvePatternTerm(atom.argument, bindings);
|
|
532
|
+
if (!subject || subject.termType === 'Literal') {
|
|
533
|
+
return undefined;
|
|
534
|
+
}
|
|
535
|
+
return this.typeFact(subject, atom.classIri);
|
|
536
|
+
}
|
|
537
|
+
const subject = this.resolvePatternTerm(atom.argument1, bindings);
|
|
538
|
+
const object = this.resolvePatternTerm(atom.argument2, bindings);
|
|
539
|
+
if (!subject || !object || subject.termType === 'Literal') {
|
|
540
|
+
return undefined;
|
|
541
|
+
}
|
|
542
|
+
return this.edgeFact(subject, atom.propertyIri, object);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
private sameTerm(left: Term, right: Term): boolean {
|
|
546
|
+
return left.termType === right.termType
|
|
547
|
+
&& left.value === right.value
|
|
548
|
+
&& (left.termType !== 'Literal'
|
|
549
|
+
|| (right.termType === 'Literal'
|
|
550
|
+
&& left.datatype.value === right.datatype.value
|
|
551
|
+
&& left.language === right.language));
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private resolveScope(modelUri: string, readModelUris: ReadonlyArray<string>): ScopeData {
|
|
555
|
+
const store = this.reasoningStore.getStore();
|
|
556
|
+
const facts = new Map<string, Fact>();
|
|
557
|
+
const baseKeys = new Set<string>();
|
|
558
|
+
const importEntKeys = new Set<string>();
|
|
559
|
+
const ownEntKeys = new Set<string>();
|
|
560
|
+
const ownBaseFacts: Fact[] = [];
|
|
561
|
+
const ownGraphs = this.reasoningStore.graphs(modelUri);
|
|
562
|
+
|
|
563
|
+
for (const uri of readModelUris) {
|
|
564
|
+
let graphs;
|
|
565
|
+
try {
|
|
566
|
+
graphs = this.reasoningStore.graphs(uri);
|
|
567
|
+
} catch {
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
for (const q of store.getQuads(null, null, null, graphs.own)) {
|
|
572
|
+
const fact = this.asFact(q);
|
|
573
|
+
const key = this.factKey(fact);
|
|
574
|
+
facts.set(key, fact);
|
|
575
|
+
baseKeys.add(key);
|
|
576
|
+
if (uri === modelUri) {
|
|
577
|
+
ownBaseFacts.push(fact);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
for (const q of store.getQuads(null, null, null, graphs.entailments)) {
|
|
582
|
+
const fact = this.asFact(q);
|
|
583
|
+
const key = this.factKey(fact);
|
|
584
|
+
facts.set(key, fact);
|
|
585
|
+
if (uri === modelUri) {
|
|
586
|
+
ownEntKeys.add(key);
|
|
587
|
+
} else {
|
|
588
|
+
importEntKeys.add(key);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return {
|
|
594
|
+
ownEntailmentGraph: ownGraphs.entailments,
|
|
595
|
+
ownBaseFacts: this.uniqueFacts(ownBaseFacts),
|
|
596
|
+
baseKeys,
|
|
597
|
+
importEntKeys,
|
|
598
|
+
ownEntKeys,
|
|
599
|
+
facts,
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
private buildIndex(facts: Map<string, Fact>): WorkingIndex {
|
|
604
|
+
const typesBySubject = new Map<string, Set<string>>();
|
|
605
|
+
const subjectsByType = new Map<string, Set<string>>();
|
|
606
|
+
const edgeFactsBySubjectProperty = new Map<string, Fact[]>();
|
|
607
|
+
const edgeFactsByProperty = new Map<string, Fact[]>();
|
|
608
|
+
const subjectsByPropertyObject = new Map<string, Set<string>>();
|
|
609
|
+
const termByKey = new Map<string, Term>();
|
|
610
|
+
|
|
611
|
+
for (const fact of facts.values()) {
|
|
612
|
+
const sKey = fact.subject.id;
|
|
613
|
+
const oKey = fact.object.id;
|
|
614
|
+
termByKey.set(sKey, fact.subject);
|
|
615
|
+
termByKey.set(oKey, fact.object);
|
|
616
|
+
|
|
617
|
+
if (fact.predicate.value === RDF_TYPE.value && fact.object.termType === 'NamedNode') {
|
|
618
|
+
const values = typesBySubject.get(sKey) ?? new Set<string>();
|
|
619
|
+
values.add(fact.object.value);
|
|
620
|
+
typesBySubject.set(sKey, values);
|
|
621
|
+
const subjects = subjectsByType.get(fact.object.value) ?? new Set<string>();
|
|
622
|
+
subjects.add(sKey);
|
|
623
|
+
subjectsByType.set(fact.object.value, subjects);
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const spKey = `${sKey}|${fact.predicate.value}`;
|
|
628
|
+
const bySp = edgeFactsBySubjectProperty.get(spKey) ?? [];
|
|
629
|
+
bySp.push(fact);
|
|
630
|
+
edgeFactsBySubjectProperty.set(spKey, bySp);
|
|
631
|
+
const byProperty = edgeFactsByProperty.get(fact.predicate.value) ?? [];
|
|
632
|
+
byProperty.push(fact);
|
|
633
|
+
edgeFactsByProperty.set(fact.predicate.value, byProperty);
|
|
634
|
+
|
|
635
|
+
const poKey = `${fact.predicate.value}|${oKey}`;
|
|
636
|
+
const subjects = subjectsByPropertyObject.get(poKey) ?? new Set<string>();
|
|
637
|
+
subjects.add(sKey);
|
|
638
|
+
subjectsByPropertyObject.set(poKey, subjects);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return {
|
|
642
|
+
facts,
|
|
643
|
+
typesBySubject,
|
|
644
|
+
subjectsByType,
|
|
645
|
+
edgeFactsBySubjectProperty,
|
|
646
|
+
edgeFactsByProperty,
|
|
647
|
+
subjectsByPropertyObject,
|
|
648
|
+
termByKey,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
private insertOwnEntailedFact(scope: ScopeData, index: WorkingIndex, store: Store, fact: Fact): void {
|
|
653
|
+
const key = this.factKey(fact);
|
|
654
|
+
if (scope.ownEntKeys.has(key)) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
scope.ownEntKeys.add(key);
|
|
658
|
+
scope.facts.set(key, fact);
|
|
659
|
+
index.facts.set(key, fact);
|
|
660
|
+
this.indexAdd(index, fact);
|
|
661
|
+
store.addQuad(quad(this.asQuadSubject(fact.subject), fact.predicate, this.asQuadObject(fact.object), scope.ownEntailmentGraph));
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
private removeOwnEntailedFact(scope: ScopeData, index: WorkingIndex, store: Store, fact: Fact): void {
|
|
665
|
+
const key = this.factKey(fact);
|
|
666
|
+
if (!scope.ownEntKeys.has(key)) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
scope.ownEntKeys.delete(key);
|
|
670
|
+
scope.facts.delete(key);
|
|
671
|
+
index.facts.delete(key);
|
|
672
|
+
this.indexRemove(index, fact);
|
|
673
|
+
store.removeQuad(quad(this.asQuadSubject(fact.subject), fact.predicate, this.asQuadObject(fact.object), scope.ownEntailmentGraph));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
private canInsertEntailedFact(scope: ScopeData, fact: Fact): boolean {
|
|
677
|
+
const key = this.factKey(fact);
|
|
678
|
+
return !scope.baseKeys.has(key) && !scope.importEntKeys.has(key) && !scope.ownEntKeys.has(key);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
private getTypes(index: WorkingIndex, subject: Term): Set<string> {
|
|
682
|
+
return index.typesBySubject.get(subject.id) ?? new Set<string>();
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
private getEdgeFacts(index: WorkingIndex, subject: Term, property: string): Fact[] {
|
|
686
|
+
return index.edgeFactsBySubjectProperty.get(`${subject.id}|${property}`) ?? [];
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
private getOutgoingEdges(index: WorkingIndex, subject: Term): Fact[] {
|
|
690
|
+
const edges: Fact[] = [];
|
|
691
|
+
const prefix = `${subject.id}|`;
|
|
692
|
+
for (const [key, facts] of index.edgeFactsBySubjectProperty.entries()) {
|
|
693
|
+
if (key.startsWith(prefix)) {
|
|
694
|
+
edges.push(...facts);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return edges;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
private getIncomingEdges(index: WorkingIndex, object: Term): Fact[] {
|
|
701
|
+
const edges: Fact[] = [];
|
|
702
|
+
const suffix = `|${object.id}`;
|
|
703
|
+
for (const [key, subjects] of index.subjectsByPropertyObject.entries()) {
|
|
704
|
+
if (!key.endsWith(suffix)) {
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
const property = key.slice(0, key.length - suffix.length);
|
|
708
|
+
for (const subjectKey of subjects) {
|
|
709
|
+
const subject = index.termByKey.get(subjectKey);
|
|
710
|
+
if (subject) {
|
|
711
|
+
edges.push(...this.getEdgeFacts(index, subject, property));
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return edges;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
private getIncomingEdgesByProperty(index: WorkingIndex, object: Term, property: string): Fact[] {
|
|
719
|
+
const subjects = index.subjectsByPropertyObject.get(`${property}|${object.id}`) ?? new Set<string>();
|
|
720
|
+
const edges: Fact[] = [];
|
|
721
|
+
for (const subjectKey of subjects) {
|
|
722
|
+
const subject = index.termByKey.get(subjectKey);
|
|
723
|
+
if (subject) {
|
|
724
|
+
edges.push(...this.getEdgeFacts(index, subject, property));
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return edges;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
private getSuperClasses(tbox: TBoxIndex, clazz: string): string[] {
|
|
731
|
+
return tbox.superClasses.get(clazz) ?? [];
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
private getSuperProperties(tbox: TBoxIndex, property: string): string[] {
|
|
735
|
+
return tbox.subProperties.get(property) ?? [property];
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
private getDomains(tbox: TBoxIndex, property: string): string[] {
|
|
739
|
+
return tbox.domain.get(property) ?? [];
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
private getObjectRanges(tbox: TBoxIndex, property: string): string[] {
|
|
743
|
+
return tbox.objectRange.get(property) ?? [];
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private getInverses(tbox: TBoxIndex, property: string): string[] {
|
|
747
|
+
return tbox.inverseRoles.get(property) ?? [];
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
private isSymmetricProperty(tbox: TBoxIndex, property: string): boolean {
|
|
751
|
+
return tbox.symmetricProperties.has(property);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
private isTransitiveProperty(tbox: TBoxIndex, property: string): boolean {
|
|
755
|
+
return tbox.transitiveProperties.has(property);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
private typeFact(subject: Term, classIri: string): Fact {
|
|
759
|
+
return { subject, predicate: RDF_TYPE, object: namedNode(classIri) };
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
private edgeFact(subject: Term, propertyIri: string, object: Term): Fact {
|
|
763
|
+
return { subject, predicate: namedNode(propertyIri), object };
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
private isTypeFact(fact: Fact): fact is Fact & { object: NamedNode } {
|
|
767
|
+
return fact.predicate.value === RDF_TYPE.value && fact.object.termType === 'NamedNode';
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
private asFact(q: Quad): Fact {
|
|
771
|
+
return {
|
|
772
|
+
subject: q.subject,
|
|
773
|
+
predicate: q.predicate as NamedNode,
|
|
774
|
+
object: q.object,
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
private asQuadSubject(term: Term): Quad['subject'] {
|
|
779
|
+
if (term.termType === 'NamedNode' || term.termType === 'BlankNode' || term.termType === 'Variable') {
|
|
780
|
+
return term;
|
|
781
|
+
}
|
|
782
|
+
throw new Error(`Invalid inferred subject term '${term.termType}'.`);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
private asQuadObject(term: Term): Quad['object'] {
|
|
786
|
+
if (term.termType === 'NamedNode'
|
|
787
|
+
|| term.termType === 'BlankNode'
|
|
788
|
+
|| term.termType === 'Variable'
|
|
789
|
+
|| term.termType === 'Literal') {
|
|
790
|
+
return term;
|
|
791
|
+
}
|
|
792
|
+
throw new Error(`Invalid inferred object term '${term.termType}'.`);
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
private factKey(fact: Fact): string {
|
|
796
|
+
return `${fact.subject.id}|${fact.predicate.id}|${fact.object.id}`;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
private uniqueFacts(facts: Fact[]): Fact[] {
|
|
800
|
+
const byKey = new Map<string, Fact>();
|
|
801
|
+
for (const fact of facts) {
|
|
802
|
+
byKey.set(this.factKey(fact), fact);
|
|
803
|
+
}
|
|
804
|
+
return [...byKey.values()];
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
private indexAdd(index: WorkingIndex, fact: Fact): void {
|
|
808
|
+
index.termByKey.set(fact.subject.id, fact.subject);
|
|
809
|
+
index.termByKey.set(fact.object.id, fact.object);
|
|
810
|
+
if (fact.predicate.value === RDF_TYPE.value && fact.object.termType === 'NamedNode') {
|
|
811
|
+
const values = index.typesBySubject.get(fact.subject.id) ?? new Set<string>();
|
|
812
|
+
values.add(fact.object.value);
|
|
813
|
+
index.typesBySubject.set(fact.subject.id, values);
|
|
814
|
+
const subjects = index.subjectsByType.get(fact.object.value) ?? new Set<string>();
|
|
815
|
+
subjects.add(fact.subject.id);
|
|
816
|
+
index.subjectsByType.set(fact.object.value, subjects);
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
const spKey = `${fact.subject.id}|${fact.predicate.value}`;
|
|
820
|
+
const bySp = index.edgeFactsBySubjectProperty.get(spKey) ?? [];
|
|
821
|
+
bySp.push(fact);
|
|
822
|
+
index.edgeFactsBySubjectProperty.set(spKey, bySp);
|
|
823
|
+
const byProperty = index.edgeFactsByProperty.get(fact.predicate.value) ?? [];
|
|
824
|
+
byProperty.push(fact);
|
|
825
|
+
index.edgeFactsByProperty.set(fact.predicate.value, byProperty);
|
|
826
|
+
|
|
827
|
+
const poKey = `${fact.predicate.value}|${fact.object.id}`;
|
|
828
|
+
const subjects = index.subjectsByPropertyObject.get(poKey) ?? new Set<string>();
|
|
829
|
+
subjects.add(fact.subject.id);
|
|
830
|
+
index.subjectsByPropertyObject.set(poKey, subjects);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
private indexRemove(index: WorkingIndex, fact: Fact): void {
|
|
834
|
+
if (fact.predicate.value === RDF_TYPE.value && fact.object.termType === 'NamedNode') {
|
|
835
|
+
const values = index.typesBySubject.get(fact.subject.id);
|
|
836
|
+
if (!values) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
values.delete(fact.object.value);
|
|
840
|
+
if (values.size === 0) {
|
|
841
|
+
index.typesBySubject.delete(fact.subject.id);
|
|
842
|
+
}
|
|
843
|
+
const subjects = index.subjectsByType.get(fact.object.value);
|
|
844
|
+
if (subjects) {
|
|
845
|
+
subjects.delete(fact.subject.id);
|
|
846
|
+
if (subjects.size === 0) {
|
|
847
|
+
index.subjectsByType.delete(fact.object.value);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
const spKey = `${fact.subject.id}|${fact.predicate.value}`;
|
|
854
|
+
const bySp = index.edgeFactsBySubjectProperty.get(spKey) ?? [];
|
|
855
|
+
const nextBySp = bySp.filter((entry) => this.factKey(entry) !== this.factKey(fact));
|
|
856
|
+
if (nextBySp.length === 0) {
|
|
857
|
+
index.edgeFactsBySubjectProperty.delete(spKey);
|
|
858
|
+
} else {
|
|
859
|
+
index.edgeFactsBySubjectProperty.set(spKey, nextBySp);
|
|
860
|
+
}
|
|
861
|
+
const byProperty = index.edgeFactsByProperty.get(fact.predicate.value) ?? [];
|
|
862
|
+
const nextByProperty = byProperty.filter((entry) => this.factKey(entry) !== this.factKey(fact));
|
|
863
|
+
if (nextByProperty.length === 0) {
|
|
864
|
+
index.edgeFactsByProperty.delete(fact.predicate.value);
|
|
865
|
+
} else {
|
|
866
|
+
index.edgeFactsByProperty.set(fact.predicate.value, nextByProperty);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const poKey = `${fact.predicate.value}|${fact.object.id}`;
|
|
870
|
+
const subjects = index.subjectsByPropertyObject.get(poKey);
|
|
871
|
+
if (!subjects) {
|
|
872
|
+
return;
|
|
873
|
+
}
|
|
874
|
+
const stillHas = nextBySp.some((entry) => entry.object.id === fact.object.id);
|
|
875
|
+
if (!stillHas) {
|
|
876
|
+
subjects.delete(fact.subject.id);
|
|
877
|
+
}
|
|
878
|
+
if (subjects.size === 0) {
|
|
879
|
+
index.subjectsByPropertyObject.delete(poKey);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
private collectValidationWarnings(index: WorkingIndex, tbox: TBoxIndex): string[] {
|
|
884
|
+
const warnings: string[] = [];
|
|
885
|
+
|
|
886
|
+
for (const fact of index.facts.values()) {
|
|
887
|
+
if (fact.predicate.value === RDF_TYPE.value) {
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
const ranges = tbox.datatypeRange.get(fact.predicate.value) ?? [];
|
|
891
|
+
if (ranges.length === 0) {
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (fact.object.termType !== 'Literal') {
|
|
895
|
+
warnings.push(`Datatype range violation on ${fact.predicate.value}: expected literal object.`);
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
if (!ranges.includes(fact.object.datatype.value)) {
|
|
899
|
+
warnings.push(
|
|
900
|
+
`Datatype range violation on ${fact.predicate.value}: expected ${ranges.join(', ')}, got ${fact.object.datatype.value}.`,
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
for (const [subjectProperty, facts] of index.edgeFactsBySubjectProperty.entries()) {
|
|
906
|
+
const [subjectId, property] = subjectProperty.split('|', 2);
|
|
907
|
+
if (subjectId.length === 0 || !tbox.functionalProperties.has(property)) {
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
const objectIds = [...new Set(facts.map((fact) => fact.object.id))];
|
|
911
|
+
if (objectIds.length > 1) {
|
|
912
|
+
warnings.push(`Functional property violation on ${property}: ${facts[0]?.subject.value ?? subjectId} has ${objectIds.length} distinct values.`);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
for (const [propertyObject, subjectIds] of index.subjectsByPropertyObject.entries()) {
|
|
917
|
+
const split = propertyObject.indexOf('|');
|
|
918
|
+
const property = split >= 0 ? propertyObject.slice(0, split) : propertyObject;
|
|
919
|
+
const objectId = split >= 0 ? propertyObject.slice(split + 1) : '';
|
|
920
|
+
if (!tbox.inverseFunctionalProperties.has(property) || subjectIds.size <= 1) {
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
const object = index.termByKey.get(objectId);
|
|
924
|
+
const objectLabel = object?.value ?? objectId;
|
|
925
|
+
warnings.push(`Inverse-functional property violation on ${property}: ${objectLabel} has ${subjectIds.size} distinct sources.`);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
return [...new Set(warnings)];
|
|
929
|
+
}
|
|
930
|
+
}
|