diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 32ba3478..7776ad41 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ce54e508..fd7e1b05 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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: diff --git a/.gitignore b/.gitignore index 7f29effb..7981b97f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,5 @@ Cargo.lock .idea *.iml -js/node_modules -lib/tests/rockdb_bc_data lib/tests/sled_bc_data venv \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 128242e2..62e4bb84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,5 @@ [workspace] members = [ - "js", "lib", "python", "server", diff --git a/README.md b/README.md index 42c53ca0..20189dd0 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/js/Cargo.toml b/js/Cargo.toml deleted file mode 100644 index 058419a6..00000000 --- a/js/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "oxigraph_js" -version = "0.2.3" -authors = ["Tpt "] -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" diff --git a/js/README.md b/js/README.md deleted file mode 100644 index 0269d8a2..00000000 --- a/js/README.md +++ /dev/null @@ -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 ` "example"` and log the name of `` 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 { ?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? 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 { ?p ?o } WHERE { ?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 { ?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 `` with the base IRI `http://example.com`: -```js -store.load(" <> .", "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 ``: -```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. diff --git a/js/package.json b/js/package.json deleted file mode 100644 index bf373b18..00000000 --- a/js/package.json +++ /dev/null @@ -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" - } -} diff --git a/js/src/lib.rs b/js/src/lib.rs deleted file mode 100644 index 9005e09e..00000000 --- a/js/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod model; -mod store; -mod utils; diff --git a/js/src/model.rs b/js/src/model.rs deleted file mode 100644 index 7c690550..00000000 --- a/js/src/model.rs +++ /dev/null @@ -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 { - 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) -> Result { - 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, - language_or_datatype: &JsValue, - ) -> Result { - 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 { - 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 { - 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 { - Ok(self.from_js.to_term(original)?.into()) - } - - #[wasm_bindgen(js_name = fromQuad)] - pub fn convert_quad(&self, original: &JsValue) -> Result { - 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 for JsNamedNode { - fn from(inner: NamedNode) -> Self { - Self { inner } - } -} - -impl From for NamedNode { - fn from(node: JsNamedNode) -> Self { - node.inner - } -} - -impl From for NamedOrBlankNode { - fn from(node: JsNamedNode) -> Self { - node.inner.into() - } -} - -impl From for Term { - fn from(node: JsNamedNode) -> Self { - node.inner.into() - } -} - -impl From 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 for JsBlankNode { - fn from(inner: BlankNode) -> Self { - Self { inner } - } -} - -impl From for BlankNode { - fn from(node: JsBlankNode) -> Self { - node.inner - } -} - -impl From for NamedOrBlankNode { - fn from(node: JsBlankNode) -> Self { - node.inner.into() - } -} - -impl From for Term { - fn from(node: JsBlankNode) -> Self { - node.inner.into() - } -} - -impl From 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 for JsLiteral { - fn from(inner: Literal) -> Self { - Self { inner } - } -} - -impl From for Literal { - fn from(node: JsLiteral) -> Self { - node.inner - } -} - -impl From 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 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 for JsTerm { - fn from(node: NamedNode) -> Self { - JsTerm::NamedNode(node.into()) - } -} - -impl From for JsTerm { - fn from(node: BlankNode) -> Self { - JsTerm::BlankNode(node.into()) - } -} - -impl From for JsTerm { - fn from(literal: Literal) -> Self { - JsTerm::Literal(literal.into()) - } -} - -impl From for JsTerm { - fn from(node: NamedOrBlankNode) -> Self { - match node { - NamedOrBlankNode::NamedNode(node) => node.into(), - NamedOrBlankNode::BlankNode(node) => node.into(), - } - } -} - -impl From 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 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 for NamedNode { - type Error = JsValue; - - fn try_from(value: JsTerm) -> Result { - 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 for NamedOrBlankNode { - type Error = JsValue; - - fn try_from(value: JsTerm) -> Result { - 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 for Term { - type Error = JsValue; - - fn try_from(value: JsTerm) -> Result { - 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 for GraphName { - type Error = JsValue; - - fn try_from(value: JsTerm) -> Result { - 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 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 for Quad { - type Error = JsValue; - - fn try_from(quad: JsQuad) -> Result { - 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 { - 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, 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 { - 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)?)?, - }) - } -} diff --git a/js/src/store.rs b/js/src/store.rs deleted file mode 100644 index 952aa423..00000000 --- a/js/src/store.rs +++ /dev/null @@ -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>) -> Result { - 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 { - 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, 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::>() - .into_boxed_slice()) - } - - pub fn query(&self, query: &str) -> Result { - 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 { - 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) - } -} diff --git a/js/src/utils.rs b/js/src/utils.rs deleted file mode 100644 index a362c31d..00000000 --- a/js/src/utils.rs +++ /dev/null @@ -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())) -} diff --git a/js/test/model.js b/js/test/model.js deleted file mode 100644 index 084698b8..00000000 --- a/js/test/model.js +++ /dev/null @@ -1,2 +0,0 @@ -const { MemoryStore } = require('../pkg/oxigraph.js') -require('../node_modules/@rdfjs/data-model/test/index.js')((new MemoryStore()).dataFactory) diff --git a/js/test/store.js b/js/test/store.js deleted file mode 100644 index 9bfe13fc..00000000 --- a/js/test/store.js +++ /dev/null @@ -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 { }') - assert.strictEqual(1, store.size) - }) - - it('DELETE DATA', function () { - const store = new MemoryStore([dataFactory.triple(ex, ex, ex)]) - store.update('DELETE DATA { }') - 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(' .', '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(' .', '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(' <> .', 'text/turtle', 'http://example.com') - assert(store.has(dataFactory.triple(ex, ex, ex))) - }) - - it('load NQuads', function () { - const store = new MemoryStore() - store.load(' .', '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 <> { <> }', '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(' .\n', store.dump('application/n-quads')) - }) - - it('dump named graph content', function () { - const store = new MemoryStore([dataFactory.quad(ex, ex, ex, ex)]) - assert.strictEqual(' .\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')) - }) - }) -}) diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 1c21e881..dcbdfabd 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -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 diff --git a/lib/src/model/xsd/date_time.rs b/lib/src/model/xsd/date_time.rs index 90f04873..9bb8a285 100644 --- a/lib/src/model/xsd/date_time.rs +++ b/lib/src/model/xsd/date_time.rs @@ -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 { - Ok(Duration::new( - 0, - Decimal::from_f64(js_sys::Date::now() / 1000.), - )) -} - -#[cfg(not(target_arch = "wasm32"))] -fn since_unix_epoch() -> Result { - use std::time::SystemTime; - SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH)? .try_into()