Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement RDF/JS dataset specification #392

Merged
merged 13 commits into from
Jul 29, 2024
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,8 @@ 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
### [`Dataset` Interface](https://rdf.js.org/dataset-spec/#dataset-interface)
This store adheres to the `Dataset` interface which exposes the following properties

Attributes:
- `size` — A non-negative integer that specifies the number of quads in the set.
Expand All @@ -320,7 +320,7 @@ Methods:
- `[Symbol.iterator]` — Implements the iterator protocol to allow iteration over all `quads` in the dataset as in the example above.

### Addition and deletion of quads
The store provides the following manipulation methods in addition to implementing the standard [`DatasetCore` Interface](https://rdf.js.org/dataset-spec/#datasetcore-interface)
The store implements the following manipulation methods in addition to the standard [`Dataset` Interface](https://rdf.js.org/dataset-spec/#dataset-interface)
([documentation](http://rdfjs.github.io/N3.js/docs/N3Store.html)):
- `addQuad` to insert one quad
- `addQuads` to insert an array of quads
Expand Down
238 changes: 231 additions & 7 deletions src/N3Store.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Readable } from 'readable-stream';
import { default as N3DataFactory, termToId, termFromId } from './N3DataFactory';
import namespaces from './IRIs';
import { isDefaultGraph } from './N3Util';
import N3Writer from './N3Writer';

const ITERATOR = Symbol('iter');

Expand Down Expand Up @@ -511,7 +512,7 @@ export default class N3Store {
// Setting any field to `undefined` or `null` indicates a wildcard.
forEach(callback, subject, predicate, object, graph) {
this.some(quad => {
callback(quad);
callback(quad, this);
return false;
}, subject, predicate, object, graph);
}
Expand All @@ -520,12 +521,7 @@ export default class N3Store {
// and returns `true` if it returns truthy for all them.
// Setting any field to `undefined` or `null` indicates a wildcard.
every(callback, subject, predicate, object, graph) {
let some = false;
const every = !this.some(quad => {
some = true;
return !callback(quad);
}, subject, predicate, object, graph);
return some && every;
return !this.some(quad => !callback(quad, this), subject, predicate, object, graph);
}

// ### `some` executes the callback on all quads,
Expand Down Expand Up @@ -786,6 +782,158 @@ export default class N3Store {
return lists;
}

/**
* Returns `true` if the current dataset is a superset of the given dataset; in other words, returns `true` if
* the given dataset is a subset of, i.e., is contained within, the current dataset.
*
* Blank Nodes will be normalized.
*/
addAll(quads) {
if (Array.isArray(quads))
this.addQuads(quads);
else {
for (const quad of quads)
this.add(quad);
}
return this;
}


/**
* Returns `true` if the current dataset is a superset of the given dataset; in other words, returns `true` if
* the given dataset is a subset of, i.e., is contained within, the current dataset.
*
* Blank Nodes will be normalized.
*/
contains(other) {
return other.every(quad => this.has(quad));
}

/**
* This method removes the quads in the current dataset that match the given arguments.
*
* The logic described in {@link https://rdf.js.org/dataset-spec/#quad-matching|Quad Matching} is applied for each
* quad in this dataset, to select the quads which will be deleted.
*
* @param subject The optional exact subject to match.
* @param predicate The optional exact predicate to match.
* @param object The optional exact object to match.
* @param graph The optional exact graph to match.
*/
deleteMatches(subject, predicate, object, graph) {
for (const quad of this.match(subject, predicate, object, graph))
this.removeQuad(quad);
return this;
}

/**
* Returns a new dataset that contains all quads from the current dataset that are not included in the given dataset.
*/
difference(other) {
return this.filter(quad => !other.has(quad));
}
Comment on lines +832 to +834
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is lots of room for optimisation here; in particular, whenever other is also an N3Store and they share an index we can just merge / diff / compare the indices for set operations.

Would be worth opening up an issue to do this once this PR is merged.


/**
* Returns true if the current dataset contains the same graph structure as the given dataset.
*
* Blank Nodes will be normalized.
*/
equals(other) {
return other === this || (this.size === other.size && this.contains(other));
}

/**
* Creates a new dataset with all the quads that pass the test implemented by the provided `iteratee`.
*
* This method is aligned with Array.prototype.filter() in ECMAScript-262.
*/
filter(iteratee) {
const store = new N3Store();
for (const quad of this)
if (iteratee(quad, this))
store.add(quad);
return store;
}

/**
* Returns a new dataset containing all quads from the current dataset that are also included in the given dataset.
*/
intersection(other) {
return this.filter(quad => other.has(quad));
}

/**
* Returns a new dataset containing all quads returned by applying `iteratee` to each quad in the current dataset.
*/
map(iteratee) {
const store = new N3Store();
for (const quad of this)
store.add(iteratee(quad, this));
return store;
}

/**
* This method calls the `iteratee` method on each `quad` of the `Dataset`. The first time the `iteratee` method
* is called, the `accumulator` value is the `initialValue`, or, if not given, equals the first quad of the `Dataset`.
* The return value of each call to the `iteratee` method is used as the `accumulator` value for the next call.
*
* This method returns the return value of the last `iteratee` call.
*
* This method is aligned with `Array.prototype.reduce()` in ECMAScript-262.
*/
reduce(callback, initialValue) {
const iter = this.readQuads();
let accumulator = initialValue === undefined ? iter.next().value : initialValue;
for (const quad of iter)
accumulator = callback(accumulator, quad, this);
return accumulator;
}

/**
* Returns the set of quads within the dataset as a host-language-native sequence, for example an `Array` in
* ECMAScript-262.
*
* Since a `Dataset` is an unordered set, the order of the quads within the returned sequence is arbitrary.
*/
toArray() {
return this.getQuads();
}

/**
* Returns an N-Quads string representation of the dataset, preprocessed with the
* {@link https://json-ld.github.io/normalization/spec/|RDF Dataset Normalization} algorithm.
*/
toCanonical() {
throw new Error('not implemented');
}

/**
* Returns a stream that contains all quads of the dataset.
*/
toStream() {
return this.match();
}

/**
* Returns an N-Quads string representation of the dataset.
*
* No prior normalization is required, therefore the results for the same quads may vary depending on the `Dataset`
* implementation.
*/
toString() {
return (new N3Writer()).quadsToString(this);
}

/**
* Returns a new `Dataset` that is a concatenation of this dataset and the quads given as an argument.
*/
union(quads) {
const store = new N3Store();
store.addAll(this);
store.addAll(quads);
return store;
}

// ### Store is an iterable.
// Can be used where iterables are expected: for...of loops, array spread operator,
// `yield*`, and destructuring assignment (order is not guaranteed).
Expand Down Expand Up @@ -831,6 +979,82 @@ class DatasetCoreAndReadableStream extends Readable {
}
}

addAll(quads) {
return this.filtered.addAll(quads);
}

contains(other) {
return this.filtered.contains(other);
}

deleteMatches(subject, predicate, object, graph) {
return this.filtered.deleteMatches(subject, predicate, object, graph);
}

difference(other) {
return this.filtered.difference(other);
}

equals(other) {
return this.filtered.equals(other);
}

every(callback, subject, predicate, object, graph) {
return this.filtered.every(callback, subject, predicate, object, graph);
}

filter(iteratee) {
return this.filtered.filter(iteratee);
}

forEach(callback, subject, predicate, object, graph) {
return this.filtered.forEach(callback, subject, predicate, object, graph);
}

import(stream) {
return this.filtered.import(stream);
}

intersection(other) {
return this.filtered.intersection(other);
}

map(iteratee) {
return this.filtered.map(iteratee);
}

some(callback, subject, predicate, object, graph) {
return this.filtered.some(callback, subject, predicate, object, graph);
}

toCanonical() {
return this.filtered.toCanonical();
}

toStream() {
return this._filtered ?
this._filtered.toStream()
: this.n3Store.match(this.subject, this.predicate, this.object, this.graph);
}

union(quads) {
return this._filtered ?
this._filtered.union(quads)
: this.n3Store.match(this.subject, this.predicate, this.object, this.graph).addAll(quads);
}

toArray() {
return this._filtered ? this._filtered.toArray() : this.n3Store.getQuads(this.subject, this.predicate, this.object, this.graph);
}

reduce(callback, initialValue) {
return this.filtered.reduce(callback, initialValue);
}

toString() {
return (new N3Writer()).quadsToString(this);
}

add(quad) {
return this.filtered.add(quad);
}
Expand Down
7 changes: 4 additions & 3 deletions src/N3Writer.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,10 @@ export default class N3Writer {

// ### `quadsToString` serializes an array of quads as a string
quadsToString(quads) {
return quads.map(t => {
return this.quadToString(t.subject, t.predicate, t.object, t.graph);
}).join('');
let quadsString = '';
for (const quad of quads)
quadsString += this.quadToString(quad.subject, quad.predicate, quad.object, quad.graph);
return quadsString;
}

// ### `_encodeSubject` represents a subject
Expand Down
Loading