@oml/owl 0.19.3 → 0.20.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/out/index.d.ts +3 -0
- package/out/index.js +3 -0
- package/out/index.js.map +1 -1
- package/out/owl/owl-abox.d.ts +16 -5
- package/out/owl/owl-abox.js +365 -188
- package/out/owl/owl-abox.js.map +1 -1
- package/out/owl/owl-imports.js +4 -2
- package/out/owl/owl-imports.js.map +1 -1
- package/out/owl/owl-interfaces.d.ts +26 -1
- package/out/owl/owl-mapper.d.ts +2 -0
- package/out/owl/owl-mapper.js +77 -38
- package/out/owl/owl-mapper.js.map +1 -1
- package/out/owl/owl-service.d.ts +4 -0
- package/out/owl/owl-service.js +139 -44
- package/out/owl/owl-service.js.map +1 -1
- package/out/owl/owl-shacl.d.ts +8 -9
- package/out/owl/owl-shacl.js +43 -117
- package/out/owl/owl-shacl.js.map +1 -1
- package/out/owl/owl-sparql-engine.d.ts +6 -0
- package/out/owl/owl-sparql-engine.js +11 -0
- package/out/owl/owl-sparql-engine.js.map +1 -0
- package/out/owl/owl-sparql-oxigraph.d.ts +34 -0
- package/out/owl/owl-sparql-oxigraph.js +436 -0
- package/out/owl/owl-sparql-oxigraph.js.map +1 -0
- package/out/owl/owl-sparql.d.ts +0 -44
- package/out/owl/owl-sparql.js +0 -320
- package/out/owl/owl-sparql.js.map +1 -1
- package/out/owl/owl-store-lean.d.ts +29 -0
- package/out/owl/owl-store-lean.js +445 -0
- package/out/owl/owl-store-lean.js.map +1 -0
- package/out/owl/owl-store.d.ts +11 -1
- package/out/owl/owl-store.js +80 -6
- package/out/owl/owl-store.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +3 -0
- package/src/owl/owl-abox.ts +401 -200
- package/src/owl/owl-imports.ts +4 -2
- package/src/owl/owl-interfaces.ts +28 -1
- package/src/owl/owl-mapper.ts +79 -41
- package/src/owl/owl-service.ts +149 -49
- package/src/owl/owl-shacl.ts +55 -132
- package/src/owl/owl-sparql-engine.ts +34 -0
- package/src/owl/owl-sparql-oxigraph.ts +527 -0
- package/src/owl/owl-sparql.ts +3 -366
- package/src/owl/owl-store-lean.ts +438 -0
- package/src/owl/owl-store.ts +86 -7
package/src/owl/owl-abox.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Copyright (c) 2026 Modelware. All rights reserved.
|
|
2
2
|
|
|
3
3
|
import { DataFactory } from 'n3';
|
|
4
|
-
import type { NamedNode, Quad,
|
|
4
|
+
import type { NamedNode, Quad, Term } from 'n3';
|
|
5
5
|
import type { OwlStore } from './owl-interfaces.js';
|
|
6
6
|
import type { HasValueRestriction, RuleAtom, RuleTermPattern, TBoxIndex } from './owl-tbox.js';
|
|
7
7
|
|
|
@@ -23,24 +23,43 @@ interface Fact {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
interface ScopeData {
|
|
26
|
+
modelUri: string;
|
|
26
27
|
ownEntailmentGraph: NamedNode;
|
|
27
28
|
ownBaseFacts: Fact[];
|
|
28
29
|
baseKeys: Set<string>;
|
|
29
30
|
importEntKeys: Set<string>;
|
|
30
31
|
ownEntKeys: Set<string>;
|
|
31
32
|
facts: Map<string, Fact>;
|
|
33
|
+
index: WorkingIndex;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
interface WorkingIndex {
|
|
35
37
|
facts: Map<string, Fact>;
|
|
38
|
+
indexed: boolean;
|
|
39
|
+
indexEdgesByObject: boolean;
|
|
40
|
+
indexSubjectsByPropertyObject: boolean;
|
|
36
41
|
typesBySubject: Map<string, Set<string>>;
|
|
37
42
|
subjectsByType: Map<string, Set<string>>;
|
|
43
|
+
edgeFactsBySubject: Map<string, Fact[]>;
|
|
44
|
+
edgeFactsByObject: Map<string, Fact[]>;
|
|
38
45
|
edgeFactsBySubjectProperty: Map<string, Fact[]>;
|
|
39
46
|
edgeFactsByProperty: Map<string, Fact[]>;
|
|
40
47
|
subjectsByPropertyObject: Map<string, Set<string>>;
|
|
41
48
|
termByKey: Map<string, Term>;
|
|
42
49
|
}
|
|
43
50
|
|
|
51
|
+
interface RuleAnchor {
|
|
52
|
+
rule: RuleAtom[];
|
|
53
|
+
head: RuleAtom[];
|
|
54
|
+
atom: RuleAtom;
|
|
55
|
+
anchorIndex: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface ForwardRuleAnchorIndex {
|
|
59
|
+
byClass: Map<string, RuleAnchor[]>;
|
|
60
|
+
byProperty: Map<string, RuleAnchor[]>;
|
|
61
|
+
}
|
|
62
|
+
|
|
44
63
|
export class ABoxEntailmentCache {
|
|
45
64
|
private readonly state = new Map<string, 'clean' | 'dirty' | 'absent'>();
|
|
46
65
|
private readonly deltaPlus = new Map<string, Map<string, Quad>>();
|
|
@@ -117,6 +136,12 @@ export class ABoxEntailmentCache {
|
|
|
117
136
|
}
|
|
118
137
|
|
|
119
138
|
export class ABoxChainer {
|
|
139
|
+
// Interned terms for rule pattern constants (NamedNode/Literal). Pattern constants are
|
|
140
|
+
// immutable and recur across every fact match, so re-creating them per call was a top
|
|
141
|
+
// allocator; equality elsewhere compares by value/id, so identity reuse is safe.
|
|
142
|
+
private readonly termCache = new Map<string, Term>();
|
|
143
|
+
private readonly ruleAnchorIndexCache = new WeakMap<TBoxIndex, ForwardRuleAnchorIndex>();
|
|
144
|
+
|
|
120
145
|
constructor(private readonly reasoningStore: OwlStore) {}
|
|
121
146
|
|
|
122
147
|
chain(
|
|
@@ -126,13 +151,11 @@ export class ABoxChainer {
|
|
|
126
151
|
deltaPlus: Quad[],
|
|
127
152
|
deltaMinus: Quad[],
|
|
128
153
|
): ChainingResult {
|
|
129
|
-
const scope = this.resolveScope(modelUri, readModelUris);
|
|
130
|
-
const store = this.reasoningStore.getStore();
|
|
131
|
-
const index = this.buildIndex(scope.facts);
|
|
132
|
-
|
|
133
154
|
const plusFacts = deltaPlus.map((q) => this.asFact(q));
|
|
134
155
|
const minusFacts = deltaMinus.map((q) => this.asFact(q));
|
|
135
156
|
const fullRecompute = plusFacts.length === 0 && minusFacts.length === 0;
|
|
157
|
+
const scope = this.resolveScope(modelUri, readModelUris, tboxIndex, !fullRecompute);
|
|
158
|
+
const index = scope.index;
|
|
136
159
|
|
|
137
160
|
let iterations = 0;
|
|
138
161
|
let newQuadsCount = 0;
|
|
@@ -141,23 +164,25 @@ export class ABoxChainer {
|
|
|
141
164
|
for (const key of [...scope.ownEntKeys]) {
|
|
142
165
|
const fact = scope.facts.get(key);
|
|
143
166
|
if (fact) {
|
|
144
|
-
this.removeOwnEntailedFact(scope, index,
|
|
167
|
+
this.removeOwnEntailedFact(scope, index, fact);
|
|
145
168
|
}
|
|
146
169
|
}
|
|
147
170
|
}
|
|
148
171
|
|
|
149
|
-
const over = fullRecompute ? [] : this.overdelete(scope, index, tboxIndex, minusFacts
|
|
172
|
+
const over = fullRecompute ? [] : this.overdelete(scope, index, tboxIndex, minusFacts);
|
|
150
173
|
const seed = fullRecompute ? scope.ownBaseFacts : [...plusFacts, ...over];
|
|
151
174
|
|
|
152
|
-
const result = this.materializeAdd(scope, index, tboxIndex, seed,
|
|
175
|
+
const result = this.materializeAdd(scope, index, tboxIndex, seed, !fullRecompute);
|
|
153
176
|
iterations += result.iterations;
|
|
154
177
|
newQuadsCount += result.newQuads;
|
|
155
178
|
|
|
179
|
+
const validationWarnings = this.collectValidationWarnings(index, tboxIndex);
|
|
180
|
+
|
|
156
181
|
return {
|
|
157
182
|
complete: true,
|
|
158
183
|
iterations,
|
|
159
184
|
newQuadsCount,
|
|
160
|
-
validationWarnings
|
|
185
|
+
validationWarnings,
|
|
161
186
|
};
|
|
162
187
|
}
|
|
163
188
|
|
|
@@ -166,13 +191,14 @@ export class ABoxChainer {
|
|
|
166
191
|
index: WorkingIndex,
|
|
167
192
|
tbox: TBoxIndex,
|
|
168
193
|
minusFacts: Fact[],
|
|
169
|
-
store: Store,
|
|
170
194
|
): Fact[] {
|
|
171
195
|
const queue = [...minusFacts];
|
|
196
|
+
let cursor = 0;
|
|
172
197
|
const overByKey = new Map<string, Fact>();
|
|
173
198
|
|
|
174
|
-
while (queue.length
|
|
175
|
-
const current = queue
|
|
199
|
+
while (cursor < queue.length) {
|
|
200
|
+
const current = queue[cursor];
|
|
201
|
+
cursor += 1;
|
|
176
202
|
if (!current) {
|
|
177
203
|
continue;
|
|
178
204
|
}
|
|
@@ -181,7 +207,7 @@ export class ABoxChainer {
|
|
|
181
207
|
if (!scope.ownEntKeys.has(key)) {
|
|
182
208
|
continue;
|
|
183
209
|
}
|
|
184
|
-
this.removeOwnEntailedFact(scope, index,
|
|
210
|
+
this.removeOwnEntailedFact(scope, index, head);
|
|
185
211
|
overByKey.set(key, head);
|
|
186
212
|
queue.push(head);
|
|
187
213
|
}
|
|
@@ -195,7 +221,7 @@ export class ABoxChainer {
|
|
|
195
221
|
index: WorkingIndex,
|
|
196
222
|
tbox: TBoxIndex,
|
|
197
223
|
seedFacts: Fact[],
|
|
198
|
-
|
|
224
|
+
checkSupport: boolean,
|
|
199
225
|
): { iterations: number; newQuads: number } {
|
|
200
226
|
const queue: Fact[] = [];
|
|
201
227
|
const queued = new Set<string>();
|
|
@@ -217,25 +243,39 @@ export class ABoxChainer {
|
|
|
217
243
|
enqueue(seed);
|
|
218
244
|
continue;
|
|
219
245
|
}
|
|
220
|
-
if (!this.canInsertEntailedFact(scope, seed)
|
|
246
|
+
if (!this.canInsertEntailedFact(scope, seed)) {
|
|
221
247
|
continue;
|
|
222
248
|
}
|
|
223
|
-
|
|
249
|
+
if (checkSupport) {
|
|
250
|
+
const supported = this.hasSupport(seed, index, tbox);
|
|
251
|
+
if (!supported) {
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
this.insertOwnEntailedFact(scope, index, seed);
|
|
224
256
|
newQuads += 1;
|
|
225
257
|
enqueue(seed);
|
|
226
258
|
}
|
|
227
259
|
|
|
228
|
-
|
|
229
|
-
|
|
260
|
+
let cursor = 0;
|
|
261
|
+
while (cursor < queue.length) {
|
|
262
|
+
const fact = queue[cursor];
|
|
263
|
+
cursor += 1;
|
|
230
264
|
if (!fact) {
|
|
231
265
|
continue;
|
|
232
266
|
}
|
|
233
267
|
iterations += 1;
|
|
234
268
|
for (const head of this.forwardRules(fact, index, tbox)) {
|
|
235
|
-
if (!this.canInsertEntailedFact(scope, head)
|
|
269
|
+
if (!this.canInsertEntailedFact(scope, head)) {
|
|
236
270
|
continue;
|
|
237
271
|
}
|
|
238
|
-
|
|
272
|
+
if (checkSupport) {
|
|
273
|
+
const supported = this.hasSupport(head, index, tbox);
|
|
274
|
+
if (!supported) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
this.insertOwnEntailedFact(scope, index, head);
|
|
239
279
|
newQuads += 1;
|
|
240
280
|
enqueue(head);
|
|
241
281
|
}
|
|
@@ -286,6 +326,7 @@ export class ABoxChainer {
|
|
|
286
326
|
heads.push(this.edgeFact(y, p, x));
|
|
287
327
|
}
|
|
288
328
|
if (this.isTransitiveProperty(tbox, p)) {
|
|
329
|
+
this.ensureWorkingIndex(index);
|
|
289
330
|
for (const next of this.getEdgeFacts(index, y, p)) {
|
|
290
331
|
heads.push(this.edgeFact(x, p, next.object));
|
|
291
332
|
}
|
|
@@ -305,6 +346,7 @@ export class ABoxChainer {
|
|
|
305
346
|
}
|
|
306
347
|
|
|
307
348
|
private hasSupport(fact: Fact, index: WorkingIndex, tbox: TBoxIndex): boolean {
|
|
349
|
+
this.ensureWorkingIndex(index);
|
|
308
350
|
if (index.facts.has(this.factKey(fact))) {
|
|
309
351
|
return true;
|
|
310
352
|
}
|
|
@@ -408,34 +450,71 @@ export class ABoxChainer {
|
|
|
408
450
|
|
|
409
451
|
private applyForwardRulesTriggeredByFact(fact: Fact, index: WorkingIndex, tbox: TBoxIndex): Fact[] {
|
|
410
452
|
const heads: Fact[] = [];
|
|
453
|
+
const anchors = this.forwardRuleAnchorsForFact(fact, tbox);
|
|
454
|
+
for (const anchor of anchors) {
|
|
455
|
+
const bindings = new Map<string, Term>();
|
|
456
|
+
if (this.matchAtomToFactMut(anchor.atom, fact, bindings) === null) {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
this.ensureWorkingIndex(index);
|
|
460
|
+
for (const resolved of this.resolveRuleBody(anchor.rule, index, bindings, anchor.anchorIndex)) {
|
|
461
|
+
for (const head of anchor.head) {
|
|
462
|
+
const inferred = this.instantiateRuleAtom(head, resolved);
|
|
463
|
+
if (inferred) {
|
|
464
|
+
heads.push(inferred);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return this.uniqueFacts(heads);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
private forwardRuleAnchorsForFact(fact: Fact, tbox: TBoxIndex): RuleAnchor[] {
|
|
473
|
+
if (tbox.forwardRules.length === 0) {
|
|
474
|
+
return [];
|
|
475
|
+
}
|
|
476
|
+
const anchorIndex = this.getForwardRuleAnchorIndex(tbox);
|
|
477
|
+
if (this.isTypeFact(fact)) {
|
|
478
|
+
return anchorIndex.byClass.get(fact.object.value) ?? [];
|
|
479
|
+
}
|
|
480
|
+
return anchorIndex.byProperty.get(fact.predicate.value) ?? [];
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private getForwardRuleAnchorIndex(tbox: TBoxIndex): ForwardRuleAnchorIndex {
|
|
484
|
+
const cached = this.ruleAnchorIndexCache.get(tbox);
|
|
485
|
+
if (cached) {
|
|
486
|
+
return cached;
|
|
487
|
+
}
|
|
488
|
+
const created: ForwardRuleAnchorIndex = {
|
|
489
|
+
byClass: new Map(),
|
|
490
|
+
byProperty: new Map(),
|
|
491
|
+
};
|
|
411
492
|
for (const rule of tbox.forwardRules) {
|
|
412
493
|
rule.body.forEach((atom, anchorIndex) => {
|
|
413
|
-
const
|
|
414
|
-
if (
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
}
|
|
423
|
-
}
|
|
494
|
+
const anchor: RuleAnchor = { rule: rule.body, head: rule.head, atom, anchorIndex };
|
|
495
|
+
if (atom.kind === 'class') {
|
|
496
|
+
const anchors = created.byClass.get(atom.classIri) ?? [];
|
|
497
|
+
anchors.push(anchor);
|
|
498
|
+
created.byClass.set(atom.classIri, anchors);
|
|
499
|
+
} else {
|
|
500
|
+
const anchors = created.byProperty.get(atom.propertyIri) ?? [];
|
|
501
|
+
anchors.push(anchor);
|
|
502
|
+
created.byProperty.set(atom.propertyIri, anchors);
|
|
424
503
|
}
|
|
425
504
|
});
|
|
426
505
|
}
|
|
427
|
-
|
|
506
|
+
this.ruleAnchorIndexCache.set(tbox, created);
|
|
507
|
+
return created;
|
|
428
508
|
}
|
|
429
509
|
|
|
430
510
|
private hasForwardRuleSupport(fact: Fact, index: WorkingIndex, tbox: TBoxIndex): boolean {
|
|
431
511
|
for (const rule of tbox.forwardRules) {
|
|
432
512
|
for (const head of rule.head) {
|
|
433
|
-
const bindings =
|
|
434
|
-
if (
|
|
513
|
+
const bindings = new Map<string, Term>();
|
|
514
|
+
if (this.matchAtomToFactMut(head, fact, bindings) === null) {
|
|
435
515
|
continue;
|
|
436
516
|
}
|
|
437
|
-
const
|
|
438
|
-
if (resolved.length > 0) {
|
|
517
|
+
for (const _resolved of this.resolveRuleBody(rule.body, index, bindings, -1)) {
|
|
439
518
|
return true;
|
|
440
519
|
}
|
|
441
520
|
}
|
|
@@ -443,107 +522,135 @@ export class ABoxChainer {
|
|
|
443
522
|
return false;
|
|
444
523
|
}
|
|
445
524
|
|
|
446
|
-
|
|
525
|
+
// Join the (non-anchor) rule body atoms against the working index, yielding each complete
|
|
526
|
+
// variable binding. The bindings Map is mutated in place and backtracked, and yielded by
|
|
527
|
+
// reference: consumers must read the binding fully before requesting the next one (they do —
|
|
528
|
+
// they instantiate the rule head synchronously). This avoids the per-binding Map copies that
|
|
529
|
+
// dominated allocation. Semantics are identical to the prior copy-based cartesian expansion.
|
|
530
|
+
private *resolveRuleBody(
|
|
447
531
|
body: RuleAtom[],
|
|
448
532
|
index: WorkingIndex,
|
|
449
533
|
bindings: Map<string, Term>,
|
|
450
534
|
satisfiedIndex: number,
|
|
451
|
-
):
|
|
452
|
-
const pending =
|
|
453
|
-
|
|
535
|
+
): Generator<Map<string, Term>> {
|
|
536
|
+
const pending: RuleAtom[] = [];
|
|
537
|
+
for (let i = 0; i < body.length; i += 1) {
|
|
538
|
+
if (i !== satisfiedIndex) {
|
|
539
|
+
pending.push(body[i]);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
yield* this.resolvePending(pending, 0, index, bindings);
|
|
454
543
|
}
|
|
455
544
|
|
|
456
|
-
private
|
|
545
|
+
private *resolvePending(
|
|
457
546
|
pending: RuleAtom[],
|
|
547
|
+
pos: number,
|
|
458
548
|
index: WorkingIndex,
|
|
459
549
|
bindings: Map<string, Term>,
|
|
460
|
-
):
|
|
461
|
-
if (pending.length
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
const [current, ...rest] = pending;
|
|
466
|
-
const matches = this.findMatchesForAtom(current, index, bindings);
|
|
467
|
-
const resolved: Array<Map<string, Term>> = [];
|
|
468
|
-
for (const nextBindings of matches) {
|
|
469
|
-
resolved.push(...this.resolvePendingAtoms(rest, index, nextBindings));
|
|
550
|
+
): Generator<Map<string, Term>> {
|
|
551
|
+
if (pos === pending.length) {
|
|
552
|
+
yield bindings;
|
|
553
|
+
return;
|
|
470
554
|
}
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
private findMatchesForAtom(atom: RuleAtom, index: WorkingIndex, bindings: Map<string, Term>): Array<Map<string, Term>> {
|
|
475
|
-
const matches: Array<Map<string, Term>> = [];
|
|
555
|
+
const atom = pending[pos];
|
|
476
556
|
for (const fact of this.candidateFacts(atom, index, bindings)) {
|
|
477
|
-
const
|
|
478
|
-
if (
|
|
479
|
-
|
|
557
|
+
const added = this.matchAtomToFactMut(atom, fact, bindings);
|
|
558
|
+
if (added === null) {
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
yield* this.resolvePending(pending, pos + 1, index, bindings);
|
|
562
|
+
for (const key of added) {
|
|
563
|
+
bindings.delete(key);
|
|
480
564
|
}
|
|
481
565
|
}
|
|
482
|
-
return matches;
|
|
483
566
|
}
|
|
484
567
|
|
|
485
|
-
private candidateFacts(atom: RuleAtom, index: WorkingIndex, bindings: Map<string, Term>): Fact
|
|
568
|
+
private *candidateFacts(atom: RuleAtom, index: WorkingIndex, bindings: Map<string, Term>): Generator<Fact> {
|
|
486
569
|
if (atom.kind === 'class') {
|
|
487
570
|
const bound = this.resolvePatternTerm(atom.argument, bindings);
|
|
488
571
|
if (bound) {
|
|
489
|
-
|
|
572
|
+
for (const classIri of this.getTypes(index, bound)) {
|
|
573
|
+
yield this.typeFact(bound, classIri);
|
|
574
|
+
}
|
|
575
|
+
return;
|
|
490
576
|
}
|
|
491
|
-
const subjects = index.subjectsByType.get(atom.classIri)
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
577
|
+
const subjects = index.subjectsByType.get(atom.classIri);
|
|
578
|
+
if (!subjects) {
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
for (const subjectKey of subjects) {
|
|
582
|
+
const subject = index.termByKey.get(subjectKey);
|
|
583
|
+
if (subject) {
|
|
584
|
+
yield this.typeFact(subject, atom.classIri);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return;
|
|
496
588
|
}
|
|
497
589
|
|
|
498
590
|
const left = this.resolvePatternTerm(atom.argument1, bindings);
|
|
499
591
|
const right = this.resolvePatternTerm(atom.argument2, bindings);
|
|
500
592
|
if (left) {
|
|
501
|
-
|
|
593
|
+
yield* this.getEdgeFacts(index, left, atom.propertyIri);
|
|
594
|
+
return;
|
|
502
595
|
}
|
|
503
596
|
if (right) {
|
|
504
|
-
|
|
597
|
+
yield* this.getIncomingEdgesByProperty(index, right, atom.propertyIri);
|
|
598
|
+
return;
|
|
505
599
|
}
|
|
506
|
-
|
|
600
|
+
yield* index.edgeFactsByProperty.get(atom.propertyIri) ?? [];
|
|
507
601
|
}
|
|
508
602
|
|
|
509
|
-
|
|
603
|
+
// Match an atom against a fact, mutating `bindings` in place. Returns the list of variable
|
|
604
|
+
// names newly bound (for the caller to backtrack), or null if the atom does not match.
|
|
605
|
+
// On a partial-then-failed match the names bound so far are undone before returning null.
|
|
606
|
+
private matchAtomToFactMut(atom: RuleAtom, fact: Fact, bindings: Map<string, Term>): string[] | null {
|
|
607
|
+
const added: string[] = [];
|
|
510
608
|
if (atom.kind === 'class') {
|
|
511
609
|
if (!this.isTypeFact(fact) || fact.object.value !== atom.classIri) {
|
|
512
|
-
return
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
if (!this.matchPatternMut(atom.argument, fact.subject, bindings, added)) {
|
|
613
|
+
this.undoBindings(bindings, added);
|
|
614
|
+
return null;
|
|
513
615
|
}
|
|
514
|
-
return
|
|
616
|
+
return added;
|
|
515
617
|
}
|
|
516
618
|
if (fact.predicate.value !== atom.propertyIri) {
|
|
517
|
-
return
|
|
619
|
+
return null;
|
|
518
620
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
621
|
+
if (!this.matchPatternMut(atom.argument1, fact.subject, bindings, added)
|
|
622
|
+
|| !this.matchPatternMut(atom.argument2, fact.object, bindings, added)) {
|
|
623
|
+
this.undoBindings(bindings, added);
|
|
624
|
+
return null;
|
|
522
625
|
}
|
|
523
|
-
return
|
|
626
|
+
return added;
|
|
524
627
|
}
|
|
525
628
|
|
|
526
|
-
private
|
|
629
|
+
private matchPatternMut(pattern: RuleTermPattern, term: Term, bindings: Map<string, Term>, added: string[]): boolean {
|
|
527
630
|
if (pattern.kind === 'variable') {
|
|
528
631
|
const existing = bindings.get(pattern.name);
|
|
529
632
|
if (existing) {
|
|
530
|
-
return this.sameTerm(existing, term)
|
|
633
|
+
return this.sameTerm(existing, term);
|
|
531
634
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return
|
|
635
|
+
bindings.set(pattern.name, term);
|
|
636
|
+
added.push(pattern.name);
|
|
637
|
+
return true;
|
|
535
638
|
}
|
|
536
639
|
if (pattern.kind === 'named') {
|
|
537
|
-
return term.termType === 'NamedNode' && term.value === pattern.iri
|
|
640
|
+
return term.termType === 'NamedNode' && term.value === pattern.iri;
|
|
538
641
|
}
|
|
539
642
|
if (term.termType !== 'Literal') {
|
|
540
|
-
return
|
|
643
|
+
return false;
|
|
541
644
|
}
|
|
542
645
|
return term.value === pattern.value
|
|
543
646
|
&& term.datatype.value === pattern.datatype
|
|
544
|
-
&& term.language === pattern.language
|
|
545
|
-
|
|
546
|
-
|
|
647
|
+
&& term.language === pattern.language;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
private undoBindings(bindings: Map<string, Term>, added: string[]): void {
|
|
651
|
+
for (const key of added) {
|
|
652
|
+
bindings.delete(key);
|
|
653
|
+
}
|
|
547
654
|
}
|
|
548
655
|
|
|
549
656
|
private resolvePatternTerm(pattern: RuleTermPattern, bindings: Map<string, Term>): Term | undefined {
|
|
@@ -551,9 +658,29 @@ export class ABoxChainer {
|
|
|
551
658
|
return bindings.get(pattern.name);
|
|
552
659
|
}
|
|
553
660
|
if (pattern.kind === 'named') {
|
|
554
|
-
return
|
|
661
|
+
return this.internNamed(pattern.iri);
|
|
662
|
+
}
|
|
663
|
+
return this.internLiteral(pattern.value, pattern.datatype, pattern.language);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
private internNamed(iri: string): Term {
|
|
667
|
+
const key = `N|${iri}`;
|
|
668
|
+
let term = this.termCache.get(key);
|
|
669
|
+
if (!term) {
|
|
670
|
+
term = namedNode(iri);
|
|
671
|
+
this.termCache.set(key, term);
|
|
672
|
+
}
|
|
673
|
+
return term;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
private internLiteral(value: string, datatype: string, language: string): Term {
|
|
677
|
+
const key = `L|${value}|${datatype}|${language}`;
|
|
678
|
+
let term = this.termCache.get(key);
|
|
679
|
+
if (!term) {
|
|
680
|
+
term = DataFactory.literal(value, language || namedNode(datatype));
|
|
681
|
+
this.termCache.set(key, term);
|
|
555
682
|
}
|
|
556
|
-
return
|
|
683
|
+
return term;
|
|
557
684
|
}
|
|
558
685
|
|
|
559
686
|
private instantiateRuleAtom(atom: RuleAtom, bindings: Map<string, Term>): Fact | undefined {
|
|
@@ -581,105 +708,96 @@ export class ABoxChainer {
|
|
|
581
708
|
&& left.language === right.language));
|
|
582
709
|
}
|
|
583
710
|
|
|
584
|
-
private resolveScope(modelUri: string, readModelUris: ReadonlyArray<string
|
|
585
|
-
const store = this.reasoningStore.getStore();
|
|
711
|
+
private resolveScope(modelUri: string, readModelUris: ReadonlyArray<string>, tbox: TBoxIndex, eagerIndex: boolean): ScopeData {
|
|
586
712
|
const facts = new Map<string, Fact>();
|
|
713
|
+
const index = this.emptyWorkingIndex(facts, tbox, eagerIndex || tbox.transitiveProperties.size > 0);
|
|
587
714
|
const baseKeys = new Set<string>();
|
|
588
715
|
const importEntKeys = new Set<string>();
|
|
589
716
|
const ownEntKeys = new Set<string>();
|
|
590
|
-
const
|
|
717
|
+
const ownBaseFactsByKey = new Map<string, Fact>();
|
|
591
718
|
const ownGraphs = this.reasoningStore.graphs(modelUri);
|
|
592
719
|
|
|
593
720
|
for (const uri of readModelUris) {
|
|
594
|
-
let graphs;
|
|
595
721
|
try {
|
|
596
|
-
|
|
722
|
+
this.reasoningStore.graphs(uri);
|
|
597
723
|
} catch {
|
|
598
724
|
continue;
|
|
599
725
|
}
|
|
726
|
+
const isOwn = uri === modelUri;
|
|
600
727
|
|
|
601
|
-
|
|
602
|
-
const fact =
|
|
603
|
-
|
|
604
|
-
facts.set(key, fact);
|
|
728
|
+
this.reasoningStore.forEachAssertedFact(uri, (subject, predicate, object, key) => {
|
|
729
|
+
const fact: Fact = { subject, predicate, object };
|
|
730
|
+
this.addResolvedScopeFact(facts, index, key, fact);
|
|
605
731
|
baseKeys.add(key);
|
|
606
|
-
if (
|
|
607
|
-
|
|
732
|
+
if (isOwn) {
|
|
733
|
+
ownBaseFactsByKey.set(key, fact);
|
|
608
734
|
}
|
|
609
|
-
}
|
|
735
|
+
});
|
|
610
736
|
|
|
611
|
-
|
|
612
|
-
const fact =
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
if (uri === modelUri) {
|
|
737
|
+
this.reasoningStore.forEachEntailedFact(uri, (subject, predicate, object, key) => {
|
|
738
|
+
const fact: Fact = { subject, predicate, object };
|
|
739
|
+
this.addResolvedScopeFact(facts, index, key, fact);
|
|
740
|
+
if (isOwn) {
|
|
616
741
|
ownEntKeys.add(key);
|
|
617
742
|
} else {
|
|
618
743
|
importEntKeys.add(key);
|
|
619
744
|
}
|
|
620
|
-
}
|
|
745
|
+
});
|
|
621
746
|
}
|
|
622
747
|
|
|
623
748
|
return {
|
|
749
|
+
modelUri,
|
|
624
750
|
ownEntailmentGraph: ownGraphs.entailments,
|
|
625
|
-
ownBaseFacts:
|
|
751
|
+
ownBaseFacts: [...ownBaseFactsByKey.values()],
|
|
626
752
|
baseKeys,
|
|
627
753
|
importEntKeys,
|
|
628
754
|
ownEntKeys,
|
|
629
755
|
facts,
|
|
756
|
+
index,
|
|
630
757
|
};
|
|
631
758
|
}
|
|
632
759
|
|
|
633
|
-
private
|
|
634
|
-
const
|
|
635
|
-
const
|
|
636
|
-
const
|
|
637
|
-
const edgeFactsByProperty = new Map<string, Fact[]>();
|
|
638
|
-
const subjectsByPropertyObject = new Map<string, Set<string>>();
|
|
639
|
-
const termByKey = new Map<string, Term>();
|
|
640
|
-
|
|
641
|
-
for (const fact of facts.values()) {
|
|
642
|
-
const sKey = fact.subject.id;
|
|
643
|
-
const oKey = fact.object.id;
|
|
644
|
-
termByKey.set(sKey, fact.subject);
|
|
645
|
-
termByKey.set(oKey, fact.object);
|
|
646
|
-
|
|
647
|
-
if (fact.predicate.value === RDF_TYPE.value && fact.object.termType === 'NamedNode') {
|
|
648
|
-
const values = typesBySubject.get(sKey) ?? new Set<string>();
|
|
649
|
-
values.add(fact.object.value);
|
|
650
|
-
typesBySubject.set(sKey, values);
|
|
651
|
-
const subjects = subjectsByType.get(fact.object.value) ?? new Set<string>();
|
|
652
|
-
subjects.add(sKey);
|
|
653
|
-
subjectsByType.set(fact.object.value, subjects);
|
|
654
|
-
continue;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
const spKey = `${sKey}|${fact.predicate.value}`;
|
|
658
|
-
const bySp = edgeFactsBySubjectProperty.get(spKey) ?? [];
|
|
659
|
-
bySp.push(fact);
|
|
660
|
-
edgeFactsBySubjectProperty.set(spKey, bySp);
|
|
661
|
-
const byProperty = edgeFactsByProperty.get(fact.predicate.value) ?? [];
|
|
662
|
-
byProperty.push(fact);
|
|
663
|
-
edgeFactsByProperty.set(fact.predicate.value, byProperty);
|
|
664
|
-
|
|
665
|
-
const poKey = `${fact.predicate.value}|${oKey}`;
|
|
666
|
-
const subjects = subjectsByPropertyObject.get(poKey) ?? new Set<string>();
|
|
667
|
-
subjects.add(sKey);
|
|
668
|
-
subjectsByPropertyObject.set(poKey, subjects);
|
|
669
|
-
}
|
|
670
|
-
|
|
760
|
+
private emptyWorkingIndex(facts: Map<string, Fact>, tbox: TBoxIndex, indexed: boolean): WorkingIndex {
|
|
761
|
+
const hasTransitiveProperties = tbox.transitiveProperties.size > 0;
|
|
762
|
+
const indexEdgesByObject = tbox.objectRange.size > 0;
|
|
763
|
+
const indexSubjectsByPropertyObject = hasTransitiveProperties;
|
|
671
764
|
return {
|
|
672
765
|
facts,
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
766
|
+
indexed,
|
|
767
|
+
indexEdgesByObject,
|
|
768
|
+
indexSubjectsByPropertyObject,
|
|
769
|
+
typesBySubject: new Map(),
|
|
770
|
+
subjectsByType: new Map(),
|
|
771
|
+
edgeFactsBySubject: new Map(),
|
|
772
|
+
edgeFactsByObject: new Map(),
|
|
773
|
+
edgeFactsBySubjectProperty: new Map(),
|
|
774
|
+
edgeFactsByProperty: new Map(),
|
|
775
|
+
subjectsByPropertyObject: new Map(),
|
|
776
|
+
termByKey: new Map(),
|
|
679
777
|
};
|
|
680
778
|
}
|
|
681
779
|
|
|
682
|
-
private
|
|
780
|
+
private addResolvedScopeFact(facts: Map<string, Fact>, index: WorkingIndex, key: string, fact: Fact): void {
|
|
781
|
+
if (facts.has(key)) {
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
facts.set(key, fact);
|
|
785
|
+
if (index.indexed) {
|
|
786
|
+
this.indexAdd(index, fact);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
private ensureWorkingIndex(index: WorkingIndex): void {
|
|
791
|
+
if (index.indexed) {
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
index.indexed = true;
|
|
795
|
+
for (const fact of index.facts.values()) {
|
|
796
|
+
this.indexAdd(index, fact);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
private insertOwnEntailedFact(scope: ScopeData, index: WorkingIndex, fact: Fact): void {
|
|
683
801
|
const key = this.factKey(fact);
|
|
684
802
|
if (scope.ownEntKeys.has(key)) {
|
|
685
803
|
return;
|
|
@@ -687,11 +805,13 @@ export class ABoxChainer {
|
|
|
687
805
|
scope.ownEntKeys.add(key);
|
|
688
806
|
scope.facts.set(key, fact);
|
|
689
807
|
index.facts.set(key, fact);
|
|
690
|
-
|
|
691
|
-
|
|
808
|
+
if (index.indexed) {
|
|
809
|
+
this.indexAdd(index, fact);
|
|
810
|
+
}
|
|
811
|
+
this.reasoningStore.addEntailment(scope.modelUri, quad(this.asQuadSubject(fact.subject), fact.predicate, this.asQuadObject(fact.object), scope.ownEntailmentGraph));
|
|
692
812
|
}
|
|
693
813
|
|
|
694
|
-
private removeOwnEntailedFact(scope: ScopeData, index: WorkingIndex,
|
|
814
|
+
private removeOwnEntailedFact(scope: ScopeData, index: WorkingIndex, fact: Fact): void {
|
|
695
815
|
const key = this.factKey(fact);
|
|
696
816
|
if (!scope.ownEntKeys.has(key)) {
|
|
697
817
|
return;
|
|
@@ -699,8 +819,10 @@ export class ABoxChainer {
|
|
|
699
819
|
scope.ownEntKeys.delete(key);
|
|
700
820
|
scope.facts.delete(key);
|
|
701
821
|
index.facts.delete(key);
|
|
702
|
-
|
|
703
|
-
|
|
822
|
+
if (index.indexed) {
|
|
823
|
+
this.indexRemove(index, fact);
|
|
824
|
+
}
|
|
825
|
+
this.reasoningStore.removeEntailment(scope.modelUri, quad(this.asQuadSubject(fact.subject), fact.predicate, this.asQuadObject(fact.object), scope.ownEntailmentGraph));
|
|
704
826
|
}
|
|
705
827
|
|
|
706
828
|
private canInsertEntailedFact(scope: ScopeData, fact: Fact): boolean {
|
|
@@ -717,35 +839,20 @@ export class ABoxChainer {
|
|
|
717
839
|
}
|
|
718
840
|
|
|
719
841
|
private getOutgoingEdges(index: WorkingIndex, subject: Term): Fact[] {
|
|
720
|
-
|
|
721
|
-
const prefix = `${subject.id}|`;
|
|
722
|
-
for (const [key, facts] of index.edgeFactsBySubjectProperty.entries()) {
|
|
723
|
-
if (key.startsWith(prefix)) {
|
|
724
|
-
edges.push(...facts);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
return edges;
|
|
842
|
+
return index.edgeFactsBySubject.get(subject.id) ?? [];
|
|
728
843
|
}
|
|
729
844
|
|
|
730
845
|
private getIncomingEdges(index: WorkingIndex, object: Term): Fact[] {
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
for (const [key, subjects] of index.subjectsByPropertyObject.entries()) {
|
|
734
|
-
if (!key.endsWith(suffix)) {
|
|
735
|
-
continue;
|
|
736
|
-
}
|
|
737
|
-
const property = key.slice(0, key.length - suffix.length);
|
|
738
|
-
for (const subjectKey of subjects) {
|
|
739
|
-
const subject = index.termByKey.get(subjectKey);
|
|
740
|
-
if (subject) {
|
|
741
|
-
edges.push(...this.getEdgeFacts(index, subject, property));
|
|
742
|
-
}
|
|
743
|
-
}
|
|
846
|
+
if (!index.indexEdgesByObject) {
|
|
847
|
+
return [];
|
|
744
848
|
}
|
|
745
|
-
return
|
|
849
|
+
return index.edgeFactsByObject.get(object.id) ?? [];
|
|
746
850
|
}
|
|
747
851
|
|
|
748
852
|
private getIncomingEdgesByProperty(index: WorkingIndex, object: Term, property: string): Fact[] {
|
|
853
|
+
if (!index.indexSubjectsByPropertyObject) {
|
|
854
|
+
return (index.edgeFactsByProperty.get(property) ?? []).filter((fact) => fact.object.id === object.id);
|
|
855
|
+
}
|
|
749
856
|
const subjects = index.subjectsByPropertyObject.get(`${property}|${object.id}`) ?? new Set<string>();
|
|
750
857
|
const edges: Fact[] = [];
|
|
751
858
|
for (const subjectKey of subjects) {
|
|
@@ -858,6 +965,14 @@ export class ABoxChainer {
|
|
|
858
965
|
index.subjectsByType.set(fact.object.value, subjects);
|
|
859
966
|
return;
|
|
860
967
|
}
|
|
968
|
+
const bySubject = index.edgeFactsBySubject.get(fact.subject.id) ?? [];
|
|
969
|
+
bySubject.push(fact);
|
|
970
|
+
index.edgeFactsBySubject.set(fact.subject.id, bySubject);
|
|
971
|
+
if (index.indexEdgesByObject) {
|
|
972
|
+
const byObject = index.edgeFactsByObject.get(fact.object.id) ?? [];
|
|
973
|
+
byObject.push(fact);
|
|
974
|
+
index.edgeFactsByObject.set(fact.object.id, byObject);
|
|
975
|
+
}
|
|
861
976
|
const spKey = `${fact.subject.id}|${fact.predicate.value}`;
|
|
862
977
|
const bySp = index.edgeFactsBySubjectProperty.get(spKey) ?? [];
|
|
863
978
|
bySp.push(fact);
|
|
@@ -866,10 +981,12 @@ export class ABoxChainer {
|
|
|
866
981
|
byProperty.push(fact);
|
|
867
982
|
index.edgeFactsByProperty.set(fact.predicate.value, byProperty);
|
|
868
983
|
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
984
|
+
if (index.indexSubjectsByPropertyObject) {
|
|
985
|
+
const poKey = `${fact.predicate.value}|${fact.object.id}`;
|
|
986
|
+
const subjects = index.subjectsByPropertyObject.get(poKey) ?? new Set<string>();
|
|
987
|
+
subjects.add(fact.subject.id);
|
|
988
|
+
index.subjectsByPropertyObject.set(poKey, subjects);
|
|
989
|
+
}
|
|
873
990
|
}
|
|
874
991
|
|
|
875
992
|
private indexRemove(index: WorkingIndex, fact: Fact): void {
|
|
@@ -892,42 +1009,71 @@ export class ABoxChainer {
|
|
|
892
1009
|
return;
|
|
893
1010
|
}
|
|
894
1011
|
|
|
1012
|
+
const bySubject = index.edgeFactsBySubject.get(fact.subject.id) ?? [];
|
|
1013
|
+
const nextBySubject = this.removeFactFromArray(bySubject, fact);
|
|
1014
|
+
if (nextBySubject.length === 0) {
|
|
1015
|
+
index.edgeFactsBySubject.delete(fact.subject.id);
|
|
1016
|
+
} else {
|
|
1017
|
+
index.edgeFactsBySubject.set(fact.subject.id, nextBySubject);
|
|
1018
|
+
}
|
|
1019
|
+
if (index.indexEdgesByObject) {
|
|
1020
|
+
const byObject = index.edgeFactsByObject.get(fact.object.id) ?? [];
|
|
1021
|
+
const nextByObject = this.removeFactFromArray(byObject, fact);
|
|
1022
|
+
if (nextByObject.length === 0) {
|
|
1023
|
+
index.edgeFactsByObject.delete(fact.object.id);
|
|
1024
|
+
} else {
|
|
1025
|
+
index.edgeFactsByObject.set(fact.object.id, nextByObject);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
895
1028
|
const spKey = `${fact.subject.id}|${fact.predicate.value}`;
|
|
896
1029
|
const bySp = index.edgeFactsBySubjectProperty.get(spKey) ?? [];
|
|
897
|
-
const nextBySp =
|
|
1030
|
+
const nextBySp = this.removeFactFromArray(bySp, fact);
|
|
898
1031
|
if (nextBySp.length === 0) {
|
|
899
1032
|
index.edgeFactsBySubjectProperty.delete(spKey);
|
|
900
1033
|
} else {
|
|
901
1034
|
index.edgeFactsBySubjectProperty.set(spKey, nextBySp);
|
|
902
1035
|
}
|
|
903
1036
|
const byProperty = index.edgeFactsByProperty.get(fact.predicate.value) ?? [];
|
|
904
|
-
const nextByProperty =
|
|
1037
|
+
const nextByProperty = this.removeFactFromArray(byProperty, fact);
|
|
905
1038
|
if (nextByProperty.length === 0) {
|
|
906
1039
|
index.edgeFactsByProperty.delete(fact.predicate.value);
|
|
907
1040
|
} else {
|
|
908
1041
|
index.edgeFactsByProperty.set(fact.predicate.value, nextByProperty);
|
|
909
1042
|
}
|
|
910
1043
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1044
|
+
if (index.indexSubjectsByPropertyObject) {
|
|
1045
|
+
const poKey = `${fact.predicate.value}|${fact.object.id}`;
|
|
1046
|
+
const subjects = index.subjectsByPropertyObject.get(poKey);
|
|
1047
|
+
if (!subjects) {
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
const stillHas = nextBySp.some((entry) => entry.object.id === fact.object.id);
|
|
1051
|
+
if (!stillHas) {
|
|
1052
|
+
subjects.delete(fact.subject.id);
|
|
1053
|
+
}
|
|
1054
|
+
if (subjects.size === 0) {
|
|
1055
|
+
index.subjectsByPropertyObject.delete(poKey);
|
|
1056
|
+
}
|
|
922
1057
|
}
|
|
923
1058
|
}
|
|
924
1059
|
|
|
1060
|
+
private removeFactFromArray(facts: Fact[], target: Fact): Fact[] {
|
|
1061
|
+
return facts.filter((fact) =>
|
|
1062
|
+
fact.subject.id !== target.subject.id
|
|
1063
|
+
|| fact.predicate.id !== target.predicate.id
|
|
1064
|
+
|| fact.object.id !== target.object.id
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
925
1068
|
private collectValidationWarnings(_index: WorkingIndex, _tbox: TBoxIndex): string[] {
|
|
926
1069
|
const warnings: string[] = [];
|
|
1070
|
+
if (!_index.indexed) {
|
|
1071
|
+
return this.collectValidationWarningsFromFacts(_index.facts.values(), _tbox);
|
|
1072
|
+
}
|
|
927
1073
|
|
|
928
1074
|
for (const propertyIri of _tbox.functionalProperties) {
|
|
929
1075
|
const bySubject = new Map<string, Set<string>>();
|
|
930
|
-
const facts =
|
|
1076
|
+
const facts = this.validationFactsForProperty(_index, propertyIri);
|
|
931
1077
|
for (const fact of facts) {
|
|
932
1078
|
const objects = bySubject.get(fact.subject.id) ?? new Set<string>();
|
|
933
1079
|
objects.add(fact.object.id);
|
|
@@ -942,7 +1088,7 @@ export class ABoxChainer {
|
|
|
942
1088
|
|
|
943
1089
|
for (const propertyIri of _tbox.inverseFunctionalProperties) {
|
|
944
1090
|
const byObject = new Map<string, Set<string>>();
|
|
945
|
-
const facts =
|
|
1091
|
+
const facts = this.validationFactsForProperty(_index, propertyIri);
|
|
946
1092
|
for (const fact of facts) {
|
|
947
1093
|
const subjects = byObject.get(fact.object.id) ?? new Set<string>();
|
|
948
1094
|
subjects.add(fact.subject.id);
|
|
@@ -957,4 +1103,59 @@ export class ABoxChainer {
|
|
|
957
1103
|
|
|
958
1104
|
return warnings;
|
|
959
1105
|
}
|
|
1106
|
+
|
|
1107
|
+
private collectValidationWarningsFromFacts(facts: Iterable<Fact>, tbox: TBoxIndex): string[] {
|
|
1108
|
+
const functionalByPropertySubject = new Map<string, Set<string>>();
|
|
1109
|
+
const inverseFunctionalByPropertyObject = new Map<string, Set<string>>();
|
|
1110
|
+
|
|
1111
|
+
for (const fact of facts) {
|
|
1112
|
+
const propertyIri = fact.predicate.value;
|
|
1113
|
+
if (tbox.functionalProperties.has(propertyIri)) {
|
|
1114
|
+
const key = `${propertyIri}|${fact.subject.id}`;
|
|
1115
|
+
const objects = functionalByPropertySubject.get(key) ?? new Set<string>();
|
|
1116
|
+
objects.add(fact.object.id);
|
|
1117
|
+
functionalByPropertySubject.set(key, objects);
|
|
1118
|
+
}
|
|
1119
|
+
if (tbox.inverseFunctionalProperties.has(propertyIri)) {
|
|
1120
|
+
const key = `${propertyIri}|${fact.object.id}`;
|
|
1121
|
+
const subjects = inverseFunctionalByPropertyObject.get(key) ?? new Set<string>();
|
|
1122
|
+
subjects.add(fact.subject.id);
|
|
1123
|
+
inverseFunctionalByPropertyObject.set(key, subjects);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
const warnings: string[] = [];
|
|
1128
|
+
for (const [key, objects] of functionalByPropertySubject) {
|
|
1129
|
+
if (objects.size <= 1) {
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
const separator = key.lastIndexOf('|');
|
|
1133
|
+
const propertyIri = key.slice(0, separator);
|
|
1134
|
+
const subjectId = key.slice(separator + 1);
|
|
1135
|
+
warnings.push(`Functional property violation: ${propertyIri} has multiple values for subject ${subjectId}`);
|
|
1136
|
+
}
|
|
1137
|
+
for (const [key, subjects] of inverseFunctionalByPropertyObject) {
|
|
1138
|
+
if (subjects.size <= 1) {
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
const separator = key.lastIndexOf('|');
|
|
1142
|
+
const propertyIri = key.slice(0, separator);
|
|
1143
|
+
const objectId = key.slice(separator + 1);
|
|
1144
|
+
warnings.push(`Inverse-functional property violation: ${propertyIri} has multiple subjects for object ${objectId}`);
|
|
1145
|
+
}
|
|
1146
|
+
return warnings;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
private validationFactsForProperty(index: WorkingIndex, propertyIri: string): Fact[] {
|
|
1150
|
+
if (index.indexed) {
|
|
1151
|
+
return index.edgeFactsByProperty.get(propertyIri) ?? [];
|
|
1152
|
+
}
|
|
1153
|
+
const facts: Fact[] = [];
|
|
1154
|
+
for (const fact of index.facts.values()) {
|
|
1155
|
+
if (fact.predicate.value === propertyIri) {
|
|
1156
|
+
facts.push(fact);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
return facts;
|
|
1160
|
+
}
|
|
960
1161
|
}
|