• Jump To … +
    BaseIRI.js IRIs.js N3DataFactory.js N3Lexer.js N3Parser.js N3Reasoner.js N3Store.js N3StoreFactory.js N3StreamParser.js N3StreamWriter.js N3Util.js N3Writer.js Util.js index.js
  • N3Store.js

  • §

    N3Store objects store N3 quads by graph in memory.

    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');
    
    function merge(target, source, depth = 4) {
      if (depth === 0)
        return Object.assign(target, source);
    
      for (const key in source)
        target[key] = merge(target[key] || Object.create(null), source[key], depth - 1);
    
      return target;
    }
    
    /**
     * Determines the intersection 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 intersection; this should
     * *not* be set as the value for an index.
     */
    function intersect(s1, s2, depth = 4) {
      let target = false;
    
      for (const key in s1) {
        if (key in s2) {
          const intersection = depth === 0 ? null : intersect(s1[key], s2[key], depth - 1);
          if (intersection !== false) {
            target = target || Object.create(null);
            target[key] = intersection;
          }
  • §

    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;
    }
    
    /**
     * 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 = {}) {
        this._id = 1;
  • §

    _ids maps entities such as http://xmlns.com/foaf/0.1/name to numbers, saving memory by using only numbers as keys in _graphs

        this._ids = Object.create(null);
        this._ids[''] = 1;
  • §

    inverse of _ids

        this._entities = Object.create(null);
        this._entities[1] = '';
  • §

    _blankNodeIndex is the index of the last automatically named blank node

        this._blankNodeIndex = 0;
        this._factory = options.factory || N3DataFactory;
      }
    
      _termFromId(id) {
        if (id[0] === '.') {
          const entities = this._entities;
          const terms = id.split('.');
          const q = this._factory.quad(
            this._termFromId(entities[terms[1]]),
            this._termFromId(entities[terms[2]]),
            this._termFromId(entities[terms[3]]),
            terms[4] && this._termFromId(entities[terms[4]]),
          );
          return q;
        }
        return termFromId(id, this._factory);
      }
    
      _termToNumericId(term) {
        if (term.termType === 'Quad') {
          const s = this._termToNumericId(term.subject),
              p = this._termToNumericId(term.predicate),
              o = this._termToNumericId(term.object);
          let g;
    
          return s && p && o && (isDefaultGraph(term.graph) || (g = this._termToNumericId(term.graph))) &&
            this._ids[g ? `.${s}.${p}.${o}.${g}` : `.${s}.${p}.${o}`];
        }
        return this._ids[termToId(term)];
      }
    
      _termToNewNumericId(term) {
  • §

    This assumes that no graph term is present - we may wish to error if there is one

        const str = term && term.termType === 'Quad' ?
          `.${this._termToNewNumericId(term.subject)}.${this._termToNewNumericId(term.predicate)}.${this._termToNewNumericId(term.object)}${
            isDefaultGraph(term.graph) ? '' : `.${this._termToNewNumericId(term.graph)}`
          }`
          : termToId(term);
    
        return this._ids[str] || (this._ids[this._entities[++this._id] = str] = this._id);
      }
    
      createBlankNode(suggestedName) {
        let name, index;
  • §

    Generate a name based on the suggested name

        if (suggestedName) {
          name = suggestedName = `_:${suggestedName}`, index = 1;
          while (this._ids[name])
            name = suggestedName + index++;
        }
  • §

    Generate a generic blank node name

        else {
          do { name = `_:b${this._blankNodeIndex++}`; }
          while (this._ids[name]);
        }
  • §

    Add the blank node to the entities, avoiding the generation of duplicates

        this._ids[name] = ++this._id;
        this._entities[this._id] = name;
        return this._factory.blankNode(name.substr(2));
      }
    }
  • §

    Constructor

    export default class N3Store {
      constructor(quads, options) {
  • §

    The number of quads is initially zero

        this._size = 0;
  • §

    _graphs contains subject, predicate, and object indexes per graph

        this._graphs = Object.create(null);
  • §

    Shift parameters if quads is not given

        if (!options && quads && !quads[0] && !(typeof quads.match === 'function'))
          options = quads, quads = null;
        options = options || {};
        this._factory = options.factory || N3DataFactory;
        this._entityIndex = options.entityIndex || new N3EntityIndex({ factory: this._factory });
        this._entities = this._entityIndex._entities;
        this._termFromId = this._entityIndex._termFromId.bind(this._entityIndex);
        this._termToNumericId = this._entityIndex._termToNumericId.bind(this._entityIndex);
        this._termToNewNumericId = this._entityIndex._termToNewNumericId.bind(this._entityIndex);
  • §

    Add quads if passed

        if (quads)
          this.addAll(quads);
      }
  • §

    Public properties

  • §

    size returns the number of quads in the store

      get size() {
  • §

    Return the quad count if if was cached

        let size = this._size;
        if (size !== null)
          return size;
  • §

    Calculate the number of quads by counting to the deepest level

        size = 0;
        const graphs = this._graphs;
        let subjects, subject;
        for (const graphKey in graphs)
          for (const subjectKey in (subjects = graphs[graphKey].subjects))
            for (const predicateKey in (subject = subjects[subjectKey]))
              size += Object.keys(subject[predicateKey]).length;
        return this._size = size;
      }
  • §

    Private methods

  • §

    _addToIndex adds a quad to a three-layered index.

    Returns if the index has changed, if the entry did not already exist.

      _addToIndex(index0, key0, key1, key2) {
  • §

    Create layers as necessary

        const index1 = index0[key0] || (index0[key0] = {});
        const index2 = index1[key1] || (index1[key1] = {});
  • §

    Setting the key to any value signals the presence of the quad

        const existed = key2 in index2;
        if (!existed)
          index2[key2] = null;
        return !existed;
      }
  • §

    _removeFromIndex removes a quad from a three-layered index

      _removeFromIndex(index0, key0, key1, key2) {
  • §

    Remove the quad from the index

        const index1 = index0[key0], index2 = index1[key1];
        delete index2[key2];
  • §

    Remove intermediary index layers if they are empty

        for (const key in index2) return;
        delete index1[key1];
        for (const key in index1) return;
        delete index0[key0];
      }
  • §

    _findInIndex finds a set of quads in a three-layered index.

    The index base is index0 and the keys at each level are key0, key1, and key2. Any of these keys can be undefined, which is interpreted as a wildcard. name0, name1, and name2 are the names of the keys at each level, used when reconstructing the resulting quad (for instance: subject, predicate, and object). Finally, graphId will be the graph of the created quads.

      *_findInIndex(index0, key0, key1, key2, name0, name1, name2, graphId) {
        let tmp, index1, index2;
        const entityKeys = this._entities;
        const graph = this._termFromId(entityKeys[graphId]);
        const parts = { subject: null, predicate: null, object: null };
  • §

    If a key is specified, use only that part of index 0.

        if (key0) (tmp = index0, index0 = {})[key0] = tmp[key0];
        for (const value0 in index0) {
          if (index1 = index0[value0]) {
            parts[name0] = this._termFromId(entityKeys[value0]);
  • §

    If a key is specified, use only that part of index 1.

            if (key1) (tmp = index1, index1 = {})[key1] = tmp[key1];
            for (const value1 in index1) {
              if (index2 = index1[value1]) {
                parts[name1] = this._termFromId(entityKeys[value1]);
  • §

    If a key is specified, use only that part of index 2, if it exists.

                const values = key2 ? (key2 in index2 ? [key2] : []) : Object.keys(index2);
  • §

    Create quads for all items found in index 2.

                for (let l = 0; l < values.length; l++) {
                  parts[name2] = this._termFromId(entityKeys[values[l]]);
                  yield this._factory.quad(parts.subject, parts.predicate, parts.object, graph);
                }
              }
            }
          }
        }
      }
  • §

    _loop executes the callback on all keys of index 0

      _loop(index0, callback) {
        for (const key0 in index0)
          callback(key0);
      }
  • §

    _loopByKey0 executes the callback on all keys of a certain entry in index 0

      _loopByKey0(index0, key0, callback) {
        let index1, key1;
        if (index1 = index0[key0]) {
          for (key1 in index1)
            callback(key1);
        }
      }
  • §

    _loopByKey1 executes the callback on given keys of all entries in index 0

      _loopByKey1(index0, key1, callback) {
        let key0, index1;
        for (key0 in index0) {
          index1 = index0[key0];
          if (index1[key1])
            callback(key0);
        }
      }
  • §

    _loopBy2Keys executes the callback on given keys of certain entries in index 2

      _loopBy2Keys(index0, key0, key1, callback) {
        let index1, index2, key2;
        if ((index1 = index0[key0]) && (index2 = index1[key1])) {
          for (key2 in index2)
            callback(key2);
        }
      }
  • §

    _countInIndex counts matching quads in a three-layered index.

    The index base is index0 and the keys at each level are key0, key1, and key2. Any of these keys can be undefined, which is interpreted as a wildcard.

      _countInIndex(index0, key0, key1, key2) {
        let count = 0, tmp, index1, index2;
  • §

    If a key is specified, count only that part of index 0

        if (key0) (tmp = index0, index0 = {})[key0] = tmp[key0];
        for (const value0 in index0) {
          if (index1 = index0[value0]) {
  • §

    If a key is specified, count only that part of index 1

            if (key1) (tmp = index1, index1 = {})[key1] = tmp[key1];
            for (const value1 in index1) {
              if (index2 = index1[value1]) {
  • §

    If a key is specified, count the quad if it exists

                if (key2) (key2 in index2) && count++;
  • §

    Otherwise, count all quads

                else count += Object.keys(index2).length;
              }
            }
          }
        }
        return count;
      }
  • §

    _getGraphs returns an array with the given graph,

    or all graphs if the argument is null or undefined.

      _getGraphs(graph) {
        graph = graph === '' ? 1 : (graph && (this._termToNumericId(graph) || -1));
        return typeof graph !== 'number' ? this._graphs : { [graph]: this._graphs[graph] };
      }
  • §

    _uniqueEntities returns a function that accepts an entity ID

    and passes the corresponding entity to callback if it hasn’t occurred before.

      _uniqueEntities(callback) {
        const uniqueIds = Object.create(null);
        return id => {
          if (!(id in uniqueIds)) {
            uniqueIds[id] = true;
            callback(this._termFromId(this._entities[id], this._factory));
          }
        };
      }
  • §

    Public methods

  • §

    add adds the specified quad to the dataset.

    Returns the dataset instance it was called on. Existing quads, as defined in Quad.equals, will be ignored.

      add(quad) {
        this.addQuad(quad);
        return this;
      }
  • §

    addQuad adds a new quad to the store.

    Returns if the quad index has changed, if the quad did not already exist.

      addQuad(subject, predicate, object, graph) {
  • §

    Shift arguments if a quad object is given instead of components

        if (!predicate)
          graph = subject.graph, object = subject.object,
            predicate = subject.predicate, subject = subject.subject;
  • §

    Convert terms to internal string representation

        graph = graph ? this._termToNewNumericId(graph) : 1;
  • §

    Find the graph that will contain the triple

        let graphItem = this._graphs[graph];
  • §

    Create the graph if it doesn’t exist yet

        if (!graphItem) {
          graphItem = this._graphs[graph] = { subjects: {}, predicates: {}, objects: {} };
  • §

    Freezing a graph helps subsequent add performance, and properties will never be modified anyway

          Object.freeze(graphItem);
        }
  • §

    Since entities can often be long IRIs, we avoid storing them in every index. Instead, we have a separate index that maps entities to numbers, which are then used as keys in the other indexes.

        subject   = this._termToNewNumericId(subject);
        predicate = this._termToNewNumericId(predicate);
        object    = this._termToNewNumericId(object);
    
        if (!this._addToIndex(graphItem.subjects,   subject,   predicate, object))
          return false;
        this._addToIndex(graphItem.predicates, predicate, object,    subject);
        this._addToIndex(graphItem.objects,    object,    subject,   predicate);
  • §

    The cached quad count is now invalid

        this._size = null;
        return true;
      }
  • §

    addQuads adds multiple quads to the store

      addQuads(quads) {
        for (let i = 0; i < quads.length; i++)
          this.addQuad(quads[i]);
      }
  • §

    delete removes the specified quad from the dataset.

    Returns the dataset instance it was called on.

      delete(quad) {
        this.removeQuad(quad);
        return this;
      }
  • §

    has determines whether a dataset includes a certain quad or quad pattern.

      has(subjectOrQuad, predicate, object, graph) {
        if (subjectOrQuad && subjectOrQuad.subject)
          ({ subject: subjectOrQuad, predicate, object, graph } = subjectOrQuad);
        return !this.readQuads(subjectOrQuad, predicate, object, graph).next().done;
      }
  • §

    import adds a stream of quads to the store

      import(stream) {
        stream.on('data', quad => { this.addQuad(quad); });
        return stream;
      }
  • §

    removeQuad removes a quad from the store if it exists

      removeQuad(subject, predicate, object, graph) {
  • §

    Shift arguments if a quad object is given instead of components

        if (!predicate)
          ({ subject, predicate, object, graph } = subject);
  • §

    Convert terms to internal string representation

        graph = graph ? this._termToNumericId(graph) : 1;
  • §

    Find internal identifiers for all components and verify the quad exists.

        const graphs = this._graphs;
        let graphItem, subjects, predicates;
        if (!(subject    = subject && this._termToNumericId(subject)) || !(predicate = predicate && this._termToNumericId(predicate)) ||
            !(object     = object && this._termToNumericId(object))  || !(graphItem = graphs[graph])  ||
            !(subjects   = graphItem.subjects[subject]) ||
            !(predicates = subjects[predicate]) ||
            !(object in predicates))
          return false;
  • §

    Remove it from all indexes

        this._removeFromIndex(graphItem.subjects,   subject,   predicate, object);
        this._removeFromIndex(graphItem.predicates, predicate, object,    subject);
        this._removeFromIndex(graphItem.objects,    object,    subject,   predicate);
        if (this._size !== null) this._size--;
  • §

    Remove the graph if it is empty

        for (subject in graphItem.subjects) return true;
        delete graphs[graph];
        return true;
      }
  • §

    removeQuads removes multiple quads from the store

      removeQuads(quads) {
        for (let i = 0; i < quads.length; i++)
          this.removeQuad(quads[i]);
      }
  • §

    remove removes a stream of quads from the store

      remove(stream) {
        stream.on('data', quad => { this.removeQuad(quad); });
        return stream;
      }
  • §

    removeMatches removes all matching quads from the store

    Setting any field to undefined or null indicates a wildcard.

      removeMatches(subject, predicate, object, graph) {
        const stream = new Readable({ objectMode: true });
    
        const iterable = this.readQuads(subject, predicate, object, graph);
        stream._read = size => {
          while (--size >= 0) {
            const { done, value } = iterable.next();
            if (done) {
              stream.push(null);
              return;
            }
            stream.push(value);
          }
        };
    
        return this.remove(stream);
      }
  • §

    deleteGraph removes all triples with the given graph from the store

      deleteGraph(graph) {
        return this.removeMatches(null, null, null, graph);
      }
  • §

    getQuads returns an array of quads matching a pattern.

    Setting any field to undefined or null indicates a wildcard.

      getQuads(subject, predicate, object, graph) {
        return [...this.readQuads(subject, predicate, object, graph)];
      }
    
      /**
       * `readQuads` returns a generator of quads matching a pattern.
       * Setting any field to `undefined` or `null` indicates a wildcard.
       * @deprecated Use `match` instead.
       */
      *readQuads(subject, predicate, object, graph) {
        const graphs = this._getGraphs(graph);
        let content, subjectId, predicateId, objectId;
  • §

    Translate IRIs to internal index keys.

        if (subject   && !(subjectId   = this._termToNumericId(subject))   ||
            predicate && !(predicateId = this._termToNumericId(predicate)) ||
            object    && !(objectId    = this._termToNumericId(object)))
          return;
    
        for (const graphId in graphs) {
  • §

    Only if the specified graph contains triples, there can be results

          if (content = graphs[graphId]) {
  • §

    Choose the optimal index, based on what fields are present

            if (subjectId) {
              if (objectId)
  • §

    If subject and object are given, the object index will be the fastest

                yield* this._findInIndex(content.objects, objectId, subjectId, predicateId,
                                  'object', 'subject', 'predicate', graphId);
              else
  • §

    If only subject and possibly predicate are given, the subject index will be the fastest

                yield* this._findInIndex(content.subjects, subjectId, predicateId, null,
                                  'subject', 'predicate', 'object', graphId);
            }
            else if (predicateId)
  • §

    If only predicate and possibly object are given, the predicate index will be the fastest

              yield* this._findInIndex(content.predicates, predicateId, objectId, null,
                                'predicate', 'object', 'subject', graphId);
            else if (objectId)
  • §

    If only object is given, the object index will be the fastest

              yield* this._findInIndex(content.objects, objectId, null, null,
                                'object', 'subject', 'predicate', graphId);
            else
  • §

    If nothing is given, iterate subjects and predicates first

              yield* this._findInIndex(content.subjects, null, null, null,
                                'subject', 'predicate', 'object', graphId);
          }
        }
      }
  • §

    match returns a new dataset that is comprised of all quads in the current instance matching the given arguments.

    The logic described in Quad Matching is applied for each quad in this dataset to check if it should be included in the output dataset. Note: This method always returns a new DatasetCore, even if that dataset contains no quads. Note: Since a DatasetCore is an unordered set, the order of the quads within the returned sequence is arbitrary. Setting any field to undefined or null indicates a wildcard. For backwards compatibility, the object return also implements the Readable stream interface.

      match(subject, predicate, object, graph) {
        return new DatasetCoreAndReadableStream(this, subject, predicate, object, graph, { entityIndex: this._entityIndex });
      }
  • §

    countQuads returns the number of quads matching a pattern.

    Setting any field to undefined or null indicates a wildcard.

      countQuads(subject, predicate, object, graph) {
        const graphs = this._getGraphs(graph);
        let count = 0, content, subjectId, predicateId, objectId;
  • §

    Translate IRIs to internal index keys.

        if (subject   && !(subjectId   = this._termToNumericId(subject))   ||
            predicate && !(predicateId = this._termToNumericId(predicate)) ||
            object    && !(objectId    = this._termToNumericId(object)))
          return 0;
    
        for (const graphId in graphs) {
  • §

    Only if the specified graph contains triples, there can be results

          if (content = graphs[graphId]) {
  • §

    Choose the optimal index, based on what fields are present

            if (subject) {
              if (object)
  • §

    If subject and object are given, the object index will be the fastest

                count += this._countInIndex(content.objects, objectId, subjectId, predicateId);
              else
  • §

    If only subject and possibly predicate are given, the subject index will be the fastest

                count += this._countInIndex(content.subjects, subjectId, predicateId, objectId);
            }
            else if (predicate) {
  • §

    If only predicate and possibly object are given, the predicate index will be the fastest

              count += this._countInIndex(content.predicates, predicateId, objectId, subjectId);
            }
            else {
  • §

    If only object is possibly given, the object index will be the fastest

              count += this._countInIndex(content.objects, objectId, subjectId, predicateId);
            }
          }
        }
        return count;
      }
  • §

    forEach executes the callback on all quads.

    Setting any field to undefined or null indicates a wildcard.

      forEach(callback, subject, predicate, object, graph) {
        this.some(quad => {
          callback(quad, this);
          return false;
        }, subject, predicate, object, graph);
      }
  • §

    every executes the callback on all quads,

    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) {
        return !this.some(quad => !callback(quad, this), subject, predicate, object, graph);
      }
  • §

    some executes the callback on all quads,

    and returns true if it returns truthy for any of them. Setting any field to undefined or null indicates a wildcard.

      some(callback, subject, predicate, object, graph) {
        for (const quad of this.readQuads(subject, predicate, object, graph))
          if (callback(quad, this))
            return true;
        return false;
      }
  • §

    getSubjects returns all subjects that match the pattern.

    Setting any field to undefined or null indicates a wildcard.

      getSubjects(predicate, object, graph) {
        const results = [];
        this.forSubjects(s => { results.push(s); }, predicate, object, graph);
        return results;
      }
  • §

    forSubjects executes the callback on all subjects that match the pattern.

    Setting any field to undefined or null indicates a wildcard.

      forSubjects(callback, predicate, object, graph) {
        const graphs = this._getGraphs(graph);
        let content, predicateId, objectId;
        callback = this._uniqueEntities(callback);
  • §

    Translate IRIs to internal index keys.

        if (predicate && !(predicateId = this._termToNumericId(predicate)) ||
            object    && !(objectId    = this._termToNumericId(object)))
          return;
    
        for (graph in graphs) {
  • §

    Only if the specified graph contains triples, there can be results

          if (content = graphs[graph]) {
  • §

    Choose optimal index based on which fields are wildcards

            if (predicateId) {
              if (objectId)
  • §

    If predicate and object are given, the POS index is best.

                this._loopBy2Keys(content.predicates, predicateId, objectId, callback);
              else
  • §

    If only predicate is given, the SPO index is best.

                this._loopByKey1(content.subjects, predicateId, callback);
            }
            else if (objectId)
  • §

    If only object is given, the OSP index is best.

              this._loopByKey0(content.objects, objectId, callback);
            else
  • §

    If no params given, iterate all the subjects

              this._loop(content.subjects, callback);
          }
        }
      }
  • §

    getPredicates returns all predicates that match the pattern.

    Setting any field to undefined or null indicates a wildcard.

      getPredicates(subject, object, graph) {
        const results = [];
        this.forPredicates(p => { results.push(p); }, subject, object, graph);
        return results;
      }
  • §

    forPredicates executes the callback on all predicates that match the pattern.

    Setting any field to undefined or null indicates a wildcard.

      forPredicates(callback, subject, object, graph) {
        const graphs = this._getGraphs(graph);
        let content, subjectId, objectId;
        callback = this._uniqueEntities(callback);
  • §

    Translate IRIs to internal index keys.

        if (subject   && !(subjectId   = this._termToNumericId(subject))   ||
            object    && !(objectId    = this._termToNumericId(object)))
          return;
    
        for (graph in graphs) {
  • §

    Only if the specified graph contains triples, there can be results

          if (content = graphs[graph]) {
  • §

    Choose optimal index based on which fields are wildcards

            if (subjectId) {
              if (objectId)
  • §

    If subject and object are given, the OSP index is best.

                this._loopBy2Keys(content.objects, objectId, subjectId, callback);
              else
  • §

    If only subject is given, the SPO index is best.

                this._loopByKey0(content.subjects, subjectId, callback);
            }
            else if (objectId)
  • §

    If only object is given, the POS index is best.

              this._loopByKey1(content.predicates, objectId, callback);
            else
  • §

    If no params given, iterate all the predicates.

              this._loop(content.predicates, callback);
          }
        }
      }
  • §

    getObjects returns all objects that match the pattern.

    Setting any field to undefined or null indicates a wildcard.

      getObjects(subject, predicate, graph) {
        const results = [];
        this.forObjects(o => { results.push(o); }, subject, predicate, graph);
        return results;
      }
  • §

    forObjects executes the callback on all objects that match the pattern.

    Setting any field to undefined or null indicates a wildcard.

      forObjects(callback, subject, predicate, graph) {
        const graphs = this._getGraphs(graph);
        let content, subjectId, predicateId;
        callback = this._uniqueEntities(callback);
  • §

    Translate IRIs to internal index keys.

        if (subject   && !(subjectId   = this._termToNumericId(subject))   ||
            predicate && !(predicateId = this._termToNumericId(predicate)))
          return;
    
        for (graph in graphs) {
  • §

    Only if the specified graph contains triples, there can be results

          if (content = graphs[graph]) {
  • §

    Choose optimal index based on which fields are wildcards

            if (subjectId) {
              if (predicateId)
  • §

    If subject and predicate are given, the SPO index is best.

                this._loopBy2Keys(content.subjects, subjectId, predicateId, callback);
              else
  • §

    If only subject is given, the OSP index is best.

                this._loopByKey1(content.objects, subjectId, callback);
            }
            else if (predicateId)
  • §

    If only predicate is given, the POS index is best.

              this._loopByKey0(content.predicates, predicateId, callback);
            else
  • §

    If no params given, iterate all the objects.

              this._loop(content.objects, callback);
          }
        }
      }
  • §

    getGraphs returns all graphs that match the pattern.

    Setting any field to undefined or null indicates a wildcard.

      getGraphs(subject, predicate, object) {
        const results = [];
        this.forGraphs(g => { results.push(g); }, subject, predicate, object);
        return results;
      }
  • §

    forGraphs executes the callback on all graphs that match the pattern.

    Setting any field to undefined or null indicates a wildcard.

      forGraphs(callback, subject, predicate, object) {
        for (const graph in this._graphs) {
          this.some(quad => {
            callback(quad.graph);
            return true; // Halt iteration of some()
          }, subject, predicate, object, this._termFromId(this._entities[graph]));
        }
      }
  • §

    createBlankNode creates a new blank node, returning its name

      createBlankNode(suggestedName) {
        return this._entityIndex.createBlankNode(suggestedName);
      }
  • §

    extractLists finds and removes all list triples

    and returns the items per list.

      extractLists({ remove = false, ignoreErrors = false } = {}) {
        const lists = {}; // has scalar keys so could be a simple Object
        const onError = ignoreErrors ? (() => true) :
                      ((node, message) => { throw new Error(`${node.value} ${message}`); });
  • §

    Traverse each list from its tail

        const tails = this.getQuads(null, namespaces.rdf.rest, namespaces.rdf.nil, null);
        const toRemove = remove ? [...tails] : [];
        tails.forEach(tailQuad => {
          const items = [];             // the members found as objects of rdf:first quads
          let malformed = false;      // signals whether the current list is malformed
          let head;                   // the head of the list (_:b1 in above example)
          let headPos;                // set to subject or object when head is set
          const graph = tailQuad.graph; // make sure list is in exactly one graph
  • §

    Traverse the list from tail to end

          let current = tailQuad.subject;
          while (current && !malformed) {
            const objectQuads = this.getQuads(null, null, current, null);
            const subjectQuads = this.getQuads(current, null, null, null);
            let quad, first = null, rest = null, parent = null;
  • §

    Find the first and rest of this list node

            for (let i = 0; i < subjectQuads.length && !malformed; i++) {
              quad = subjectQuads[i];
              if (!quad.graph.equals(graph))
                malformed = onError(current, 'not confined to single graph');
              else if (head)
                malformed = onError(current, 'has non-list arcs out');
  • §

    one rdf:first

              else if (quad.predicate.value === namespaces.rdf.first) {
                if (first)
                  malformed = onError(current, 'has multiple rdf:first arcs');
                else
                  toRemove.push(first = quad);
              }
  • §

    one rdf:rest

              else if (quad.predicate.value === namespaces.rdf.rest) {
                if (rest)
                  malformed = onError(current, 'has multiple rdf:rest arcs');
                else
                  toRemove.push(rest = quad);
              }
  • §

    alien triple

              else if (objectQuads.length)
                malformed = onError(current, 'can\'t be subject and object');
              else {
                head = quad; // e.g. { (1 2 3) :p :o }
                headPos = 'subject';
              }
            }
  • §

    { :s :p (1 2) } arrives here with no head { (1 2) :p :o } arrives here with head set to the list.

            for (let i = 0; i < objectQuads.length && !malformed; ++i) {
              quad = objectQuads[i];
              if (head)
                malformed = onError(current, 'can\'t have coreferences');
  • §

    one rdf:rest

              else if (quad.predicate.value === namespaces.rdf.rest) {
                if (parent)
                  malformed = onError(current, 'has incoming rdf:rest arcs');
                else
                  parent = quad;
              }
              else {
                head = quad; // e.g. { :s :p (1 2) }
                headPos = 'object';
              }
            }
  • §

    Store the list item and continue with parent

            if (!first)
              malformed = onError(current, 'has no list head');
            else
              items.unshift(first.object);
            current = parent && parent.subject;
          }
  • §

    Don’t remove any quads if the list is malformed

          if (malformed)
            remove = false;
  • §

    Store the list under the value of its head

          else if (head)
            lists[head[headPos].value] = items;
        });
  • §

    Remove list quads if requested

        if (remove)
          this.removeQuads(toRemove);
        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 (quads instanceof DatasetCoreAndReadableStream)
          quads = quads.filtered;
    
        if (Array.isArray(quads))
          this.addQuads(quads);
        else if (quads instanceof N3Store && quads._entityIndex === this._entityIndex) {
          if (quads._size !== 0) {
            this._graphs = merge(this._graphs, quads._graphs);
            this._size = null; // Invalidate the cached size
          }
        }
        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) {
        if (other instanceof DatasetCoreAndReadableStream)
          other = other.filtered;
    
        if (other === this)
          return true;
    
        if (!(other instanceof N3Store) || this._entityIndex !== other._entityIndex)
          return other.every(quad => this.has(quad));
    
        const g1 = this._graphs, g2 = other._graphs;
        let s1, s2, p1, p2, o1;
        for (const graph in g2) {
          if (!(s1 = g1[graph])) return false;
          s1 = s1.subjects;
          for (const subject in (s2 = g2[graph].subjects)) {
            if (!(p1 = s1[subject])) return false;
            for (const predicate in (p2 = s2[subject])) {
              if (!(o1 = p1[predicate])) return false;
              for (const object in p2[predicate])
                if (!(object in o1)) return false;
            }
          }
        }
        return true;
      }
    
      /**
       * 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) {
        if (other && other instanceof DatasetCoreAndReadableStream)
          other = other.filtered;
    
        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));
      }
    
      /**
       * Returns true if the current dataset contains the same graph structure as the given dataset.
       *
       * Blank Nodes will be normalized.
       */
      equals(other) {
        if (other instanceof DatasetCoreAndReadableStream)
          other = other.filtered;
    
        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({ entityIndex: this._entityIndex });
        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) {
        if (other instanceof DatasetCoreAndReadableStream)
          other = other.filtered;
    
        if (other === this) {
          const store = new N3Store({ entityIndex: this._entityIndex });
          store._graphs = merge(Object.create(null), this._graphs);
          store._size = this._size;
          return store;
        }
        else if ((other instanceof N3Store) && this._entityIndex === other._entityIndex) {
          const store = new N3Store({ entityIndex: this._entityIndex });
          const graphs = intersect(other._graphs, this._graphs);
          if (graphs) {
            store._graphs = graphs;
            store._size = null;
          }
          return store;
        }
    
        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({ entityIndex: this._entityIndex });
        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({ entityIndex: this._entityIndex });
        store._graphs = merge(Object.create(null), this._graphs);
        store._size = this._size;
    
        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).

      *[Symbol.iterator]() {
        yield* this.readQuads();
      }
    }
    
    /**
     * Returns a subset of the `index` with that part of the index
     * matching the `ids` array. `ids` contains 3 elements that are
     * either numerical ids; or `null`.
     *
     * `false` is returned when there are no matching indices; this should
     * *not* be set as the value for an index.
     */
    function indexMatch(index, ids, depth = 0) {
      const ind = ids[depth];
      if (ind && !(ind in index))
        return false;
    
      let target = false;
      for (const key in (ind ? { [ind]: index[ind] } : index)) {
        const result = depth === 2 ? null : indexMatch(index[key], ids, depth + 1);
    
        if (result !== false) {
          target = target || Object.create(null);
          target[key] = result;
        }
      }
      return target;
    }
    
    /**
     * A class that implements both DatasetCore and Readable.
     */
    class DatasetCoreAndReadableStream extends Readable {
      constructor(n3Store, subject, predicate, object, graph, options) {
        super({ objectMode: true });
        Object.assign(this, { n3Store, subject, predicate, object, graph, options });
      }
    
      get filtered() {
        if (!this._filtered) {
          const { n3Store, graph, object, predicate, subject } = this;
          const newStore = this._filtered = new N3Store({ factory: n3Store._factory, entityIndex: this.options.entityIndex });
    
          let subjectId, predicateId, objectId;
  • §

    Translate IRIs to internal index keys.

          if (subject   && !(subjectId   = newStore._termToNumericId(subject))   ||
              predicate && !(predicateId = newStore._termToNumericId(predicate)) ||
              object    && !(objectId    = newStore._termToNumericId(object)))
            return newStore;
    
          const graphs = n3Store._getGraphs(graph);
          for (const graphKey in graphs) {
            let subjects, predicates, objects, content;
            if (content = graphs[graphKey]) {
              if (!subjectId && predicateId) {
                if (predicates = indexMatch(content.predicates, [predicateId, objectId, subjectId])) {
                  subjects = indexMatch(content.subjects, [subjectId, predicateId, objectId]);
                  objects = indexMatch(content.objects, [objectId, subjectId, predicateId]);
                }
              }
              else if (objectId) {
                if (objects = indexMatch(content.objects, [objectId, subjectId, predicateId])) {
                  subjects = indexMatch(content.subjects, [subjectId, predicateId, objectId]);
                  predicates = indexMatch(content.predicates, [predicateId, objectId, subjectId]);
                }
              }
              else if (subjects = indexMatch(content.subjects, [subjectId, predicateId, objectId])) {
                predicates = indexMatch(content.predicates, [predicateId, objectId, subjectId]);
                objects = indexMatch(content.objects, [objectId, subjectId, predicateId]);
              }
    
              if (subjects)
                newStore._graphs[graphKey] = { subjects, predicates, objects };
            }
          }
          newStore._size = null;
        }
        return this._filtered;
      }
    
      get size() {
        return this.filtered.size;
      }
    
      _read(size) {
        if (size > 0 && !this[ITERATOR])
          this[ITERATOR] = this[Symbol.iterator]();
        const iterable = this[ITERATOR];
        while (--size >= 0) {
          const { done, value } = iterable.next();
          if (done) {
            this.push(null);
            return;
          }
          this.push(value);
        }
      }
    
      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);
      }
    
      delete(quad) {
        return this.filtered.delete(quad);
      }
    
      has(quad) {
        return this.filtered.has(quad);
      }
    
      match(subject, predicate, object, graph) {
        return new DatasetCoreAndReadableStream(this.filtered, subject, predicate, object, graph, this.options);
      }
    
      *[Symbol.iterator]() {
        yield* this._filtered || this.n3Store.readQuads(this.subject, this.predicate, this.object, this.graph);
      }
    }