@jahed/sparql-engine
Version:
SPARQL query engine for servers and web browsers.
129 lines • 5.32 kB
JavaScript
// SPDX-License-Identifier: MIT
import { BinarySearchTree } from "@seald-io/binary-search-tree";
import { differenceWith, findIndex, maxBy } from "lodash-es";
import { termToString } from "rdf-string";
import { Bindings } from "../../rdf/bindings.js";
import { termToValue, tripleEquals } from "../../utils/rdf.js";
import { Pipeline } from "../pipeline/pipeline.js";
import { AsyncLRUCache } from "./cache-base.js";
function hashTriple(triple) {
return `s=${termToString(triple.subject)}&p=${termToString(triple.predicate)}&o=${termToString(triple.object)}`;
}
function hashBasicGraphPattern(bgp) {
return `${bgp.patterns.map(hashTriple).join(";")}&graph-iri=${termToValue(bgp.graphIRI)}`;
}
/**
* An implementation of a {@link BGPCache} using an {@link AsyncLRUCache}
*/
export class LRUBGPCache {
// Main index: for each triple pattern, register the BGP where their occurs
// Used to speed up the #findSubset method
_allKeys;
// Secondary index: track the triple patterns of each BGP.
// Used to clear the primary index when items slides out from the cache
_patternsPerBGP;
// AsyncCache used to store set of solution bindings
_cache;
constructor(maxSize, ttl) {
this._patternsPerBGP = new Map();
this._allKeys = new BinarySearchTree({
checkValueEquality: (a, b) => a.key === b.key,
});
this._cache = new AsyncLRUCache({
maxSize,
ttl,
sizeCalculation: (item) => {
return item.content.length;
},
dispose: (v, key) => {
// remove index entries when they slide out
if (this._patternsPerBGP.has(key)) {
const bgp = this._patternsPerBGP.get(key);
bgp.patterns.forEach((pattern) => this._allKeys.delete(hashTriple(pattern), { bgp, key }));
this._patternsPerBGP.delete(key);
}
},
});
}
has(bgp) {
return this._cache.has(hashBasicGraphPattern(bgp));
}
update(bgp, item, writerID) {
const key = hashBasicGraphPattern(bgp);
if (!this._cache.has(key)) {
// update the indexes
this._patternsPerBGP.set(key, bgp);
bgp.patterns.forEach((pattern) => this._allKeys.insert(hashTriple(pattern), { bgp, key }));
}
this._cache.update(key, item, writerID);
}
get(bgp) {
return this._cache.get(hashBasicGraphPattern(bgp));
}
getAsPipeline(bgp, onCancel) {
const bindings = this.get(bgp);
if (bindings === null) {
return Pipeline.getInstance().empty();
}
let iterator = Pipeline.getInstance().from(bindings);
return Pipeline.getInstance().mergeMap(iterator, (bindings) => {
// if the results is empty AND the cache do not contains the BGP
// it means that the entry has been deleted before its insertion completed
if (bindings.length === 0 && !this.has(bgp)) {
return onCancel === undefined
? Pipeline.getInstance().empty()
: onCancel();
}
return Pipeline.getInstance().from(bindings.map((b) => b.clone()));
});
}
commit(bgp, writerID) {
this._cache.commit(hashBasicGraphPattern(bgp), writerID);
}
delete(bgp, writerID) {
const key = hashBasicGraphPattern(bgp);
this._cache.delete(key, writerID);
// clear the indexes
this._patternsPerBGP.delete(key);
bgp.patterns.forEach((pattern) => this._allKeys.delete(hashTriple(pattern), { bgp, key }));
}
count() {
return this._cache.count();
}
findSubset(bgp) {
// if the bgp is in the cache, then the computation is simple
if (this.has(bgp)) {
return [bgp.patterns, []];
}
// otherwise, we search for all candidate subsets
let matches = [];
for (let pattern of bgp.patterns) {
const searchResults = this._allKeys
.search(hashTriple(pattern))
.filter((v) => {
// remove all BGPs that are not a subset of the input BGP
// we use lodash.findIndex + rdf.tripleEquals to check for triple pattern equality
return v.bgp.patterns.every((a) => findIndex(bgp.patterns, (b) => tripleEquals(a, b)) > -1);
});
matches.push({ pattern, searchResults });
}
// compute the largest subset BGP and the missing patterns (missingPatterns = input_BGP - subset_BGP)
let foundPatterns = [];
let maxBGPLength = -1;
for (let match of matches) {
if (match.searchResults.length > 0) {
const localMax = maxBy(match.searchResults, (v) => v.bgp.patterns.length);
if (localMax !== undefined &&
localMax.bgp.patterns.length > maxBGPLength) {
maxBGPLength = localMax.bgp.patterns.length;
foundPatterns = localMax.bgp.patterns;
}
}
}
return [
foundPatterns,
differenceWith(bgp.patterns, foundPatterns, tripleEquals),
];
}
}
//# sourceMappingURL=bgp-cache.js.map