From 94b70885b46643123dfc6dde3ccba13723bc3e90 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 19:27:04 +1000 Subject: [PATCH 1/8] feat: shared entity index --- src/N3Store.js | 78 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 660a25fe..17f7f76b 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -4,30 +4,15 @@ import { default as N3DataFactory, termToId, termFromId } from './N3DataFactory' import namespaces from './IRIs'; import { isDefaultGraph } from './N3Util'; -// ## Constructor -export default class N3Store { - constructor(quads, options) { - // The number of quads is initially zero - this._size = 0; - // `_graphs` contains subject, predicate, and object indexes per graph - this._graphs = Object.create(null); +export class N3EntityIndex { + constructor(options) { + this._id = 0; // `_ids` maps entities such as `http://xmlns.com/foaf/0.1/name` to numbers, // saving memory by using only numbers as keys in `_graphs` - this._id = 0; this._ids = Object.create(null); - this._entities = Object.create(null); // inverse of `_ids` - // `_blankNodeIndex` is the index of the last automatically named blank node - this._blankNodeIndex = 0; - - // Shift parameters if `quads` is not given - if (!options && quads && !quads[0]) - options = quads, quads = null; - options = options || {}; - this._factory = options.factory || N3DataFactory; - - // Add quads if passed - if (quads) - this.addQuads(quads); + // inverse of `_ids` + this._entities = Object.create(null); + this._factory = options.factory; } _termFromId(id, factory) { @@ -68,6 +53,57 @@ export default class N3Store { return this._ids[str] || (this._ids[this._entities[++this._id] = str] = this._id); } +} + +// ## Constructor +export default class N3Store { + constructor(quads, options) { + // The number of quads is initially zero + this._size = 0; + // `_graphs` contains subject, predicate, and object indexes per graph + this._graphs = Object.create(null); + // `_blankNodeIndex` is the index of the last automatically named blank node + this._blankNodeIndex = 0; + + // Shift parameters if `quads` is not given + if (!options && quads && !quads[0]) + options = quads, quads = null; + options = options || {}; + this._factory = options.factory || N3DataFactory; + this._entityIndex = new N3EntityIndex({ factory: this._factory }); + + // Add quads if passed + if (quads) + this.addQuads(quads); + } + + _termFromId(id, factory) { + return this._entityIndex._termFromId(id, factory); + } + + _termToNumericId(term) { + return this._entityIndex._termToNumericId(term); + } + + _termToNewNumericId(term) { + return this._entityIndex._termToNewNumericId(term); + } + + get _entities() { + return this._entityIndex._entities; + } + + get _ids() { + return this._entityIndex._ids; + } + + get _id() { + return this._entityIndex._id; + } + + set _id(value) { + return this._entityIndex._id = value; + } // ## Public properties From 18bc59ee56c7565af7be87ea9d39a092767d29e6 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 21:07:09 +1000 Subject: [PATCH 2/8] chore: remove unused getters/setter --- src/N3Store.js | 54 +++++++++++++++++++++----------------------------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 17f7f76b..956378eb 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -12,7 +12,9 @@ export class N3EntityIndex { this._ids = Object.create(null); // inverse of `_ids` this._entities = Object.create(null); - this._factory = options.factory; + // `_blankNodeIndex` is the index of the last automatically named blank node + this._blankNodeIndex = 0; + this._factory = options.factory || N3DataFactory; } _termFromId(id, factory) { @@ -53,6 +55,25 @@ export class N3EntityIndex { return this._ids[str] || (this._ids[this._entities[++this._id] = str] = this._id); } + + createBlankNode(suggestedName) { + let name, index; + // Generate a name based on the suggested name + if (suggestedName) { + name = suggestedName = `_:${suggestedName}`, index = 1; + while (this._ids[name]) + name = suggestedName + index++; + } + // Generate a generic blank node name + else { + do { name = `_:b${this._blankNodeIndex++}`; } + while (this._ids[name]); + } + // Add the blank node to the entities, avoiding the generation of duplicates + this._ids[name] = ++this._id; + this._entities[this._id] = name; + return this._factory.blankNode(name.substr(2)); + } } // ## Constructor @@ -62,8 +83,6 @@ export default class N3Store { this._size = 0; // `_graphs` contains subject, predicate, and object indexes per graph this._graphs = Object.create(null); - // `_blankNodeIndex` is the index of the last automatically named blank node - this._blankNodeIndex = 0; // Shift parameters if `quads` is not given if (!options && quads && !quads[0]) @@ -93,18 +112,6 @@ export default class N3Store { return this._entityIndex._entities; } - get _ids() { - return this._entityIndex._ids; - } - - get _id() { - return this._entityIndex._id; - } - - set _id(value) { - return this._entityIndex._id = value; - } - // ## Public properties // ### `size` returns the number of quads in the store @@ -695,22 +702,7 @@ export default class N3Store { // ### `createBlankNode` creates a new blank node, returning its name createBlankNode(suggestedName) { - let name, index; - // Generate a name based on the suggested name - if (suggestedName) { - name = suggestedName = `_:${suggestedName}`, index = 1; - while (this._ids[name]) - name = suggestedName + index++; - } - // Generate a generic blank node name - else { - do { name = `_:b${this._blankNodeIndex++}`; } - while (this._ids[name]); - } - // Add the blank node to the entities, avoiding the generation of duplicates - this._ids[name] = ++this._id; - this._entities[this._id] = name; - return this._factory.blankNode(name.substr(2)); + return this._entityIndex.createBlankNode(suggestedName); } // ### `extractLists` finds and removes all list triples From 4a1275af822b8f1235a398814f0489dc0e8fd972 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 21:18:57 +1000 Subject: [PATCH 3/8] chore: update function calls --- src/N3Store.js | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 956378eb..062e65a3 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -90,28 +90,16 @@ export default class N3Store { options = options || {}; this._factory = options.factory || N3DataFactory; this._entityIndex = new N3EntityIndex({ factory: this._factory }); + this._entities = this._entityIndex._entities; + this._termFromId = this._entityIndex._termFromId.bind(this._entityIndex); + this._termToNumericId = this._entityIndex._termToNumericId.bind(this._entityIndex); + this._termToNewNumericId = this._entityIndex._termToNewNumericId.bind(this._entityIndex); // Add quads if passed if (quads) this.addQuads(quads); } - _termFromId(id, factory) { - return this._entityIndex._termFromId(id, factory); - } - - _termToNumericId(term) { - return this._entityIndex._termToNumericId(term); - } - - _termToNewNumericId(term) { - return this._entityIndex._termToNewNumericId(term); - } - - get _entities() { - return this._entityIndex._entities; - } - // ## Public properties // ### `size` returns the number of quads in the store From f0ee8c98b6f214b8bbb0bd9e833a29a7722bec0b Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 21:34:51 +1000 Subject: [PATCH 4/8] chore: get full test coverage --- src/N3Store.js | 16 ++++++++-------- src/index.js | 3 ++- test/N3Store-test.js | 16 ++++++++++++++++ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 062e65a3..771d650b 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -5,7 +5,7 @@ import namespaces from './IRIs'; import { isDefaultGraph } from './N3Util'; export class N3EntityIndex { - constructor(options) { + constructor(options = {}) { this._id = 0; // `_ids` maps entities such as `http://xmlns.com/foaf/0.1/name` to numbers, // saving memory by using only numbers as keys in `_graphs` @@ -17,7 +17,7 @@ export class N3EntityIndex { this._factory = options.factory || N3DataFactory; } - _termFromId(id, factory) { + _termFromId(id) { if (id[0] === '.') { const entities = this._entities; const terms = id.split('.'); @@ -29,7 +29,7 @@ export class N3EntityIndex { ); return q; } - return termFromId(id, factory); + return termFromId(id, this._factory); } _termToNumericId(term) { @@ -89,7 +89,7 @@ export default class N3Store { options = quads, quads = null; options = options || {}; this._factory = options.factory || N3DataFactory; - this._entityIndex = new N3EntityIndex({ factory: this._factory }); + this._entityIndex = options.entityIndex || new N3EntityIndex({ factory: this._factory }); this._entities = this._entityIndex._entities; this._termFromId = this._entityIndex._termFromId.bind(this._entityIndex); this._termToNumericId = this._entityIndex._termToNumericId.bind(this._entityIndex); @@ -158,24 +158,24 @@ export default class N3Store { *_findInIndex(index0, key0, key1, key2, name0, name1, name2, graphId) { let tmp, index1, index2; const entityKeys = this._entities; - const graph = this._termFromId(graphId, this._factory); + const graph = this._termFromId(graphId); const parts = { subject: null, predicate: null, object: null }; // If a key is specified, use only that part of index 0. if (key0) (tmp = index0, index0 = {})[key0] = tmp[key0]; for (const value0 in index0) { if (index1 = index0[value0]) { - parts[name0] = this._termFromId(entityKeys[value0], this._factory); + parts[name0] = this._termFromId(entityKeys[value0]); // If a key is specified, use only that part of index 1. if (key1) (tmp = index1, index1 = {})[key1] = tmp[key1]; for (const value1 in index1) { if (index2 = index1[value1]) { - parts[name1] = this._termFromId(entityKeys[value1], this._factory); + parts[name1] = this._termFromId(entityKeys[value1]); // If a key is specified, use only that part of index 2, if it exists. const values = key2 ? (key2 in index2 ? [key2] : []) : Object.keys(index2); // Create quads for all items found in index 2. for (let l = 0; l < values.length; l++) { - parts[name2] = this._termFromId(entityKeys[values[l]], this._factory); + parts[name2] = this._termFromId(entityKeys[values[l]]); yield this._factory.quad(parts.subject, parts.predicate, parts.object, graph); } } diff --git a/src/index.js b/src/index.js index 8307958e..0e19e1a0 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import Lexer from './N3Lexer'; import Parser from './N3Parser'; import Writer from './N3Writer'; -import Store from './N3Store'; +import Store, { N3EntityIndex as EntityIndex } from './N3Store'; import StreamParser from './N3StreamParser'; import StreamWriter from './N3StreamWriter'; import * as Util from './N3Util'; @@ -28,6 +28,7 @@ export { Parser, Writer, Store, + EntityIndex, StreamParser, StreamWriter, Util, diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 15b954d4..4386c35e 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -1,6 +1,7 @@ import { Store, termFromId, termToId, + EntityIndex, } from '../src'; import { NamedNode, @@ -2013,6 +2014,21 @@ describe('Store', () => { }); }); +describe('EntityIndex', () => { + it('should be a constructor', () => { + expect(new EntityIndex()).toBeInstanceOf(EntityIndex); + }); + + it('custom index should be used when instantiated with store', () => { + const entityIndex = new EntityIndex(); + const store = new Store([ + new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o0')), + ], { entityIndex }); + expect(store.size).toBe(1); + expect(entityIndex._id).toEqual(3); + }); +}); + function alwaysTrue() { return true; } function alwaysFalse() { return false; } From c15eb470edfdf983fb9aeae6606c9cddc355f8f6 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 21:48:20 +1000 Subject: [PATCH 5/8] chore: add tests for sharing of custom index --- src/N3Store.js | 10 +++++----- test/N3Store-test.js | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 771d650b..34f2c88d 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -460,7 +460,7 @@ export default class N3Store { // Setting any field to `undefined` or `null` indicates a wildcard. // For backwards compatibility, the object return also implements the Readable stream interface. match(subject, predicate, object, graph) { - return new DatasetCoreAndReadableStream(this, subject, predicate, object, graph); + return new DatasetCoreAndReadableStream(this, subject, predicate, object, graph, { entityIndex: this._entityIndex }); } // ### `countQuads` returns the number of quads matching a pattern. @@ -808,15 +808,15 @@ function isString(s) { * A class that implements both DatasetCore and Readable. */ class DatasetCoreAndReadableStream extends Readable { - constructor(n3Store, subject, predicate, object, graph) { + constructor(n3Store, subject, predicate, object, graph, options) { super({ objectMode: true }); - Object.assign(this, { n3Store, subject, predicate, object, graph }); + Object.assign(this, { n3Store, subject, predicate, object, graph, options }); } get filtered() { if (!this._filtered) { const { n3Store, graph, object, predicate, subject } = this; - const newStore = this._filtered = new N3Store({ factory: n3Store._factory }); + const newStore = this._filtered = new N3Store({ factory: n3Store._factory, entityIndex: this.options.entityIndex }); for (const quad of n3Store.readQuads(subject, predicate, object, graph)) newStore.addQuad(quad); } @@ -846,7 +846,7 @@ class DatasetCoreAndReadableStream extends Readable { } match(subject, predicate, object, graph) { - return new DatasetCoreAndReadableStream(this.filtered, subject, predicate, object, graph); + return new DatasetCoreAndReadableStream(this.filtered, subject, predicate, object, graph, this.options); } *[Symbol.iterator]() { diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 4386c35e..3a048521 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -2015,17 +2015,50 @@ describe('Store', () => { }); describe('EntityIndex', () => { + let entityIndex; + beforeEach(() => { + entityIndex = new EntityIndex(); + }); + it('should be a constructor', () => { - expect(new EntityIndex()).toBeInstanceOf(EntityIndex); + expect(entityIndex).toBeInstanceOf(EntityIndex); }); it('custom index should be used when instantiated with store', () => { - const entityIndex = new EntityIndex(); const store = new Store([ new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o0')), ], { entityIndex }); expect(store.size).toBe(1); expect(entityIndex._id).toEqual(3); + + const substore = store.match(); + substore.add(new Quad(new NamedNode('s2'), new NamedNode('p2'), new NamedNode('o2'))); + expect(store.size).toBe(1); + expect(substore.size).toBe(2); + expect(entityIndex._id).toEqual(6); + expect(entityIndex._ids).toEqual({ + s1: 1, + p1: 2, + o0: 3, + s2: 4, + p2: 5, + o2: 6, + }); + + const store2 = new Store([ + new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o5')), + ], { entityIndex }); + expect(store2.size).toBe(1); + expect(entityIndex._id).toEqual(7); + expect(entityIndex._ids).toEqual({ + s1: 1, + p1: 2, + o0: 3, + s2: 4, + p2: 5, + o2: 6, + o5: 7, + }); }); }); From 508259bfebafcecce526eda286648b88c6c1ed48 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 28 Jul 2024 21:57:05 +1000 Subject: [PATCH 6/8] chore: add documentation for the shared entity index --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 8ca5a6f7..b56df8bf 100644 --- a/README.md +++ b/README.md @@ -304,6 +304,13 @@ for (const quad of store.match(namedNode('http://ex.org/Mickey'), null, null)) console.log(quad); ``` +If you are using multiple stores then you can reduce memory consumption by allowing them to share an entity index: +```JavaScript +const entityIndex = new N3.EntityIndex(); +const store1 = new N3.Store([], { entityIndex }); +const store2 = new N3.Store([], { entityIndex }); +``` + ### [`DatasetCore` Interface](https://rdf.js.org/dataset-spec/#datasetcore-interface) This store adheres to the `DatasetCore` interface which exposes the following properties From bab24631f742a69a28f4e42a93cc971392f67472 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 29 Jul 2024 17:18:57 +1000 Subject: [PATCH 7/8] chore: use ids for graphs --- src/N3Store.js | 40 +++++++++------------------------------- test/N3Store-test.js | 34 ++++++++++++++++------------------ 2 files changed, 25 insertions(+), 49 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 34f2c88d..79850db6 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -6,12 +6,14 @@ import { isDefaultGraph } from './N3Util'; export class N3EntityIndex { constructor(options = {}) { - this._id = 0; + this._id = 1; // `_ids` maps entities such as `http://xmlns.com/foaf/0.1/name` to numbers, // saving memory by using only numbers as keys in `_graphs` this._ids = Object.create(null); + this._ids[''] = 1; // inverse of `_ids` this._entities = Object.create(null); + this._entities[1] = ''; // `_blankNodeIndex` is the index of the last automatically named blank node this._blankNodeIndex = 0; this._factory = options.factory || N3DataFactory; @@ -158,7 +160,7 @@ export default class N3Store { *_findInIndex(index0, key0, key1, key2, name0, name1, name2, graphId) { let tmp, index1, index2; const entityKeys = this._entities; - const graph = this._termFromId(graphId); + const graph = this._termFromId(entityKeys[graphId]); const parts = { subject: null, predicate: null, object: null }; // If a key is specified, use only that part of index 0. @@ -246,11 +248,8 @@ export default class N3Store { // ### `_getGraphs` returns an array with the given graph, // or all graphs if the argument is null or undefined. _getGraphs(graph) { - if (!isString(graph)) - return this._graphs; - const graphs = {}; - graphs[graph] = this._graphs[graph]; - return graphs; + graph = graph === '' ? 1 : (graph && (this._termToNumericId(graph) || -1)); + return typeof graph !== 'number' ? this._graphs : { [graph]: this._graphs[graph] }; } // ### `_uniqueEntities` returns a function that accepts an entity ID @@ -284,7 +283,7 @@ export default class N3Store { predicate = subject.predicate, subject = subject.subject; // Convert terms to internal string representation - graph = termToId(graph); + graph = graph ? this._termToNewNumericId(graph) : 1; // Find the graph that will contain the triple let graphItem = this._graphs[graph]; @@ -344,9 +343,8 @@ export default class N3Store { if (!predicate) graph = subject.graph, object = subject.object, predicate = subject.predicate, subject = subject.subject; - // Convert terms to internal string representation - graph = termToId(graph); + graph = graph ? this._termToNumericId(graph) : 1; // Find internal identifiers for all components // and verify the quad exists. @@ -411,9 +409,6 @@ export default class N3Store { // ### `readQuads` returns an generator of quads matching a pattern. // Setting any field to `undefined` or `null` indicates a wildcard. *readQuads(subject, predicate, object, graph) { - // Convert terms to internal string representation - graph = graph && termToId(graph); - const graphs = this._getGraphs(graph); let content, subjectId, predicateId, objectId; @@ -466,9 +461,6 @@ export default class N3Store { // ### `countQuads` returns the number of quads matching a pattern. // Setting any field to `undefined` or `null` indicates a wildcard. countQuads(subject, predicate, object, graph) { - // Convert terms to internal string representation - graph = graph && termToId(graph); - const graphs = this._getGraphs(graph); let count = 0, content, subjectId, predicateId, objectId; @@ -545,9 +537,6 @@ export default class N3Store { // ### `forSubjects` executes the callback on all subjects that match the pattern. // Setting any field to `undefined` or `null` indicates a wildcard. forSubjects(callback, predicate, object, graph) { - // Convert terms to internal string representation - graph = graph && termToId(graph); - const graphs = this._getGraphs(graph); let content, predicateId, objectId; callback = this._uniqueEntities(callback); @@ -590,9 +579,6 @@ export default class N3Store { // ### `forPredicates` executes the callback on all predicates that match the pattern. // Setting any field to `undefined` or `null` indicates a wildcard. forPredicates(callback, subject, object, graph) { - // Convert terms to internal string representation - graph = graph && termToId(graph); - const graphs = this._getGraphs(graph); let content, subjectId, objectId; callback = this._uniqueEntities(callback); @@ -635,9 +621,6 @@ export default class N3Store { // ### `forObjects` executes the callback on all objects that match the pattern. // Setting any field to `undefined` or `null` indicates a wildcard. forObjects(callback, subject, predicate, graph) { - // Convert terms to internal string representation - graph = graph && termToId(graph); - const graphs = this._getGraphs(graph); let content, subjectId, predicateId; callback = this._uniqueEntities(callback); @@ -684,7 +667,7 @@ export default class N3Store { this.some(quad => { callback(quad.graph); return true; // Halt iteration of some() - }, subject, predicate, object, graph); + }, subject, predicate, object, this._termFromId(this._entities[graph])); } } @@ -799,11 +782,6 @@ export default class N3Store { } } -// Determines whether the argument is a string -function isString(s) { - return typeof s === 'string' || s instanceof String; -} - /** * A class that implements both DatasetCore and Readable. */ diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 3a048521..fa610ae6 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -2025,39 +2025,37 @@ describe('EntityIndex', () => { }); it('custom index should be used when instantiated with store', () => { + const index = { + '': 1, + 's1': 2, + 'p1': 3, + 'o0': 4, + 's2': 5, + 'p2': 6, + 'o2': 7, + }; + const store = new Store([ new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o0')), ], { entityIndex }); expect(store.size).toBe(1); - expect(entityIndex._id).toEqual(3); + expect(entityIndex._id).toEqual(4); const substore = store.match(); substore.add(new Quad(new NamedNode('s2'), new NamedNode('p2'), new NamedNode('o2'))); expect(store.size).toBe(1); expect(substore.size).toBe(2); - expect(entityIndex._id).toEqual(6); - expect(entityIndex._ids).toEqual({ - s1: 1, - p1: 2, - o0: 3, - s2: 4, - p2: 5, - o2: 6, - }); + expect(entityIndex._id).toEqual(7); + expect(entityIndex._ids).toEqual(index); const store2 = new Store([ new Quad(new NamedNode('s1'), new NamedNode('p1'), new NamedNode('o5')), ], { entityIndex }); expect(store2.size).toBe(1); - expect(entityIndex._id).toEqual(7); + expect(entityIndex._id).toEqual(8); expect(entityIndex._ids).toEqual({ - s1: 1, - p1: 2, - o0: 3, - s2: 4, - p2: 5, - o2: 6, - o5: 7, + ...index, + o5: 8, }); }); }); From 3468233b9126b4e7e90e61f7bb28bcb8879ae848 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:11:08 +1000 Subject: [PATCH 8/8] perf: perfom matching over index --- src/N3Store.js | 52 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 79850db6..b2c2b8e2 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -793,8 +793,56 @@ class DatasetCoreAndReadableStream extends Readable { get filtered() { if (!this._filtered) { - const { n3Store, graph, object, predicate, subject } = this; - const newStore = this._filtered = new N3Store({ factory: n3Store._factory, entityIndex: this.options.entityIndex }); + const { n3Store, graph, object, predicate, subject, options: { entityIndex } } = this; + const newStore = this._filtered = new N3Store({ factory: n3Store._factory, entityIndex }); + const graphs = this.n3Store._getGraphs(graph); + + + const subjectId = subject && entityIndex._termToNumericId(subject); + const predicateId = predicate && entityIndex._termToNumericId(predicate); + const objectId = object && entityIndex._termToNumericId(object); + + + + // If only predicate and possibly object are given, the predicate index will be the fastest + yield* this._findInIndex(content.predicates, predicateId, objectId, null, + 'predicate', 'object', 'subject', graphId); +else if (objectId) +// If only object is given, the object index will be the fastest +yield* this._findInIndex(content.objects, objectId, null, null, + 'object', 'subject', 'predicate', graphId); +else +// If nothing is given, iterate subjects and predicates first +yield* this._findInIndex(content.subjects, null, null, null, + 'subject', 'predicate', 'object', graphId); + + for (const graph in graphs) { + let { subjects } = graphs[graph]; + + const newSubjects = false; + const subjectIndex = subject ? ((subjectId in subjects) ? { [subjectId]: subjects[subjectId] } : {}) : subjects; + for (const sid in subjectIndex) { + const newPredicates = false; + const predicates = subjectIndex[sid]; + const predicateIndex = predicate ? ((predicateId in predicates) ? { [predicateId]: predicates[predicateId] } : {}) : predicates; + for (const pid in predicateIndex) { + const objects = predicateIndex[pid] + if (!object && obj) { + (newPredicates ||= {})[pid] = { ...objects }; + } else if (objectId in objects) { + (newPredicates ||= {})[pid] = { [objectId]: null }; + } + } + subjectIndex[id] = predicateIndex; + } + + + newStore._graphs[graph] = { subjects: {}, predicates: {}, objects: {} }; + } + + + + for (const quad of n3Store.readQuads(subject, predicate, object, graph)) newStore.addQuad(quad); }