@rubeneschauzier/rdf-stores
Version:
A TypeScript/JavaScript implementation of the RDF/JS store interface with support for quoted triples.
401 lines • 18.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RdfStoreIndexNestedMapSampling = void 0;
const OrderUtils_1 = require("../OrderUtils");
const RdfStoreIndexNestedMap_1 = require("./RdfStoreIndexNestedMap");
class RdfStoreIndexNestedMapSampling extends RdfStoreIndexNestedMap_1.RdfStoreIndexNestedMap {
constructor(options) {
super(options);
this.protectedCountKey = Symbol('_count_');
this.protectedArrayKey = Symbol('_array_');
this.features = {
quotedTripleFiltering: false,
sampling: true,
};
}
set(terms, value) {
const map0 = this.nestedMap;
let map1 = map0.get(terms[0]);
if (!map1) {
map1 = new Map();
map1.set(this.protectedCountKey, { count: 0 });
map0.set(terms[0], map1);
}
let map2 = map1.get(terms[1]);
if (!map2) {
map2 = new Map();
map2.set(this.protectedCountKey, { count: 0 });
map1.set(terms[1], map2);
}
let map3 = map2.get(terms[2]);
if (!map3) {
map3 = new Map();
map3.set(this.protectedArrayKey, []);
map2.set(terms[2], map3);
}
const contained = map3.has(terms[3]);
if (!contained) {
map3.set(terms[3], value);
map3.get(this.protectedArrayKey).push(terms[3]);
// New quad requires increment of preceding map counts
map1.get(this.protectedCountKey).count++;
map2.get(this.protectedCountKey).count++;
}
return !contained;
}
remove(terms) {
const map0 = this.nestedMap;
const map1 = map0.get(terms[0]);
if (!map1) {
return false;
}
const map2 = map1.get(terms[1]);
if (!map2) {
return false;
}
const map3 = map2.get(terms[2]);
if (!map3) {
return false;
}
const ret = map3.delete(terms[3]);
if (ret) {
map1.get(this.protectedCountKey).count--;
map2.get(this.protectedCountKey).count--;
const arrayIndex = map3.get(this.protectedArrayKey);
const elementIndex = arrayIndex.indexOf(terms[3]);
// TODO: Validate speed of these two approaches
// eslint-disable-next-line unicorn/prefer-at
arrayIndex[elementIndex] = arrayIndex[arrayIndex.length - 1];
arrayIndex.pop();
// When order matters (not in place)
// const updatedArray = arrayIndex.splice(elementIndex, 1);
// map3.set(<any> this.protectedArrayKey, <any> updatedArray);
}
// Clean up intermediate maps, empty maps will always have either a
// size attribute or array attribute so we use === 1 to validate emptiness
if (ret && map3.size === 1) {
map2.delete(terms[2]);
if (map2.size === 1) {
map1.delete(terms[1]);
if (map1.size === 1) {
map0.delete(terms[0]);
}
}
}
return ret;
}
*find(terms) {
const ids = (0, OrderUtils_1.encodeOptionalTerms)(terms, this.dictionary);
if (!ids) {
return;
}
const [id0, id1, id2, id3] = ids;
const [term0, term1, term2, term3] = terms;
let partialQuad0;
let partialQuad1;
let partialQuad2;
let partialQuad3;
let map1;
let map2;
let map3;
const map0 = this.nestedMap;
const map0Keys = id0 !== undefined ? (map0.has(id0) ? [id0] : []) : map0.keys();
for (const key1 of map0Keys) {
map1 = map0.get(key1);
partialQuad0 = term0 || this.dictionary.decode(key1);
const map1Keys = id1 !== undefined ? (map1.has(id1) ? [id1] : []) : map1.keys();
for (const key2 of map1Keys) {
if (key2 === this.protectedArrayKey || key2 === this.protectedCountKey) {
continue;
}
map2 = map1.get(key2);
partialQuad1 = term1 || this.dictionary.decode(key2);
const map2Keys = id2 !== undefined ? (map2.has(id2) ? [id2] : []) : map2.keys();
for (const key3 of map2Keys) {
if (key3 === this.protectedArrayKey || key3 === this.protectedCountKey) {
continue;
}
map3 = map2.get(key3);
partialQuad2 = term2 || this.dictionary.decode(key3);
const map3Keys = id3 !== undefined ? (map3.has(id3) ? [id3] : []) : map3.keys();
for (const key4 of map3Keys) {
if (key4 === this.protectedArrayKey || key4 === this.protectedCountKey) {
continue;
}
partialQuad3 = term3 || this.dictionary.decode(key4);
yield [partialQuad0, partialQuad1, partialQuad2, partialQuad3];
}
}
}
}
}
*findEncoded(ids, terms) {
const [id0, id1, id2, id3] = ids;
let map1;
let map2;
let map3;
const map0 = this.nestedMap;
const map0Keys = id0 !== undefined ? (map0.has(id0) ? [id0] : []) : map0.keys();
for (const key1 of map0Keys) {
map1 = map0.get(key1);
const map1Keys = id1 !== undefined ? (map1.has(id1) ? [id1] : []) : map1.keys();
for (const key2 of map1Keys) {
if (key2 === this.protectedArrayKey || key2 === this.protectedCountKey) {
continue;
}
map2 = map1.get(key2);
const map2Keys = id2 !== undefined ? (map2.has(id2) ? [id2] : []) : map2.keys();
for (const key3 of map2Keys) {
if (key3 === this.protectedArrayKey || key3 === this.protectedCountKey) {
continue;
}
map3 = map2.get(key3);
const map3Keys = id3 !== undefined ? (map3.has(id3) ? [id3] : []) : map3.keys();
for (const key4 of map3Keys) {
if (key4 === this.protectedArrayKey || key4 === this.protectedCountKey) {
continue;
}
yield [key1, key2, key3, key4];
}
}
}
}
}
count(terms) {
let count = 0;
const ids = (0, OrderUtils_1.encodeOptionalTerms)(terms, this.dictionary);
if (!ids) {
return 0;
}
const [id0, id1, id2, id3] = ids;
let map1;
let map2;
let map3;
const map0 = this.nestedMap;
const map0Keys = id0 !== undefined ? (map0.has(id0) ? [id0] : []) : map0.keys();
for (const key1 of map0Keys) {
if (key1 !== this.protectedArrayKey && key1 !== this.protectedCountKey) {
map1 = map0.get(key1);
const map1Keys = id1 !== undefined ? (map1.has(id1) ? [id1] : []) : map1.keys();
for (const key2 of map1Keys) {
if (key2 !== this.protectedArrayKey && key2 !== this.protectedCountKey) {
map2 = map1.get(key2);
const map2Keys = id2 !== undefined ? (map2.has(id2) ? [id2] : []) : map2.keys();
for (const key3 of map2Keys) {
if (key3 !== this.protectedArrayKey && key3 !== this.protectedCountKey) {
map3 = map2.get(key3);
if (id3 !== undefined) {
if (map3.has(id3)) {
count++;
}
}
else {
// Reduce by one to ensure the _array_ property isnt counted
count += map3.size - 1;
}
}
}
}
}
}
}
return count;
}
/**
* Extracts triple patterns at given indexes. In GSPO index, if terms = [g0, s0, undefined, undefined]
* indexes = [1, 5] and index = g0: {s0:{ p1: [o1, o2], p2: [o3, o4], p3: [o5, o6] } } it will return (s0 p1 o2),
* (s0 p3 o6). Note that the indexes values should be lower than the number of triples existing at the
* non-undefined terms. So for terms [g0, s0, p1, undefined] indexes [1,5] would return nothing as # triples
* for the term = 2
* When passed a term: [undefined, s0, p0, o0] this method will use a slower method to do sampling, as the _size
* based sampling is no longer correct.
* @param terms
* @param n
* @param indexes
* @returns
*/
*sample(terms, indexes) {
const ids = (0, OrderUtils_1.encodeOptionalTerms)(terms, this.dictionary);
if (!ids) {
return;
}
if (indexes.length === 0) {
return;
}
const [id0, id1, id2, id3] = ids;
const outOfOrder = ids.some((id, i) => id === undefined && ids.slice(i + 1)
.some(next => next !== undefined));
if (outOfOrder) {
for (const sampled of this.sampleOutOfOrder(id0, id1, id2, id3, indexes)) {
yield sampled;
}
}
else {
for (const sampled of this.sampleInOrder(id0, id1, id2, id3, indexes)) {
yield sampled;
}
}
}
*sampleInOrder(id0, id1, id2, id3, indexes) {
let partialQuad0;
let partialQuad1;
let partialQuad2;
let partialQuad3;
for (const index of indexes) {
// The search index taking into account how many triples we've skipped during traversal of index
let searchIndex = 0;
const map0 = this.nestedMap;
if (id0 !== undefined && !map0.has(id0)) {
return;
}
const searchResultMap0 = this.searchMap(id0, map0, searchIndex, index);
searchIndex = searchResultMap0.searchIndex;
partialQuad0 = this.dictionary.decode(searchResultMap0.key);
const map1 = map0.get(searchResultMap0.key);
if (id1 !== undefined && !map1.has(id1)) {
return;
}
const searchResultMap1 = this.searchMap(id1, map1, searchIndex, index);
searchIndex = searchResultMap1.searchIndex;
partialQuad1 = this.dictionary.decode(searchResultMap1.key);
const map2 = map1.get(searchResultMap1.key);
if (id2 !== undefined) {
if (!map2.has(id2)) {
return;
}
const withinArrayIndex = index - searchIndex;
if (withinArrayIndex >= map2.get(id2).size - 1) {
// Temp debugging code, just return here usually
throw new Error(`Invalid index encountered: ${withinArrayIndex}, size map: ` +
`${map2.get(id2).size - 1}`);
}
const termArray = map2.get(id2).get(this.protectedArrayKey);
partialQuad2 = this.dictionary.decode(id2);
partialQuad3 = this.dictionary.decode(termArray[withinArrayIndex]);
yield [partialQuad0, partialQuad1, partialQuad2, partialQuad3];
}
else {
for (const key2 of map2.keys()) {
if (key2 !== this.protectedArrayKey && key2 !== this.protectedCountKey) {
const size = map2.get(key2).size - 1;
searchIndex += size;
if (searchIndex > index) {
searchIndex -= size;
const termArray = map2.get(key2).get(this.protectedArrayKey);
// Find correct index within the array
const tripleIndex = index - (searchIndex);
partialQuad2 = this.dictionary.decode(key2);
partialQuad3 = this.dictionary.decode(termArray[tripleIndex]);
yield [partialQuad0, partialQuad1, partialQuad2, partialQuad3];
break;
}
}
}
}
}
}
/**
* Function searching the key in a given map that contains the triple at a given index. Returns
* the value of the searchIndex and the key where the result will be located. This assumes that
* any key not undefined exists in the map.
* @param id
* @param map
* @param searchIndex
* @param index
* @returns the key of the map where the triple at index is located, and the incremented search index
*/
searchMap(id, map, searchIndex, index) {
// If we're here there is a match in the map for id0
const mapKeys = [...(id !== undefined ? [id] : map.keys())];
// Console.log(`Map keys: ${JSON.stringify(mapKeys)}, ${id}`)
let key;
if (mapKeys.length > 1) {
for (const mapKey of mapKeys) {
if (mapKey !== this.protectedArrayKey && mapKey !== this.protectedCountKey) {
const nTriplesInMap = map.get(mapKey)
.get(this.protectedCountKey).count;
searchIndex += nTriplesInMap;
if (searchIndex > index) {
// Go back one step
searchIndex -= nTriplesInMap;
key = mapKey;
break;
}
}
}
if (key === undefined) {
// Error here for easy debugging, however we can also just return if this were to be actually put to use.
throw new Error('Tried to sample an index which is higher than the number of triples in store.');
}
}
else {
// Take first element, which either means there is only one key or id0 is not undefined. In both cases
// we don't have to do a search.
key = mapKeys[0];
}
return { key, searchIndex };
}
*sampleOutOfOrder(id0, id1, id2, id3, indexes) {
// Sort in reverse order and pop each index when found
indexes.sort((a, b) => b - a);
let searchIndex = 0;
let indexToFind = indexes.pop();
let map1;
let map2;
let map3;
const map0 = this.nestedMap;
const map0Keys = id0 !== undefined ? (map0.has(id0) ? [id0] : []) : map0.keys();
for (const key1 of map0Keys) {
if (key1 !== this.protectedArrayKey && key1 !== this.protectedCountKey) {
map1 = map0.get(key1);
const map1Keys = id1 !== undefined ? (map1.has(id1) ? [id1] : []) : map1.keys();
for (const key2 of map1Keys) {
if (key2 !== this.protectedArrayKey && key2 !== this.protectedCountKey) {
map2 = map1.get(key2);
const map2Keys = id2 !== undefined ? (map2.has(id2) ? [id2] : []) : map2.keys();
for (const key3 of map2Keys) {
if (key3 !== this.protectedArrayKey && key3 !== this.protectedCountKey) {
map3 = map2.get(key3);
if (id3 !== undefined) {
if (map3.has(id3)) {
// If we find our searchIndex === indexToFind, we keep popping the indexes array
// For the case of duplicate indexes
while (searchIndex === indexToFind) {
yield [this.dictionary.decode(key1), this.dictionary.decode(key2),
this.dictionary.decode(key3), this.dictionary.decode(id3)];
indexToFind = indexes.pop();
// We are done sampling
if (indexToFind === undefined) {
return;
}
}
searchIndex++;
}
}
else {
// Reduce by one to ensure the _array_ property isnt counted
searchIndex += map3.size - 1;
if (searchIndex > indexToFind) {
while (searchIndex > indexToFind) {
const tripleIndex = indexToFind - (searchIndex - map3.size + 1);
const termArray = map2.get(key3).get(this.protectedArrayKey);
const finalTerm = this.dictionary.decode(termArray[tripleIndex]);
yield [this.dictionary.decode(key1), this.dictionary.decode(key2),
this.dictionary.decode(key3), finalTerm];
indexToFind = indexes.pop();
if (indexToFind === undefined) {
return;
}
}
}
}
}
}
}
}
}
}
}
}
exports.RdfStoreIndexNestedMapSampling = RdfStoreIndexNestedMapSampling;
//# sourceMappingURL=RdfStoreIndexNestedMapSampling.js.map