Coder Social home page Coder Social logo

levelgraph-jsonld's Introduction

LevelGraph

Logo

NPM

LevelGraph is a Graph Database. Unlike many other graph database, LevelGraph is built on the uber-fast key-value store LevelDB through the powerful level library. You can use it inside your node.js application or in any IndexedDB-powered Browser.

LevelGraph loosely follows the Hexastore approach as presented in the article: Hexastore: sextuple indexing for semantic web data management C Weiss, P Karras, A Bernstein - Proceedings of the VLDB Endowment, 2008. Following this approach, LevelGraph uses six indices for every triple, in order to access them as fast as it is possible.

LevelGraph was presented in the paper Graph databases in the browser: using LevelGraph to explore New Delhi - A. Maccioni, M. Collina - Proceedings of the VLDB Endowment, 2016.

Check out a slideshow that introduces you to LevelGraph by @matteocollina at http://nodejsconf.it.

Also, give the LevelGraph Playground to get a quick feel for adding JSON-LD and N3/Turtle documents to a filter-able subject, predicate, object table. The db variable in the browser console is very useful for checking out the full power of LevelGraph.

LevelGraph is an OPEN Open Source Project, see the Contributing section to find out what this means.

Table of Contents

Install

On Node.JS

npm install --save levelgraph

Testing of levelgraph is only done using Node.JS 18 and 20. Other versions may be supported, but your mileage may vary.

In the Browser

Just download levelgraph.min.js and you are done!

Alternatively, you could load levelgraph in your project and bundle using browserify or esbuild.

Usage

The LevelGraph API remains the same for Node.JS and the browsers, however the initialization change slightly.

Initializing a database is very easy:

var { Level }  = require("level");
var levelgraph = require("levelgraph");

// just use this in the browser with the provided bundle
var db = levelgraph(new Level("yourdb"));

Get and Put

Inserting a triple in the database is extremely easy:

var triple = { subject: "a", predicate: "b", object: "c" };
db.put(triple, function(err) {
  // do something after the triple is inserted
});

Retrieving it through pattern-matching is extremely easy:

db.get({ subject: "a" }, function(err, list) {
  console.log(list);
});

It even supports a Stream interface:

var stream = db.getStream({ predicate: "b" });
stream.on("data", function(data) {
  console.log(data);
});

Triple Properties

LevelGraph supports adding properties to triples with very little overhead (apart from storage costs). It is very easy:

var triple = { subject: "a", predicate: "b", object: "c", "someStuff": 42 };
db.put(triple, function() {
  db.get({ subject: "a" }, function(err, list) {
    console.log(list);
  });
});

Limit and Offset

It is possible to implement pagination of get results by using 'offset' and 'limit', like so:

db.get({ subject: "a", limit: 4, offset: 2}, function(err, list) {
  console.log(list);
});

Reverse Order

It is possible to get results in reverse lexicographical order using the 'reverse' option. This option is only supported by get() and getStream() and not available in search().

db.get({ predicate: "b", reverse: true }, function (err, list) {
  console.log(list);
});

Updating

LevelGraph does not support in-place update, as there are no constraint in the graph. In order to update a triple, you should first delete it:

var triple = { subject: "a", predicate: "b", object: "c" };
db.put(triple, function(err) {
  db.del(triple, function(err) {
    triple.object = 'd';
    db.put(triple, function(err) {
      // do something with your update
    });
  });
});

Multiple Puts

LevelGraph also supports putting multiple triples:

var triple1 = { subject: "a1", predicate: "b", object: "c" };
var triple2 = { subject: "a2", predicate: "b", object: "d" };
db.put([triple1, triple2],  function(err) {
  // do something after the triples are inserted
});

Deleting

Deleting is easy too:

var triple = { subject: "a", predicate: "b", object: "c" };
db.del(triple, function(err) {
  // do something after the triple is deleted
});

Searches

LevelGraph also supports searches:

db.put([{
  subject: "matteo",
  predicate: "friend",
  object: "daniele"
}, {
  subject: "daniele",
  predicate: "friend",
  object: "matteo"
}, {
  subject: "daniele",
  predicate: "friend",
  object: "marco"
}, {
  subject: "lucio",
  predicate: "friend",
  object: "matteo"
}, {
  subject: "lucio",
  predicate: "friend",
  object: "marco"
}, {
  subject: "marco",
  predicate: "friend",
  object: "davide"
}], function () {

  var stream = db.searchStream([{
    subject: "matteo",
    predicate: "friend",
    object: db.v("x")
  }, {
    subject: db.v("x"),
    predicate: "friend",
    object: db.v("y")
  }, {
    subject: db.v("y"),
    predicate: "friend",
    object: "davide"
  }]);

  stream.on("data", function(data) {
    // this will print "{ x: 'daniele', y: 'marco' }"
    console.log(data);
  });
});

Search Without Streams

It also supports a similar API without streams:

db.put([{
 //...
}], function () {

  db.search([{
    subject: "matteo",
    predicate: "friend",
    object: db.v("x")
  }, {
    subject: db.v("x"),
    predicate: "friend",
    object: db.v("y")
  }, {
    subject: db.v("y"),
    predicate: "friend",
    object: "davide"
  }], function(err, results) {
    // this will print "[{ x: 'daniele', y: 'marco' }]"
    console.log(results);
  });
});

Triple Generation

