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 @@ -304,8 +304,8 @@ for (const quad of store.match(namedNode('http://ex.org/Mickey'), null, null))
console.log(quad);
```

### [`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 @@ -318,7 +318,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 provides the following manipulation methods in addition to implementing the standard [`Dataset` Interface](https://rdf.js.org/dataset-spec/#dataset-interface)
jeswr marked this conversation as resolved.
Show resolved Hide resolved
([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';

// ## Constructor
export default class N3Store {
Expand Down Expand Up @@ -476,7 +477,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 @@ -485,12 +486,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 @@ -775,6 +771,158 @@ export default class N3Store {
return lists;
}

/**
* Returns `true` if the current instance is a superset of the given dataset; differently put: if the given dataset
* is a subset of, is contained in the current dataset.
jeswr marked this conversation as resolved.
Show resolved Hide resolved
*
* 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 instance is a superset of the given dataset; differently put: if the given dataset
* is a subset of, is contained in the current dataset.
jeswr marked this conversation as resolved.
Show resolved Hide resolved
*
* Blank Nodes will be normalized.
*/
contains(other) {
return other.every(quad => this.has(quad));
}

/**
* This method removes the quads in the current instance that match the given arguments.
jeswr marked this conversation as resolved.
Show resolved Hide resolved
*
* 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.
jeswr marked this conversation as resolved.
Show resolved Hide resolved
*
* @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, not included in the given dataset.
jeswr marked this conversation as resolved.
Show resolved Hide resolved
*/
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 instance contains the same graph structure as the given dataset.
jeswr marked this conversation as resolved.
Show resolved Hide resolved
*
* 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 alls quads from the current dataset that are also included in the given dataset.
jeswr marked this conversation as resolved.
Show resolved Hide resolved
*/
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` on each `quad` of the `Dataset`. The first time the `iteratee` is called, the
* `accumulator` value is the `initialValue` or, if not given, equals to the first quad of the `Dataset`. The return
* value of the `iteratee` is used as `accumulator` value for the next calls.
jeswr marked this conversation as resolved.
Show resolved Hide resolved
*
* 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
jeswr marked this conversation as resolved.
Show resolved Hide resolved
* 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
jeswr marked this conversation as resolved.
Show resolved Hide resolved
* {@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 @@ -817,6 +965,82 @@ class DatasetCoreAndReadableStream extends Readable {
this.push(null);
}

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