diff --git a/src/N3Store.js b/src/N3Store.js index 675b44f0..4bd66c3b 100644 --- a/src/N3Store.js +++ b/src/N3Store.js @@ -45,6 +45,40 @@ function intersect(s1, s2, depth = 4) { return target; } +/** + * Determines the difference 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 difference; this should + * *not* be set as the value for an index. + */ +function difference(s1, s2, depth = 4) { + let target = false; + + for (const key in s1) { + // When the key is not in the index, then none of the triples defined by s1[key] are + // in s2 and so we want to copy them over to the resultant store. + if (!(key in s2)) { + target = target || Object.create(null); + target[key] = depth === 0 ? null : merge({}, s1[key], depth - 1); + } + else if (depth !== 0) { + const diff = difference(s1[key], s2[key], depth - 1); + if (diff !== false) { + target = target || Object.create(null); + target[key] = diff; + } + // 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; + } + } + } + + return target; +} + // ## Constructor export class N3EntityIndex { constructor(options = {}) { @@ -896,6 +930,16 @@ export default class N3Store { if (other === this) return new N3Store({ entityIndex: this._entityIndex }); + if ((other instanceof N3Store) && other._entityIndex === this._entityIndex) { + const store = new N3Store({ entityIndex: this._entityIndex }); + const graphs = difference(this._graphs, other._graphs); + if (graphs) { + store._graphs = 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 2945687a..210c2ee7 100644 --- a/test/N3Store-test.js +++ b/test/N3Store-test.js @@ -2139,6 +2139,25 @@ describe('Store', () => { expect(store.difference(store).size).toEqual(0); expect(store2.difference(store2).size).toEqual(0); + expect(store.difference(new Store([...store])).size).toEqual(0); + expect(store2.difference(new Store([...store2])).size).toEqual(0); + + const stores = [store, store1, store2, store3, store4, storeb, storeg, empty]; + for (const s1 of stores) { + for (const s2 of stores) { + expect(s1.difference(s2).size).toBeLessThanOrEqual(s1.size); + expect(s1.difference(s2)._graphs).toBeTruthy(); + expect(s1.union(s2).difference(s1).equals(s2.difference(s1))).toBe(true); + expect(s1.difference(s2).union(s1).equals(s1)).toBe(true); + expect(new Store([...s1.union(s2).difference(s1)]).equals(new Store([...s2.difference(s1)]))).toBe(true); + expect(new Store([...s1.difference(s2).union(s1)]).equals(new Store([...s1]))).toBe(true); + + const newStore = s1.difference(s2); + const size = newStore.size; + newStore.add(new Quad(new NamedNode('mys1'), new NamedNode('myp1'), new NamedNode('myo1'))); + expect(newStore.size).toBe(size + 1); + } + } }); });