It also allows to generate a stream of triples, instead of a solution:

  db.search([{
    subject: db.v("a"),
    predicate: "friend",
    object: db.v("x")
  }, {
    subject: db.v("x"),
    predicate: "friend",
    object: db.v("y")
  }, {
    subject: db.v("y"),
    predicate: "friend",
    object: db.v("b")
  }], {
    materialized: {
      subject: db.v("a"),
      predicate: "friend-of-a-friend",
      object: db.v("b")
    }
  }, function(err, results) {
    // this will print all the 'friend of a friend triples..'
    // like so: {
    //   subject: "lucio",
    //   predicate: "friend-of-a-friend",
    //   object: "daniele"
    // }
  });

Limit and Offset

It is possible to implement pagination of search results by using 'offset' and 'limit', like so:

db.search([{
    subject: db.v("a"),
    predicate: "friend",
    object: db.v("x")
  }, {
    subject: db.v("x"),
    predicate: "friend",
    object: db.v("y")
  }], { limit: 4, offset: 2 }, function(err, list) {

  console.log(list);
});

Filtering

LevelGraph supports filtering of triples when calling get() and solutions when calling search(), and streams are supported too.

It is possible to filter the matching triples during a get():

db.get({
    subject: 'matteo'
  , predicate: 'friend'
  , filter: function filter(triple) {
      return triple.object !== 'daniele';
    }
}, function process(err, results) {
  // results will not contain any triples that
  // have 'daniele' as object
});

Moreover, it is possible to filter the triples during a search()

db.search({
    subject: 'matteo'
  , predicate: 'friend'
  , object: db.v('x')
  , filter: function filter(triple) {
      return triple.object !== 'daniele';
    }
}, function process(err, solutions) {
  // results will not contain any solutions that
  // have { x: 'daniele' }
});

Finally, LevelGraph supports filtering full solutions:

db.search({
    subject: 'matteo'
  , predicate: 'friend'
  , object: db.v('x')
}, {
    filter: function filter(solution, callback) {
      if (solution.x !== 'daniele') {
        // confirm the solution
        callback(null, solution);
      } else {
        // refute the solution
        callback(null);
      }
    }
}, function process(err, solutions) {
  // results will not contain any solutions that
  // have { x: 'daniele' }
});

Thanks to solultion filtering, it is possible to implement a negation:

db.search({
    subject: 'matteo'
  , predicate: 'friend'
  , object: db.v('x')
}, {
    filter: function filter(solution, callback) {
      db.get({
          subject: solution.x
        , predicate: 'friend'
        , object: 'marco'
      }, function (err, results) {
        if (err) {
          callback(err);
          return;
        }
        if (results.length > 0) {
          // confirm the solution
          callback(null, solution);
        } else {
          // refute the solution
          callback();
        }
      });
    }
}, function process(err, solutions) {
  // results will not contain any solutions that
  // do not satisfy the filter
});

The heavier method is filtering solutions, so we recommend filtering the triples whenever possible.

Putting and Deleting through Streams

It is also possible to put or del triples from the store using a Stream2 interface:

var t1 = { subject: "a", predicate: "b", object: "c" };
var t2 = { subject: "a", predicate: "b", object: "d" };
var stream = db.putStream();

stream.write(t1);
stream.end(t2);

stream.on("close", function() {
  // do something, the writes are done
});

Generate batch operations

You can also generate a put and del batch, so you can manage the batching yourself:

var triple = { subject: "a", predicate: "b", object: "c" };

// Produces a batch of put operations
var putBatch = db.generateBatch(triple);

// Produces a batch of del operations
var delBatch = db.generateBatch(triple, 'del');

Generate level-read-stream query

Return the leveldb query for the given triple.

var { ValueStream } = require("level-read-stream");
var query           = db.createQuery({ predicate: "b"});

var stream = new ValueStream(leveldb, query);

Navigator API

The Navigator API is a fluent API for LevelGraph, loosely inspired by Gremlin It allows to specify how to search our graph in a much more compact way and navigate between vertexes.

Here is an example, using the same dataset as before:

db.nav("matteo").archIn("friend").archOut("friend").
  solutions(function(err, results) {
  // prints:
  // [ { x0: 'daniele', x1: 'marco' },
  //   { x0: 'daniele', x1: 'matteo' },
  //   { x0: 'lucio', x1: 'marco' },
  //   { x0: 'lucio', x1: 'matteo' } ]
  console.log(results);
});

The above example match the same triples of:

db.search([{
  subject: db.v("x0"),
  predicate: 'friend',
  object: 'matteo'
}, {
  subject: db.v("x0"),
  predicate: 'friend',
  object: db.v("x1")
}], function(err, results) {
  // prints:
  // [ { x0: 'daniele', x1: 'marco'  },
  //   { x0: 'daniele', x1: 'matteo' },
  //   { x0: 'lucio'  , x1: 'marco'  },
  //   { x0: 'lucio'  , x1: 'matteo' } ]
  console.log(results);
});

It allows to see just the last reached vertex:

    db.nav("matteo").archIn("friend").archOut("friend").
      values(function(err, results) {
      // prints [ 'marco', 'matteo' ]
      console.log(results);
    });

Variable names can also be specified, like so:

db.nav("marco").archIn("friend").as("a").archOut("friend").archOut("friend").as("a").
      solutions(function(err, friends) {

  console.log(friends); // will print [{ a: "daniele" }]
});

Variables can also be bound to a specific value, like so:

db.nav("matteo").archIn("friend").bind("lucio").archOut("friend").bind("marco").
      values(function(err, friends) {
  console.log(friends); // this will print ['marco']
});

A materialized search can also be produced, like so:

