@oml/owl 0.19.3 → 0.20.1

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.
Files changed (46) hide show
  1. package/out/index.d.ts +3 -0
  2. package/out/index.js +3 -0
  3. package/out/index.js.map +1 -1
  4. package/out/owl/owl-abox.d.ts +16 -5
  5. package/out/owl/owl-abox.js +365 -188
  6. package/out/owl/owl-abox.js.map +1 -1
  7. package/out/owl/owl-imports.js +4 -2
  8. package/out/owl/owl-imports.js.map +1 -1
  9. package/out/owl/owl-interfaces.d.ts +26 -1
  10. package/out/owl/owl-mapper.d.ts +2 -0
  11. package/out/owl/owl-mapper.js +77 -38
  12. package/out/owl/owl-mapper.js.map +1 -1
  13. package/out/owl/owl-service.d.ts +4 -0
  14. package/out/owl/owl-service.js +139 -44
  15. package/out/owl/owl-service.js.map +1 -1
  16. package/out/owl/owl-shacl.d.ts +8 -9
  17. package/out/owl/owl-shacl.js +43 -117
  18. package/out/owl/owl-shacl.js.map +1 -1
  19. package/out/owl/owl-sparql-engine.d.ts +6 -0
  20. package/out/owl/owl-sparql-engine.js +11 -0
  21. package/out/owl/owl-sparql-engine.js.map +1 -0
  22. package/out/owl/owl-sparql-oxigraph.d.ts +34 -0
  23. package/out/owl/owl-sparql-oxigraph.js +436 -0
  24. package/out/owl/owl-sparql-oxigraph.js.map +1 -0
  25. package/out/owl/owl-sparql.d.ts +0 -44
  26. package/out/owl/owl-sparql.js +0 -320
  27. package/out/owl/owl-sparql.js.map +1 -1
  28. package/out/owl/owl-store-lean.d.ts +29 -0
  29. package/out/owl/owl-store-lean.js +445 -0
  30. package/out/owl/owl-store-lean.js.map +1 -0
  31. package/out/owl/owl-store.d.ts +11 -1
  32. package/out/owl/owl-store.js +80 -6
  33. package/out/owl/owl-store.js.map +1 -1
  34. package/package.json +3 -3
  35. package/src/index.ts +3 -0
  36. package/src/owl/owl-abox.ts +401 -200
  37. package/src/owl/owl-imports.ts +4 -2
  38. package/src/owl/owl-interfaces.ts +28 -1
  39. package/src/owl/owl-mapper.ts +79 -41
  40. package/src/owl/owl-service.ts +149 -49
  41. package/src/owl/owl-shacl.ts +55 -132
  42. package/src/owl/owl-sparql-engine.ts +34 -0
  43. package/src/owl/owl-sparql-oxigraph.ts +527 -0
  44. package/src/owl/owl-sparql.ts +3 -366
  45. package/src/owl/owl-store-lean.ts +438 -0
  46. package/src/owl/owl-store.ts +86 -7
@@ -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, Store, Term } from 'n3';
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, store, fact);
167
+ this.removeOwnEntailedFact(scope, index, fact);
145
168
  }
146
169
  }
147
170
  }
148
171
 
