From d0ea9919e6555dc28730711ca3901e391c72d1cc Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Fri, 6 Sep 2024 23:36:52 +1000 Subject: [PATCH 1/8] perf: compute intersection over indices --- src/N3Store.js | 27 +++++++++++++++++++++++++++ test/N3Store-test.js | 24 ++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/src/N3Store.js b/src/N3Store.js index 46236cba..4a4f7ff4 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -17,6 +17,26 @@ function merge(target, source, depth = 4) { return target; } +function intersect(s1, s2, depth = 4) { + let target = null; + + for (const key in s1) { + if (key in s2) { + let intersection = null; + if (depth > 0) { + intersection = intersect(s1[key], s2[key], depth - 1); + if (intersection === null) + continue; + } + + target ||= Object.create(null); + target[key] = intersection; + } + } + + return target; +} + // ## Constructor export class N3EntityIndex { constructor(options = {}) { @@ -901,7 +921,14 @@ export default class N3Store { const store = new N3Store({ entityIndex: this._entityIndex }); store._graphs = merge(Object.create(null), this._graphs); store._size = this._size; + return store; + } else if ((other instanceof N3Store) && this._entityIndex === other._entityIndex) { + const store = new N3Store({ entityIndex: this._entityIndex }); + store._graphs = intersect(other._graphs, this._graphs); + store._size = null; + return store; } + return this.filter(quad => other.has(quad)); } diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 5d9403db..859180a1 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -2137,8 +2137,32 @@ describe('Store', () => { expect(store2.size).toEqual(2); expect(store.size).toEqual(1); + const stores = [store, store1, store2, store3, storeb, storeg, empty]; + for (const s1 of stores) { + for (const s2 of stores) { + expect(s1.intersection(s2).size).toBeLessThanOrEqual(s1.size); + expect(s1.intersection(s2).size).toBeLessThanOrEqual(s2.size); + expect(s1.intersection(s2).equals(s2.intersection(s1))); + expect(s1.union(s2).intersection(s1).equals(s1)); + expect(s1.intersection(s2).union(s1).equals(s1)); + } + } + expect(store.intersection(store).size).toEqual(1); expect(store2.intersection(store2).size).toEqual(2); + expect(storeg.intersection(store).size).toBe(0); + expect(store.intersection(storeg).size).toBe(0); + expect(storeg.intersection(storeb).size).toBe(1); + expect(store.intersection(storeb).size).toBe(1); + expect(store.intersection(store1).size).toBe(1); + expect(store.intersection(store3).size).toBe(1); + expect(store.intersection(store2).size).toBe(1); + expect(empty.intersection(store1).size).toBe(0); + expect(empty.intersection(store2).size).toBe(0); + expect(store2.intersection(store1).size).toBe(1); + expect(store1.intersection(store2).size).toBe(1); + expect(store1.intersection(storeb).size).toBe(1); + expect(storeb.intersection(store1).size).toBe(1); }); }); From ae500ad64e327a4499ed4a6834225c4536ce2358 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Fri, 6 Sep 2024 23:39:33 +1000 Subject: [PATCH 2/8] chore: dont use ||= syntax --- src/N3Store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/N3Store.js b/src/N3Store.js index 4a4f7ff4..6db515ef 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -29,7 +29,7 @@ function intersect(s1, s2, depth = 4) { continue; } - target ||= Object.create(null); + target = target || Object.create(null); target[key] = intersection; } } From 78932b4f8c595b7454af370b04fb9b781e31b986 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Fri, 6 Sep 2024 23:42:18 +1000 Subject: [PATCH 3/8] chore: fix lint errors --- src/N3Store.js | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 6db515ef..a8e829c6 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -18,19 +18,15 @@ function merge(target, source, depth = 4) { } function intersect(s1, s2, depth = 4) { - let target = null; + let target = false; for (const key in s1) { if (key in s2) { - let intersection = null; - if (depth > 0) { - intersection = intersect(s1[key], s2[key], depth - 1); - if (intersection === null) - continue; + const intersection = depth === 0 ? null : intersect(s1[key], s2[key], depth - 1); + if (intersection !== false) { + target = target || Object.create(null); + target[key] = intersection; } - - target = target || Object.create(null); - target[key] = intersection; } } @@ -922,7 +918,8 @@ export default class N3Store { store._graphs = merge(Object.create(null), this._graphs); store._size = this._size; return store; - } else if ((other instanceof N3Store) && this._entityIndex === other._entityIndex) { + } + else if ((other instanceof N3Store) && this._entityIndex === other._entityIndex) { const store = new N3Store({ entityIndex: this._entityIndex }); store._graphs = intersect(other._graphs, this._graphs); store._size = null; From 37c73807c42e4f163c6453c0aa0f9985e27dc6e6 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 8 Sep 2024 10:31:44 +1000 Subject: [PATCH 4/8] fix: dont set _graphs = false --- src/N3Store.js | 7 +++++-- test/N3Store-test.js | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index a8e829c6..626a1502 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -921,8 +921,11 @@ export default class N3Store { } else if ((other instanceof N3Store) && this._entityIndex === other._entityIndex) { const store = new N3Store({ entityIndex: this._entityIndex }); - store._graphs = intersect(other._graphs, this._graphs); - store._size = null; + const graphs = intersect(other._graphs, this._graphs); + if (graphs) { + store._graphs = graphs; + store._size = null; + } return store; } diff --git a/test/N3Store-test.js b/test/N3Store-test.js index 859180a1..ec1c5387 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -2145,6 +2145,13 @@ describe('Store', () => { expect(s1.intersection(s2).equals(s2.intersection(s1))); expect(s1.union(s2).intersection(s1).equals(s1)); expect(s1.intersection(s2).union(s1).equals(s1)); + expect(new Store([...s1.union(s2).intersection(s1)]).equals(new Store([...s1]))); + expect(new Store([...s1.intersection(s2).union(s1)]).equals(new Store([...s2]))); + + const newStore = s1.intersection(s2); + const size = newStore.size; + newStore.add(new Quad(new NamedNode('mys1'), new NamedNode('myp1'), new NamedNode('myo1'))); + expect(newStore.size).toBe(size + 1); } } From 7d086a43c706cecd42ce6ac3820170c23fbdd661 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 8 Sep 2024 10:33:45 +1000 Subject: [PATCH 5/8] chore: explicitly add truthiness test for graphs --- test/N3Store-test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/N3Store-test.js b/test/N3Store-test.js index ec1c5387..fa8b139e 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -2142,6 +2142,7 @@ describe('Store', () => { for (const s2 of stores) { expect(s1.intersection(s2).size).toBeLessThanOrEqual(s1.size); expect(s1.intersection(s2).size).toBeLessThanOrEqual(s2.size); + expect(s1.intersection(s2)._graphs).toBeTruthy(); expect(s1.intersection(s2).equals(s2.intersection(s1))); expect(s1.union(s2).intersection(s1).equals(s1)); expect(s1.intersection(s2).union(s1).equals(s1)); From e3534fb93f7317d95afd024fd92eb91937ad6467 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 8 Sep 2024 10:39:55 +1000 Subject: [PATCH 6/8] chore: add docs --- src/N3Store.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/N3Store.js b/src/N3Store.js index 626a1502..e91d690d 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -17,6 +17,13 @@ function merge(target, source, depth = 4) { return target; } +/** + * Determines the intersection of the `_graphs` index s1 and s2. + * s1 and s2 *must* belong to Stores that share an `_entityIndex`. + * + * False is returned when there is no intersection; this should + * *not* be set as the value for an index. + */ function intersect(s1, s2, depth = 4) { let target = false; From 0eb3e62daaa743b13e89964b93352eaa948f549e Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 8 Sep 2024 10:41:28 +1000 Subject: [PATCH 7/8] chore: fix lint errors --- src/N3Store.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/N3Store.js b/src/N3Store.js index e91d690d..791ab0ef 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -20,7 +20,7 @@ function merge(target, source, depth = 4) { /** * Determines the intersection of the `_graphs` index s1 and s2. * s1 and s2 *must* belong to Stores that share an `_entityIndex`. - * + * * False is returned when there is no intersection; this should * *not* be set as the value for an index. */ From 2cf330c751af1eb543f12acc6a7a8479fcabb757 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Sun, 8 Sep 2024 11:00:35 +1000 Subject: [PATCH 8/8] perf: remove unecessary lookups --- src/N3Store.js | 5 +++++ test/N3Store-test.js | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/N3Store.js b/src/N3Store.js index 791ab0ef..10df07a6 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -34,6 +34,11 @@ function intersect(s1, s2, depth = 4) { target = target || Object.create(null); target[key] = intersection; } + // Depth 3 is the 'subjects', 'predicates' and 'objects' keys. + // If the 'subjects' index is empty, so will the 'predicates' and 'objects' index. + else if (depth === 3) { + return false; + } } } diff --git a/test/N3Store-test.js b/test/N3Store-test.js index fa8b139e..fc750745 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -2030,7 +2030,7 @@ describe('Store', () => { const matrix = [true, false, 'instantiated'].flatMap(match => [true, false].map(share => [match, share])); describe.each(matrix)('RDF/JS Dataset Methods [DatasetCoreAndReadableStream: %s] [sharedIndex: %s]', (match, shareIndex) => { - let q, store, store1, store2, store3, storeg, storeb, empty, options; + let q, store, store1, store2, store3, store4, storeg, storeb, empty, options; beforeEach(() => { options = shareIndex ? { entityIndex: new EntityIndex() } : {}; @@ -2046,6 +2046,7 @@ describe('Store', () => { store1 = new Store([q[0], q[1]], options); store2 = new Store([q[0], q[2]], options); store3 = new Store([q[0], q[3]], options); + store4 = new Store([new Quad(new NamedNode('a'), new NamedNode('b'), new NamedNode('c'))], options); if (match) { empty = store2.match(new NamedNode('sn')); @@ -2137,7 +2138,7 @@ describe('Store', () => { expect(store2.size).toEqual(2); expect(store.size).toEqual(1); - const stores = [store, store1, store2, store3, storeb, storeg, empty]; + const stores = [store, store1, store2, store3, store4, storeb, storeg, empty]; for (const s1 of stores) { for (const s2 of stores) { expect(s1.intersection(s2).size).toBeLessThanOrEqual(s1.size);