db.nav("matteo").archOut("friend").bind("lucio").archOut("friend").bind("marco").
      triples({:
        materialized: {
        subject: db.v("a"),
        predicate: "friend-of-a-friend",
        object: db.v("b")
      }
    }, function(err, results) {

  // this will return all the 'friend of a friend triples..'
  // like so: {
  //   subject: "lucio",
  //   predicate: "friend-of-a-friend",
  //   object: "daniele"
  // }

  console.log(results);
});

It is also possible to change the current vertex:

db.nav("marco").archIn("friend").as("a").go("matteo").archOut("friend").as("b").
      solutions(function(err, solutions) {

   //  solutions is: [{
   //    a: "daniele",
   //    b: "daniele"
   //   }, {
   //     a: "lucio",
   //     b: "daniele"
   //   }]

});

Plugin integration

LevelGraph allows to leverage the full power of all level plugins.

Initializing a database with plugin support is very easy:

var { Level }  = require("level");
var levelgraph = require("levelgraph");
var db         = levelgraph(new Level("yourdb"));

Usage with sublevels

An extremely powerful usage of LevelGraph is to partition your LevelDB using sublevels, somewhat resembling tables in a relational database.

var { Level }  = require("level");
var levelgraph = require("levelgraph");
var db         = new Level("yourdb");
var graph      = levelgraph(db.sublevel('graph'));

Browser usage

You can use browserify or esbuild to bundle your module and all it's dependencies, including levelgraph, into a single script-tag friendly js file for use in webpages. For the convenience of people unfamiliar with browserify or esbuild, a pre-bundled version of levelgraph is included in the build folder in this repository.

Simply require("levelgraph") in your browser modules:

var levelgraph = require("levelgraph");
var { Level }  = require("level");

var db = levelgraph(new Level("yourdb"));

Testling

Follow the Testling install instructions and run testling in the levelgraph directory to run the test suite against a headless browser using level.js

RDF support

LevelGraph does not support out of the box loading serialized RDF or storing it. Such functionality is provided by extensions:

Extensions

You can use multiple extensions at the same time. Just check if one depends on another one to nest them in correct order! (LevelGraph-N3 and LevelGraph-JSONLD are independent)

var lg       = require('levelgraph');
var lgN3     = require('levelgraph-n3');
var lgJSONLD = require('levelgraph-jsonld');

var db = lgJSONLD(lgN3(lg("yourdb")));
// gives same result as
var db = lgN3(lgJSONLD(lg("yourdb")));

TODO

There are plenty of things that this library is missing. If you feel you want a feature added, just do it and submit a pull-request.

Here are some ideas:

  • Return the matching triples in the search results.
  • Support for Query Planning in search.
  • Added a Sort-Join algorithm.
  • Add more database operators (grouping, filtering).
  • Browser support #10
  • Live searches #3
  • Extensions
    • RDFa
    • RDF/XML
    • Microdata

Contributing

LevelGraph is an OPEN Open Source Project. This means that:

Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project.

See the CONTRIBUTING.md file for more details.

Credits

LevelGraph builds on the excellent work on both the level community and the LevelDB and Snappy teams from Google and additional contributors. LevelDB and Snappy are both issued under the New BSD Licence.

Contributors

LevelGraph is only possible due to the excellent work of the following contributors:

Name Github Twitter/X
Matteo Collina mcollina @matteocollina
Jeremy Taylor jez0990
Elf Pavlik elf-pavlik @elfpavlik
Riceball LEE snowyu
Brian Woodward doowb @doowb
Leon Chen transcranial @transcranial
Yersa Nordman finwo

LICENSE - "MIT License"

Copyright (c) 2013-2024 Matteo Collina and LevelGraph Contributors

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

levelgraph-jsonld's People

Contributors

ameensol avatar aredridel avatar bigbluehat avatar finwo avatar jmatsushita avatar mcollina avatar ralphtheninja avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

levelgraph-jsonld's Issues

Set preserve to true by default

It is probably a fair expectation that data isn't deleted by default in unpredictable ways so I'd suggest we set the preserve option to true as a breaking change and allow to get the old behavior back by explicitly setting preserve to false.

This will also help with the simplicity of the conflict/overwrite API at #46

cc @BigBlueHat

Question: Looking up types with search

Hi there,

This may seem like a pretty basic question but I'm having a heck of a time getting it to function as I expect. I have a whole graph stored with a type Blog. Here's an example node:

{
  '@type': 'Blog',
  '@id': 'http://www.example.com/2d4dfb70-9a2d-11e6-b4b4-21e1a79b8864'
  '@context': { '@vocab': 'http://www.example.com/' },
  wpid: '1511',
  title: 'New Speakers on the Circuit!',
  slug: 'new-speakers-on-the-circuit',
  post_excerpt: '',
  menu_order: 0,
  mime_type: '',
  parent: 0,
}

When I search I don't get any results:

this.db.search([
  {
    'predicate': 'Blog'
  }
], function(err, list) {
  console.log(list);
});

I suspect I'm using search incorrectly, but can't seem to muster the right syntax.

A push in the right direction would be awesome.

Thanks.

Incompatibility with levelgraph 1.0.0

I installed levelgraph-jsonld 0.4.0 and levelgraph 1.0.0 through bower, but when I try to insert a JSON-LD object into the database I have the following error:

Uncaught TypeError: leveldb.createWriteStream is not a function
at Object.3.doActionStream [as delStream] (levelgraph.js:11178)
at Object.1.graphdb.jsonld.del (levelgraph-jsonld.js:107)
at Object.1.graphdb.jsonld.put (levelgraph-jsonld.js:85)
at new <anonymous> (MainCtrl.js:131)
at invoke (ionic.bundle.js:12884)
at Object.instantiate (ionic.bundle.js:12892)
at ionic.bundle.js:17161
at IonicModule.controller.self.appendViewElement (ionic.bundle.js:48253)
at Object.IonicModule.factory.ionicViewSwitcher.create.switcher.render (ionic.bundle.js:46450)
at Object.IonicModule.factory.ionicViewSwitcher.create.switcher.init (ionic.bundle.js:46370)

