Drops Oxigraph JS

Not supported by Sled
pull/171/head
Tpt 3 years ago
parent 7668f032f4
commit 122db6a2c3
  1. 11
      .github/workflows/build.yml
  2. 18
      .github/workflows/release.yml
  3. 2
      .gitignore
  4. 1
      Cargo.toml
  5. 3
      README.md
  6. 23
      js/Cargo.toml
  7. 211
      js/README.md
  8. 15
      js/package.json
  9. 3
      js/src/lib.rs
  10. 597
      js/src/model.rs
  11. 221
      js/src/store.rs
  12. 16
      js/src/utils.rs
  13. 2
      js/test/model.js
  14. 156
      js/test/store.js
  15. 7
      lib/Cargo.toml
  16. 12
      lib/src/model/xsd/date_time.rs

@ -39,17 +39,6 @@ jobs:
env:
RUST_BACKTRACE: 1
js:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: rustup update
- run: cargo install wasm-pack
- run: npm install
working-directory: ./js
- run: npm test
working-directory: ./js
python:
runs-on: ubuntu-latest
steps:

@ -101,24 +101,6 @@ jobs:
- run: pip install 'maturin>=0.9.2,<0.10'
- run: maturin publish --no-sdist -u __token__ -p ${{ secrets.PYPI_PASSWORD }}
working-directory: ./python
publish_npm:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 14
registry-url: https://registry.npmjs.org
- run: rustup update
- run: cargo install wasm-pack
- run: npm install
working-directory: ./js
- run: npm run build
working-directory: ./js
- run: npm run release
working-directory: ./js
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
publish_python_doc:
runs-on: ubuntu-latest
steps:

2
.gitignore vendored

@ -3,7 +3,5 @@
Cargo.lock
.idea
*.iml
js/node_modules
lib/tests/rockdb_bc_data
lib/tests/sled_bc_data
venv

