n3 2.7.11 → 2.8.0
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/README.md +33 -0
- package/browser/n3.esm.min.js +11 -11
- package/browser/n3.min.js +11 -11
- package/lib/N3Lexer.js +102 -25
- package/lib/N3Reasoner.js +13 -4
- package/lib/N3Store.js +385 -27
- package/package.json +1 -1
- package/src/N3Lexer.js +90 -27
- package/src/N3Reasoner.js +14 -4
- package/src/N3Store.js +418 -32
package/src/N3Store.js
CHANGED
|
@@ -8,6 +8,15 @@ import N3Writer from './N3Writer';
|
|
|
8
8
|
const ITERATOR = Symbol('iter');
|
|
9
9
|
const SIZE = Symbol('size');
|
|
10
10
|
|
|
11
|
+
// Neither a registration nor its cleanup record may keep a view or store alive.
|
|
12
|
+
// Initialize lazily so importing N3 and using lazy views needs no weak-reference APIs.
|
|
13
|
+
let observerRegistry;
|
|
14
|
+
function finalizeObserver({ store, observer }) {
|
|
15
|
+
const target = store.deref();
|
|
16
|
+
if (target)
|
|
17
|
+
target._removeObserver(observer);
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
function hasInIndex(index0, key0, key1, key2) {
|
|
12
21
|
const index1 = index0 && index0[key0];
|
|
13
22
|
const index2 = index1 && index1[key1];
|
|
@@ -189,12 +198,15 @@ export default class N3Store {
|
|
|
189
198
|
this._size = 0;
|
|
190
199
|
// `_graphs` contains subject, predicate, and object indexes per graph
|
|
191
200
|
this._graphs = Object.create(null);
|
|
201
|
+
// `_observers` contains weak references to views notified before every mutation
|
|
202
|
+
this._observers = null;
|
|
192
203
|
|
|
193
204
|
// Shift parameters if `quads` is not given
|
|
194
205
|
if (!options && quads && !quads[0] && !(typeof quads.match === 'function'))
|
|
195
206
|
options = quads, quads = null;
|
|
196
207
|
options = options || {};
|
|
197
208
|
this._factory = options.factory || N3DataFactory;
|
|
209
|
+
this._matchSemantics = validateMatchSemantics(options.matchSemantics);
|
|
198
210
|
this._entityIndex = options.entityIndex || new N3EntityIndex({ factory: this._factory });
|
|
199
211
|
this._entities = this._entityIndex._entities;
|
|
200
212
|
this._termFromId = this._entityIndex._termFromId.bind(this._entityIndex);
|
|
@@ -402,6 +414,38 @@ export default class N3Store {
|
|
|
402
414
|
return typeof graph !== 'number' ? this._graphs : { [graph]: this._graphs[graph] };
|
|
403
415
|
}
|
|
404
416
|
|
|
417
|
+
// ### `_addObserver` registers a mutation observer.
|
|
418
|
+
_addObserver(view) {
|
|
419
|
+
const observer = new WeakRef(view);
|
|
420
|
+
const registry = observerRegistry || (observerRegistry = new FinalizationRegistry(finalizeObserver));
|
|
421
|
+
registry.register(view, { store: new WeakRef(this), observer }, observer);
|
|
422
|
+
(this._observers || (this._observers = new Set())).add(observer);
|
|
423
|
+
return observer;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ### `_removeObserver` unregisters a mutation observer.
|
|
427
|
+
_removeObserver(observer) {
|
|
428
|
+
observerRegistry.unregister(observer);
|
|
429
|
+
if (this._observers) {
|
|
430
|
+
this._observers.delete(observer);
|
|
431
|
+
if (this._observers.size === 0)
|
|
432
|
+
this._observers = null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ### `_notifyObservers` notifies observers of a mutation.
|
|
437
|
+
_notifyObservers(subjectId, predicateId, objectId, graphId, added) {
|
|
438
|
+
// Observers can remove themselves during Set iteration
|
|
439
|
+
for (const observer of this._observers) {
|
|
440
|
+
const view = observer.deref();
|
|
441
|
+
if (view)
|
|
442
|
+
view._onParentMutation(subjectId, predicateId, objectId, graphId, added);
|
|
443
|
+
else
|
|
444
|
+
// Finalization may be delayed; also prune collected views during notification.
|
|
445
|
+
this._removeObserver(observer);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
405
449
|
// ### `_uniqueEntities` returns a function that accepts an entity ID
|
|
406
450
|
// and passes the corresponding entity to callback if it hasn't occurred before.
|
|
407
451
|
_uniqueEntities(callback) {
|
|
@@ -435,6 +479,15 @@ export default class N3Store {
|
|
|
435
479
|
// Convert terms to internal string representation
|
|
436
480
|
graph = graph ? this._termToNewNumericId(graph) : 1;
|
|
437
481
|
|
|
482
|
+
// Map long IRIs to shared numeric identifiers before updating the indexes.
|
|
483
|
+
subject = this._termToNewNumericId(subject);
|
|
484
|
+
predicate = this._termToNewNumericId(predicate);
|
|
485
|
+
object = this._termToNewNumericId(object);
|
|
486
|
+
return this._addQuad(subject, predicate, object, graph);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// ### `_addQuad` adds a quad using identifiers from this store's entity index.
|
|
490
|
+
_addQuad(subject, predicate, object, graph) {
|
|
438
491
|
// Find the graph that will contain the triple
|
|
439
492
|
let graphItem = this._graphs[graph];
|
|
440
493
|
// Create the graph if it doesn't exist yet
|
|
@@ -449,12 +502,12 @@ export default class N3Store {
|
|
|
449
502
|
Object.freeze(graphItem);
|
|
450
503
|
}
|
|
451
504
|
|
|
452
|
-
//
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
505
|
+
// Notify observers before inserting a new quad so snapshots retain their prior contents
|
|
506
|
+
if (this._observers !== null) {
|
|
507
|
+
if (hasInIndex(graphItem.subjects, subject, predicate, object))
|
|
508
|
+
return false;
|
|
509
|
+
this._notifyObservers(subject, predicate, object, graph, true);
|
|
510
|
+
}
|
|
458
511
|
|
|
459
512
|
if (!this._addToIndex(graphItem.subjects, subject, predicate, object))
|
|
460
513
|
return false;
|
|
@@ -510,17 +563,25 @@ export default class N3Store {
|
|
|
510
563
|
// Convert terms to internal string representation
|
|
511
564
|
graph = graph ? this._termToNumericId(graph) : 1;
|
|
512
565
|
|
|
513
|
-
|
|
514
|
-
|
|
566
|
+
if (!(subject = subject && this._termToNumericId(subject)) ||
|
|
567
|
+
!(predicate = predicate && this._termToNumericId(predicate)) ||
|
|
568
|
+
!(object = object && this._termToNumericId(object)))
|
|
569
|
+
return false;
|
|
570
|
+
return this._removeQuad(subject, predicate, object, graph);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// ### `_removeQuad` removes a quad using identifiers from this store's entity index.
|
|
574
|
+
_removeQuad(subject, predicate, object, graph) {
|
|
575
|
+
// Verify the quad exists before notifying observers or changing any indexes.
|
|
515
576
|
const graphs = this._graphs;
|
|
516
|
-
|
|
517
|
-
if (!
|
|
518
|
-
!(object = object && this._termToNumericId(object)) || !(graphItem = graphs[graph]) ||
|
|
519
|
-
!(subjects = graphItem.subjects[subject]) ||
|
|
520
|
-
!(predicates = subjects[predicate]) ||
|
|
521
|
-
!(object in predicates))
|
|
577
|
+
const graphItem = graphs[graph];
|
|
578
|
+
if (!graphItem || !hasInIndex(graphItem.subjects, subject, predicate, object))
|
|
522
579
|
return false;
|
|
523
580
|
|
|
581
|
+
// Notify observers before the mutation
|
|
582
|
+
if (this._observers !== null)
|
|
583
|
+
this._notifyObservers(subject, predicate, object, graph, false);
|
|
584
|
+
|
|
524
585
|
// Remove it from all indexes
|
|
525
586
|
this._removeFromIndex(graphItem.subjects, subject, predicate, object);
|
|
526
587
|
this._removeFromIndex(graphItem.predicates, predicate, object, subject);
|
|
@@ -627,8 +688,13 @@ export default class N3Store {
|
|
|
627
688
|
// Note: Since a DatasetCore is an unordered set, the order of the quads within the returned sequence is arbitrary.
|
|
628
689
|
// Setting any field to `undefined` or `null` indicates a wildcard.
|
|
629
690
|
// For backwards compatibility, the object return also implements the Readable stream interface.
|
|
630
|
-
|
|
631
|
-
|
|
691
|
+
// `options.matchSemantics` controls how the view reacts to later mutations.
|
|
692
|
+
match(subject, predicate, object, graph, options = null) {
|
|
693
|
+
return new DatasetCoreAndReadableStream(this, subject, predicate, object, graph, {
|
|
694
|
+
entityIndex: this._entityIndex,
|
|
695
|
+
matchSemantics: options && options.matchSemantics !== undefined ?
|
|
696
|
+
options.matchSemantics : this._matchSemantics,
|
|
697
|
+
});
|
|
632
698
|
}
|
|
633
699
|
|
|
634
700
|
// ### `countQuads` returns the number of quads matching a pattern.
|
|
@@ -957,7 +1023,8 @@ export default class N3Store {
|
|
|
957
1023
|
|
|
958
1024
|
if (Array.isArray(quads))
|
|
959
1025
|
this.addQuads(quads);
|
|
960
|
-
|
|
1026
|
+
// Index merging bypasses observer notifications
|
|
1027
|
+
else if (this._observers === null && quads instanceof N3Store && quads._entityIndex === this._entityIndex) {
|
|
961
1028
|
if (quads._size !== 0) {
|
|
962
1029
|
this._graphs = merge(this._graphs, quads._graphs);
|
|
963
1030
|
this._size = null; // Invalidate the cached size
|
|
@@ -1015,7 +1082,7 @@ export default class N3Store {
|
|
|
1015
1082
|
* @param graph The optional exact graph to match.
|
|
1016
1083
|
*/
|
|
1017
1084
|
deleteMatches(subject, predicate, object, graph) {
|
|
1018
|
-
for (const quad of this.match(subject, predicate, object, graph))
|
|
1085
|
+
for (const quad of this.match(subject, predicate, object, graph, { matchSemantics: 'lazy' }))
|
|
1019
1086
|
this.removeQuad(quad);
|
|
1020
1087
|
return this;
|
|
1021
1088
|
}
|
|
@@ -1143,7 +1210,7 @@ export default class N3Store {
|
|
|
1143
1210
|
* Returns a stream that contains all quads of the dataset.
|
|
1144
1211
|
*/
|
|
1145
1212
|
toStream() {
|
|
1146
|
-
return this.match();
|
|
1213
|
+
return this.match(null, null, null, null, { matchSemantics: 'lazy' });
|
|
1147
1214
|
}
|
|
1148
1215
|
|
|
1149
1216
|
/**
|
|
@@ -1203,6 +1270,98 @@ function indexMatch(index, ids, depth = 0) {
|
|
|
1203
1270
|
return target;
|
|
1204
1271
|
}
|
|
1205
1272
|
|
|
1273
|
+
// Capture a read in the same index order as readQuads, without constructing RDF terms.
|
|
1274
|
+
// A flat list avoids allocating a Quad (and its terms) for every unread result.
|
|
1275
|
+
function snapshotMatch(store, subject, predicate, object, graph) {
|
|
1276
|
+
const snapshot = { store, ids: [] };
|
|
1277
|
+
const subjectId = subject && store._termToNumericId(subject);
|
|
1278
|
+
const predicateId = predicate && store._termToNumericId(predicate);
|
|
1279
|
+
const objectId = object && store._termToNumericId(object);
|
|
1280
|
+
// Only active reads are frozen: bound terms and graphs have already resolved,
|
|
1281
|
+
// and notifications run before deletion. Entity IDs are never removed.
|
|
1282
|
+
|
|
1283
|
+
// Keep this choice aligned with readQuads: changing order would repeat or skip
|
|
1284
|
+
// results when an iterator resumes partway through its snapshot.
|
|
1285
|
+
let indexName, key0, key1, key2, positions;
|
|
1286
|
+
if (objectId && (subjectId || !predicateId)) {
|
|
1287
|
+
indexName = 'objects';
|
|
1288
|
+
[key0, key1, key2] = [objectId, subjectId, predicateId];
|
|
1289
|
+
positions = [2, 0, 1];
|
|
1290
|
+
}
|
|
1291
|
+
else if (!subjectId && predicateId) {
|
|
1292
|
+
indexName = 'predicates';
|
|
1293
|
+
[key0, key1, key2] = [predicateId, objectId, subjectId];
|
|
1294
|
+
positions = [1, 2, 0];
|
|
1295
|
+
}
|
|
1296
|
+
else {
|
|
1297
|
+
indexName = 'subjects';
|
|
1298
|
+
[key0, key1, key2] = [subjectId, predicateId, objectId];
|
|
1299
|
+
positions = [0, 1, 2];
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
const graphs = store._getGraphs(graph), parts = [];
|
|
1303
|
+
for (const graphId in graphs) {
|
|
1304
|
+
const index = graphs[graphId][indexName];
|
|
1305
|
+
const graphKey = Number(graphId);
|
|
1306
|
+
for (const value0 in (key0 ? { [key0]: index[key0] } : index)) {
|
|
1307
|
+
const index1 = index[value0];
|
|
1308
|
+
if (!index1) continue; // eslint-disable-line no-continue
|
|
1309
|
+
parts[positions[0]] = Number(value0);
|
|
1310
|
+
for (const value1 in (key1 ? { [key1]: index1[key1] } : index1)) {
|
|
1311
|
+
const index2 = index1[value1];
|
|
1312
|
+
if (!index2) continue; // eslint-disable-line no-continue
|
|
1313
|
+
parts[positions[1]] = Number(value1);
|
|
1314
|
+
for (const value2 in (key2 ? (key2 in index2 ? { [key2]: null } : {}) : index2)) {
|
|
1315
|
+
parts[positions[2]] = Number(value2);
|
|
1316
|
+
snapshot.ids.push(parts[0], parts[1], parts[2], graphKey);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
return snapshot;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// Skip already-yielded IDs in constant time; only materialize the requested tail.
|
|
1325
|
+
function* iterateSnapshot({ store, ids }, offset) {
|
|
1326
|
+
const entities = store._entities;
|
|
1327
|
+
let subject, predicate, object, graph;
|
|
1328
|
+
let subjectId, predicateId, objectId, graphId;
|
|
1329
|
+
for (let i = offset * 4; i < ids.length; i += 4) {
|
|
1330
|
+
if (subjectId !== ids[i])
|
|
1331
|
+
subject = store._termFromId(entities[subjectId = ids[i]]);
|
|
1332
|
+
if (predicateId !== ids[i + 1])
|
|
1333
|
+
predicate = store._termFromId(entities[predicateId = ids[i + 1]]);
|
|
1334
|
+
if (objectId !== ids[i + 2])
|
|
1335
|
+
object = store._termFromId(entities[objectId = ids[i + 2]]);
|
|
1336
|
+
if (graphId !== ids[i + 3])
|
|
1337
|
+
graph = store._termFromId(entities[graphId = ids[i + 3]]);
|
|
1338
|
+
yield store._factory.quad(subject, predicate, object, graph);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
function validateMatchSemantics(semantics = 'lazy') {
|
|
1343
|
+
if (semantics !== 'lazy' && semantics !== 'snapshot' && semantics !== 'forwarded')
|
|
1344
|
+
throw new Error(`Unknown matchSemantics: ${semantics}`);
|
|
1345
|
+
if (semantics !== 'lazy' && (typeof WeakRef !== 'function' || typeof FinalizationRegistry !== 'function'))
|
|
1346
|
+
throw new Error('Non-lazy matchSemantics requires WeakRef and FinalizationRegistry support');
|
|
1347
|
+
return semantics;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// Returns the intersection of two quad patterns, or false if they conflict.
|
|
1351
|
+
function intersectMatchPatterns(left, right) {
|
|
1352
|
+
const result = new Array(4);
|
|
1353
|
+
for (let i = 0; i < 4; i++) {
|
|
1354
|
+
const leftTerm = left[i], rightTerm = right[i];
|
|
1355
|
+
if (leftTerm === null || leftTerm === undefined)
|
|
1356
|
+
result[i] = rightTerm;
|
|
1357
|
+
else if (rightTerm === null || rightTerm === undefined || termToId(leftTerm) === termToId(rightTerm))
|
|
1358
|
+
result[i] = leftTerm;
|
|
1359
|
+
else
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
return result;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1206
1365
|
/**
|
|
1207
1366
|
* A class that implements both DatasetCore and Readable.
|
|
1208
1367
|
*/
|
|
@@ -1210,13 +1369,103 @@ class DatasetCoreAndReadableStream extends Readable {
|
|
|
1210
1369
|
constructor(n3Store, subject, predicate, object, graph, options) {
|
|
1211
1370
|
super({ objectMode: true });
|
|
1212
1371
|
Object.assign(this, { n3Store, subject, predicate, object, graph, options });
|
|
1372
|
+
const semantics = this._semantics = validateMatchSemantics(options.matchSemantics);
|
|
1373
|
+
|
|
1374
|
+
if (options.matchesNothing) {
|
|
1375
|
+
this._matchesNothing = true;
|
|
1376
|
+
this._filtered = new N3Store({ factory: n3Store._factory, entityIndex: options.entityIndex });
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
if (semantics !== 'lazy') {
|
|
1380
|
+
// Active reads share a snapshot only when their source changes.
|
|
1381
|
+
this._readers = null;
|
|
1382
|
+
// Cache pattern ids on first use
|
|
1383
|
+
this._subjectId = this._predicateId = this._objectId = this._graphId = undefined;
|
|
1384
|
+
if (!this._matchesNothing) {
|
|
1385
|
+
this._observer = n3Store._addObserver(this);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// ### `_matchesPattern` tests quad ids against this view.
|
|
1391
|
+
_matchesPattern(subjectId, predicateId, objectId, graphId) {
|
|
1392
|
+
const { subject, predicate, object, graph, n3Store } = this;
|
|
1393
|
+
if (subject && subjectId !== (this._subjectId || (this._subjectId = n3Store._termToNumericId(subject))))
|
|
1394
|
+
return false;
|
|
1395
|
+
if (predicate && predicateId !== (this._predicateId || (this._predicateId = n3Store._termToNumericId(predicate))))
|
|
1396
|
+
return false;
|
|
1397
|
+
if (object && objectId !== (this._objectId || (this._objectId = n3Store._termToNumericId(object))))
|
|
1398
|
+
return false;
|
|
1399
|
+
return graph === null || graph === undefined ||
|
|
1400
|
+
graphId === (this._graphId || (this._graphId =
|
|
1401
|
+
graph === '' || isDefaultGraph(graph) ? 1 : n3Store._termToNumericId(graph)));
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// ### `_matchesQuad` tests a Quad against this view.
|
|
1405
|
+
_matchesQuad(quad) {
|
|
1406
|
+
const { subject, predicate, object, graph } = this;
|
|
1407
|
+
return !this._matchesNothing &&
|
|
1408
|
+
(subject === null || subject === undefined || termToId(subject) === termToId(quad.subject)) &&
|
|
1409
|
+
(predicate === null || predicate === undefined || termToId(predicate) === termToId(quad.predicate)) &&
|
|
1410
|
+
(object === null || object === undefined || termToId(object) === termToId(quad.object)) &&
|
|
1411
|
+
(graph === null || graph === undefined || termToId(graph) === termToId(quad.graph));
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// ### `_assertMatchesPattern` rejects a Quad outside this view.
|
|
1415
|
+
_assertMatchesPattern(quad) {
|
|
1416
|
+
if (!this._matchesQuad(quad))
|
|
1417
|
+
throw new Error('Quad does not match the forwarded view pattern');
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// ### `_sourceIterator` returns an iterator over the current backing store.
|
|
1421
|
+
_sourceIterator() {
|
|
1422
|
+
return this._filtered ? this._filtered[Symbol.iterator]() :
|
|
1423
|
+
this.n3Store.readQuads(this.subject, this.predicate, this.object, this.graph);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// ### `_freezeCurrentIterators` freezes only readers still using the live source.
|
|
1427
|
+
_freezeCurrentIterators() {
|
|
1428
|
+
if (this._readers) {
|
|
1429
|
+
this._readers.snapshot = this._filtered ? snapshotMatch(this._filtered) :
|
|
1430
|
+
snapshotMatch(this.n3Store, this.subject, this.predicate, this.object, this.graph);
|
|
1431
|
+
// Each iterator retains its own group. New reads must see the new source;
|
|
1432
|
+
// the view must not retain snapshots belonging to suspended or abandoned reads.
|
|
1433
|
+
this._readers = null;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// ### `_onParentMutation` applies a parent mutation to this view.
|
|
1438
|
+
_onParentMutation(subjectId, predicateId, objectId, graphId, added) {
|
|
1439
|
+
if (!this._matchesPattern(subjectId, predicateId, objectId, graphId))
|
|
1440
|
+
return;
|
|
1441
|
+
|
|
1442
|
+
this._freezeCurrentIterators();
|
|
1443
|
+
|
|
1444
|
+
// Keep using the parent until materialization
|
|
1445
|
+
if (this._semantics === 'forwarded') {
|
|
1446
|
+
if (this._filtered) {
|
|
1447
|
+
if (added)
|
|
1448
|
+
this._filtered._addQuad(subjectId, predicateId, objectId, graphId);
|
|
1449
|
+
else
|
|
1450
|
+
this._filtered._removeQuad(subjectId, predicateId, objectId, graphId);
|
|
1451
|
+
}
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// Capture the pre-mutation snapshot
|
|
1456
|
+
this._filtered = this.filtered;
|
|
1213
1457
|
}
|
|
1214
1458
|
|
|
1215
1459
|
get filtered() {
|
|
1216
1460
|
if (!this._filtered) {
|
|
1217
1461
|
const { n3Store, graph, object, predicate, subject } = this;
|
|
1462
|
+
if (this._semantics !== 'lazy')
|
|
1463
|
+
this._freezeCurrentIterators();
|
|
1218
1464
|
const newStore = this._filtered = new N3Store({ factory: n3Store._factory, entityIndex: this.options.entityIndex });
|
|
1219
1465
|
|
|
1466
|
+
if (this._semantics === 'snapshot')
|
|
1467
|
+
this._detachObserver();
|
|
1468
|
+
|
|
1220
1469
|
let subjectId, predicateId, objectId;
|
|
1221
1470
|
|
|
1222
1471
|
// Translate IRIs to internal index keys.
|
|
@@ -1273,7 +1522,42 @@ class DatasetCoreAndReadableStream extends Readable {
|
|
|
1273
1522
|
}
|
|
1274
1523
|
}
|
|
1275
1524
|
|
|
1525
|
+
// ### `_destroy` closes the cached iterator.
|
|
1526
|
+
_destroy(error, callback) {
|
|
1527
|
+
if (this[ITERATOR]) {
|
|
1528
|
+
this[ITERATOR].return();
|
|
1529
|
+
this[ITERATOR] = null;
|
|
1530
|
+
}
|
|
1531
|
+
callback(error);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// ### `_detachObserver` stops observing the parent store.
|
|
1535
|
+
_detachObserver() {
|
|
1536
|
+
if (this._observer) {
|
|
1537
|
+
this.n3Store._removeObserver(this._observer);
|
|
1538
|
+
this._observer = null;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// ### `_detach` freezes the view and stops observing the parent.
|
|
1543
|
+
_detach() {
|
|
1544
|
+
this._filtered = this.filtered;
|
|
1545
|
+
this._detachObserver();
|
|
1546
|
+
// Lazy views deferred this state
|
|
1547
|
+
if (this._semantics === 'lazy')
|
|
1548
|
+
this._readers = null;
|
|
1549
|
+
this._semantics = 'snapshot';
|
|
1550
|
+
return this;
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1276
1553
|
addAll(quads) {
|
|
1554
|
+
if (this._semantics === 'forwarded') {
|
|
1555
|
+
for (const quad of quads) {
|
|
1556
|
+
this._assertMatchesPattern(quad);
|
|
1557
|
+
this.n3Store.addQuad(quad);
|
|
1558
|
+
}
|
|
1559
|
+
return this;
|
|
1560
|
+
}
|
|
1277
1561
|
return this.filtered.addAll(quads);
|
|
1278
1562
|
}
|
|
1279
1563
|
|
|
@@ -1282,6 +1566,19 @@ class DatasetCoreAndReadableStream extends Readable {
|
|
|
1282
1566
|
}
|
|
1283
1567
|
|
|
1284
1568
|
deleteMatches(subject, predicate, object, graph) {
|
|
1569
|
+
if (this._semantics === 'forwarded') {
|
|
1570
|
+
// No deletion pattern can match a view with conflicting ancestor patterns.
|
|
1571
|
+
if (this._matchesNothing)
|
|
1572
|
+
throw new Error('Cannot delete from a conflicting forwarded view pattern');
|
|
1573
|
+
const pattern = intersectMatchPatterns(
|
|
1574
|
+
[this.subject, this.predicate, this.object, this.graph],
|
|
1575
|
+
[subject, predicate, object, graph],
|
|
1576
|
+
);
|
|
1577
|
+
if (!pattern)
|
|
1578
|
+
throw new Error('Deletion pattern does not match the forwarded view pattern');
|
|
1579
|
+
this.n3Store.deleteMatches(...pattern);
|
|
1580
|
+
return this;
|
|
1581
|
+
}
|
|
1285
1582
|
return this.filtered.deleteMatches(subject, predicate, object, graph);
|
|
1286
1583
|
}
|
|
1287
1584
|
|
|
@@ -1294,19 +1591,41 @@ class DatasetCoreAndReadableStream extends Readable {
|
|
|
1294
1591
|
}
|
|
1295
1592
|
|
|
1296
1593
|
every(callback, subject, predicate, object, graph) {
|
|
1297
|
-
return this.filtered.every(
|
|
1594
|
+
return this.filtered.every(this._semantics === 'forwarded' ?
|
|
1595
|
+
quad => callback(quad, this) : callback, subject, predicate, object, graph);
|
|
1298
1596
|
}
|
|
1299
1597
|
|
|
1300
1598
|
filter(iteratee) {
|
|
1301
|
-
return this.filtered.filter(
|
|
1599
|
+
return this.filtered.filter(this._semantics === 'forwarded' ?
|
|
1600
|
+
quad => iteratee(quad, this) : iteratee);
|
|
1302
1601
|
}
|
|
1303
1602
|
|
|
1304
1603
|
forEach(callback, subject, predicate, object, graph) {
|
|
1305
|
-
return this.filtered.forEach(
|
|
1604
|
+
return this.filtered.forEach(this._semantics === 'forwarded' ?
|
|
1605
|
+
quad => callback(quad, this) : callback, subject, predicate, object, graph);
|
|
1306
1606
|
}
|
|
1307
1607
|
|
|
1308
1608
|
import(stream) {
|
|
1309
|
-
|
|
1609
|
+
if (this._semantics !== 'forwarded')
|
|
1610
|
+
return this.filtered.import(stream);
|
|
1611
|
+
|
|
1612
|
+
const view = this;
|
|
1613
|
+
function onData(quad) {
|
|
1614
|
+
try {
|
|
1615
|
+
view._assertMatchesPattern(quad);
|
|
1616
|
+
view.n3Store.addQuad(quad);
|
|
1617
|
+
}
|
|
1618
|
+
catch (error) {
|
|
1619
|
+
// Stop this import; RDF/JS streams need not implement destroy()
|
|
1620
|
+
stream.removeListener('data', onData);
|
|
1621
|
+
if (typeof stream.destroy === 'function')
|
|
1622
|
+
stream.destroy(error);
|
|
1623
|
+
else
|
|
1624
|
+
stream.emit('error', error);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
stream.on('data', onData);
|
|
1628
|
+
return stream;
|
|
1310
1629
|
}
|
|
1311
1630
|
|
|
1312
1631
|
intersection(other) {
|
|
@@ -1314,11 +1633,13 @@ class DatasetCoreAndReadableStream extends Readable {
|
|
|
1314
1633
|
}
|
|
1315
1634
|
|
|
1316
1635
|
map(iteratee) {
|
|
1317
|
-
return this.filtered.map(
|
|
1636
|
+
return this.filtered.map(this._semantics === 'forwarded' ?
|
|
1637
|
+
quad => iteratee(quad, this) : iteratee);
|
|
1318
1638
|
}
|
|
1319
1639
|
|
|
1320
1640
|
some(callback, subject, predicate, object, graph) {
|
|
1321
|
-
return this.filtered.some(
|
|
1641
|
+
return this.filtered.some(this._semantics === 'forwarded' ?
|
|
1642
|
+
quad => callback(quad, this) : callback, subject, predicate, object, graph);
|
|
1322
1643
|
}
|
|
1323
1644
|
|
|
1324
1645
|
toCanonical() {
|
|
@@ -1326,23 +1647,29 @@ class DatasetCoreAndReadableStream extends Readable {
|
|
|
1326
1647
|
}
|
|
1327
1648
|
|
|
1328
1649
|
toStream() {
|
|
1650
|
+
if (this._semantics !== 'lazy')
|
|
1651
|
+
// Use a fresh sync iterator instead of consuming this view's own readable stream.
|
|
1652
|
+
return Readable.from(this[Symbol.iterator]());
|
|
1329
1653
|
return this._filtered ?
|
|
1330
1654
|
this._filtered.toStream()
|
|
1331
|
-
: this.n3Store.match(this.subject, this.predicate, this.object, this.graph);
|
|
1655
|
+
: this.n3Store.match(this.subject, this.predicate, this.object, this.graph, { matchSemantics: 'lazy' });
|
|
1332
1656
|
}
|
|
1333
1657
|
|
|
1334
1658
|
union(quads) {
|
|
1335
1659
|
return this._filtered ?
|
|
1336
1660
|
this._filtered.union(quads)
|
|
1337
|
-
: this.n3Store.match(this.subject, this.predicate, this.object, this.graph).addAll(quads);
|
|
1661
|
+
: this.n3Store.match(this.subject, this.predicate, this.object, this.graph, { matchSemantics: 'lazy' }).addAll(quads);
|
|
1338
1662
|
}
|
|
1339
1663
|
|
|
1340
1664
|
toArray() {
|
|
1665
|
+
if (this._semantics !== 'lazy')
|
|
1666
|
+
return [...this];
|
|
1341
1667
|
return this._filtered ? this._filtered.toArray() : this.n3Store.getQuads(this.subject, this.predicate, this.object, this.graph);
|
|
1342
1668
|
}
|
|
1343
1669
|
|
|
1344
1670
|
reduce(callback, initialValue) {
|
|
1345
|
-
return this.filtered.reduce(
|
|
1671
|
+
return this.filtered.reduce(this._semantics === 'forwarded' ?
|
|
1672
|
+
(accumulator, quad) => callback(accumulator, quad, this) : callback, initialValue);
|
|
1346
1673
|
}
|
|
1347
1674
|
|
|
1348
1675
|
toString() {
|
|
@@ -1350,10 +1677,20 @@ class DatasetCoreAndReadableStream extends Readable {
|
|
|
1350
1677
|
}
|
|
1351
1678
|
|
|
1352
1679
|
add(quad) {
|
|
1680
|
+
if (this._semantics === 'forwarded') {
|
|
1681
|
+
this._assertMatchesPattern(quad);
|
|
1682
|
+
this.n3Store.addQuad(quad);
|
|
1683
|
+
return this;
|
|
1684
|
+
}
|
|
1353
1685
|
return this.filtered.add(quad);
|
|
1354
1686
|
}
|
|
1355
1687
|
|
|
1356
1688
|
delete(quad) {
|
|
1689
|
+
if (this._semantics === 'forwarded') {
|
|
1690
|
+
this._assertMatchesPattern(quad);
|
|
1691
|
+
this.n3Store.removeQuad(quad);
|
|
1692
|
+
return this;
|
|
1693
|
+
}
|
|
1357
1694
|
return this.filtered.delete(quad);
|
|
1358
1695
|
}
|
|
1359
1696
|
|
|
@@ -1361,12 +1698,61 @@ class DatasetCoreAndReadableStream extends Readable {
|
|
|
1361
1698
|
return this.filtered.has(quad);
|
|
1362
1699
|
}
|
|
1363
1700
|
|
|
1364
|
-
match(subject, predicate, object, graph) {
|
|
1365
|
-
|
|
1701
|
+
match(subject, predicate, object, graph, options = null) {
|
|
1702
|
+
const requestedSemantics = options && options.matchSemantics;
|
|
1703
|
+
if (options && requestedSemantics !== undefined) {
|
|
1704
|
+
validateMatchSemantics(requestedSemantics);
|
|
1705
|
+
if (requestedSemantics !== this._semantics)
|
|
1706
|
+
throw new Error(`Cannot override matchSemantics on a view: inherited "${this._semantics}", received "${requestedSemantics}"`);
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
if (this._semantics !== 'forwarded')
|
|
1710
|
+
return new DatasetCoreAndReadableStream(this.filtered, subject, predicate, object, graph, {
|
|
1711
|
+
entityIndex: this.options.entityIndex,
|
|
1712
|
+
matchSemantics: this._semantics,
|
|
1713
|
+
});
|
|
1714
|
+
|
|
1715
|
+
const pattern = !this._matchesNothing && intersectMatchPatterns(
|
|
1716
|
+
[this.subject, this.predicate, this.object, this.graph],
|
|
1717
|
+
[subject, predicate, object, graph],
|
|
1718
|
+
);
|
|
1719
|
+
const [matchedSubject, matchedPredicate, matchedObject, matchedGraph] = pattern || [null, null, null, null];
|
|
1720
|
+
return new DatasetCoreAndReadableStream(
|
|
1721
|
+
this.n3Store, matchedSubject, matchedPredicate, matchedObject, matchedGraph, {
|
|
1722
|
+
entityIndex: this.options.entityIndex,
|
|
1723
|
+
matchSemantics: 'forwarded',
|
|
1724
|
+
matchesNothing: !pattern,
|
|
1725
|
+
});
|
|
1366
1726
|
}
|
|
1367
1727
|
|
|
1368
1728
|
[Symbol.iterator]() {
|
|
1369
|
-
return this.
|
|
1370
|
-
|
|
1729
|
+
return this._semantics === 'lazy' ? this._sourceIterator() : this._iterateStable();
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
*_iterateStable() {
|
|
1733
|
+
const readers = this._readers || (this._readers = { count: 0, snapshot: null });
|
|
1734
|
+
const source = this._sourceIterator();
|
|
1735
|
+
let yielded = 0;
|
|
1736
|
+
readers.count++;
|
|
1737
|
+
try {
|
|
1738
|
+
while (!readers.snapshot) {
|
|
1739
|
+
const { value, done } = source.next();
|
|
1740
|
+
// A custom factory can mutate the store inside next(), even on its first call.
|
|
1741
|
+
if (readers.snapshot || done)
|
|
1742
|
+
break;
|
|
1743
|
+
yielded++;
|
|
1744
|
+
yield value;
|
|
1745
|
+
}
|
|
1746
|
+
if (readers.snapshot)
|
|
1747
|
+
yield* iterateSnapshot(readers.snapshot, yielded);
|
|
1748
|
+
}
|
|
1749
|
+
finally {
|
|
1750
|
+
source.return();
|
|
1751
|
+
if (--readers.count === 0) {
|
|
1752
|
+
readers.snapshot = null;
|
|
1753
|
+
if (this._readers === readers)
|
|
1754
|
+
this._readers = null;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1371
1757
|
}
|
|
1372
1758
|
}
|