The code I am trying to run is:

var manu = {
  "@context": {
    "name": "http://xmlns.com/foaf/0.1/name",
    "homepage": {
      "@id": "http://xmlns.com/foaf/0.1/homepage",
      "@type": "@id"
    }
  },
  "@id": "http://manu.sporny.org#person",
  "name": "Manu Sporny",
  "homepage": "http://manu.sporny.org/"
};

db.jsonld.put(manu, function(err, obj) {
  // do something after the obj is inserted
});

Although I get this error in levelgraph 1.0.0, if I install levelgraph 0.10.5 everything works as expected, so perhaps this is some incompatibility with version 1.0.0

Document meaning of db.('webid') in the search example

Where can I find the API for db.v('webid')?

  db.search([{
    subject: manu['@id'],
    predicate: 'http://xmlns.com/foaf/0.1/knows',
    object: db.v('webid')
  }, {
    subject: db.v('webid'),

I see another example on the levelgraph Readme, so now I guess it is just a way to indicate which subjects should match which objects and such in the query. Makes sense, reminds me of Datomic from ClojureScript, using q? convention to the same effect... Would be nice if that was documented in the examples. Thanks :)

  var stream = db.searchStream([{
    subject: "matteo",
    predicate: "friend",
    object: db.v("x")
  }, {
    subject: db.v("x"),

the best way to add / update a property / predicate

Hi, nice lib, but should be updated( see my pull req on levelgraph levelgraph/levelgraph#196)

let's say i have inserted to the db something like

var manu = {
  "@context": {
    "@vocab": "http://xmlns.com/foaf/0.1/",
    "homepage": { "@type": "@id" },
    "knows": { "@type": "@id" },
    "based_near": { "@type": "@id" }
  },
  "@id": "http://manu.sporny.org#person",
  "name": "Manu Sporny",
  "homepage": "http://manu.sporny.org/",
  "knows": [{
    "@id": "https://my-profile.eu/people/deiu/card#me",
    "name": "Andrei Vlad Sambra",
    "based_near": "http://dbpedia.org/resource/Paris"
  }, {
    "@id": "http://melvincarvalho.com/#me",
    "name": "Melvin Carvalho",
    "based_near": "http://dbpedia.org/resource/Honolulu"
  }, {
    "@id": "http://bblfish.net/people/henry/card#me",
    "name": "Henry Story",
    "based_near": "http://dbpedia.org/resource/Paris"
  }, {
    "@id": "http://presbrey.mit.edu/foaf#presbrey",
    "name": "Joe Presbrey",
    "based_near": "http://dbpedia.org/resource/Cambridge"
  }]
};
```

what is the best way to add /update a key, like for example add a new foaf:knows or update the @id of one already present ? 
Do i have to deal in jsonld / put for overwriting the existing one, should i get deal with subject/predicate/object ? or should i delete the manu object and recreate it with the updated properties ?

Manage @list

Currently the @list keyword is not dealt with properly.

For instance when using the levelgraph playground

{
  "@context": {
    "@vocab": "http://xmlns.com/foaf/0.1/"
  },
  "@id": "http://example.org/people#joebob",
  "nick":
  {
    "@list": [ "joe", "bob", "jaybee" ]
  }
}

Reuses the same identifier for blank nodes:

image

And using .get() returns an empty list.

@id validation

From #62 there should be error messages when @id don't validate (for instance recommending to set the base option), and the IRI validation should be better.

levelgraph peerDependencies upgrade?

Loving levelgraph and much thanks for it! ❀️

Are their plans to make this work with levelgraph 1.3.1? Right now, the peerDependencies section states a dependency on 1.1.1 and if I try to depend on both levelgraph ^1.3.1 and levelgraph-jsonld ^0.5.0 here's what NPM says:

npm WARN peerDependencies The peer dependency levelgraph@~1.1.1 included from levelgraph-jsonld will no
npm WARN peerDependencies longer be automatically installed to fulfill the peerDependency
npm WARN peerDependencies in npm 3+. Your application will need to depend on it explicitly.
npm ERR! Linux 3.4.0+
npm ERR! argv "/usr/bin/nodejs" "/usr/bin/npm" "i"
npm ERR! node v4.6.2
npm ERR! npm  v2.15.11
npm ERR! code EPEERINVALID

npm ERR! peerinvalid The package [email protected] does not satisfy its siblings' peerDependencies requirements!
npm ERR! peerinvalid Peer [email protected] wants levelgraph@~1.1.1

Not sure what's changed in those two minor point releases, but thought it'd be worth noting--if nothing else for folks who may also be facing this issue.

Cheers! And keep up the greatness!! 😁

Query languages based on framing

It might be very possible to build a query language on top of JSON-LD framing, as suggested by @lanthaler:

Have a look at my playground: http://www.markus-lanthaler.com/jsonld/playground/

And click on the "Library" example at the top. Then make sure that the
"Framed" tab is selected. If you look at the input document on the left side
you'll see that the data isn't nested at all, the result however is. This is
done according the specified frame on the right hand side.

In my implementation you can also filter by @id, e.g., so if you would have
multiple libraries, you could select the one you are interested in.

There's no documentation for this right now but maybe looking at my
implementation helps!? The interesting parts are in the nodeMatchesFrame
method: https://github.com/lanthaler/JsonLD/blob/master/Processor.php#L2278

It still needs a bit of love and doesn't handle certain things yet (lists,
reverse properties) but you should get the idea. Probably it's much easier
to implement in LevelGraph since you can query more easily over triples.
Dunno. Unfortunately I don't have the time right now to get my hands dirty
but I can at least answer questions you may have :-P

The other resource you might wanna look at are the framing tests: https://github.com/json-ld/json-ld.org/tree/master/test-suite/tests

I do not pass all the tests since I modified some behavior, most of my
modifications are described here

clarify overriding resources

moving it here from #1 (comment)

"We may also need to give a good thought on handling cases of documents with embedded (nested) resources. I can imagine case of someone first creating a resource using big number of statements and then inserting another document with same resource embedded but only including few relevant statements. We can NOT just delete all the statement about this resource and replace them with just those few statements from embedded one! I would prefer not to get into this topic in more depth here and maybe I'll create separate issue for it... previous issue relevant this topic #8 I would prefer to leave for discussing blank nodes -- a big topic on its own"

"Conflict" detection

I'm working on a feature to identify existing triples that match a document that is being put and returns a list of conflicts to allow the client to choose whether to overwrite or leave be, maybe after doing access control checks.

Problem using ['@type'] arrays

graphdb.jsonld.get causes an error when the jsonld document being retrieved has a '@type' property that is an array.

This is the LD document I was attempting to retreive: http://dbpedia.org/data/Ratatat.jsonld

Here is the error (I logged it then threw it):

{ name: 'jsonld.CompactError',
  message: 'Could not expand input before compaction.',
  details: 
   { cause: 
      { name: 'jsonld.SyntaxError',
        message: 'Invalid JSON-LD syntax; "@type" value must a string, an array of strings, or an empty object.',
        details: [Object] } } }

/home/ameen/Desktop/Projects/journalism/jsonld/test.js:239
    if (err) throw err;
                   ^
jsonld.CompactError: Could not expand input before compaction.
    at /home/ameen/Desktop/Projects/journalism/jsonld/node_modules/levelgraph-jsonld/node_modules/jsonld/js/jsonld.js:137:23
    at /home/ameen/Desktop/Projects/journalism/jsonld/node_modules/levelgraph-jsonld/node_modules/jsonld/js/jsonld.js:381:16
    at finished (/home/ameen/Desktop/Projects/journalism/jsonld/node_modules/levelgraph-jsonld/node_modules/jsonld/js/jsonld.js:5838:7)
    at _retrieveContextUrls.retrieve (/home/ameen/Desktop/Projects/journalism/jsonld/node_modules/levelgraph-jsonld/node_modules/jsonld/js/jsonld.js:5844:7)
    at _retrieveContextUrls (/home/ameen/Desktop/Projects/journalism/jsonld/node_modules/levelgraph-jsonld/node_modules/jsonld/js/jsonld.js:5953:3)
    at expand (/home/ameen/Desktop/Projects/journalism/jsonld/node_modules/levelgraph-jsonld/node_modules/jsonld/js/jsonld.js:326:5)
    at /home/ameen/Desktop/Projects/journalism/jsonld/node_modules/levelgraph-jsonld/node_modules/jsonld/js/jsonld.js:304:5
    at process._tickCallback (node.js:415:13)

At first I thought it might be an issue with jsonld, but using jsonld.expand on the document directly produced no errors.

The problem lies with the jsonld document that is being passed to jsonld.expand in levelgraph - more specifically, in how the ['@type'] array is expanded from the triples.

This is the block responsible for expanding the type array from triples.

if (triple.predicate === RDFTYPE) {
  if (acc[triple.subject]['@type']) {
    acc[triple.subject]['@type'] = [acc[triple.subject]['@type']];
    acc[triple.subject]['@type'].push(triple.object);
  } else {
    acc[triple.subject]['@type'] = triple.object;
  }
  cb(null, acc);
}

When this line is removed, it works for me

acc[triple.subject]['@type'] = [acc[triple.subject]['@type']];

Otherwise my ['@type'] array ends up looking something like this:

"@type": [
    [
      [
        [
          [
            [
              [
                [
                  [
                    [
                      [
                        [
                          [
                            [
                              [
                                [
                                  [
                                    [
                                      [
                                        [
                                          [
                                            [
                                              [
                                                [
                                                  "http://dbpedia.org/class/yago/Abstraction100002137",
                                                  "http://dbpedia.org/class/yago/AmericanHouseMusicGroups"
                                                ],
                                                "http://dbpedia.org/class/yago/AmericanPost-rockGroups"
                                              ],
                                              "http://dbpedia.org/class/yago/Communication100033020"
                                            ],
                                            "http://dbpedia.org/class/yago/Couple113743605"
                                          ],
                                          "http://dbpedia.org/class/yago/DefiniteQuantity113576101"
                                        ],
                                        "http://dbpedia.org/class/yago/Device107068844"
                                      ],
                                      "http://dbpedia.org/class/yago/Digit113741022"
                                    ],
                                    "http://dbpedia.org/class/yago/ElectronicMusicDuos"
                                  ],
                                  "http://dbpedia.org/class/yago/ElectronicMusicGroupsFromNewYork"
                                ],
                                "http://dbpedia.org/class/yago/ExpressiveStyle107066659"
                              ],
                              "http://dbpedia.org/class/yago/Group100031264"
                            ],
                            "http://dbpedia.org/class/yago/Integer113728499"
                          ],
                          "http://dbpedia.org/class/yago/Measure100033615"
                        ],
                        "http://dbpedia.org/class/yago/Number113582013"
                      ],
                      "http://dbpedia.org/class/yago/Onomatopoeia107104574"
                    ],
                    "http://dbpedia.org/class/yago/Onomatopoeias"
                  ],
                  "http://dbpedia.org/class/yago/RhetoricalDevice107098193"
                ],
                "http://dbpedia.org/class/yago/Two113743269"
              ],
              "http://dbpedia.org/ontology/Agent"
            ],
            "http://dbpedia.org/ontology/Band"
          ],
          "http://dbpedia.org/ontology/Organisation"
        ],
        "http://schema.org/MusicGroup"
      ],
      "http://schema.org/Organization"
    ],
    "http://www.w3.org/2002/07/owl#Thing"
  ]

Matching regular expressions in search

Hello!

Sorry for being quite a noob as i don't know whether this is possible or not and if it is possible, how it could be achieved. I tried matching regular expressions, searching for subjects of a simple levelgraph-jsonld database:

db = levelgraphJSONLD(levelgraph('database'));

    var key = req.body.key;
    var value = req.body.value;

    db.search([{
        subject: db.v("id"),
        predicate: key,
        object: value
    }], function(err, results) {
        if (err) {
            console.log("ERROR");
            console.log(err);

        } else {
            res.status(200).json(results);
            db.close();
        }
    });

so basically the server receives a post request with key and value, while key and value stand for predicate and object, and returns the matching subjects. Now how can i try finding subjects that match the relation but the objects (for example) just CONTAIN a certain string? I tried querying the database with regular expressions handed over as object/value, e.g. /.?/ /(.?)/ or plain .*? but no results. I already read, that the filter function might be a solution for this. But aren't there any possibilities to match regular expressions or use wildcard characters in the query to avoid filtering overhead somehow?

I hope the answer of this isn't way too easy and that i haven't just been blind looking for the answer and still that it can somehow be achieved..

Thanks in advance for the help!

Missing information after `db.jsonld.get()`

@jmatsushita here's a case in point from my BigBlueHat/page-notes#9 digging...

.put()ing this JSON-LD...

{
  "@context": "http://www.w3.org/ns/anno.jsonld",
  "id": "http://example.org/anno9",
  "type": "Annotation",
  "body": [
    "http://example.org/description1",
    {
      "type": "TextualBody",
      "value": "tag1"
    }
  ],
  "target": [
    "http://example.org/image1",
    "http://example.org/image2",
    {
      "source": "http://example.org/"
    }
  ]
}

...and then:

db.jsonld.get('http://example.org/anno9', {}, (err, obj) => console.log(JSON.stringify(obj, null, '  '));

...gets me this...

{
  "@id": "http://example.org/anno9",
  "@type": "http://www.w3.org/ns/oa#Annotation",
  "http://www.w3.org/ns/oa#hasBody": {
    "@id": "http://example.org/description1"
  },
  "http://www.w3.org/ns/oa#hasTarget": [
    {
      "@id": "http://example.org/image1"
    },
    {
      "@id": "http://example.org/image2"
    }
  ]
}

So. The 2 blank nodes disappeared--which is where I'm beginning to think the bug/issue is (possibly with levelgraph-jsonld).

However, on the JSON-LD Playground things work as they should and I've essentially got the same JSON-LD back.

The issue is the same when passing in a @context (the one from the original .put() for simplicity):

{
  "@context": "http://www.w3.org/ns/anno.jsonld",
  "undefined": [
    "http://example.org/anno9",
    "Annotation"
  ],
  "body": "http://example.org/description1",
  "target": [
    "http://example.org/image1",
    "http://example.org/image2"
  ]
}

Blank nodes gone missing again...

Colliding keyword detection fails on "id" mapped to "@id"

The code that generates the error is here:
https://github.com/mcollina/levelgraph-jsonld/blob/e07ce027b0c4da9a5e2a8b5b34b394f95940379c/build/levelgraph-jsonld.js#L5239-L5245

A sample JSON-LD document + @context that will cause the error to be thrown is here:

{
  "@context": {
    "id": "@id",
    "@vocab": "http://xmlns.com/foaf/0.1/"
  },
  "id": "http://bigbluehat.com/#",
  "name": "BigBlueHat",
  "knows": [

    {
      "id": "http://manu.sporny.org#person",
      "name": "Manu Sporny",
      "homepage": "http://manu.sporny.org/"
    }
  ]
}

I found this because the Web Annotation Data Model (among others) maps id to @id in this way.

Removing the 3rd line in the above example will make it work.

You can try pasting it in the playground I'm building for this project. 🎁 😍
https://bigbluehat.github.io/levelgraph-playground/
(watch the console if you try it)

Not sure what the extra JSON-LD validation is meant for...so I don't just want to rip it out. πŸ˜„ Hence this issue. 😁

Thanks!
🎩

Graph "scrubbing" is too agressive

I'm working on a test for this (and understanding more about what changed), but here's the set of steps for playing with on the playground.

  1. empty graph
  2. put
{"@context": "https://schema.org/", "@id": "http://bigbluehat.com/#", "name": "BigBlueHat"}
  1. expect(triples).to.have.length(1) is true
  2. put
{"@context": "https://schema.org/", "name": "BigBlueHat"}
  1. expect(triples).to.have.length(2) is false...there's only one. It replaced (maybe?) the original statement that had the ID. πŸ˜•
  2. put the JSON-LD from # 2 (above)
  3. expect(triples).to.have.length(2) is true...um...OK...
  4. put the JSON-LD from # 4 (above)...trying again
  5. expect(triples).to.have.length(3) is true...gah! WAT?! I'm back down to 1 😒

Obviously...I'm missing something important here... πŸ˜€

@jmatsushita thoughts?

level is not a function

using level > 8.0.0

initialisation should be something like

import { Level } from 'level'
const bloidDb = new Level('./BloidDb')
levelgraph = require('levelgraph'),
jsonld     = require('levelgraph-jsonld'),
opts       = { base: 'http://scenaristeur.github.io/bloid' },
db         = jsonld(levelgraph(bloidDb), opts);

Some IRIs not read correctly

The code uses a regex to recognize IRIs in the object position during a get. The problem is that this regex does not recognize many IRIs that it should. In particular, an IRI may have any scheme and need not have slashes right after the scheme.

For example, try using "mailto:[email protected]" in an object and it will interpreted as the @value of an object rather than as an @id (IRI).

Make `.get()` match LevelGraph and levelgraph-n3 APIs

levelgraph-jsonld uses db.jsonld.get("...@id value..."), but levelgraph (db.get()) and levegraph-n3 (db.n3.get()) require an SPO object with at least subject set (ex: {subject: "...@id value..."}).

Additionally, if you pass any string (instead of an object) to these other .get() APIs, you will get the entire database of results (possibly a very costly situation...).

I do like the simpler code (avoiding the object expression), but object-based approach is also useful when you're already dealing with them.

So, I'd propose that we:

  • make db.jsonld.get() support both a string and the {subject: ""} style
  • make db.get() and db.n3.get() handle a string--which would map to {subject: ""} internally (a short hand for that...essentially).

That would:
a) clear up confusion
b) avoid full-db return results
c) clear up confusion πŸ˜‰

Thoughts? πŸ’­

Clarify blank nodes deletion

The code currently skips blank nodes when deleting including when the blank nodes have received a UUID.

Hmm... After reading various specs and #8 this is more complicated than I thought.

It seems that the default behavior should be that blank nodes are not persistent or portable in other words blank node identifiers are understood to denote a fresh blank node (as described for LD Patch), so following the PATCH DELETE operation (without the possibility to BIND), then the default should be to NOT be able to delete blank nodes.

In that sense the previous levelgraph-jsonld's del function acted more like the LD Patch CUT operation which can delete a whole tree made of blank nodes.

So this means that my original intuition that after assigning a blank node identifier, then you should be able to refer to that identifier is not how blank nodes should be handled in the general case, however there is a provision for handling blank nodes in that way which is skolemization which assigns a unique IRI to a blank node, which then makes it persistent and portable, and can also allow to differentiate IRIs that have been generated for a blank nodes to allow other systems to treat them as such (all of which is an optional part of the spec).

So what I propose is as follow:

  • Rename the previous del function which has the delAllTriples recursive blank node deletion, as a new cut operation (and maybe also have a cut option available for del).
  • Change the default behavior of del to not delete blank nodes at all. (maybe except when store scope identifiers are provided, see below).
  • Introduce a new skolemize option to put which would generate IRIs for blank nodes with the recommended path /.well-known/genid/ (not sure how that works with no @base).
  • Introduce skolemize and unskolemize functions which would take a document (implying a selection of triples, see below comment about frame) and persist blank nodes to skolem IRIs, and respectively revert skolem IRIs to blank nodes.

This might mean exposing frame in get and elsewhere (which touches on #2) to help specify the right part of the document that needs to be handled. More long term, this work might benefit from being seen as part as a lower-level API for an LDP implementation (which touches levelgraph/levelgraph#111).

The remaining case is for rdf store scope blank node identifiers which are close to what we have now (i.e. blank nodes are assigned a uuid - _:abcd... which is returned in the callback of put). Then it seems that:

  • When a document is passed to del without @id for its blank nodes then they are treated as blank and not deleted.
  • When a document is passed to del in the shape returned from put (i.e. with store scope blank node identifiers) then they should be deleted (this seems to me like a intuitive - ? - default which most people would expect), with an option to ignore_blanks (where it would treat store blanks as abstract blanks and ignore them). That's the main bit where I feel this might be twisting the meaning of the spec.

This behavior would mean that we need to distinguish blanks from assigned store scope blanks, is that what the prefix _:c14n is for?

Handle named graphs

With the recent version update, it is now possible to submit JSON-LD docs which includes an @graph key. However, named graphs are not handled yet. (More precisely, if there is a top level @id then the @graph value is just ignored).

Given the discussion on levelgraph/levelgraph#43 I'm thinking of first implementing this using a graph triple property for put, and a slow filter JS-land approach for get.

The performance penalty will be big on anything that tries to use named graphs but it's probably good to start here before doing anything more involved. Things will probably take shape as I implement but does this make sense @mcollina ?

tests with remote @context

i start messing with jsonld.js and its way of loading remote contexts. while on it i better add some relevant tests here as well!

very likely we may hit some CORS related issues when testing with browser...

clarify handling of blank nodes

NOTE: this issue started with focuss on embedding but then drifted into blank nodes πŸ˜–

http://json-ld.org/spec/latest/json-ld/#embedding

i tried adding embedded nodes to one of the fixtures

{
    "@context": {
        "@vocab": "http://xmlns.com/foaf/0.1/",
        "homepage": {
            "@type": "@id"
        }
    },
    "@id": "http://manu.sporny.org#person",
    "name": "Manu Sporny",
    "homepage": "http://manu.sporny.org/",
    "knows": [
        {
            "@id": "http://gregkellog.com",
            "name": "Greg Kellog"
        },
        {
            "@id": "http://markus-lanthaler.com",
            "name": "Markus Lanthaler"
        }
    ]
}

and then run test

db.jsonld.put(manu, function() {
  db.jsonld.del(manu["@id"], function() {
    db.get({}, function(err, triples) {
      // getting the full db
      expect(triples).to.be.empty;
      done();
    });
  });
});

it breaks since we don't delete embeded nodes , it looks like we manage it already in correct way but may just need to document better and test for desired behavior!

when we use blank nodes, they get deleted which makes sense

{
    "@context": {
        "@vocab": "http://xmlns.com/foaf/0.1/",
        "homepage": {
            "@type": "@id"
        }
    },
    "@id": "http://manu.sporny.org#person",
    "name": "Manu Sporny",
    "homepage": "http://manu.sporny.org/",
    "knows": [
        {
            "name": "Greg Kellog"
        },
        {
            "name": "Markus Lanthaler"
        }
    ]
}

improve usability of Literals - matching, searching, indexing

Shortly for now just to capture it!

In a way we store literals currently. One needs to make extremely precise matching while using raw LevelGraph db.get(), as well as raw db.search() since currently we don't provide any kind of db.jsonld.search()

Having for example number stored as

"1.2345E1"^^<http://www.w3.org/2001/XMLSchema#double>

While we run our search query we need to match given literal very exactly, and make sure not to confuse for example #double with #float

Also searching literals with language tags seems sub-optimal, having stored:

"Big Buck Bunny"@en

Currently would not get matches if searched for

"Big Buck Bunny"

Plus most people may need to get N3Util to work with data in a convenient way... We could at least expose one included in LevelGraph-JSONLD somehow? Still on its own it won't address issues with searching and matching a Literal

del() empties entire storage

So...this seems to be close to the root of the new problems.

  1. Store some stuff.
  2. db.jsonld.del({}, console.log.bind(console))
  3. db.get({}, (err, list) => console.log(list.length)) ... returns 0

Doing the same with db.del({}, console.log.bind(console)) doesn't empty anything--which would be the expected behavior.

I'm reading through the code now to see where I can help. Thanks!

del() cannot accept strings, but put() can

var jld = {"@context": "https://schema.org/", "name": "BigBlueHat"};
db.jsonld.put(jld, console.log.bind(console));
db.jsonld.put(JSON.stringify(jld), console.log.bind(console));
// have the same result
db.jsonld.del(jld, console.log.bind(console)); // works
db.jsonld.del(JSON.stringify(jld), console.log.bind(console)); // doesn't...
//results in...

JsonLdError

{
  code: "loading document failed",
  url: "{"@context":{"@vocab":"http://xmlns.com/foaf/0.1/"},"@id":"http://bigbluehat.com/#","name":"BigBlueHat","knows":[{"@id":"http://manu.sporny.org#person","name":"Manu Sporny","homepage":"http://manu.sporny.org/"}]}",
  __proto__: {
    message: "URL could not be dereferenced; only "http" and "https" URLs are supported.",
    name: "jsonld.InvalidUrl",
    stack: "Error↡    at JsonLdError (http://localhost:8000/dist/index.js:20808:19)↡    at loader (http://localhost:8000/dist/index.js:20344:23)↡    at http://localhost:8000/dist/index.js:19809:8↡    at Function.jsonld.promisify (http://localhost:8000/dist/index.js:19808:10)↡    at jsonld.RequestQueue._loader (http://localhost:8000/dist/index.js:20337:21)↡    at http://localhost:8000/dist/index.js:19966:43↡    at jsonld.RequestQueue.add (http://localhost:8000/dist/index.js:19962:12)↡    at Object.jsonld.loadDocument [as documentLoader] (http://localhost:8000/dist/index.js:19670:24)↡    at http://localhost:8000/dist/index.js:18732:29↡    at Item.run (http://localhost:8000/dist/index.js:52966:14)"
  }
}

Apparently jsonld.js thinks any string is a URL, so input will need JSON.parse()'ing first.

Whats the correct way to query

Hi,
I am trying to search on a jsonld representation using below code

var building = require('./db/dbfile.jsonld');
var levelgraph = require('levelgraph'),
    levelup = require("levelup"),
    db = levelup('/does/not/matter', { db: require('memdown') });
var levelgraphJSONLD = require('levelgraph-jsonld'),
db = levelgraphJSONLD(levelgraph(db));

db.jsonld.put(building, function(){
db.search([{
    subject: db.v('x'),
    predicate: 'http://buildsys.org/ontologies/BrickFrame#isRelatedTo',
    object: db.v('y'),
  }], function(err, data) {
    console.log(data, err);
  });
});

This works , however i want to not specify predicate as full namespace instead i want to leverage the context information when searching.
something like

db.search([{
    subject: db.v('x'),
    predicate: 'rel:isRelatedTo',
    object: db.v('y'),
  }],....

following is the context object as specified in my /db/dbfile.jsonld file

"@context": {
        "feeds": {
            "@id": "rel:feeds",
            "@type": "@id"
        },
        "id": "@id",
        "isRelatedTo": {
            "@id": "rel:isPointOf",
            "@type": "@id"
        },
        "rel": "http://buildsys.org/ontologies/BrickFrame#",
        "site": "http://my.org/ontology/site#",
        "tagset": "http://buildsys.org/ontologies/Brick#",
        "type": "@type"
    },
    "@graph": [
        {
            "id": "site:id0",
            "isRelatedTo": "site:id7",
            "type": "tagset:Point"
        },
        {
            "id": "site:id1",
            "isRelatedTo": "site:id8",
            "type": "tagset:Point"
        },....

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.