@ -1,6 +1,5 @@
[workspace]
members = [
"js",
"lib",
"python",
"server",

@ -4,7 +4,6 @@ Oxigraph
[![Latest Version](https://img.shields.io/crates/v/oxigraph.svg)](https://crates.io/crates/oxigraph)
[![Released API docs](https://docs.rs/oxigraph/badge.svg)](https://docs.rs/oxigraph)
[![PyPI](https://img.shields.io/pypi/v/pyoxigraph)](https://pypi.org/project/pyoxigraph/)
[![npm](https://img.shields.io/npm/v/oxigraph)](https://www.npmjs.com/package/oxigraph)
[![actions status](https://github.com/oxigraph/oxigraph/workflows/build/badge.svg)](https://github.com/oxigraph/oxigraph/actions)
[![Gitter](https://badges.gitter.im/oxigraph/community.svg)](https://gitter.im/oxigraph/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
@ -21,8 +20,6 @@ It is split into multiple parts:
[![Latest Version](https://img.shields.io/crates/v/oxigraph.svg)](https://crates.io/crates/oxigraph)
[![Released API docs](https://docs.rs/oxigraph/badge.svg)](https://docs.rs/oxigraph)
* [`pyoxigraph` that exposes Oxigraph to the Python world](https://oxigraph.org/pyoxigraph/). Its source code is in the `python` directory. [![PyPI](https://img.shields.io/pypi/v/pyoxigraph)](https://pypi.org/project/pyoxigraph/)
* [JavaScript bindings for Oxigraph](https://www.npmjs.com/package/oxigraph). WebAssembly is used to package Oxigraph into a NodeJS compatible NPM package. Its source code is in the `js` directory.
[![npm](https://img.shields.io/npm/v/oxigraph)](https://www.npmjs.com/package/oxigraph)
* [Oxigraph server](https://crates.io/crates/oxigraph_server) that provides a standalone binary of a web server implementing the [SPARQL 1.1 Protocol](https://www.w3.org/TR/sparql11-protocol/) and the [SPARQL 1.1 Graph Store Protocol](https://www.w3.org/TR/sparql11-http-rdf-update/). Its source code is in the `server` directory.
[![Latest Version](https://img.shields.io/crates/v/oxigraph_server.svg)](https://crates.io/crates/oxigraph_server)
[![Docker Image Version (latest semver)](https://img.shields.io/docker/v/oxigraph/oxigraph?sort=semver)](https://hub.docker.com/repository/docker/oxigraph/oxigraph)

@ -1,23 +0,0 @@
[package]
name = "oxigraph_js"
version = "0.2.3"
authors = ["Tpt <thomas@pellissier-tanon.fr>"]
license = "MIT OR Apache-2.0"
readme = "README.md"
keywords = ["RDF", "N-Triples", "Turtle", "RDF/XML", "SPARQL"]
repository = "https://github.com/oxigraph/oxigraph/tree/master/js"
description = "JavaScript bindings of Oxigraph"
edition = "2018"
[lib]
crate-type = ["cdylib"]
name = "oxigraph"
[dependencies]
oxigraph = { version = "0.2", path="../lib" }
wasm-bindgen = "0.2"
js-sys = "0.3"
console_error_panic_hook = "0.1"
[dev-dependencies]
wasm-bindgen-test = "0.3"

@ -1,211 +0,0 @@
Oxigraph for JavaScript
=======================
[![npm](https://img.shields.io/npm/v/oxigraph)](https://www.npmjs.com/package/oxigraph)
[![actions status](https://github.com/oxigraph/oxigraph/workflows/build/badge.svg)](https://github.com/oxigraph/oxigraph/actions)
[![Gitter](https://badges.gitter.im/oxigraph/community.svg)](https://gitter.im/oxigraph/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
This package provides a JavaScript API on top of [Oxigraph](https://crates.io/crates/oxigraph), compiled with WebAssembly.
Oxigraph is a graph database written in Rust implementing the [SPARQL](https://www.w3.org/TR/sparql11-overview/) standard.
Oxigraph for JavaScript is a work in progress and currently offers a simple in-memory store with [SPARQL 1.1 Query](https://www.w3.org/TR/sparql11-query/) and [SPARQL 1.1 Update](https://www.w3.org/TR/sparql11-update/) capabilities.
The store is also able to load RDF serialized in [Turtle](https://www.w3.org/TR/turtle/), [TriG](https://www.w3.org/TR/trig/), [N-Triples](https://www.w3.org/TR/n-triples/), [N-Quads](https://www.w3.org/TR/n-quads/) and [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/).
It is distributed using a [a NPM package](https://www.npmjs.com/package/oxigraph) that should work with nodeJS 12+.
```bash
npm install oxigraph
```
```js
const oxigraph = require('oxigraph');
```
## Example
Insert the triple `<http://example/> <http://schema.org/name> "example"` and log the name of `<http://example/>` in SPARQL:
```js
const { MemoryStore } = require('oxigraph');
const store = new MemoryStore();
const dataFactory = store.dataFactory;
const ex = dataFactory.namedNode("http://example/");
const schemaName = dataFactory.namedNode("http://schema.org/name");
store.add(dataFactory.triple(ex, schemaName, dataFactory.literal("example")));
for (binding of store.query("SELECT ?name WHERE { <http://example/> <http://schema.org/name> ?name }")) {
console.log(binding.get("name").value);
}
```
## API
Oxigraph currently provides a simple JS API.
It is centered around the `MemoryStore` class.
The `NamedNode`, `BlankNode`, `Literal`, `DefaultGraph`, `Quad` and `DataFactory` types
are following the [RDF/JS datamodel specification](https://rdf.js.org/data-model-spec/).
To import `MemoryStore` using Node:
```js
const { MemoryStore } = require('oxigraph');
```
### `MemoryStore`
#### `MemoryStore(optional sequence<Quad>? quads)` (constructor)
```js
const store = new MemoryStore();
```
If provided, the `MemoryStore` will be initialized with a sequence of quads.
#### `MemoryStore.dataFactory`
Returns a `DataFactory` following [RDF/JS datamodel specification](https://rdf.js.org/data-model-spec/).
Example:
```js
const store = new MemoryStore();
const ex = store.dataFactory.namedNode("http://example.com");
const blank = store.dataFactory.blankNode();
const foo = store.dataFactory.literal("foo");
const quad = store.dataFactory.quad(blank, ex, foo);
```
#### `MemoryStore.prototype.add(Quad quad)`
Inserts a quad in the store.
Example:
```js
store.add(quad);
```
#### `MemoryStore.prototype.delete(Quad quad)`
Removes a quad from the store.
Example:
```js
store.delete(quad);
```
#### `MemoryStore.prototype.has(Quad quad)`
Returns a boolean stating if the store contains the quad.
Example:
```js
store.has(quad);
```
#### `MemoryStore.prototype.match(optional Term? subject, optional Term? predicate, optional Term? object, optional Term? graph)`
Returns an array with all the quads matching a given quad pattern.
Example to get all quads in the default graph with `ex` for subject:
```js
store.match(ex, null, null, store.dataFactory.defaultGraph());
```
Example to get all quads:
```js
store.match();
```
#### `MemoryStore.prototype.query(String query)`
Executes a [SPARQL 1.1 Query](https://www.w3.org/TR/sparql11-query/).
For `SELECT` queries the return type is an array of `Map` which keys are the bound variables and values are the values the result is bound to.
For `CONSTRUCT` and `ÐESCRIBE` queries the return type is an array of `Quad`.
For `ASK` queries the return type is a boolean.
Example of SELECT query:
```js
for (binding of store.query("SELECT DISTINCT ?s WHERE { ?s ?p ?o }")) {
console.log(binding.get("s").value);
}
```
Example of CONSTRUCT query:
```js
const filteredStore = new MemoryStore(store.query("CONSTRUCT { <http:/example.com/> ?p ?o } WHERE { <http:/example.com/> ?p ?o }"));
```
Example of ASK query:
```js
if (store.query("ASK { ?s ?s ?s }")) {
console.log("there is a triple with same subject, predicate and object");
}
```
#### `MemoryStore.prototype.update(String query)`
Executes a [SPARQL 1.1 Update](https://www.w3.org/TR/sparql11-update/).
The [`LOAD` operation](https://www.w3.org/TR/sparql11-update/#load) is not supported yet.
Example of update:
```js
store.update("DELETE WHERE { <http://example.com/s> ?p ?o }")
```
### `MemoryStore.prototype.load(String data, String mimeType, NamedNode|String? baseIRI, NamedNode|BlankNode|DefaultGraph? toNamedGraph)`
Loads serialized RDF triples or quad into the store.
The method arguments are:
1. `data`: the serialized RDF triples or quads.
2. `mimeType`: the MIME type of the serialization. See below for the supported mime types.
3. `baseIRI`: the base IRI to use to resolve the relative IRIs in the serialization.
4. `toNamedGraph`: for triple serialization formats, the name of the named graph the triple should be loaded to.
The available formats are:
* [Turtle](https://www.w3.org/TR/turtle/): `text/turtle`
* [TriG](https://www.w3.org/TR/trig/): `application/trig`
* [N-Triples](https://www.w3.org/TR/n-triples/): `application/n-triples`
* [N-Quads](https://www.w3.org/TR/n-quads/): `application/n-quads`
* [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/): `application/rdf+xml`
Example of loading a Turtle file into the named graph `<http://example.com/graph>` with the base IRI `http://example.com`:
```js
store.load("<http://example.com> <http://example.com> <> .", "text/turtle", "http://example.com", store.dataFactory.namedNode("http://example.com/graph"));
```
### `MemoryStore.prototype.dump(String mimeType, NamedNode|BlankNode|DefaultGraph? fromNamedGraph)`
Returns serialized RDF triples or quad from the store.
The method arguments are:
1. `mimeType`: the MIME type of the serialization. See below for the supported mime types.
2. `fromNamedGraph`: for triple serialization formats, the name of the named graph the triple should be loaded from.
The available formats are:
* [Turtle](https://www.w3.org/TR/turtle/): `text/turtle`
* [TriG](https://www.w3.org/TR/trig/): `application/trig`
* [N-Triples](https://www.w3.org/TR/n-triples/): `application/n-triples`
* [N-Quads](https://www.w3.org/TR/n-quads/): `application/n-quads`
* [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/): `application/rdf+xml`
Example of building a Turtle file from the named graph `<http://example.com/graph>`:
```js
store.dump("text/turtle", store.dataFactory.namedNode("http://example.com/graph"));
```
## How to contribute
The Oxigraph bindings are written in Rust using [the Rust WASM toolkit](https://rustwasm.github.io/docs.html).
The [The Rust Wasm Book](https://rustwasm.github.io/docs/book/) is a great tutorial to get started.
To run the tests of the JS bindings written in JS run `npm test`.
## License
This project is licensed under either of
* Apache License, Version 2.0, ([LICENSE-APACHE](../LICENSE-APACHE) or
http://www.apache.org/licenses/LICENSE-2.0)
* MIT license ([LICENSE-MIT](../LICENSE-MIT) or
http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Futures by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

@ -1,15 +0,0 @@
{
"name": "oxigraph_tests",
"description": "Oxigraph JS build and tests",
"private": true,
"devDependencies": {
"mocha": "^8.0.1",
"@rdfjs/data-model": "1.1.2",
"standard": "^16.0.0"
},
"scripts": {
"test": "standard test/*.js && wasm-pack build --dev --target nodejs && mocha",
"build": "wasm-pack build --release --target nodejs && sed -i 's/oxigraph_js/oxigraph/g' pkg/package.json",
"release": "wasm-pack pack && wasm-pack publish"
}
}

@ -1,3 +0,0 @@
mod model;
mod store;
mod utils;

@ -1,597 +0,0 @@
use crate::format_err;
use crate::utils::to_err;
use js_sys::{Reflect, UriError};
use oxigraph::model::*;
use std::convert::TryFrom;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(js_name = DataFactory)]
#[derive(Default)]
pub struct JsDataFactory {
from_js: FromJsConverter,
}
#[wasm_bindgen(js_class = DataFactory)]
impl JsDataFactory {
#[wasm_bindgen(js_name = namedNode)]
pub fn named_node(&self, value: String) -> Result<JsNamedNode, JsValue> {
NamedNode::new(value)
.map(|v| v.into())
.map_err(|v| UriError::new(&v.to_string()).into())
}
#[wasm_bindgen(js_name = blankNode)]
pub fn blank_node(&self, value: Option<String>) -> Result<JsBlankNode, JsValue> {
Ok(if let Some(value) = value {
BlankNode::new(value).map_err(to_err)?
} else {
BlankNode::default()
}
.into())
}
#[wasm_bindgen]
pub fn literal(
&self,
value: Option<String>,
language_or_datatype: &JsValue,
) -> Result<JsLiteral, JsValue> {
if language_or_datatype.is_null() || language_or_datatype.is_undefined() {
Ok(Literal::new_simple_literal(value.unwrap_or_else(String::new)).into())
} else if language_or_datatype.is_string() {
Ok(Literal::new_language_tagged_literal(
value.unwrap_or_else(String::new),
language_or_datatype.as_string().unwrap_or_else(String::new),
)
.map_err(to_err)?
.into())
} else if let JsTerm::NamedNode(datatype) = self.from_js.to_term(language_or_datatype)? {
Ok(Literal::new_typed_literal(value.unwrap_or_else(String::new), datatype).into())
} else {
Err(format_err!("The literal datatype should be a NamedNode"))
}
}
#[wasm_bindgen(js_name = defaultGraph)]
pub fn default_graph(&self) -> JsDefaultGraph {
JsDefaultGraph {}
}
#[wasm_bindgen(js_name = triple)]
pub fn triple(
&self,
subject: &JsValue,
predicate: &JsValue,
object: &JsValue,
) -> Result<JsQuad, JsValue> {
Ok(JsQuad {
subject: self.from_js.to_term(subject)?,
predicate: self.from_js.to_term(predicate)?,
object: self.from_js.to_term(object)?,
graph_name: JsTerm::DefaultGraph(JsDefaultGraph {}),
})
}
#[wasm_bindgen(js_name = quad)]
pub fn quad(
&self,
subject: &JsValue,
predicate: &JsValue,
object: &JsValue,
graph: &JsValue,
) -> Result<JsQuad, JsValue> {
Ok(JsQuad {
subject: self.from_js.to_term(subject)?,
predicate: self.from_js.to_term(predicate)?,
object: self.from_js.to_term(object)?,
graph_name: if graph.is_undefined() || graph.is_null() {
JsTerm::DefaultGraph(JsDefaultGraph {})
} else {
self.from_js.to_term(&graph)?
},
})
}
#[wasm_bindgen(js_name = fromTerm)]
pub fn convert_term(&self, original: &JsValue) -> Result<JsValue, JsValue> {
Ok(self.from_js.to_term(original)?.into())
}
#[wasm_bindgen(js_name = fromQuad)]
pub fn convert_quad(&self, original: &JsValue) -> Result<JsQuad, JsValue> {
self.from_js.to_quad(original)
}
}
#[wasm_bindgen(js_name = NamedNode)]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct JsNamedNode {
inner: NamedNode,
}
#[wasm_bindgen(js_class = NamedNode)]
impl JsNamedNode {
#[wasm_bindgen(getter = termType)]
pub fn term_type(&self) -> String {
"NamedNode".to_owned()
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> String {
self.inner.as_str().to_owned()
}
pub fn equals(&self, other: &JsValue) -> bool {
if let Ok(Some(JsTerm::NamedNode(other))) =
FromJsConverter::default().to_optional_term(&other)
{
self == &other
} else {
false
}
}
}
impl From<NamedNode> for JsNamedNode {
fn from(inner: NamedNode) -> Self {
Self { inner }
}
}
impl From<JsNamedNode> for NamedNode {
fn from(node: JsNamedNode) -> Self {
node.inner
}
}
impl From<JsNamedNode> for NamedOrBlankNode {
fn from(node: JsNamedNode) -> Self {
node.inner.into()
}
}
impl From<JsNamedNode> for Term {
fn from(node: JsNamedNode) -> Self {
node.inner.into()
}
}
impl From<JsNamedNode> for GraphName {
fn from(node: JsNamedNode) -> Self {
node.inner.into()
}
}
#[wasm_bindgen(js_name = BlankNode)]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct JsBlankNode {
inner: BlankNode,
}
#[wasm_bindgen(js_class = BlankNode)]
impl JsBlankNode {
#[wasm_bindgen(getter = termType)]
pub fn term_type(&self) -> String {
"BlankNode".to_owned()
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> String {
self.inner.as_str().to_owned()
}
pub fn equals(&self, other: &JsValue) -> bool {
if let Ok(Some(JsTerm::BlankNode(other))) =
FromJsConverter::default().to_optional_term(&other)
{
self == &other
} else {
false
}
}
}
impl From<BlankNode> for JsBlankNode {
fn from(inner: BlankNode) -> Self {
Self { inner }
}
}
impl From<JsBlankNode> for BlankNode {
fn from(node: JsBlankNode) -> Self {
node.inner
}
}
impl From<JsBlankNode> for NamedOrBlankNode {
fn from(node: JsBlankNode) -> Self {
node.inner.into()
}
}
impl From<JsBlankNode> for Term {
fn from(node: JsBlankNode) -> Self {
node.inner.into()
}
}
impl From<JsBlankNode> for GraphName {
fn from(node: JsBlankNode) -> Self {
node.inner.into()
}
}
#[wasm_bindgen(js_name = Literal)]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct JsLiteral {
inner: Literal,
}
#[wasm_bindgen(js_class = Literal)]
impl JsLiteral {
#[wasm_bindgen(getter = termType)]
pub fn term_type(&self) -> String {
"Literal".to_owned()
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> String {
self.inner.value().to_owned()
}
#[wasm_bindgen(getter)]
pub fn language(&self) -> String {
self.inner.language().unwrap_or("").to_owned()
}
#[wasm_bindgen(getter)]
pub fn datatype(&self) -> JsNamedNode {
self.inner.datatype().into_owned().into()
}
pub fn equals(&self, other: &JsValue) -> bool {
if let Ok(Some(JsTerm::Literal(other))) =
FromJsConverter::default().to_optional_term(&other)
{
self == &other
} else {
false
}
}
}
impl From<Literal> for JsLiteral {
fn from(inner: Literal) -> Self {
Self { inner }
}
}
impl From<JsLiteral> for Literal {
fn from(node: JsLiteral) -> Self {
node.inner
}
}
impl From<JsLiteral> for Term {
fn from(node: JsLiteral) -> Self {
node.inner.into()
}
}
#[wasm_bindgen(js_name = DefaultGraph)]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct JsDefaultGraph {}
#[wasm_bindgen(js_class = DefaultGraph)]
impl JsDefaultGraph {
#[wasm_bindgen(getter = termType)]
pub fn term_type(&self) -> String {
"DefaultGraph".to_owned()
}
#[wasm_bindgen(getter)]
pub fn value(&self) -> String {
"".to_owned()
}
pub fn equals(&self, other: &JsValue) -> bool {
if let Ok(Some(JsTerm::DefaultGraph(other))) =
FromJsConverter::default().to_optional_term(&other)
{
self == &other
} else {
false
}
}
}
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub enum JsTerm {
NamedNode(JsNamedNode),
BlankNode(JsBlankNode),
Literal(JsLiteral),
DefaultGraph(JsDefaultGraph),
}
impl From<JsTerm> for JsValue {
fn from(value: JsTerm) -> Self {
match value {
JsTerm::NamedNode(v) => v.into(),
JsTerm::BlankNode(v) => v.into(),
JsTerm::Literal(v) => v.into(),
JsTerm::DefaultGraph(v) => v.into(),
}
}
}
impl From<NamedNode> for JsTerm {
fn from(node: NamedNode) -> Self {
JsTerm::NamedNode(node.into())
}
}
impl From<BlankNode> for JsTerm {
fn from(node: BlankNode) -> Self {
JsTerm::BlankNode(node.into())
}
}
impl From<Literal> for JsTerm {
fn from(literal: Literal) -> Self {
JsTerm::Literal(literal.into())
}
}
impl From<NamedOrBlankNode> for JsTerm {
fn from(node: NamedOrBlankNode) -> Self {
match node {
NamedOrBlankNode::NamedNode(node) => node.into(),
NamedOrBlankNode::BlankNode(node) => node.into(),
}
}
}
impl From<Term> for JsTerm {
fn from(term: Term) -> Self {
match term {
Term::NamedNode(node) => node.into(),
Term::BlankNode(node) => node.into(),
Term::Literal(literal) => literal.into(),
}
}
}
impl From<GraphName> for JsTerm {
fn from(name: GraphName) -> Self {
match name {
GraphName::NamedNode(node) => node.into(),
GraphName::BlankNode(node) => node.into(),
GraphName::DefaultGraph => JsTerm::DefaultGraph(JsDefaultGraph {}),
}
}
}
impl TryFrom<JsTerm> for NamedNode {
type Error = JsValue;
fn try_from(value: JsTerm) -> Result<Self, JsValue> {
match value {
JsTerm::NamedNode(node) => Ok(node.into()),
JsTerm::BlankNode(node) => Err(format_err!(
"The blank node {} is not a named node",
node.inner
)),
JsTerm::Literal(literal) => Err(format_err!(
"The literal {} is not a named node",
literal.inner
)),
JsTerm::DefaultGraph(_) => Err(format_err!("The default graph is not a named node")),
}
}
}
impl TryFrom<JsTerm> for NamedOrBlankNode {
type Error = JsValue;
fn try_from(value: JsTerm) -> Result<Self, JsValue> {
match value {
JsTerm::NamedNode(node) => Ok(node.into()),
JsTerm::BlankNode(node) => Ok(node.into()),
JsTerm::Literal(literal) => Err(format_err!(
"The literal {} is not a possible named or blank node term",
literal.inner
)),
JsTerm::DefaultGraph(_) => {
Err(format_err!("The default graph is not a possible RDF term"))
}
}
}
}
impl TryFrom<JsTerm> for Term {
type Error = JsValue;
fn try_from(value: JsTerm) -> Result<Self, JsValue> {
match value {
JsTerm::NamedNode(node) => Ok(node.into()),
JsTerm::BlankNode(node) => Ok(node.into()),
JsTerm::Literal(literal) => Ok(literal.into()),
JsTerm::DefaultGraph(_) => {
Err(format_err!("The default graph is not a possible RDF term"))
}
}
}
}
impl TryFrom<JsTerm> for GraphName {
type Error = JsValue;
fn try_from(value: JsTerm) -> Result<Self, JsValue> {
match value {
JsTerm::NamedNode(node) => Ok(node.into()),
JsTerm::BlankNode(node) => Ok(node.into()),
JsTerm::Literal(literal) => Err(format_err!(
"The literal {} is not a possible graph name",
literal.inner
)),
JsTerm::DefaultGraph(_) => Ok(GraphName::DefaultGraph),
}
}
}
#[wasm_bindgen(js_name = Quad)]
#[derive(Eq, PartialEq, Debug, Clone, Hash)]
pub struct JsQuad {
subject: JsTerm,
predicate: JsTerm,
object: JsTerm,
graph_name: JsTerm,
}
#[wasm_bindgen(js_class = Quad)]
impl JsQuad {
#[wasm_bindgen(getter = subject)]
pub fn subject(&self) -> JsValue {
self.subject.clone().into()
}
#[wasm_bindgen(getter = predicate)]
pub fn predicate(&self) -> JsValue {
self.predicate.clone().into()
}
#[wasm_bindgen(getter = object)]
pub fn object(&self) -> JsValue {
self.object.clone().into()
}
#[wasm_bindgen(getter = graph)]
pub fn graph(&self) -> JsValue {
self.graph_name.clone().into()
}
pub fn equals(&self, other: &JsValue) -> bool {
FromJsConverter::default()
.to_quad(&other)
.map_or(false, |other| self == &other)
}
}
impl From<Quad> for JsQuad {
fn from(quad: Quad) -> Self {
Self {
subject: quad.subject.into(),
predicate: quad.predicate.into(),
object: quad.object.into(),
graph_name: quad.graph_name.into(),
}
}
}
impl TryFrom<JsQuad> for Quad {
type Error = JsValue;
fn try_from(quad: JsQuad) -> Result<Self, JsValue> {
Ok(Quad {
subject: NamedOrBlankNode::try_from(quad.subject)?,
predicate: NamedNode::try_from(quad.predicate)?,
object: Term::try_from(quad.object)?,
graph_name: GraphName::try_from(quad.graph_name)?,
})
}
}
pub struct FromJsConverter {
term_type: JsValue,
value: JsValue,
language: JsValue,
datatype: JsValue,
subject: JsValue,
predicate: JsValue,
object: JsValue,
graph: JsValue,
}
impl Default for FromJsConverter {
fn default() -> Self {
Self {
term_type: JsValue::from_str("termType"),
value: JsValue::from_str("value"),
language: JsValue::from_str("language"),
datatype: JsValue::from_str("datatype"),
subject: JsValue::from_str("subject"),
predicate: JsValue::from_str("predicate"),
object: JsValue::from_str("object"),
graph: JsValue::from_str("graph"),
}
}
}
impl FromJsConverter {
pub fn to_term(&self, value: &JsValue) -> Result<JsTerm, JsValue> {
let term_type = Reflect::get(&value, &self.term_type)?;
if let Some(term_type) = term_type.as_string() {
match term_type.as_str() {
"NamedNode" => Ok(NamedNode::new(
Reflect::get(&value, &self.value)?
.as_string()
.ok_or_else(|| format_err!("NamedNode should have a string value"))?,
)
.map_err(|v| UriError::new(&v.to_string()))?
.into()),
"BlankNode" => Ok(BlankNode::new(
&Reflect::get(&value, &self.value)?
.as_string()
.ok_or_else(|| format_err!("BlankNode should have a string value"))?,
)
.map_err(to_err)?
.into()),
"Literal" => {
if let JsTerm::NamedNode(datatype) =
self.to_term(&Reflect::get(&value, &self.datatype)?)?
{
let datatype = NamedNode::from(datatype);
let literal_value = Reflect::get(&value, &self.value)?
.as_string()
.ok_or_else(|| format_err!("Literal should have a string value"))?;
Ok(match datatype.as_str() {
"http://www.w3.org/2001/XMLSchema#string" => Literal::new_simple_literal(literal_value),
"http://www.w3.org/1999/02/22-rdf-syntax-ns#langString" => Literal::new_language_tagged_literal(literal_value, Reflect::get(&value, &self.language)?.as_string().ok_or_else(
|| format_err!("Literal with rdf:langString datatype should have a language"),
)?).map_err(to_err)?,
_ => Literal::new_typed_literal(literal_value, datatype)
}.into())
} else {
Err(format_err!(
"Literal should have a datatype that is a NamedNode"
))
}
}
"DefaultGraph" => Ok(JsTerm::DefaultGraph(JsDefaultGraph {})),
_ => Err(format_err!(
"The termType {} is not supported by Oxigraph",
term_type
)),
}
} else {
Err(format_err!("The object termType field should be a string"))
}
}
pub fn to_optional_term(&self, value: &JsValue) -> Result<Option<JsTerm>, JsValue> {
if value.is_null() || value.is_undefined() {
Ok(None)
} else {
self.to_term(value).map(Some)
}
}
pub fn to_quad(&self, value: &JsValue) -> Result<JsQuad, JsValue> {
Ok(JsQuad {
subject: self.to_term(&Reflect::get(&value, &self.subject)?)?,
predicate: self.to_term(&Reflect::get(&value, &self.predicate)?)?,
object: self.to_term(&Reflect::get(&value, &self.object)?)?,
graph_name: self.to_term(&Reflect::get(&value, &self.graph)?)?,
})
}
}

@ -1,221 +0,0 @@
use crate::format_err;
use crate::model::*;
use crate::utils::to_err;
use js_sys::{Array, Map};
use oxigraph::io::{DatasetFormat, GraphFormat};
use oxigraph::model::*;
use oxigraph::sparql::QueryResults;
use oxigraph::MemoryStore;
use std::convert::{TryFrom, TryInto};
use std::io::Cursor;
use wasm_bindgen::prelude::*;
#[wasm_bindgen(js_name = MemoryStore)]
#[derive(Default)]
pub struct JsMemoryStore {
store: MemoryStore,
from_js: FromJsConverter,
}
#[wasm_bindgen(js_class = MemoryStore)]
impl JsMemoryStore {
#[wasm_bindgen(constructor)]
pub fn new(quads: Option<Box<[JsValue]>>) -> Result<JsMemoryStore, JsValue> {
console_error_panic_hook::set_once();
let store = Self::default();
if let Some(quads) = quads {
for quad in quads.iter() {
store.add(quad)?;
}
}
Ok(store)
}
#[wasm_bindgen(js_name = dataFactory, getter)]
pub fn data_factory(&self) -> JsDataFactory {
JsDataFactory::default()
}
pub fn add(&self, quad: &JsValue) -> Result<(), JsValue> {
self.store
.insert(Quad::try_from(self.from_js.to_quad(quad)?)?);
Ok(())
}
pub fn delete(&self, quad: &JsValue) -> Result<(), JsValue> {
self.store.remove(&self.from_js.to_quad(quad)?.try_into()?);
Ok(())
}
pub fn has(&self, quad: &JsValue) -> Result<bool, JsValue> {
Ok(self
.store
.contains(&self.from_js.to_quad(quad)?.try_into()?))
}
#[wasm_bindgen(getter=size)]
pub fn size(&self) -> usize {
self.store.len()
}
#[wasm_bindgen(js_name = match)]
pub fn match_quads(
&self,
subject: &JsValue,
predicate: &JsValue,
object: &JsValue,
graph_name: &JsValue,
) -> Result<Box<[JsValue]>, JsValue> {
Ok(self
.store
.quads_for_pattern(
if let Some(subject) = self.from_js.to_optional_term(subject)? {
Some(subject.try_into()?)
} else {
None
}
.as_ref()
.map(|t: &NamedOrBlankNode| t.into()),
if let Some(predicate) = self.from_js.to_optional_term(predicate)? {
Some(NamedNode::try_from(predicate)?)
} else {
None
}
.as_ref()
.map(|t: &NamedNode| t.into()),
if let Some(object) = self.from_js.to_optional_term(object)? {
Some(object.try_into()?)
} else {
None
}
.as_ref()
.map(|t: &Term| t.into()),
if let Some(graph_name) = self.from_js.to_optional_term(graph_name)? {
Some(graph_name.try_into()?)
} else {
None
}
.as_ref()
.map(|t: &GraphName| t.into()),
)
.map(|v| JsQuad::from(v).into())
.collect::<Vec<_>>()
.into_boxed_slice())
}
pub fn query(&self, query: &str) -> Result<JsValue, JsValue> {
let results = self.store.query(query).map_err(to_err)?;
let output = match results {
QueryResults::Solutions(solutions) => {
let results = Array::new();
for solution in solutions {
let solution = solution.map_err(to_err)?;
let result = Map::new();
for (variable, value) in solution.iter() {
result.set(
&variable.as_str().into(),
&JsTerm::from(value.clone()).into(),
);
}
results.push(&result.into());
}
results.into()
}
QueryResults::Graph(quads) => {
let results = Array::new();
for quad in quads {
results.push(&JsQuad::from(quad.map_err(to_err)?.in_graph(None)).into());
}
results.into()
}
QueryResults::Boolean(b) => b.into(),
};
Ok(output)
}
pub fn update(&self, update: &str) -> Result<(), JsValue> {
self.store.update(update).map_err(to_err)
}
pub fn load(
&self,
data: &str,
mime_type: &str,
base_iri: &JsValue,
to_graph_name: &JsValue,
) -> Result<(), JsValue> {
let base_iri = if base_iri.is_null() || base_iri.is_undefined() {
None
} else if base_iri.is_string() {
base_iri.as_string()
} else if let JsTerm::NamedNode(base_iri) = self.from_js.to_term(&base_iri)? {
Some(base_iri.value())
} else {
return Err(format_err!(
"If provided, the base IRI should be a NamedNode or a string"
));
};
let to_graph_name =
if let Some(graph_name) = self.from_js.to_optional_term(to_graph_name)? {
Some(graph_name.try_into()?)
} else {
None
};
if let Some(graph_format) = GraphFormat::from_media_type(mime_type) {
self.store
.load_graph(
Cursor::new(data),
graph_format,
&to_graph_name.unwrap_or(GraphName::DefaultGraph),
base_iri.as_deref(),
)
.map_err(to_err)
} else if let Some(dataset_format) = DatasetFormat::from_media_type(mime_type) {
if to_graph_name.is_some() {
return Err(format_err!(
"The target graph name parameter is not available for dataset formats"
));
}
self.store
.load_dataset(Cursor::new(data), dataset_format, base_iri.as_deref())
.map_err(to_err)
} else {
Err(format_err!("Not supported MIME type: {}", mime_type))
}
}
pub fn dump(&self, mime_type: &str, from_graph_name: &JsValue) -> Result<String, JsValue> {
let from_graph_name =
if let Some(graph_name) = self.from_js.to_optional_term(from_graph_name)? {
Some(graph_name.try_into()?)
} else {
None
};
let mut buffer = Vec::new();
if let Some(graph_format) = GraphFormat::from_media_type(mime_type) {
self.store
.dump_graph(
&mut buffer,
graph_format,
&from_graph_name.unwrap_or(GraphName::DefaultGraph),
)
.map_err(to_err)?;
} else if let Some(dataset_format) = DatasetFormat::from_media_type(mime_type) {
if from_graph_name.is_some() {
return Err(format_err!(
"The target graph name parameter is not available for dataset formats"
));
}
self.store
.dump_dataset(&mut buffer, dataset_format)
.map_err(to_err)?;
} else {
return Err(format_err!("Not supported MIME type: {}", mime_type));
}
String::from_utf8(buffer).map_err(to_err)
}
}

@ -1,16 +0,0 @@
use js_sys::Error;
use wasm_bindgen::JsValue;
#[macro_export]
macro_rules! format_err {
($msg:literal $(,)?) => {
::wasm_bindgen::JsValue::from(::js_sys::Error::new($msg))
};
($fmt:literal, $($arg:tt)*) => {
::wasm_bindgen::JsValue::from(::js_sys::Error::new(&format!($fmt, $($arg)*)))
};
}
pub fn to_err(e: impl ToString) -> JsValue {
JsValue::from(Error::new(&e.to_string()))
}

@ -1,2 +0,0 @@
const { MemoryStore } = require('../pkg/oxigraph.js')
require('../node_modules/@rdfjs/data-model/test/index.js')((new MemoryStore()).dataFactory)

@ -1,156 +0,0 @@
/* global describe, it */
const { MemoryStore } = require('../pkg/oxigraph.js')
const assert = require('assert')
const dataFactory = require('@rdfjs/data-model')
const ex = dataFactory.namedNode('http://example.com')
describe('MemoryStore', function () {
describe('#add()', function () {
it('an added quad should be in the store', function () {
const store = new MemoryStore()
store.add(dataFactory.triple(ex, ex, ex))
assert(store.has(dataFactory.triple(ex, ex, ex)))
})
})
describe('#delete()', function () {
it('an removed quad should not be in the store anymore', function () {
const store = new MemoryStore([dataFactory.triple(ex, ex, ex)])
assert(store.has(dataFactory.triple(ex, ex, ex)))
store.delete(dataFactory.triple(ex, ex, ex))
assert(!store.has(dataFactory.triple(ex, ex, ex)))
})
})
describe('#has()', function () {
it('an added quad should be in the store', function () {
const store = new MemoryStore([dataFactory.triple(ex, ex, ex)])
assert(store.has(dataFactory.triple(ex, ex, ex)))
})
})
describe('#size()', function () {
it('A store with one quad should have 1 for size', function () {
const store = new MemoryStore([dataFactory.triple(ex, ex, ex)])
assert.strictEqual(1, store.size)
})
})
describe('#match_quads()', function () {
it('blank pattern should return all quads', function () {
const store = new MemoryStore([dataFactory.triple(ex, ex, ex)])
const results = store.match()
assert.strictEqual(1, results.length)
assert(dataFactory.triple(ex, ex, ex).equals(results[0]))
})
})
describe('#query()', function () {
it('ASK true', function () {
const store = new MemoryStore([dataFactory.triple(ex, ex, ex)])
assert.strictEqual(true, store.query('ASK { ?s ?s ?s }'))
})
it('ASK false', function () {
const store = new MemoryStore()
assert.strictEqual(false, store.query('ASK { FILTER(false)}'))
})
it('CONSTRUCT', function () {
const store = new MemoryStore([dataFactory.triple(ex, ex, ex)])
const results = store.query('CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }')
assert.strictEqual(1, results.length)
assert(dataFactory.triple(ex, ex, ex).equals(results[0]))
})
it('SELECT', function () {
const store = new MemoryStore([dataFactory.triple(ex, ex, ex)])
const results = store.query('SELECT ?s WHERE { ?s ?p ?o }')
assert.strictEqual(1, results.length)
assert(ex.equals(results[0].get('s')))
})
it('SELECT with NOW()', function () {
const store = new MemoryStore([dataFactory.triple(ex, ex, ex)])
const results = store.query('SELECT (YEAR(NOW()) AS ?y) WHERE {}')
assert.strictEqual(1, results.length)
})
it('SELECT with RAND()', function () {
const store = new MemoryStore([dataFactory.triple(ex, ex, ex)])
const results = store.query('SELECT (RAND() AS ?y) WHERE {}')
assert.strictEqual(1, results.length)
})
})
describe('#update()', function () {
it('INSERT DATA', function () {
const store = new MemoryStore()
store.update('INSERT DATA { <http://example.com> <http://example.com> <http://example.com> }')
assert.strictEqual(1, store.size)
})
it('DELETE DATA', function () {
const store = new MemoryStore([dataFactory.triple(ex, ex, ex)])
store.update('DELETE DATA { <http://example.com> <http://example.com> <http://example.com> }')
assert.strictEqual(0, store.size)
})
it('DELETE WHERE', function () {
const store = new MemoryStore([dataFactory.triple(ex, ex, ex)])
store.update('DELETE WHERE { ?v ?v ?v }')
assert.strictEqual(0, store.size)
})
})
describe('#load()', function () {
it('load NTriples in the default graph', function () {
const store = new MemoryStore()
store.load('<http://example.com> <http://example.com> <http://example.com> .', 'application/n-triples')
assert(store.has(dataFactory.triple(ex, ex, ex)))
})
it('load NTriples in an other graph', function () {
const store = new MemoryStore()
store.load('<http://example.com> <http://example.com> <http://example.com> .', 'application/n-triples', null, ex)
assert(store.has(dataFactory.quad(ex, ex, ex, ex)))
})
it('load Turtle with a base IRI', function () {
const store = new MemoryStore()
store.load('<http://example.com> <http://example.com> <> .', 'text/turtle', 'http://example.com')
assert(store.has(dataFactory.triple(ex, ex, ex)))
})
it('load NQuads', function () {
const store = new MemoryStore()
store.load('<http://example.com> <http://example.com> <http://example.com> <http://example.com> .', 'application/n-quads')
assert(store.has(dataFactory.quad(ex, ex, ex, ex)))
})
it('load TriG with a base IRI', function () {
const store = new MemoryStore()
store.load('GRAPH <> { <http://example.com> <http://example.com> <> }', 'application/trig', 'http://example.com')
assert(store.has(dataFactory.quad(ex, ex, ex, ex)))
})
})
describe('#dump()', function () {
it('dump dataset content', function () {
const store = new MemoryStore([dataFactory.quad(ex, ex, ex, ex)])
assert.strictEqual('<http://example.com> <http://example.com> <http://example.com> <http://example.com> .\n', store.dump('application/n-quads'))
})
it('dump named graph content', function () {
const store = new MemoryStore([dataFactory.quad(ex, ex, ex, ex)])
assert.strictEqual('<http://example.com> <http://example.com> <http://example.com> .\n', store.dump('application/n-triples', ex))
})
it('dump default graph content', function () {
const store = new MemoryStore([dataFactory.quad(ex, ex, ex, ex)])
assert.strictEqual('', store.dump('application/n-triples'))
})
})
})

@ -45,18 +45,11 @@ http = "0.2"
httparse = { version = "1", optional = true }
native-tls = { version = "0.2", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
js-sys = "0.3"
getrandom = {version="0.2", features=["js"]}
[dev-dependencies]
rayon = "1"
criterion = "0.3"
sophia_api = { version = "0.6.2", features = ["test_macro"] }
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "0.3"
[[bench]]
name = "store"
harness = false

@ -10,6 +10,7 @@ use std::error::Error;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::str::FromStr;
use std::time::SystemTime;
use std::time::SystemTimeError;
/// [XML Schema `dateTime` datatype](https://www.w3.org/TR/xmlschema11-2/#dateTime) implementation.
@ -1341,18 +1342,7 @@ impl Timestamp {
}
}
#[cfg(target_arch = "wasm32")]
fn since_unix_epoch() -> Result<Duration, DateTimeError> {
Ok(Duration::new(
0,
Decimal::from_f64(js_sys::Date::now() / 1000.),
))
}
#[cfg(not(target_arch = "wasm32"))]
fn since_unix_epoch() -> Result<Duration, DateTimeError> {
use std::time::SystemTime;
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)?
.try_into()

Loading…
Cancel
Save