All files / reasoners triple-index.ts

100% Statements 75/75
100% Branches 38/38
100% Functions 8/8
100% Lines 54/54

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139                              238x     238x     238x     238x     238x     54735x 54735x 54735x 54735x   54735x 11735x       54735x   54735x 54735x   54735x 54735x   54735x 25489x     25489x   25489x 25489x   25489x 25489x     25489x   25489x 25489x   25489x 25489x     25489x   25489x 25489x   25489x 25489x   25489x         202016x         138349x         2x         2x         2x   2x   1x 2x   2x 2x             228434x   228434x   193369x 212403x   191679x       54735x 106550x   25489x       10x 10x  
import * as rdfjs from '@rdfjs/types';
 
/**
 * A multi-dimensional index over RDF triples for O(1) pattern lookups.
 *
 * Maintains three access paths:
 *   - by predicate → subject → set of objects
 *   - by predicate → object → set of subjects
 *   - by subject → set of predicates (for property chain and key joins)
 *
 * The index is used by the semi-naive fixpoint engine to perform joins
 * efficiently without scanning the full working store.
 */
export class TripleIndex {
    /** predicate → subject → objects */
    readonly byPredSubj = new Map<string, Map<string, Set<rdfjs.Quad_Object>>>();
 
    /** predicate → object → subjects */
    readonly byPredObj = new Map<string, Map<string, Set<rdfjs.Quad_Subject>>>();
 
    /** subject → predicate → objects */
    readonly bySubjPred = new Map<string, Map<string, Set<rdfjs.Quad_Object>>>();
 
    /** object (URI/bnode) → predicate → subjects */
    readonly byObjPred = new Map<string, Map<string, Set<rdfjs.Quad_Subject>>>();
 
    /** subject value → subject term (for term recovery in pairs()) */
    private readonly bySubjTerm = new Map<string, rdfjs.Quad_Subject>();
 
    add(quad: rdfjs.Quad): boolean {
        const s = quad.subject.value;
        const p = quad.predicate.value;
        const o = quad.object;
        const ov = o.value;
 
        if (!this.bySubjTerm.has(s)) {
            this.bySubjTerm.set(s, quad.subject);
        }
 
        // byPredSubj
        let bySubj = this.byPredSubj.get(p);
 
        if (!bySubj) { bySubj = new Map(); this.byPredSubj.set(p, bySubj); }
        let objs = bySubj.get(s);
 
        if (!objs) { objs = new Set(); bySubj.set(s, objs); }
        const isNew = !this._hasObject(objs, o);
 
        if (!isNew) return false;
        objs.add(o);
 
        // byPredObj
        let byObj = this.byPredObj.get(p);
 
        if (!byObj) { byObj = new Map(); this.byPredObj.set(p, byObj); }
        let subjs = byObj.get(ov);
 
        if (!subjs) { subjs = new Set(); byObj.set(ov, subjs); }
        subjs.add(quad.subject);
 
        // bySubjPred
        let predMap = this.bySubjPred.get(s);
 
        if (!predMap) { predMap = new Map(); this.bySubjPred.set(s, predMap); }
        let objsS = predMap.get(p);
 
        if (!objsS) { objsS = new Set(); predMap.set(p, objsS); }
        objsS.add(o);
 
        // byObjPred
        let objPredMap = this.byObjPred.get(ov);
 
        if (!objPredMap) { objPredMap = new Map(); this.byObjPred.set(ov, objPredMap); }
        let subjsO = objPredMap.get(p);
 
        if (!subjsO) { subjsO = new Set(); objPredMap.set(p, subjsO); }
        subjsO.add(quad.subject);
 
        return true;
    }
 
    /** Look up all objects for a given predicate + subject. */
    getObjects(predicate: string, subject: string): ReadonlySet<rdfjs.Quad_Object> {
        return this.byPredSubj.get(predicate)?.get(subject) ?? _empty;
    }
 
    /** Look up all subjects for a given predicate + object value. */
    getSubjects(predicate: string, objectValue: string): ReadonlySet<rdfjs.Quad_Subject> {
        return this.byPredObj.get(predicate)?.get(objectValue) ?? _emptySubj;
    }
 
    /** Look up all objects a subject has for a given predicate. */
    getObjectsForSubject(subject: string, predicate: string): ReadonlySet<rdfjs.Quad_Object> {
        return this.bySubjPred.get(subject)?.get(predicate) ?? _empty;
    }
 
    /** Look up all subjects that have a given object for a given predicate. */
    getSubjectsForObject(objectValue: string, predicate: string): ReadonlySet<rdfjs.Quad_Subject> {
        return this.byObjPred.get(objectValue)?.get(predicate) ?? _emptySubj;
    }
 
    /** Iterate all (subject, object) pairs for a given predicate. */
    *pairs(predicate: string): Iterable<[rdfjs.Quad_Subject, rdfjs.Quad_Object]> {
        const bySubj = this.byPredSubj.get(predicate);
 
        if (!bySubj) return;
 
        for (const [sv, objs] of bySubj) {
            const subjectTerm = this.bySubjTerm.get(sv)!;
 
            for (const obj of objs) {
                yield [subjectTerm, obj];
            }
        }
    }
 
    /** Check if the index contains a triple with this predicate, subject, and object value. */
    has(predicate: string, subject: string, objectValue: string): boolean {
        const objs = this.byPredSubj.get(predicate)?.get(subject);
 
        if (!objs) return false;
 
        for (const o of objs) {
            if (o.value === objectValue) return true;
        }
        return false;
    }
 
    private _hasObject(set: Set<rdfjs.Quad_Object>, obj: rdfjs.Quad_Object): boolean {
        for (const o of set) {
            if (o.value === obj.value && o.termType === obj.termType) return true;
        }
        return false;
    }
}
 
const _empty: ReadonlySet<rdfjs.Quad_Object> = new Set();
const _emptySubj: ReadonlySet<rdfjs.Quad_Subject> = new Set();