n3 2.13.5 → 2.13.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/N3Lexer.js CHANGED
@@ -218,7 +218,7 @@ class N3Lexer {
218
218
  this._blank = /_:((?:[0-9A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)(?:[ \t]+|(?=\.?[,;:!\^\s#()\[\]\{\}"'<>]))/y;
219
219
  this._number = /[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/y;
220
220
  this._boolean = /(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/y;
221
- this._atKeyword = /@[a-z]+(?=[\s#<:"'])/iy;
221
+ this._atKeyword = /@[a-z]+(?=[\s#<:"'.])/iy;
222
222
  this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<"'])/iy;
223
223
  this._n3Verb = /(?:has|is|of)(?=[\s#()\[\]\{\}"'<>?_+\-0-9])/y;
224
224
  this._n3Id = /id(?=[\s#<])/y;
package/lib/N3Store.js CHANGED
@@ -32,94 +32,6 @@ function hasInIndex(index0, key0, key1, key2) {
32
32
  return !!index2 && key2 in index2;
33
33
  }
34
34
 
35
- // The number of index leaves that the last `merge` call added
36
- let mergedLeaves = 0;
37
- function merge(target, source, depth = 4) {
38
- let size = target[SIZE] || 0;
39
- for (const key in source) {
40
- if (!(key in target)) {
41
- size++;
42
- if (depth === 0) mergedLeaves++;
43
- target[key] = depth === 0 ? null : merge(Object.create(null), source[key], depth - 1);
44
- }
45
- // Merge into the existing object in place,
46
- // as graph objects are frozen and cannot be reassigned
47
- else if (depth !== 0) merge(target[key], source[key], depth - 1);
48
- }
49
- // Depth 2 is the level of the `subjects`, `predicates`, and `objects` indexes.
50
- if (depth <= 2) target[SIZE] = size;
51
- return target;
52
- }
53
-
54
- /**
55
- * Determines the intersection of the `_graphs` index s1 and s2.
56
- * s1 and s2 *must* belong to Stores that share an `_entityIndex`.
57
- *
58
- * False is returned when there is no intersection; this should
59
- * *not* be set as the value for an index.
60
- */
61
- function intersect(s1, s2, depth = 4) {
62
- let target = false,
63
- size = 0;
64
- if (depth <= 2 && s2[SIZE] < s1[SIZE]) [s1, s2] = [s2, s1];
65
- for (const key in s1) {
66
- if (key in s2) {
67
- const intersection = depth === 0 ? null : intersect(s1[key], s2[key], depth - 1);
68
- if (intersection !== false) {
69
- target = target || Object.create(null);
70
- target[key] = intersection;
71
- size++;
72
- }
73
- // Depth 3 is the 'subjects', 'predicates' and 'objects' keys.
74
- // If the 'subjects' index is empty, so will the 'predicates' and 'objects' index.
75
- else if (depth === 3) {
76
- return false;
77
- }
78
- }
79
- }
80
-
81
- // Depth 2 is the level of the `subjects`, `predicates`, and `objects` indexes.
82
- if (depth <= 2 && target) target[SIZE] = size;
83
- return target;
84
- }
85
-
86
- /**
87
- * Determines the difference of the `_graphs` index s1 and s2.
88
- * s1 and s2 *must* belong to Stores that share an `_entityIndex`.
89
- *
90
- * False is returned when there is no difference; this should
91
- * *not* be set as the value for an index.
92
- */
93
- function difference(s1, s2, depth = 4) {
94
- let target = false,
95
- size = 0;
96
- for (const key in s1) {
97
- // When the key is not in the index, then none of the triples defined by s1[key] are
98
- // in s2 and so we want to copy them over to the resultant store.
99
- if (!(key in s2)) {
100
- target = target || Object.create(null);
101
- target[key] = depth === 0 ? null : merge({}, s1[key], depth - 1);
102
- size++;
103
- } else if (depth !== 0) {
104
- const diff = difference(s1[key], s2[key], depth - 1);
105
- if (diff !== false) {
106
- target = target || Object.create(null);
107
- target[key] = diff;
108
- size++;
109
- }
110
- // Depth 3 is the 'subjects', 'predicates' and 'objects' keys.
111
- // If the 'subjects' index is empty, so will the 'predicates' and 'objects' index.
112
- else if (depth === 3) {
113
- return false;
114
- }
115
- }
116
- }
117
-
118
- // Depth 2 is the level of the `subjects`, `predicates`, and `objects` indexes.
119
- if (depth <= 2 && target) target[SIZE] = size;
120
- return target;
121
- }
122
-
123
35
  // Returns the key of a term in the entity index.
124
36
  // Keys mark the term type by their first character, so the IRI of a named node
125
37
  // that starts with such a marker (as relative IRIs can) is wrapped in < and >.
@@ -234,6 +146,7 @@ class N3Store {
234
146
  this._size = 0;
235
147
  // `_graphs` contains subject, predicate, and object indexes per graph
236
148
  this._graphs = Object.create(null);
149
+ this._graphCount = 0;
237
150
  // `_observers` contains weak references to views notified before every mutation
238
151
  this._observers = null;
239
152
 
@@ -528,6 +441,7 @@ class N3Store {
528
441
  // Freezing a graph helps subsequent `add` performance,
529
442
  // and properties will never be modified anyway
530
443
  Object.freeze(graphItem);
444
+ this._graphCount++;
531
445
  }
532
446
 
533
447
  // Notify observers before inserting a new quad so snapshots retain their prior contents
@@ -542,6 +456,64 @@ class N3Store {
542
456
  return true;
543
457
  }
544
458
 
459
+ // ### `_addFromIndex` adds the quads in the given graph indexes of a store
460
+ // that shares this store's entity index, so no terms need to be converted.
461
+ // With `otherGraphs`, it only adds the quads that are not in those indexes.
462
+ // Index keys are strings, so the ids are converted back to the numbers that
463
+ // `addQuad` uses: forwarded views compare the ids they are notified with to
464
+ // their own numeric ids.
465
+ _addFromIndex(graphs, otherGraphs = null) {
466
+ for (const graphKey in graphs) {
467
+ const subjects = graphs[graphKey].subjects,
468
+ other = otherGraphs && otherGraphs[graphKey];
469
+ const otherSubjects = other ? other.subjects : null;
470
+ for (const subjectKey in subjects) {
471
+ const subject = Number(subjectKey),
472
+ predicates = subjects[subjectKey];
473
+ const otherPredicates = otherSubjects && otherSubjects[subjectKey];
474
+ for (const predicateKey in predicates) {
475
+ const predicate = Number(predicateKey),
476
+ objects = predicates[predicateKey];
477
+ const otherObjects = otherPredicates && otherPredicates[predicateKey];
478
+ for (const objectKey in objects) {
479
+ if (!otherObjects || !(objectKey in otherObjects)) this._addQuad(subject, predicate, Number(objectKey), Number(graphKey));
480
+ }
481
+ }
482
+ }
483
+ }
484
+ }
485
+
486
+ // ### `_addIntersectionFromIndex` adds the quads that are in both of the
487
+ // given graph indexes, walking the graphs of the first and, below them,
488
+ // whichever index has fewer keys
489
+ _addIntersectionFromIndex(graphs, otherGraphs) {
490
+ for (const graphKey in graphs) {
491
+ const other = otherGraphs[graphKey];
492
+ if (other) {
493
+ let subjects = graphs[graphKey].subjects,
494
+ otherSubjects = other.subjects;
495
+ if (otherSubjects[SIZE] < subjects[SIZE]) [subjects, otherSubjects] = [otherSubjects, subjects];
496
+ for (const subjectKey in subjects) {
497
+ let predicates = subjects[subjectKey],
498
+ otherPredicates = otherSubjects[subjectKey];
499
+ if (otherPredicates) {
500
+ if (otherPredicates[SIZE] < predicates[SIZE]) [predicates, otherPredicates] = [otherPredicates, predicates];
501
+ for (const predicateKey in predicates) {
502
+ let objects = predicates[predicateKey],
503
+ otherObjects = otherPredicates[predicateKey];
504
+ if (otherObjects) {
505
+ if (otherObjects[SIZE] < objects[SIZE]) [objects, otherObjects] = [otherObjects, objects];
506
+ for (const objectKey in objects) {
507
+ if (objectKey in otherObjects) this._addQuad(Number(subjectKey), Number(predicateKey), Number(objectKey), Number(graphKey));
508
+ }
509
+ }
510
+ }
511
+ }
512
+ }
513
+ }
514
+ }
515
+ }
516
+
545
517
  // ### `addQuads` adds multiple quads to the store
546
518
  addQuads(quads) {
547
519
  for (let i = 0; i < quads.length; i++) this.addQuad(quads[i]);
@@ -614,7 +586,10 @@ class N3Store {
614
586
  if (this._size !== null) this._size--;
615
587
 
616
588
  // Remove the graph if it is empty
617
- if (graphItem.subjects[SIZE] === 0) delete graphs[graph];
589
+ if (graphItem.subjects[SIZE] === 0) {
590
+ delete graphs[graph];
591
+ this._graphCount--;
592
+ }
618
593
  return true;
619
594
  }
620
595
 
@@ -1025,15 +1000,8 @@ class N3Store {
1025
1000
  addAll(quads) {
1026
1001
  if (quads instanceof DatasetCoreAndReadableStream) quads = quads.filtered;
1027
1002
  if (Array.isArray(quads)) this.addQuads(quads);
1028
- // Index merging bypasses observer notifications
1029
- else if (this._observers === null && quads instanceof N3Store && quads._entityIndex === this._entityIndex) {
1030
- if (quads.size !== 0) {
1031
- // Each new quad adds one leaf to each of the three indexes
1032
- mergedLeaves = 0;
1033
- this._graphs = merge(this._graphs, quads._graphs);
1034
- if (this._size !== null) this._size += mergedLeaves / 3;
1035
- }
1036
- } else {
1003
+ // A store with the same entity index can be copied by identifier
1004
+ else if (quads instanceof N3Store && quads._entityIndex === this._entityIndex) this._addFromIndex(quads._graphs);else {
1037
1005
  for (const quad of quads) this.add(quad);
1038
1006
  }
1039
1007
  return this;
@@ -1105,11 +1073,7 @@ class N3Store {
1105
1073
  const store = new N3Store({
1106
1074
  entityIndex: this._entityIndex
1107
1075
  });
1108
- const graphs = difference(this._graphs, other._graphs);
1109
- if (graphs) {
1110
- store._graphs = graphs;
1111
- store._size = null;
1112
- }
1076
+ store._addFromIndex(this._graphs, other._graphs);
1113
1077
  return store;
1114
1078
  }
1115
1079
  return this.filter(quad => !other.has(quad));
@@ -1147,17 +1111,15 @@ class N3Store {
1147
1111
  const store = new N3Store({
1148
1112
  entityIndex: this._entityIndex
1149
1113
  });
1150
- store._graphs = merge(Object.create(null), this._graphs);
1151
- store._size = this.size;
1114
+ store._addFromIndex(this._graphs);
1152
1115
  return store;
1153
1116
  } else if (other instanceof N3Store && this._entityIndex === other._entityIndex) {
1154
1117
  const store = new N3Store({
1155
1118
  entityIndex: this._entityIndex
1156
1119
  });
1157
- const graphs = intersect(other._graphs, this._graphs);
1158
- if (graphs) {
1159
- store._graphs = graphs;
1160
- store._size = null;
1120
+ // Starting a loop over no graphs costs more than checking for an empty store
1121
+ if (this._size !== 0 && other._size !== 0) {
1122
+ if (other._graphCount < this._graphCount) store._addIntersectionFromIndex(other._graphs, this._graphs);else store._addIntersectionFromIndex(this._graphs, other._graphs);
1161
1123
  }
1162
1124
  return store;
1163
1125
  }
@@ -1250,8 +1212,7 @@ class N3Store {
1250
1212
  const store = new N3Store({
1251
1213
  entityIndex: this._entityIndex
1252
1214
  });
1253
- store._graphs = merge(Object.create(null), this._graphs);
1254
- store._size = this.size;
1215
+ store._addFromIndex(this._graphs);
1255
1216
  store.addAll(quads);
1256
1217
  return store;
1257
1218
  }
@@ -1516,11 +1477,14 @@ class DatasetCoreAndReadableStream extends _readableStream.Readable {
1516
1477
  predicates = indexMatch(content.predicates, [predicateId, objectId, subjectId]);
1517
1478
  objects = indexMatch(content.objects, [objectId, subjectId, predicateId]);
1518
1479
  }
1519
- if (subjects) newStore._graphs[graphKey] = {
1520
- subjects,
1521
- predicates,
1522
- objects
1523
- };
1480
+ if (subjects) {
1481
+ newStore._graphs[graphKey] = {
1482
+ subjects,
1483
+ predicates,
1484
+ objects
1485
+ };
1486
+ newStore._graphCount++;
1487
+ }
1524
1488
  }
1525
1489
  }
1526
1490
  newStore._size = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n3",
3
- "version": "2.13.5",
3
+ "version": "2.13.7",
4
4
  "description": "Lightning fast, asynchronous, streaming Turtle / N3 / RDF library.",
5
5
  "author": "Ruben Verborgh <ruben.verborgh@gmail.com>",
6
6
  "keywords": [
@@ -131,7 +131,12 @@
131
131
  }
132
132
  ],
133
133
  "@semantic-release/npm",
134
- "@semantic-release/github"
134
+ [
135
+ "@semantic-release/github",
136
+ {
137
+ "successCommentCondition": "<% return !branch.prerelease; %>"
138
+ }
139
+ ]
135
140
  ]
136
141
  },
137
142
  "publishConfig": {
package/src/N3Lexer.js CHANGED
@@ -203,7 +203,7 @@ export default class N3Lexer {
203
203
  this._blank = /_:((?:[0-9A-Z_a-z\xc0-\xd6\xd8-\xf6\xf8-\u02ff\u0370-\u037d\u037f-\u1fff\u200c\u200d\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])(?:\.?[\-0-9A-Z_a-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u037f-\u1fff\u200c\u200d\u203f\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]|[\ud800-\udb7f][\udc00-\udfff])*)(?:[ \t]+|(?=\.?[,;:!\^\s#()\[\]\{\}"'<>]))/y;
204
204
  this._number = /[\-+]?(?:(\d+\.\d*|\.?\d+)[eE][\-+]?\d+|(?=\.?\d)\d*(?:(\.)\d+)?)(?=\.?[,;:!\^\s#()\[\]\{\}"'<>])/y;
205
205
  this._boolean = /(?:true|false)(?=[.,;!\^\s#()\[\]\{\}"'<>])/y;
206
- this._atKeyword = /@[a-z]+(?=[\s#<:"'])/iy;
206
+ this._atKeyword = /@[a-z]+(?=[\s#<:"'.])/iy;
207
207
  this._keyword = /(?:PREFIX|BASE|VERSION|GRAPH)(?=[\s#<"'])/iy;
208
208
  this._n3Verb = /(?:has|is|of)(?=[\s#()\[\]\{\}"'<>?_+\-0-9])/y;
209
209
  this._n3Id = /id(?=[\s#<])/y;
package/src/N3Store.js CHANGED
@@ -23,105 +23,6 @@ function hasInIndex(index0, key0, key1, key2) {
23
23
  return !!index2 && key2 in index2;
24
24
  }
25
25
 
26
- // The number of index leaves that the last `merge` call added
27
- let mergedLeaves = 0;
28
-
29
- function merge(target, source, depth = 4) {
30
- let size = target[SIZE] || 0;
31
- for (const key in source) {
32
- if (!(key in target)) {
33
- size++;
34
- if (depth === 0) mergedLeaves++;
35
- target[key] = depth === 0 ? null : merge(Object.create(null), source[key], depth - 1);
36
- }
37
- // Merge into the existing object in place,
38
- // as graph objects are frozen and cannot be reassigned
39
- else if (depth !== 0)
40
- merge(target[key], source[key], depth - 1);
41
- }
42
- // Depth 2 is the level of the `subjects`, `predicates`, and `objects` indexes.
43
- if (depth <= 2)
44
- target[SIZE] = size;
45
-
46
- return target;
47
- }
48
-
49
- /**
50
- * Determines the intersection of the `_graphs` index s1 and s2.
51
- * s1 and s2 *must* belong to Stores that share an `_entityIndex`.
52
- *
53
- * False is returned when there is no intersection; this should
54
- * *not* be set as the value for an index.
55
- */
56
- function intersect(s1, s2, depth = 4) {
57
- let target = false, size = 0;
58
-
59
- if (depth <= 2 && s2[SIZE] < s1[SIZE])
60
- [s1, s2] = [s2, s1];
61
-
62
- for (const key in s1) {
63
- if (key in s2) {
64
- const intersection = depth === 0 ? null : intersect(s1[key], s2[key], depth - 1);
65
- if (intersection !== false) {
66
- target = target || Object.create(null);
67
- target[key] = intersection;
68
- size++;
69
- }
70
- // Depth 3 is the 'subjects', 'predicates' and 'objects' keys.
71
- // If the 'subjects' index is empty, so will the 'predicates' and 'objects' index.
72
- else if (depth === 3) {
73
- return false;
74
- }
75
- }
76
- }
77
-
78
- // Depth 2 is the level of the `subjects`, `predicates`, and `objects` indexes.
79
- if (depth <= 2 && target)
80
- target[SIZE] = size;
81
-
82
- return target;
83
- }
84
-
85
- /**
86
- * Determines the difference of the `_graphs` index s1 and s2.
87
- * s1 and s2 *must* belong to Stores that share an `_entityIndex`.
88
- *
89
- * False is returned when there is no difference; this should
90
- * *not* be set as the value for an index.
91
- */
92
- function difference(s1, s2, depth = 4) {
93
- let target = false, size = 0;
94
-
95
- for (const key in s1) {
96
- // When the key is not in the index, then none of the triples defined by s1[key] are
97
- // in s2 and so we want to copy them over to the resultant store.
98
- if (!(key in s2)) {
99
- target = target || Object.create(null);
100
- target[key] = depth === 0 ? null : merge({}, s1[key], depth - 1);
101
- size++;
102
- }
103
- else if (depth !== 0) {
104
- const diff = difference(s1[key], s2[key], depth - 1);
105
- if (diff !== false) {
106
- target = target || Object.create(null);
107
- target[key] = diff;
108
- size++;
109
- }
110
- // Depth 3 is the 'subjects', 'predicates' and 'objects' keys.
111
- // If the 'subjects' index is empty, so will the 'predicates' and 'objects' index.
112
- else if (depth === 3) {
113
- return false;
114
- }
115
- }
116
- }
117
-
118
- // Depth 2 is the level of the `subjects`, `predicates`, and `objects` indexes.
119
- if (depth <= 2 && target)
120
- target[SIZE] = size;
121
-
122
- return target;
123
- }
124
-
125
26
  // Returns the key of a term in the entity index.
126
27
  // Keys mark the term type by their first character, so the IRI of a named node
127
28
  // that starts with such a marker (as relative IRIs can) is wrapped in < and >.
@@ -259,6 +160,7 @@ export default class N3Store {
259
160
  this._size = 0;
260
161
  // `_graphs` contains subject, predicate, and object indexes per graph
261
162
  this._graphs = Object.create(null);
163
+ this._graphCount = 0;
262
164
  // `_observers` contains weak references to views notified before every mutation
263
165
  this._observers = null;
264
166
 
@@ -555,6 +457,7 @@ export default class N3Store {
555
457
  // Freezing a graph helps subsequent `add` performance,
556
458
  // and properties will never be modified anyway
557
459
  Object.freeze(graphItem);
460
+ this._graphCount++;
558
461
  }
559
462
 
560
463
  // Notify observers before inserting a new quad so snapshots retain their prior contents
@@ -573,6 +476,63 @@ export default class N3Store {
573
476
  return true;
574
477
  }
575
478
 
479
+ // ### `_addFromIndex` adds the quads in the given graph indexes of a store
480
+ // that shares this store's entity index, so no terms need to be converted.
481
+ // With `otherGraphs`, it only adds the quads that are not in those indexes.
482
+ // Index keys are strings, so the ids are converted back to the numbers that
483
+ // `addQuad` uses: forwarded views compare the ids they are notified with to
484
+ // their own numeric ids.
485
+ _addFromIndex(graphs, otherGraphs = null) {
486
+ for (const graphKey in graphs) {
487
+ const subjects = graphs[graphKey].subjects, other = otherGraphs && otherGraphs[graphKey];
488
+ const otherSubjects = other ? other.subjects : null;
489
+ for (const subjectKey in subjects) {
490
+ const subject = Number(subjectKey), predicates = subjects[subjectKey];
491
+ const otherPredicates = otherSubjects && otherSubjects[subjectKey];
492
+ for (const predicateKey in predicates) {
493
+ const predicate = Number(predicateKey), objects = predicates[predicateKey];
494
+ const otherObjects = otherPredicates && otherPredicates[predicateKey];
495
+ for (const objectKey in objects) {
496
+ if (!otherObjects || !(objectKey in otherObjects))
497
+ this._addQuad(subject, predicate, Number(objectKey), Number(graphKey));
498
+ }
499
+ }
500
+ }
501
+ }
502
+ }
503
+
504
+ // ### `_addIntersectionFromIndex` adds the quads that are in both of the
505
+ // given graph indexes, walking the graphs of the first and, below them,
506
+ // whichever index has fewer keys
507
+ _addIntersectionFromIndex(graphs, otherGraphs) {
508
+ for (const graphKey in graphs) {
509
+ const other = otherGraphs[graphKey];
510
+ if (other) {
511
+ let subjects = graphs[graphKey].subjects, otherSubjects = other.subjects;
512
+ if (otherSubjects[SIZE] < subjects[SIZE])
513
+ [subjects, otherSubjects] = [otherSubjects, subjects];
514
+ for (const subjectKey in subjects) {
515
+ let predicates = subjects[subjectKey], otherPredicates = otherSubjects[subjectKey];
516
+ if (otherPredicates) {
517
+ if (otherPredicates[SIZE] < predicates[SIZE])
518
+ [predicates, otherPredicates] = [otherPredicates, predicates];
519
+ for (const predicateKey in predicates) {
520
+ let objects = predicates[predicateKey], otherObjects = otherPredicates[predicateKey];
521
+ if (otherObjects) {
522
+ if (otherObjects[SIZE] < objects[SIZE])
523
+ [objects, otherObjects] = [otherObjects, objects];
524
+ for (const objectKey in objects) {
525
+ if (objectKey in otherObjects)
526
+ this._addQuad(Number(subjectKey), Number(predicateKey), Number(objectKey), Number(graphKey));
527
+ }
528
+ }
529
+ }
530
+ }
531
+ }
532
+ }
533
+ }
534
+ }
535
+
576
536
  // ### `addQuads` adds multiple quads to the store
577
537
  addQuads(quads) {
578
538
  for (let i = 0; i < quads.length; i++)
@@ -643,8 +603,10 @@ export default class N3Store {
643
603
  if (this._size !== null) this._size--;
644
604
 
645
605
  // Remove the graph if it is empty
646
- if (graphItem.subjects[SIZE] === 0)
606
+ if (graphItem.subjects[SIZE] === 0) {
647
607
  delete graphs[graph];
608
+ this._graphCount--;
609
+ }
648
610
  return true;
649
611
  }
650
612
 
@@ -1104,15 +1066,9 @@ export default class N3Store {
1104
1066
 
1105
1067
  if (Array.isArray(quads))
1106
1068
  this.addQuads(quads);
1107
- // Index merging bypasses observer notifications
1108
- else if (this._observers === null && quads instanceof N3Store && quads._entityIndex === this._entityIndex) {
1109
- if (quads.size !== 0) {
1110
- // Each new quad adds one leaf to each of the three indexes
1111
- mergedLeaves = 0;
1112
- this._graphs = merge(this._graphs, quads._graphs);
1113
- if (this._size !== null) this._size += mergedLeaves / 3;
1114
- }
1115
- }
1069
+ // A store with the same entity index can be copied by identifier
1070
+ else if (quads instanceof N3Store && quads._entityIndex === this._entityIndex)
1071
+ this._addFromIndex(quads._graphs);
1116
1072
  else {
1117
1073
  for (const quad of quads)
1118
1074
  this.add(quad);
@@ -1189,11 +1145,7 @@ export default class N3Store {
1189
1145
 
1190
1146
  if ((other instanceof N3Store) && other._entityIndex === this._entityIndex) {
1191
1147
  const store = new N3Store({ entityIndex: this._entityIndex });
1192
- const graphs = difference(this._graphs, other._graphs);
1193
- if (graphs) {
1194
- store._graphs = graphs;
1195
- store._size = null;
1196
- }
1148
+ store._addFromIndex(this._graphs, other._graphs);
1197
1149
  return store;
1198
1150
  }
1199
1151
 
@@ -1234,16 +1186,17 @@ export default class N3Store {
1234
1186
 
1235
1187
  if (other === this) {
1236
1188
  const store = new N3Store({ entityIndex: this._entityIndex });
1237
- store._graphs = merge(Object.create(null), this._graphs);
1238
- store._size = this.size;
1189
+ store._addFromIndex(this._graphs);
1239
1190
  return store;
1240
1191
  }
1241
1192
  else if ((other instanceof N3Store) && this._entityIndex === other._entityIndex) {
1242
1193
  const store = new N3Store({ entityIndex: this._entityIndex });
1243
- const graphs = intersect(other._graphs, this._graphs);
1244
- if (graphs) {
1245
- store._graphs = graphs;
1246
- store._size = null;
1194
+ // Starting a loop over no graphs costs more than checking for an empty store
1195
+ if (this._size !== 0 && other._size !== 0) {
1196
+ if (other._graphCount < this._graphCount)
1197
+ store._addIntersectionFromIndex(other._graphs, this._graphs);
1198
+ else
1199
+ store._addIntersectionFromIndex(this._graphs, other._graphs);
1247
1200
  }
1248
1201
  return store;
1249
1202
  }
@@ -1331,9 +1284,7 @@ export default class N3Store {
1331
1284
  */
1332
1285
  union(quads) {
1333
1286
  const store = new N3Store({ entityIndex: this._entityIndex });
1334
- store._graphs = merge(Object.create(null), this._graphs);
1335
- store._size = this.size;
1336
-
1287
+ store._addFromIndex(this._graphs);
1337
1288
  store.addAll(quads);
1338
1289
  return store;
1339
1290
  }
@@ -1598,8 +1549,10 @@ class DatasetCoreAndReadableStream extends Readable {
1598
1549
  objects = indexMatch(content.objects, [objectId, subjectId, predicateId]);
1599
1550
  }
1600
1551
 
1601
- if (subjects)
1552
+ if (subjects) {
1602
1553
  newStore._graphs[graphKey] = { subjects, predicates, objects };
1554
+ newStore._graphCount++;
1555
+ }
1603
1556
  }
1604
1557
  }
1605
1558
  newStore._size = null;