149
- const over = fullRecompute ? [] : this.overdelete(scope, index, tboxIndex, minusFacts, store);
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, store);
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: this.collectValidationWarnings(index, tboxIndex),
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 > 0) {
175
- const current = queue.shift();
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, store, head);
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
- store: Store,
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) || !this.hasSupport(seed, index, tbox)) {
246
+ if (!this.canInsertEntailedFact(scope, seed)) {
221
247
  continue;
222
248
  }
223
- this.insertOwnEntailedFact(scope, index, store, seed);
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
- while (queue.length > 0) {
229
- const fact = queue.shift();
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) || !this.hasSupport(head, index, tbox)) {
269
+ if (!this.canInsertEntailedFact(scope, head)) {
236
270
  continue;
237
271
  }
238
- this.insertOwnEntailedFact(scope, index, store, head);
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 bindings = this.matchAtomToFact(atom, fact, new Map<string, Term>());
414
- if (!bindings) {
415
- return;
416
- }
417
- for (const resolved of this.resolveRuleBody(rule.body, index, bindings, anchorIndex)) {
418
- for (const head of rule.head) {
419
- const inferred = this.instantiateRuleAtom(head, resolved);
420
- if (inferred) {
421
- heads.push(inferred);
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
- return this.uniqueFacts(heads);
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 = this.matchAtomToFact(head, fact, new Map<string, Term>());
434
- if (!bindings) {
513
+ const bindings = new Map<string, Term>();
514
+ if (this.matchAtomToFactMut(head, fact, bindings) === null) {
435
515
  continue;
436
516
  }
437
- const resolved = this.resolveRuleBody(rule.body, index, bindings, -1);
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
- private resolveRuleBody(
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
- ): Array<Map<string, Term>> {
452
- const pending = body.filter((_atom, index) => index !== satisfiedIndex);
453
- return this.resolvePendingAtoms(pending, index, bindings);
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 resolvePendingAtoms(
545
+ private *resolvePending(
457
546
  pending: RuleAtom[],
547
+ pos: number,
458
548
  index: WorkingIndex,
459
549
  bindings: Map<string, Term>,
460
- ): Array<Map<string, Term>> {
461
- if (pending.length === 0) {
462
- return [bindings];
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
- return resolved;
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 nextBindings = this.matchAtomToFact(atom, fact, bindings);
478
- if (nextBindings) {
479
- matches.push(nextBindings);
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
- return [...this.getTypes(index, bound)].map((classIri) => this.typeFact(bound, classIri));
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) ?? new Set<string>();
492
- return [...subjects]
493
- .map((subjectKey) => index.termByKey.get(subjectKey))
494
- .filter((term): term is Term => Boolean(term))
495
- .map((subject) => this.typeFact(subject, atom.classIri));
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
- return this.getEdgeFacts(index, left, atom.propertyIri);
593
+ yield* this.getEdgeFacts(index, left, atom.propertyIri);
594
+ return;
502
595
  }
503
596
  if (right) {
504
- return this.getIncomingEdgesByProperty(index, right, atom.propertyIri);
597
+ yield* this.getIncomingEdgesByProperty(index, right, atom.propertyIri);
598
+ return;
505
599
  }
506
- return index.edgeFactsByProperty.get(atom.propertyIri) ?? [];
600
+ yield* index.edgeFactsByProperty.get(atom.propertyIri) ?? [];
507
601
  }
508
602
 
509
- private matchAtomToFact(atom: RuleAtom, fact: Fact, bindings: Map<string, Term>): Map<string, Term> | undefined {
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 undefined;
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 this.matchPattern(atom.argument, fact.subject, bindings);
616
+ return added;
515
617
  }
516
618
  if (fact.predicate.value !== atom.propertyIri) {
517
- return undefined;
619
+ return null;
518
620
  }
519
- const afterLeft = this.matchPattern(atom.argument1, fact.subject, bindings);
520
- if (!afterLeft) {
521
- return undefined;
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 this.matchPattern(atom.argument2, fact.object, afterLeft);
626
+ return added;
524
627
  }
525
628
 
526
- private matchPattern(pattern: RuleTermPattern, term: Term, bindings: Map<string, Term>): Map<string, Term> | undefined {
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) ? bindings : undefined;
633
+ return this.sameTerm(existing, term);
531
634
  }
532
- const next = new Map(bindings);
533
- next.set(pattern.name, term);
534
- return next;
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 ? bindings : undefined;
640
+ return term.termType === 'NamedNode' && term.value === pattern.iri;
538
641
  }
539
642
  if (term.termType !== 'Literal') {
540
- return undefined;
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
- ? bindings
546
- : undefined;
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 namedNode(pattern.iri);
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 DataFactory.literal(pattern.value, pattern.language || namedNode(pattern.datatype));
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>): ScopeData {
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 ownBaseFacts: Fact[] = [];
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
- graphs = this.reasoningStore.graphs(uri);
722
+ this.reasoningStore.graphs(uri);
597
723
  } catch {
598
724
  continue;
599
725
  }
726
+ const isOwn = uri === modelUri;
600
727
 
601
- for (const q of store.getQuads(null, null, null, graphs.own)) {
602
- const fact = this.asFact(q);
603
- const key = this.factKey(fact);
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 (uri === modelUri) {
607
- ownBaseFacts.push(fact);
732
+ if (isOwn) {
733
+ ownBaseFactsByKey.set(key, fact);
608
734
  }
609
- }
735
+ });
610
736
 
611
- for (const q of store.getQuads(null, null, null, graphs.entailments)) {
612
- const fact = this.asFact(q);
613
- const key = this.factKey(fact);
614
- facts.set(key, fact);
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: this.uniqueFacts(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 buildIndex(facts: Map<string, Fact>): WorkingIndex {
634
- const typesBySubject = new Map<string, Set<string>>();
635
- const subjectsByType = new Map<string, Set<string>>();
636
- const edgeFactsBySubjectProperty = new Map<string, Fact[]>();
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
- typesBySubject,
674
- subjectsByType,
675
- edgeFactsBySubjectProperty,
676
- edgeFactsByProperty,
677
- subjectsByPropertyObject,
678
- termByKey,
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 insertOwnEntailedFact(scope: ScopeData, index: WorkingIndex, store: Store, fact: Fact): void {
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
- this.indexAdd(index, fact);
691
- store.addQuad(quad(this.asQuadSubject(fact.subject), fact.predicate, this.asQuadObject(fact.object), scope.ownEntailmentGraph));
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, store: Store, fact: Fact): void {
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
- this.indexRemove(index, fact);
703
- store.removeQuad(quad(this.asQuadSubject(fact.subject), fact.predicate, this.asQuadObject(fact.object), scope.ownEntailmentGraph));
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
- const edges: Fact[] = [];
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
- const edges: Fact[] = [];
732
- const suffix = `|${object.id}`;
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 edges;
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
- const poKey = `${fact.predicate.value}|${fact.object.id}`;
870
- const subjects = index.subjectsByPropertyObject.get(poKey) ?? new Set<string>();
871
- subjects.add(fact.subject.id);
872
- index.subjectsByPropertyObject.set(poKey, subjects);
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 = bySp.filter((entry) => this.factKey(entry) !== this.factKey(fact));
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 = byProperty.filter((entry) => this.factKey(entry) !== this.factKey(fact));
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
- const poKey = `${fact.predicate.value}|${fact.object.id}`;
912
- const subjects = index.subjectsByPropertyObject.get(poKey);
913
- if (!subjects) {
914
- return;
915
- }
916
- const stillHas = nextBySp.some((entry) => entry.object.id === fact.object.id);
917
- if (!stillHas) {
918
- subjects.delete(fact.subject.id);
919
- }
920
- if (subjects.size === 0) {
921
- index.subjectsByPropertyObject.delete(poKey);
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 = _index.edgeFactsByProperty.get(propertyIri) ?? [];
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 = _index.edgeFactsByProperty.get(propertyIri) ?? [];
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
  }