git-subtree-dir: oxigraph git-subtree-split: c5e23fb0dd1a6b6cc36a4d021276df46f55bc6e4pull/19/head
commit
c20417c18c
@ -0,0 +1,59 @@ |
||||
[package] |
||||
name = "oxigraph" |
||||
version.workspace = true |
||||
authors.workspace = true |
||||
license.workspace = true |
||||
readme = "README.md" |
||||
keywords = ["RDF", "SPARQL", "graph-database", "database"] |
||||
categories = ["database-implementations"] |
||||
repository = "https://github.com/oxigraph/oxigraph/tree/main/lib/oxigraph" |
||||
homepage = "https://oxigraph.org/" |
||||
documentation = "https://docs.rs/oxigraph" |
||||
description = """ |
||||
a SPARQL database and RDF toolkit |
||||
""" |
||||
edition.workspace = true |
||||
rust-version.workspace = true |
||||
|
||||
[features] |
||||
js = ["getrandom/js", "oxsdatatypes/js", "js-sys"] |
||||
|
||||
|
||||
[dependencies] |
||||
digest.workspace = true |
||||
hex.workspace = true |
||||
json-event-parser.workspace = true |
||||
md-5.workspace = true |
||||
oxilangtag.workspace = true |
||||
oxiri.workspace = true |
||||
oxrdf = { workspace = true, features = ["rdf-star", "oxsdatatypes"] } |
||||
oxrdfio = { workspace = true, features = ["rdf-star"] } |
||||
oxsdatatypes.workspace = true |
||||
rand.workspace = true |
||||
regex.workspace = true |
||||
sha1.workspace = true |
||||
sha2.workspace = true |
||||
siphasher.workspace = true |
||||
sparesults = { workspace = true, features = ["rdf-star"] } |
||||
spargebra = { workspace = true, features = ["rdf-star", "sep-0002", "sep-0006"] } |
||||
sparopt = { workspace = true, features = ["rdf-star", "sep-0002", "sep-0006"] } |
||||
thiserror.workspace = true |
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies] |
||||
libc = "0.2" |
||||
rocksdb.workspace = true |
||||
|
||||
[target.'cfg(all(target_family = "wasm", target_os = "unknown"))'.dependencies] |
||||
getrandom.workspace = true |
||||
js-sys = { workspace = true, optional = true } |
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies] |
||||
codspeed-criterion-compat.workspace = true |
||||
zstd.workspace = true |
||||
|
||||
[lints] |
||||
workspace = true |
||||
|
||||
[package.metadata.docs.rs] |
||||
rustdoc-args = ["--cfg", "docsrs"] |
||||
|
@ -0,0 +1,82 @@ |
||||
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) |
||||
[![Crates.io downloads](https://img.shields.io/crates/d/oxigraph)](https://crates.io/crates/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) |
||||
|
||||
Oxigraph is a graph database library implementing the [SPARQL](https://www.w3.org/TR/sparql11-overview/) standard. |
||||
|
||||
Its goal is to provide a compliant, safe and fast on-disk graph database. |
||||
It also provides a set of utility functions for reading, writing, and processing RDF files. |
||||
|
||||
Oxigraph is in heavy development and SPARQL query evaluation has not been optimized yet. |
||||
|
||||
Oxigraph also provides [a CLI tool](https://crates.io/crates/oxigraph-cli) and [a Python library](https://pyoxigraph.readthedocs.io/) based on this library. |
||||
|
||||
|
||||
Oxigraph implements the following specifications: |
||||
* [SPARQL 1.1 Query](https://www.w3.org/TR/sparql11-query/), [SPARQL 1.1 Update](https://www.w3.org/TR/sparql11-update/), and [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/). |
||||
* [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/) RDF serialization formats for both data ingestion and retrieval. |
||||
* [SPARQL Query Results XML Format](https://www.w3.org/TR/rdf-sparql-XMLres/), [SPARQL 1.1 Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/) and [SPARQL 1.1 Query Results CSV and TSV Formats](https://www.w3.org/TR/sparql11-results-csv-tsv/). |
||||
|
||||
A preliminary benchmark [is provided](../bench/README.md). Oxigraph internal design [is described on the wiki](https://github.com/oxigraph/oxigraph/wiki/Architecture). |
||||
|
||||
The main entry point of Oxigraph is the [`Store`](store::Store) struct: |
||||
```rust |
||||
use oxigraph::store::Store; |
||||
use oxigraph::model::*; |
||||
use oxigraph::sparql::QueryResults; |
||||
|
||||
let store = Store::new().unwrap(); |
||||
|
||||
// insertion |
||||
let ex = NamedNode::new("http://example.com").unwrap(); |
||||
let quad = Quad::new(ex.clone(), ex.clone(), ex.clone(), GraphName::DefaultGraph); |
||||
store.insert(&quad).unwrap(); |
||||
|
||||
// quad filter |
||||
let results = store.quads_for_pattern(Some(ex.as_ref().into()), None, None, None).collect::<Result<Vec<Quad>,_>>().unwrap(); |
||||
assert_eq!(vec![quad], results); |
||||
|
||||
// SPARQL query |
||||
if let QueryResults::Solutions(mut solutions) = store.query("SELECT ?s WHERE { ?s ?p ?o }").unwrap() { |
||||
assert_eq!(solutions.next().unwrap().unwrap().get("s"), Some(&ex.into())); |
||||
} |
||||
``` |
||||
|
||||
It is based on these crates that can be used separately: |
||||
* [`oxrdf`](https://crates.io/crates/oxrdf), datastructures encoding RDF basic concepts (the [`oxigraph::model`](crate::model) module). |
||||
* [`oxrdfio`](https://crates.io/crates/oxrdfio), a unified parser and serializer API for RDF formats (the [`oxigraph::io`](crate::io) module). It itself relies on: |
||||
* [`oxttl`](https://crates.io/crates/oxttl), N-Triple, N-Quad, Turtle, TriG and N3 parsing and serialization. |
||||
* [`oxrdfxml`](https://crates.io/crates/oxrdfxml), RDF/XML parsing and serialization. |
||||
* [`spargebra`](https://crates.io/crates/spargebra), a SPARQL parser. |
||||
* [`sparesults`](https://crates.io/crates/sparesults), parsers and serializers for SPARQL result formats (the [`oxigraph::sparql::results`](crate::sparql::results) module). |
||||
* [`sparopt`](https://crates.io/crates/sparesults), a SPARQL optimizer. |
||||
* [`oxsdatatypes`](https://crates.io/crates/oxsdatatypes), an implementation of some XML Schema datatypes. |
||||
|
||||
To build the library locally, don't forget to clone the submodules using `git clone --recursive https://github.com/oxigraph/oxigraph.git` to clone the repository including submodules or `git submodule update --init` to add submodules to the already cloned repository. |
||||
|
||||
It is possible to disable the RocksDB storage backend to only use the in-memory fallback by disabling the `rocksdb` default feature: |
||||
```toml |
||||
oxigraph = { version = "*", default-features = false } |
||||
``` |
||||
This is the default behavior when compiling Oxigraph to WASM. |
||||
|
||||
## 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 Oxigraph by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. |
@ -0,0 +1,301 @@ |
||||
#![allow(deprecated)] |
||||
|
||||
use oxrdfio::{RdfFormat, RdfParser, RdfSerializer}; |
||||
|
||||
/// [RDF graph](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-graph) serialization formats.
|
||||
///
|
||||
/// This enumeration is non exhaustive. New formats like JSON-LD will be added in the future.
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)] |
||||
#[non_exhaustive] |
||||
#[deprecated(note = "use RdfFormat instead", since = "0.4.0")] |
||||
pub enum GraphFormat { |
||||
/// [N-Triples](https://www.w3.org/TR/n-triples/)
|
||||
NTriples, |
||||
/// [Turtle](https://www.w3.org/TR/turtle/)
|
||||
Turtle, |
||||
/// [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/)
|
||||
RdfXml, |
||||
} |
||||
|
||||
impl GraphFormat { |
||||
/// The format canonical IRI according to the [Unique URIs for file formats registry](https://www.w3.org/ns/formats/).
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::GraphFormat;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// GraphFormat::NTriples.iri(),
|
||||
/// "http://www.w3.org/ns/formats/N-Triples"
|
||||
/// )
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn iri(self) -> &'static str { |
||||
match self { |
||||
Self::NTriples => "http://www.w3.org/ns/formats/N-Triples", |
||||
Self::Turtle => "http://www.w3.org/ns/formats/Turtle", |
||||
Self::RdfXml => "http://www.w3.org/ns/formats/RDF_XML", |
||||
} |
||||
} |
||||
|
||||
/// The format [IANA media type](https://tools.ietf.org/html/rfc2046).
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::GraphFormat;
|
||||
///
|
||||
/// assert_eq!(GraphFormat::NTriples.media_type(), "application/n-triples")
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn media_type(self) -> &'static str { |
||||
match self { |
||||
Self::NTriples => "application/n-triples", |
||||
Self::Turtle => "text/turtle", |
||||
Self::RdfXml => "application/rdf+xml", |
||||
} |
||||
} |
||||
|
||||
/// The format [IANA-registered](https://tools.ietf.org/html/rfc2046) file extension.
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::GraphFormat;
|
||||
///
|
||||
/// assert_eq!(GraphFormat::NTriples.file_extension(), "nt")
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn file_extension(self) -> &'static str { |
||||
match self { |
||||
Self::NTriples => "nt", |
||||
Self::Turtle => "ttl", |
||||
Self::RdfXml => "rdf", |
||||
} |
||||
} |
||||
|
||||
/// Looks for a known format from a media type.
|
||||
///
|
||||
/// It supports some media type aliases.
|
||||
/// For example, "application/xml" is going to return `GraphFormat::RdfXml` even if it is not its canonical media type.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// use oxigraph::io::GraphFormat;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// GraphFormat::from_media_type("text/turtle; charset=utf-8"),
|
||||
/// Some(GraphFormat::Turtle)
|
||||
/// )
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn from_media_type(media_type: &str) -> Option<Self> { |
||||
match media_type.split(';').next()?.trim() { |
||||
"application/n-triples" | "text/plain" => Some(Self::NTriples), |
||||
"text/turtle" | "application/turtle" | "application/x-turtle" => Some(Self::Turtle), |
||||
"application/rdf+xml" | "application/xml" | "text/xml" => Some(Self::RdfXml), |
||||
_ => None, |
||||
} |
||||
} |
||||
|
||||
/// Looks for a known format from an extension.
|
||||
///
|
||||
/// It supports some aliases.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// use oxigraph::io::GraphFormat;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// GraphFormat::from_extension("nt"),
|
||||
/// Some(GraphFormat::NTriples)
|
||||
/// )
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn from_extension(extension: &str) -> Option<Self> { |
||||
match extension { |
||||
"nt" | "txt" => Some(Self::NTriples), |
||||
"ttl" => Some(Self::Turtle), |
||||
"rdf" | "xml" => Some(Self::RdfXml), |
||||
_ => None, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<GraphFormat> for RdfFormat { |
||||
#[inline] |
||||
fn from(format: GraphFormat) -> Self { |
||||
match format { |
||||
GraphFormat::NTriples => Self::NTriples, |
||||
GraphFormat::Turtle => Self::Turtle, |
||||
GraphFormat::RdfXml => Self::RdfXml, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<GraphFormat> for RdfParser { |
||||
#[inline] |
||||
fn from(format: GraphFormat) -> Self { |
||||
RdfFormat::from(format).into() |
||||
} |
||||
} |
||||
|
||||
impl From<GraphFormat> for RdfSerializer { |
||||
#[inline] |
||||
fn from(format: GraphFormat) -> Self { |
||||
RdfFormat::from(format).into() |
||||
} |
||||
} |
||||
|
||||
/// [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset) serialization formats.
|
||||
///
|
||||
/// This enumeration is non exhaustive. New formats like JSON-LD will be added in the future.
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)] |
||||
#[non_exhaustive] |
||||
#[deprecated(note = "use RdfFormat instead", since = "0.4.0")] |
||||
pub enum DatasetFormat { |
||||
/// [N-Quads](https://www.w3.org/TR/n-quads/)
|
||||
NQuads, |
||||
/// [TriG](https://www.w3.org/TR/trig/)
|
||||
TriG, |
||||
} |
||||
|
||||
impl DatasetFormat { |
||||
/// The format canonical IRI according to the [Unique URIs for file formats registry](https://www.w3.org/ns/formats/).
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::DatasetFormat;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// DatasetFormat::NQuads.iri(),
|
||||
/// "http://www.w3.org/ns/formats/N-Quads"
|
||||
/// )
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn iri(self) -> &'static str { |
||||
match self { |
||||
Self::NQuads => "http://www.w3.org/ns/formats/N-Quads", |
||||
Self::TriG => "http://www.w3.org/ns/formats/TriG", |
||||
} |
||||
} |
||||
|
||||
/// The format [IANA media type](https://tools.ietf.org/html/rfc2046).
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::DatasetFormat;
|
||||
///
|
||||
/// assert_eq!(DatasetFormat::NQuads.media_type(), "application/n-quads")
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn media_type(self) -> &'static str { |
||||
match self { |
||||
Self::NQuads => "application/n-quads", |
||||
Self::TriG => "application/trig", |
||||
} |
||||
} |
||||
|
||||
/// The format [IANA-registered](https://tools.ietf.org/html/rfc2046) file extension.
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::DatasetFormat;
|
||||
///
|
||||
/// assert_eq!(DatasetFormat::NQuads.file_extension(), "nq")
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn file_extension(self) -> &'static str { |
||||
match self { |
||||
Self::NQuads => "nq", |
||||
Self::TriG => "trig", |
||||
} |
||||
} |
||||
|
||||
/// Looks for a known format from a media type.
|
||||
///
|
||||
/// It supports some media type aliases.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// use oxigraph::io::DatasetFormat;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// DatasetFormat::from_media_type("application/n-quads; charset=utf-8"),
|
||||
/// Some(DatasetFormat::NQuads)
|
||||
/// )
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn from_media_type(media_type: &str) -> Option<Self> { |
||||
match media_type.split(';').next()?.trim() { |
||||
"application/n-quads" | "text/x-nquads" | "text/nquads" => Some(Self::NQuads), |
||||
"application/trig" | "application/x-trig" => Some(Self::TriG), |
||||
_ => None, |
||||
} |
||||
} |
||||
|
||||
/// Looks for a known format from an extension.
|
||||
///
|
||||
/// It supports some aliases.
|
||||
///
|
||||
/// Example:
|
||||
/// ```
|
||||
/// use oxigraph::io::DatasetFormat;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// DatasetFormat::from_extension("nq"),
|
||||
/// Some(DatasetFormat::NQuads)
|
||||
/// )
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn from_extension(extension: &str) -> Option<Self> { |
||||
match extension { |
||||
"nq" | "txt" => Some(Self::NQuads), |
||||
"trig" => Some(Self::TriG), |
||||
_ => None, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<DatasetFormat> for RdfFormat { |
||||
#[inline] |
||||
fn from(format: DatasetFormat) -> Self { |
||||
match format { |
||||
DatasetFormat::NQuads => Self::NQuads, |
||||
DatasetFormat::TriG => Self::TriG, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<DatasetFormat> for RdfParser { |
||||
#[inline] |
||||
fn from(format: DatasetFormat) -> Self { |
||||
RdfFormat::from(format).into() |
||||
} |
||||
} |
||||
|
||||
impl From<DatasetFormat> for RdfSerializer { |
||||
#[inline] |
||||
fn from(format: DatasetFormat) -> Self { |
||||
RdfFormat::from(format).into() |
||||
} |
||||
} |
||||
|
||||
impl TryFrom<DatasetFormat> for GraphFormat { |
||||
type Error = (); |
||||
|
||||
/// Attempts to find a graph format that is a subset of this [`DatasetFormat`].
|
||||
#[inline] |
||||
fn try_from(value: DatasetFormat) -> Result<Self, Self::Error> { |
||||
match value { |
||||
DatasetFormat::NQuads => Ok(Self::NTriples), |
||||
DatasetFormat::TriG => Ok(Self::Turtle), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl TryFrom<GraphFormat> for DatasetFormat { |
||||
type Error = (); |
||||
|
||||
/// Attempts to find a dataset format that is a superset of this [`GraphFormat`].
|
||||
#[inline] |
||||
fn try_from(value: GraphFormat) -> Result<Self, Self::Error> { |
||||
match value { |
||||
GraphFormat::NTriples => Ok(Self::NQuads), |
||||
GraphFormat::Turtle => Ok(Self::TriG), |
||||
GraphFormat::RdfXml => Err(()), |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,39 @@ |
||||
//! Utilities to read and write RDF graphs and datasets using [OxRDF I/O](https://crates.io/crates/oxrdfio).
|
||||
//!
|
||||
//! The entry points of this module are the two [`RdfParser`] and [`RdfSerializer`] structs.
|
||||
//!
|
||||
//! Usage example converting a Turtle file to a N-Triples file:
|
||||
//! ```
|
||||
//! use oxigraph::io::{RdfFormat, RdfParser, RdfSerializer};
|
||||
//!
|
||||
//! let turtle_file = b"@base <http://example.com/> .
|
||||
//! @prefix schema: <http://schema.org/> .
|
||||
//! <foo> a schema:Person ;
|
||||
//! schema:name \"Foo\" .
|
||||
//! <bar> a schema:Person ;
|
||||
//! schema:name \"Bar\" .";
|
||||
//!
|
||||
//! let ntriples_file = b"<http://example.com/foo> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
|
||||
//! <http://example.com/foo> <http://schema.org/name> \"Foo\" .
|
||||
//! <http://example.com/bar> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://schema.org/Person> .
|
||||
//! <http://example.com/bar> <http://schema.org/name> \"Bar\" .
|
||||
//! ";
|
||||
//!
|
||||
//! let mut writer = RdfSerializer::from_format(RdfFormat::NTriples).serialize_to_write(Vec::new());
|
||||
//! for quad in RdfParser::from_format(RdfFormat::Turtle).parse_read(turtle_file.as_ref()) {
|
||||
//! writer.write_quad(&quad.unwrap()).unwrap();
|
||||
//! }
|
||||
//! assert_eq!(writer.finish().unwrap(), ntriples_file);
|
||||
//! ```
|
||||
|
||||
mod format; |
||||
pub mod read; |
||||
pub mod write; |
||||
|
||||
#[allow(deprecated)] |
||||
pub use self::format::{DatasetFormat, GraphFormat}; |
||||
#[allow(deprecated)] |
||||
pub use self::read::{DatasetParser, GraphParser}; |
||||
#[allow(deprecated)] |
||||
pub use self::write::{DatasetSerializer, GraphSerializer}; |
||||
pub use oxrdfio::*; |
@ -0,0 +1,199 @@ |
||||
#![allow(deprecated)] |
||||
|
||||
//! Utilities to read RDF graphs and datasets.
|
||||
|
||||
use crate::io::{DatasetFormat, GraphFormat}; |
||||
use crate::model::*; |
||||
use oxrdfio::{FromReadQuadReader, RdfParseError, RdfParser}; |
||||
use std::io::Read; |
||||
|
||||
/// Parsers for RDF graph serialization formats.
|
||||
///
|
||||
/// It currently supports the following formats:
|
||||
/// * [N-Triples](https://www.w3.org/TR/n-triples/) ([`GraphFormat::NTriples`])
|
||||
/// * [Turtle](https://www.w3.org/TR/turtle/) ([`GraphFormat::Turtle`])
|
||||
/// * [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/) ([`GraphFormat::RdfXml`])
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::{GraphFormat, GraphParser};
|
||||
///
|
||||
/// let file = "<http://example.com/s> <http://example.com/p> <http://example.com/o> .";
|
||||
///
|
||||
/// let parser = GraphParser::from_format(GraphFormat::NTriples);
|
||||
/// let triples = parser
|
||||
/// .read_triples(file.as_bytes())
|
||||
/// .collect::<Result<Vec<_>, _>>()?;
|
||||
///
|
||||
/// assert_eq!(triples.len(), 1);
|
||||
/// assert_eq!(triples[0].subject.to_string(), "<http://example.com/s>");
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
#[deprecated(note = "use RdfParser instead", since = "0.4.0")] |
||||
pub struct GraphParser { |
||||
inner: RdfParser, |
||||
} |
||||
|
||||
impl GraphParser { |
||||
/// Builds a parser for the given format.
|
||||
#[inline] |
||||
pub fn from_format(format: GraphFormat) -> Self { |
||||
Self { |
||||
inner: RdfParser::from_format(format.into()) |
||||
.without_named_graphs() |
||||
.rename_blank_nodes(), |
||||
} |
||||
} |
||||
|
||||
/// Provides an IRI that could be used to resolve the file relative IRIs.
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::{GraphFormat, GraphParser};
|
||||
///
|
||||
/// let file = "</s> </p> </o> .";
|
||||
///
|
||||
/// let parser =
|
||||
/// GraphParser::from_format(GraphFormat::Turtle).with_base_iri("http://example.com")?;
|
||||
/// let triples = parser
|
||||
/// .read_triples(file.as_bytes())
|
||||
/// .collect::<Result<Vec<_>, _>>()?;
|
||||
///
|
||||
/// assert_eq!(triples.len(), 1);
|
||||
/// assert_eq!(triples[0].subject.to_string(), "<http://example.com/s>");
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn with_base_iri(self, base_iri: impl Into<String>) -> Result<Self, IriParseError> { |
||||
Ok(Self { |
||||
inner: self.inner.with_base_iri(base_iri)?, |
||||
}) |
||||
} |
||||
|
||||
/// Executes the parsing itself on a [`Read`] implementation and returns an iterator of triples.
|
||||
pub fn read_triples<R: Read>(self, reader: R) -> TripleReader<R> { |
||||
TripleReader { |
||||
parser: self.inner.parse_read(reader), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// An iterator yielding read triples.
|
||||
/// Could be built using a [`GraphParser`].
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::{GraphFormat, GraphParser};
|
||||
///
|
||||
/// let file = "<http://example.com/s> <http://example.com/p> <http://example.com/o> .";
|
||||
///
|
||||
/// let parser = GraphParser::from_format(GraphFormat::NTriples);
|
||||
/// let triples = parser
|
||||
/// .read_triples(file.as_bytes())
|
||||
/// .collect::<Result<Vec<_>, _>>()?;
|
||||
///
|
||||
/// assert_eq!(triples.len(), 1);
|
||||
/// assert_eq!(triples[0].subject.to_string(), "<http://example.com/s>");
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
#[must_use] |
||||
pub struct TripleReader<R: Read> { |
||||
parser: FromReadQuadReader<R>, |
||||
} |
||||
|
||||
impl<R: Read> Iterator for TripleReader<R> { |
||||
type Item = Result<Triple, RdfParseError>; |
||||
|
||||
fn next(&mut self) -> Option<Self::Item> { |
||||
Some(self.parser.next()?.map(Into::into).map_err(Into::into)) |
||||
} |
||||
} |
||||
|
||||
/// A parser for RDF dataset serialization formats.
|
||||
///
|
||||
/// It currently supports the following formats:
|
||||
/// * [N-Quads](https://www.w3.org/TR/n-quads/) ([`DatasetFormat::NQuads`])
|
||||
/// * [TriG](https://www.w3.org/TR/trig/) ([`DatasetFormat::TriG`])
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::{DatasetFormat, DatasetParser};
|
||||
///
|
||||
/// let file = "<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .";
|
||||
///
|
||||
/// let parser = DatasetParser::from_format(DatasetFormat::NQuads);
|
||||
/// let quads = parser.read_quads(file.as_bytes()).collect::<Result<Vec<_>,_>>()?;
|
||||
///
|
||||
/// assert_eq!(quads.len(), 1);
|
||||
/// assert_eq!(quads[0].subject.to_string(), "<http://example.com/s>");
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
#[deprecated(note = "use RdfParser instead", since = "0.4.0")] |
||||
pub struct DatasetParser { |
||||
inner: RdfParser, |
||||
} |
||||
|
||||
impl DatasetParser { |
||||
/// Builds a parser for the given format.
|
||||
#[inline] |
||||
pub fn from_format(format: DatasetFormat) -> Self { |
||||
Self { |
||||
inner: RdfParser::from_format(format.into()).rename_blank_nodes(), |
||||
} |
||||
} |
||||
|
||||
/// Provides an IRI that could be used to resolve the file relative IRIs.
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::{DatasetFormat, DatasetParser};
|
||||
///
|
||||
/// let file = "<g> { </s> </p> </o> }";
|
||||
///
|
||||
/// let parser =
|
||||
/// DatasetParser::from_format(DatasetFormat::TriG).with_base_iri("http://example.com")?;
|
||||
/// let triples = parser
|
||||
/// .read_quads(file.as_bytes())
|
||||
/// .collect::<Result<Vec<_>, _>>()?;
|
||||
///
|
||||
/// assert_eq!(triples.len(), 1);
|
||||
/// assert_eq!(triples[0].subject.to_string(), "<http://example.com/s>");
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn with_base_iri(self, base_iri: impl Into<String>) -> Result<Self, IriParseError> { |
||||
Ok(Self { |
||||
inner: self.inner.with_base_iri(base_iri)?, |
||||
}) |
||||
} |
||||
|
||||
/// Executes the parsing itself on a [`Read`] implementation and returns an iterator of quads.
|
||||
pub fn read_quads<R: Read>(self, reader: R) -> QuadReader<R> { |
||||
QuadReader { |
||||
parser: self.inner.parse_read(reader), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// An iterator yielding read quads.
|
||||
/// Could be built using a [`DatasetParser`].
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::{DatasetFormat, DatasetParser};
|
||||
///
|
||||
/// let file = "<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .";
|
||||
///
|
||||
/// let parser = DatasetParser::from_format(DatasetFormat::NQuads);
|
||||
/// let quads = parser.read_quads(file.as_bytes()).collect::<Result<Vec<_>,_>>()?;
|
||||
///
|
||||
/// assert_eq!(quads.len(), 1);
|
||||
/// assert_eq!(quads[0].subject.to_string(), "<http://example.com/s>");
|
||||
/// # std::io::Result::Ok(())
|
||||
/// ```
|
||||
#[must_use] |
||||
pub struct QuadReader<R: Read> { |
||||
parser: FromReadQuadReader<R>, |
||||
} |
||||
|
||||
impl<R: Read> Iterator for QuadReader<R> { |
||||
type Item = Result<Quad, RdfParseError>; |
||||
|
||||
fn next(&mut self) -> Option<Self::Item> { |
||||
Some(self.parser.next()?.map_err(Into::into)) |
||||
} |
||||
} |
@ -0,0 +1,185 @@ |
||||
#![allow(deprecated)] |
||||
|
||||
//! Utilities to write RDF graphs and datasets.
|
||||
|
||||
use crate::io::{DatasetFormat, GraphFormat}; |
||||
use crate::model::*; |
||||
use oxrdfio::{RdfSerializer, ToWriteQuadWriter}; |
||||
use std::io::{self, Write}; |
||||
|
||||
/// A serializer for RDF graph serialization formats.
|
||||
///
|
||||
/// It currently supports the following formats:
|
||||
/// * [N-Triples](https://www.w3.org/TR/n-triples/) ([`GraphFormat::NTriples`])
|
||||
/// * [Turtle](https://www.w3.org/TR/turtle/) ([`GraphFormat::Turtle`])
|
||||
/// * [RDF/XML](https://www.w3.org/TR/rdf-syntax-grammar/) ([`GraphFormat::RdfXml`])
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::{GraphFormat, GraphSerializer};
|
||||
/// use oxigraph::model::*;
|
||||
///
|
||||
/// let mut buffer = Vec::new();
|
||||
/// let mut writer = GraphSerializer::from_format(GraphFormat::NTriples).triple_writer(&mut buffer);
|
||||
/// writer.write(&Triple {
|
||||
/// subject: NamedNode::new("http://example.com/s")?.into(),
|
||||
/// predicate: NamedNode::new("http://example.com/p")?,
|
||||
/// object: NamedNode::new("http://example.com/o")?.into(),
|
||||
/// })?;
|
||||
/// writer.finish()?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// buffer.as_slice(),
|
||||
/// "<http://example.com/s> <http://example.com/p> <http://example.com/o> .\n".as_bytes()
|
||||
/// );
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
#[deprecated(note = "use RdfSerializer instead", since = "0.4.0")] |
||||
pub struct GraphSerializer { |
||||
inner: RdfSerializer, |
||||
} |
||||
|
||||
impl GraphSerializer { |
||||
/// Builds a serializer for the given format
|
||||
#[inline] |
||||
pub fn from_format(format: GraphFormat) -> Self { |
||||
Self { |
||||
inner: RdfSerializer::from_format(format.into()), |
||||
} |
||||
} |
||||
|
||||
/// Returns a [`TripleWriter`] allowing writing triples into the given [`Write`] implementation
|
||||
pub fn triple_writer<W: Write>(self, write: W) -> TripleWriter<W> { |
||||
TripleWriter { |
||||
writer: self.inner.serialize_to_write(write), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Allows writing triples.
|
||||
/// Could be built using a [`GraphSerializer`].
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// Do not forget to run the [`finish`](TripleWriter::finish()) method to properly write the last bytes of the file.</div>
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::{GraphFormat, GraphSerializer};
|
||||
/// use oxigraph::model::*;
|
||||
///
|
||||
/// let mut buffer = Vec::new();
|
||||
/// let mut writer = GraphSerializer::from_format(GraphFormat::NTriples).triple_writer(&mut buffer);
|
||||
/// writer.write(&Triple {
|
||||
/// subject: NamedNode::new("http://example.com/s")?.into(),
|
||||
/// predicate: NamedNode::new("http://example.com/p")?,
|
||||
/// object: NamedNode::new("http://example.com/o")?.into(),
|
||||
/// })?;
|
||||
/// writer.finish()?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// buffer.as_slice(),
|
||||
/// "<http://example.com/s> <http://example.com/p> <http://example.com/o> .\n".as_bytes()
|
||||
/// );
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
#[must_use] |
||||
pub struct TripleWriter<W: Write> { |
||||
writer: ToWriteQuadWriter<W>, |
||||
} |
||||
|
||||
impl<W: Write> TripleWriter<W> { |
||||
/// Writes a triple
|
||||
pub fn write<'a>(&mut self, triple: impl Into<TripleRef<'a>>) -> io::Result<()> { |
||||
self.writer.write_triple(triple) |
||||
} |
||||
|
||||
/// Writes the last bytes of the file
|
||||
pub fn finish(self) -> io::Result<()> { |
||||
self.writer.finish()?.flush() |
||||
} |
||||
} |
||||
|
||||
/// A serializer for RDF graph serialization formats.
|
||||
///
|
||||
/// It currently supports the following formats:
|
||||
/// * [N-Quads](https://www.w3.org/TR/n-quads/) ([`DatasetFormat::NQuads`])
|
||||
/// * [TriG](https://www.w3.org/TR/trig/) ([`DatasetFormat::TriG`])
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::{DatasetFormat, DatasetSerializer};
|
||||
/// use oxigraph::model::*;
|
||||
///
|
||||
/// let mut buffer = Vec::new();
|
||||
/// let mut writer = DatasetSerializer::from_format(DatasetFormat::NQuads).quad_writer(&mut buffer);
|
||||
/// writer.write(&Quad {
|
||||
/// subject: NamedNode::new("http://example.com/s")?.into(),
|
||||
/// predicate: NamedNode::new("http://example.com/p")?,
|
||||
/// object: NamedNode::new("http://example.com/o")?.into(),
|
||||
/// graph_name: NamedNode::new("http://example.com/g")?.into(),
|
||||
/// })?;
|
||||
/// writer.finish()?;
|
||||
///
|
||||
/// assert_eq!(buffer.as_slice(), "<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n".as_bytes());
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
#[deprecated(note = "use RdfSerializer instead", since = "0.4.0")] |
||||
pub struct DatasetSerializer { |
||||
inner: RdfSerializer, |
||||
} |
||||
|
||||
impl DatasetSerializer { |
||||
/// Builds a serializer for the given format
|
||||
#[inline] |
||||
pub fn from_format(format: DatasetFormat) -> Self { |
||||
Self { |
||||
inner: RdfSerializer::from_format(format.into()), |
||||
} |
||||
} |
||||
|
||||
/// Returns a [`QuadWriter`] allowing writing triples into the given [`Write`] implementation
|
||||
pub fn quad_writer<W: Write>(self, write: W) -> QuadWriter<W> { |
||||
QuadWriter { |
||||
writer: self.inner.serialize_to_write(write), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Allows writing triples.
|
||||
/// Could be built using a [`DatasetSerializer`].
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// Do not forget to run the [`finish`](QuadWriter::finish()) method to properly write the last bytes of the file.</div>
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::{DatasetFormat, DatasetSerializer};
|
||||
/// use oxigraph::model::*;
|
||||
///
|
||||
/// let mut buffer = Vec::new();
|
||||
/// let mut writer = DatasetSerializer::from_format(DatasetFormat::NQuads).quad_writer(&mut buffer);
|
||||
/// writer.write(&Quad {
|
||||
/// subject: NamedNode::new("http://example.com/s")?.into(),
|
||||
/// predicate: NamedNode::new("http://example.com/p")?,
|
||||
/// object: NamedNode::new("http://example.com/o")?.into(),
|
||||
/// graph_name: NamedNode::new("http://example.com/g")?.into(),
|
||||
/// })?;
|
||||
/// writer.finish()?;
|
||||
///
|
||||
/// assert_eq!(buffer.as_slice(), "<http://example.com/s> <http://example.com/p> <http://example.com/o> <http://example.com/g> .\n".as_bytes());
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
#[must_use] |
||||
pub struct QuadWriter<W: Write> { |
||||
writer: ToWriteQuadWriter<W>, |
||||
} |
||||
|
||||
impl<W: Write> QuadWriter<W> { |
||||
/// Writes a quad
|
||||
pub fn write<'a>(&mut self, quad: impl Into<QuadRef<'a>>) -> io::Result<()> { |
||||
self.writer.write_quad(quad) |
||||
} |
||||
|
||||
/// Writes the last bytes of the file
|
||||
pub fn finish(self) -> io::Result<()> { |
||||
self.writer.finish()?.flush() |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
#![doc = include_str!("../README.md")] |
||||
#![doc(test(attr(deny(warnings))))] |
||||
#![doc(test(attr(allow(deprecated))))] |
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))] |
||||
#![doc(html_favicon_url = "https://raw.githubusercontent.com/oxigraph/oxigraph/main/logo.svg")] |
||||
#![doc(html_logo_url = "https://raw.githubusercontent.com/oxigraph/oxigraph/main/logo.svg")] |
||||
|
||||
pub mod io; |
||||
pub mod model; |
||||
pub mod sparql; |
||||
mod storage; |
||||
pub mod store; |
@ -0,0 +1,22 @@ |
||||
//! Implements data structures for [RDF 1.1 Concepts](https://www.w3.org/TR/rdf11-concepts/) using [OxRDF](https://crates.io/crates/oxrdf).
|
||||
//!
|
||||
//! Usage example:
|
||||
//!
|
||||
//! ```
|
||||
//! use oxigraph::model::*;
|
||||
//!
|
||||
//! let mut graph = Graph::default();
|
||||
//!
|
||||
//! // insertion
|
||||
//! let ex = NamedNodeRef::new("http://example.com").unwrap();
|
||||
//! let triple = TripleRef::new(ex, ex, ex);
|
||||
//! graph.insert(triple);
|
||||
//!
|
||||
//! // simple filter
|
||||
//! let results: Vec<_> = graph.triples_for_subject(ex).collect();
|
||||
//! assert_eq!(vec![triple], results);
|
||||
//! ```
|
||||
|
||||
pub use oxrdf::*; |
||||
|
||||
pub use spargebra::term::GroundQuad; |
@ -0,0 +1,311 @@ |
||||
//! [SPARQL 1.1 Query Algebra](https://www.w3.org/TR/sparql11-query/#sparqlQuery)
|
||||
//!
|
||||
//! The root type for SPARQL queries is [`Query`] and the root type for updates is [`Update`].
|
||||
|
||||
use crate::model::*; |
||||
use crate::sparql::eval::Timer; |
||||
use oxsdatatypes::DayTimeDuration; |
||||
use spargebra::GraphUpdateOperation; |
||||
use std::fmt; |
||||
use std::str::FromStr; |
||||
|
||||
/// A parsed [SPARQL query](https://www.w3.org/TR/sparql11-query/).
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::model::NamedNode;
|
||||
/// use oxigraph::sparql::Query;
|
||||
///
|
||||
/// let query_str = "SELECT ?s ?p ?o WHERE { ?s ?p ?o . }";
|
||||
/// let mut query = Query::parse(query_str, None)?;
|
||||
///
|
||||
/// assert_eq!(query.to_string(), query_str);
|
||||
///
|
||||
/// // We edit the query dataset specification
|
||||
/// let default = vec![NamedNode::new("http://example.com")?.into()];
|
||||
/// query.dataset_mut().set_default_graph(default.clone());
|
||||
/// assert_eq!(
|
||||
/// query.dataset().default_graph_graphs(),
|
||||
/// Some(default.as_slice())
|
||||
/// );
|
||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||
/// ```
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Hash)] |
||||
pub struct Query { |
||||
pub(super) inner: spargebra::Query, |
||||
pub(super) dataset: QueryDataset, |
||||
pub(super) parsing_duration: Option<DayTimeDuration>, |
||||
} |
||||
|
||||
impl Query { |
||||
/// Parses a SPARQL query with an optional base IRI to resolve relative IRIs in the query.
|
||||
pub fn parse( |
||||
query: &str, |
||||
base_iri: Option<&str>, |
||||
) -> Result<Self, spargebra::SparqlSyntaxError> { |
||||
let start = Timer::now(); |
||||
let query = Self::from(spargebra::Query::parse(query, base_iri)?); |
||||
Ok(Self { |
||||
dataset: query.dataset, |
||||
inner: query.inner, |
||||
parsing_duration: start.elapsed(), |
||||
}) |
||||
} |
||||
|
||||
/// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset)
|
||||
pub fn dataset(&self) -> &QueryDataset { |
||||
&self.dataset |
||||
} |
||||
|
||||
/// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset)
|
||||
pub fn dataset_mut(&mut self) -> &mut QueryDataset { |
||||
&mut self.dataset |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for Query { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.inner.fmt(f) // TODO: override
|
||||
} |
||||
} |
||||
|
||||
impl FromStr for Query { |
||||
type Err = spargebra::SparqlSyntaxError; |
||||
|
||||
fn from_str(query: &str) -> Result<Self, Self::Err> { |
||||
Self::parse(query, None) |
||||
} |
||||
} |
||||
|
||||
impl TryFrom<&str> for Query { |
||||
type Error = spargebra::SparqlSyntaxError; |
||||
|
||||
fn try_from(query: &str) -> Result<Self, Self::Error> { |
||||
Self::from_str(query) |
||||
} |
||||
} |
||||
|
||||
impl TryFrom<&String> for Query { |
||||
type Error = spargebra::SparqlSyntaxError; |
||||
|
||||
fn try_from(query: &String) -> Result<Self, Self::Error> { |
||||
Self::from_str(query) |
||||
} |
||||
} |
||||
|
||||
impl From<spargebra::Query> for Query { |
||||
fn from(query: spargebra::Query) -> Self { |
||||
Self { |
||||
dataset: QueryDataset::from_algebra(match &query { |
||||
spargebra::Query::Select { dataset, .. } |
||||
| spargebra::Query::Construct { dataset, .. } |
||||
| spargebra::Query::Describe { dataset, .. } |
||||
| spargebra::Query::Ask { dataset, .. } => dataset, |
||||
}), |
||||
inner: query, |
||||
parsing_duration: None, |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// A parsed [SPARQL update](https://www.w3.org/TR/sparql11-update/).
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::sparql::Update;
|
||||
///
|
||||
/// let update_str = "CLEAR ALL ;";
|
||||
/// let update = Update::parse(update_str, None)?;
|
||||
///
|
||||
/// assert_eq!(update.to_string().trim(), update_str);
|
||||
/// # Ok::<_, oxigraph::sparql::SparqlSyntaxError>(())
|
||||
/// ```
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Hash)] |
||||
pub struct Update { |
||||
pub(super) inner: spargebra::Update, |
||||
pub(super) using_datasets: Vec<Option<QueryDataset>>, |
||||
} |
||||
|
||||
impl Update { |
||||
/// Parses a SPARQL update with an optional base IRI to resolve relative IRIs in the query.
|
||||
pub fn parse( |
||||
update: &str, |
||||
base_iri: Option<&str>, |
||||
) -> Result<Self, spargebra::SparqlSyntaxError> { |
||||
Ok(spargebra::Update::parse(update, base_iri)?.into()) |
||||
} |
||||
|
||||
/// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset) in [DELETE/INSERT operations](https://www.w3.org/TR/sparql11-update/#deleteInsert).
|
||||
pub fn using_datasets(&self) -> impl Iterator<Item = &QueryDataset> { |
||||
self.using_datasets.iter().filter_map(Option::as_ref) |
||||
} |
||||
|
||||
/// Returns [the query dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset) in [DELETE/INSERT operations](https://www.w3.org/TR/sparql11-update/#deleteInsert).
|
||||
pub fn using_datasets_mut(&mut self) -> impl Iterator<Item = &mut QueryDataset> { |
||||
self.using_datasets.iter_mut().filter_map(Option::as_mut) |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for Update { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.inner.fmt(f) |
||||
} |
||||
} |
||||
|
||||
impl FromStr for Update { |
||||
type Err = spargebra::SparqlSyntaxError; |
||||
|
||||
fn from_str(update: &str) -> Result<Self, Self::Err> { |
||||
Self::parse(update, None) |
||||
} |
||||
} |
||||
|
||||
impl TryFrom<&str> for Update { |
||||
type Error = spargebra::SparqlSyntaxError; |
||||
|
||||
fn try_from(update: &str) -> Result<Self, Self::Error> { |
||||
Self::from_str(update) |
||||
} |
||||
} |
||||
|
||||
impl TryFrom<&String> for Update { |
||||
type Error = spargebra::SparqlSyntaxError; |
||||
|
||||
fn try_from(update: &String) -> Result<Self, Self::Error> { |
||||
Self::from_str(update) |
||||
} |
||||
} |
||||
|
||||
impl From<spargebra::Update> for Update { |
||||
fn from(update: spargebra::Update) -> Self { |
||||
Self { |
||||
using_datasets: update |
||||
.operations |
||||
.iter() |
||||
.map(|operation| { |
||||
if let GraphUpdateOperation::DeleteInsert { using, .. } = operation { |
||||
Some(QueryDataset::from_algebra(using)) |
||||
} else { |
||||
None |
||||
} |
||||
}) |
||||
.collect(), |
||||
inner: update, |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// A SPARQL query [dataset specification](https://www.w3.org/TR/sparql11-query/#specifyingDataset)
|
||||
#[derive(Eq, PartialEq, Debug, Clone, Hash)] |
||||
pub struct QueryDataset { |
||||
default: Option<Vec<GraphName>>, |
||||
named: Option<Vec<NamedOrBlankNode>>, |
||||
} |
||||
|
||||
impl QueryDataset { |
||||
pub(crate) fn new() -> Self { |
||||
Self { |
||||
default: None, |
||||
named: None, |
||||
} |
||||
} |
||||
|
||||
fn from_algebra(inner: &Option<spargebra::algebra::QueryDataset>) -> Self { |
||||
if let Some(inner) = inner { |
||||
Self { |
||||
default: Some(inner.default.iter().map(|g| g.clone().into()).collect()), |
||||
named: inner |
||||
.named |
||||
.as_ref() |
||||
.map(|named| named.iter().map(|g| g.clone().into()).collect()), |
||||
} |
||||
} else { |
||||
Self { |
||||
default: Some(vec![GraphName::DefaultGraph]), |
||||
named: None, |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// Checks if this dataset specification is the default one
|
||||
/// (i.e. the default graph is the store default graph and all the store named graphs are available)
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::sparql::Query;
|
||||
///
|
||||
/// assert!(Query::parse("SELECT ?s ?p ?o WHERE { ?s ?p ?o . }", None)?
|
||||
/// .dataset()
|
||||
/// .is_default_dataset());
|
||||
/// assert!(!Query::parse(
|
||||
/// "SELECT ?s ?p ?o FROM <http://example.com> WHERE { ?s ?p ?o . }",
|
||||
/// None
|
||||
/// )?
|
||||
/// .dataset()
|
||||
/// .is_default_dataset());
|
||||
///
|
||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||
/// ```
|
||||
pub fn is_default_dataset(&self) -> bool { |
||||
self.default |
||||
.as_ref() |
||||
.map_or(false, |t| t == &[GraphName::DefaultGraph]) |
||||
&& self.named.is_none() |
||||
} |
||||
|
||||
/// Returns the list of the store graphs that are available to the query as the default graph or `None` if the union of all graphs is used as the default graph
|
||||
/// This list is by default only the store default graph
|
||||
pub fn default_graph_graphs(&self) -> Option<&[GraphName]> { |
||||
self.default.as_deref() |
||||
} |
||||
|
||||
/// Sets if the default graph for the query should be the union of all the graphs in the queried store
|
||||
pub fn set_default_graph_as_union(&mut self) { |
||||
self.default = None; |
||||
} |
||||
|
||||
/// Sets the list of graphs the query should consider as being part of the default graph.
|
||||
///
|
||||
/// By default only the store default graph is considered.
|
||||
/// ```
|
||||
/// use oxigraph::model::NamedNode;
|
||||
/// use oxigraph::sparql::Query;
|
||||
///
|
||||
/// let mut query = Query::parse("SELECT ?s ?p ?o WHERE { ?s ?p ?o . }", None)?;
|
||||
/// let default = vec![NamedNode::new("http://example.com")?.into()];
|
||||
/// query.dataset_mut().set_default_graph(default.clone());
|
||||
/// assert_eq!(
|
||||
/// query.dataset().default_graph_graphs(),
|
||||
/// Some(default.as_slice())
|
||||
/// );
|
||||
///
|
||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||
/// ```
|
||||
pub fn set_default_graph(&mut self, graphs: Vec<GraphName>) { |
||||
self.default = Some(graphs) |
||||
} |
||||
|
||||
/// Returns the list of the available named graphs for the query or `None` if all graphs are available
|
||||
pub fn available_named_graphs(&self) -> Option<&[NamedOrBlankNode]> { |
||||
self.named.as_deref() |
||||
} |
||||
|
||||
/// Sets the list of allowed named graphs in the query.
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::model::NamedNode;
|
||||
/// use oxigraph::sparql::Query;
|
||||
///
|
||||
/// let mut query = Query::parse("SELECT ?s ?p ?o WHERE { ?s ?p ?o . }", None)?;
|
||||
/// let named = vec![NamedNode::new("http://example.com")?.into()];
|
||||
/// query
|
||||
/// .dataset_mut()
|
||||
/// .set_available_named_graphs(named.clone());
|
||||
/// assert_eq!(
|
||||
/// query.dataset().available_named_graphs(),
|
||||
/// Some(named.as_slice())
|
||||
/// );
|
||||
///
|
||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
|
||||
/// ```
|
||||
pub fn set_available_named_graphs(&mut self, named_graphs: Vec<NamedOrBlankNode>) { |
||||
self.named = Some(named_graphs); |
||||
} |
||||
} |
@ -0,0 +1,184 @@ |
||||
use crate::model::TermRef; |
||||
use crate::sparql::algebra::QueryDataset; |
||||
use crate::sparql::EvaluationError; |
||||
use crate::storage::numeric_encoder::{insert_term, EncodedQuad, EncodedTerm, StrHash, StrLookup}; |
||||
use crate::storage::{StorageError, StorageReader}; |
||||
use std::cell::RefCell; |
||||
use std::collections::hash_map::Entry; |
||||
use std::collections::HashMap; |
||||
use std::iter::empty; |
||||
|
||||
pub struct DatasetView { |
||||
reader: StorageReader, |
||||
extra: RefCell<HashMap<StrHash, String>>, |
||||
dataset: EncodedDatasetSpec, |
||||
} |
||||
|
||||
impl DatasetView { |
||||
pub fn new(reader: StorageReader, dataset: &QueryDataset) -> Self { |
||||
let dataset = EncodedDatasetSpec { |
||||
default: dataset |
||||
.default_graph_graphs() |
||||
.map(|graphs| graphs.iter().map(|g| g.as_ref().into()).collect::<Vec<_>>()), |
||||
named: dataset |
||||
.available_named_graphs() |
||||
.map(|graphs| graphs.iter().map(|g| g.as_ref().into()).collect::<Vec<_>>()), |
||||
}; |
||||
Self { |
||||
reader, |
||||
extra: RefCell::new(HashMap::default()), |
||||
dataset, |
||||
} |
||||
} |
||||
|
||||
fn store_encoded_quads_for_pattern( |
||||
&self, |
||||
subject: Option<&EncodedTerm>, |
||||
predicate: Option<&EncodedTerm>, |
||||
object: Option<&EncodedTerm>, |
||||
graph_name: Option<&EncodedTerm>, |
||||
) -> impl Iterator<Item = Result<EncodedQuad, EvaluationError>> + 'static { |
||||
self.reader |
||||
.quads_for_pattern(subject, predicate, object, graph_name) |
||||
.map(|t| t.map_err(Into::into)) |
||||
} |
||||
|
||||
#[allow(clippy::needless_collect)] |
||||
pub fn encoded_quads_for_pattern( |
||||
&self, |
||||
subject: Option<&EncodedTerm>, |
||||
predicate: Option<&EncodedTerm>, |
||||
object: Option<&EncodedTerm>, |
||||
graph_name: Option<&EncodedTerm>, |
||||
) -> Box<dyn Iterator<Item = Result<EncodedQuad, EvaluationError>>> { |
||||
if let Some(graph_name) = graph_name { |
||||
if graph_name.is_default_graph() { |
||||
if let Some(default_graph_graphs) = &self.dataset.default { |
||||
if default_graph_graphs.len() == 1 { |
||||
// Single graph optimization
|
||||
Box::new( |
||||
self.store_encoded_quads_for_pattern( |
||||
subject, |
||||
predicate, |
||||
object, |
||||
Some(&default_graph_graphs[0]), |
||||
) |
||||
.map(|quad| { |
||||
let quad = quad?; |
||||
Ok(EncodedQuad::new( |
||||
quad.subject, |
||||
quad.predicate, |
||||
quad.object, |
||||
EncodedTerm::DefaultGraph, |
||||
)) |
||||
}), |
||||
) |
||||
} else { |
||||
let iters = default_graph_graphs |
||||
.iter() |
||||
.map(|graph_name| { |
||||
self.store_encoded_quads_for_pattern( |
||||
subject, |
||||
predicate, |
||||
object, |
||||
Some(graph_name), |
||||
) |
||||
}) |
||||
.collect::<Vec<_>>(); |
||||
Box::new(iters.into_iter().flatten().map(|quad| { |
||||
let quad = quad?; |
||||
Ok(EncodedQuad::new( |
||||
quad.subject, |
||||
quad.predicate, |
||||
quad.object, |
||||
EncodedTerm::DefaultGraph, |
||||
)) |
||||
})) |
||||
} |
||||
} else { |
||||
Box::new( |
||||
self.store_encoded_quads_for_pattern(subject, predicate, object, None) |
||||
.map(|quad| { |
||||
let quad = quad?; |
||||
Ok(EncodedQuad::new( |
||||
quad.subject, |
||||
quad.predicate, |
||||
quad.object, |
||||
EncodedTerm::DefaultGraph, |
||||
)) |
||||
}), |
||||
) |
||||
} |
||||
} else if self |
||||
.dataset |
||||
.named |
||||
.as_ref() |
||||
.map_or(true, |d| d.contains(graph_name)) |
||||
{ |
||||
Box::new(self.store_encoded_quads_for_pattern( |
||||
subject, |
||||
predicate, |
||||
object, |
||||
Some(graph_name), |
||||
)) |
||||
} else { |
||||
Box::new(empty()) |
||||
} |
||||
} else if let Some(named_graphs) = &self.dataset.named { |
||||
let iters = named_graphs |
||||
.iter() |
||||
.map(|graph_name| { |
||||
self.store_encoded_quads_for_pattern( |
||||
subject, |
||||
predicate, |
||||
object, |
||||
Some(graph_name), |
||||
) |
||||
}) |
||||
.collect::<Vec<_>>(); |
||||
Box::new(iters.into_iter().flatten()) |
||||
} else { |
||||
Box::new( |
||||
self.store_encoded_quads_for_pattern(subject, predicate, object, None) |
||||
.filter(|quad| match quad { |
||||
Err(_) => true, |
||||
Ok(quad) => !quad.graph_name.is_default_graph(), |
||||
}), |
||||
) |
||||
} |
||||
} |
||||
|
||||
pub fn encode_term<'a>(&self, term: impl Into<TermRef<'a>>) -> EncodedTerm { |
||||
let term = term.into(); |
||||
let encoded = term.into(); |
||||
insert_term(term, &encoded, &mut |key, value| { |
||||
self.insert_str(key, value); |
||||
Ok(()) |
||||
}) |
||||
.unwrap(); |
||||
encoded |
||||
} |
||||
|
||||
pub fn insert_str(&self, key: &StrHash, value: &str) { |
||||
if let Entry::Vacant(e) = self.extra.borrow_mut().entry(*key) { |
||||
if !matches!(self.reader.contains_str(key), Ok(true)) { |
||||
e.insert(value.to_owned()); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl StrLookup for DatasetView { |
||||
fn get_str(&self, key: &StrHash) -> Result<Option<String>, StorageError> { |
||||
Ok(if let Some(value) = self.extra.borrow().get(key) { |
||||
Some(value.clone()) |
||||
} else { |
||||
self.reader.get_str(key)? |
||||
}) |
||||
} |
||||
} |
||||
|
||||
struct EncodedDatasetSpec { |
||||
default: Option<Vec<EncodedTerm>>, |
||||
named: Option<Vec<EncodedTerm>>, |
||||
} |
@ -0,0 +1,84 @@ |
||||
use crate::io::RdfParseError; |
||||
use crate::model::NamedNode; |
||||
use crate::sparql::results::QueryResultsParseError as ResultsParseError; |
||||
use crate::sparql::SparqlSyntaxError; |
||||
use crate::storage::StorageError; |
||||
use std::convert::Infallible; |
||||
use std::error::Error; |
||||
use std::io; |
||||
|
||||
/// A SPARQL evaluation error.
|
||||
#[derive(Debug, thiserror::Error)] |
||||
#[non_exhaustive] |
||||
pub enum EvaluationError { |
||||
/// An error in SPARQL parsing.
|
||||
#[error(transparent)] |
||||
Parsing(#[from] SparqlSyntaxError), |
||||
/// An error from the storage.
|
||||
#[error(transparent)] |
||||
Storage(#[from] StorageError), |
||||
/// An error while parsing an external RDF file.
|
||||
#[error(transparent)] |
||||
GraphParsing(#[from] RdfParseError), |
||||
/// An error while parsing an external result file (likely from a federated query).
|
||||
#[error(transparent)] |
||||
ResultsParsing(#[from] ResultsParseError), |
||||
/// An error returned during results serialization.
|
||||
#[error(transparent)] |
||||
ResultsSerialization(#[from] io::Error), |
||||
/// Error during `SERVICE` evaluation
|
||||
#[error("{0}")] |
||||
Service(#[source] Box<dyn Error + Send + Sync + 'static>), |
||||
/// Error when `CREATE` tries to create an already existing graph
|
||||
#[error("The graph {0} already exists")] |
||||
GraphAlreadyExists(NamedNode), |
||||
/// Error when `DROP` or `CLEAR` tries to remove a not existing graph
|
||||
#[error("The graph {0} does not exist")] |
||||
GraphDoesNotExist(NamedNode), |
||||
/// The variable storing the `SERVICE` name is unbound
|
||||
#[error("The variable encoding the service name is unbound")] |
||||
UnboundService, |
||||
/// The given `SERVICE` is not supported
|
||||
#[error("The service {0} is not supported")] |
||||
UnsupportedService(NamedNode), |
||||
/// The given content media type returned from an HTTP response is not supported (`SERVICE` and `LOAD`)
|
||||
#[error("The content media type {0} is not supported")] |
||||
UnsupportedContentType(String), |
||||
/// The `SERVICE` call has not returns solutions
|
||||
#[error("The service is not returning solutions but a boolean or a graph")] |
||||
ServiceDoesNotReturnSolutions, |
||||
/// The results are not a RDF graph
|
||||
#[error("The query results are not a RDF graph")] |
||||
NotAGraph, |
||||
} |
||||
|
||||
impl From<Infallible> for EvaluationError { |
||||
#[inline] |
||||
fn from(error: Infallible) -> Self { |
||||
match error {} |
||||
} |
||||
} |
||||
|
||||
impl From<EvaluationError> for io::Error { |
||||
#[inline] |
||||
fn from(error: EvaluationError) -> Self { |
||||
match error { |
||||
EvaluationError::Parsing(error) => Self::new(io::ErrorKind::InvalidData, error), |
||||
EvaluationError::GraphParsing(error) => error.into(), |
||||
EvaluationError::ResultsParsing(error) => error.into(), |
||||
EvaluationError::ResultsSerialization(error) => error, |
||||
EvaluationError::Storage(error) => error.into(), |
||||
EvaluationError::Service(error) => match error.downcast() { |
||||
Ok(error) => *error, |
||||
Err(error) => Self::new(io::ErrorKind::Other, error), |
||||
}, |
||||
EvaluationError::GraphAlreadyExists(_) |
||||
| EvaluationError::GraphDoesNotExist(_) |
||||
| EvaluationError::UnboundService |
||||
| EvaluationError::UnsupportedService(_) |
||||
| EvaluationError::UnsupportedContentType(_) |
||||
| EvaluationError::ServiceDoesNotReturnSolutions |
||||
| EvaluationError::NotAGraph => Self::new(io::ErrorKind::InvalidInput, error), |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,34 @@ |
||||
//! Simple HTTP client
|
||||
|
||||
use std::io::{Empty, Error, ErrorKind, Result}; |
||||
use std::time::Duration; |
||||
|
||||
pub struct Client; |
||||
|
||||
impl Client { |
||||
pub fn new(_timeout: Option<Duration>, _redirection_limit: usize) -> Self { |
||||
Self |
||||
} |
||||
|
||||
#[allow(clippy::unused_self)] |
||||
pub fn get(&self, _url: &str, _accept: &'static str) -> Result<(String, Empty)> { |
||||
Err(Error::new( |
||||
ErrorKind::Unsupported, |
||||
"HTTP client is not available. Enable the feature 'http-client'", |
||||
)) |
||||
} |
||||
|
||||
#[allow(clippy::unused_self, clippy::needless_pass_by_value)] |
||||
pub fn post( |
||||
&self, |
||||
_url: &str, |
||||
_payload: Vec<u8>, |
||||
_content_type: &'static str, |
||||
_accept: &'static str, |
||||
) -> Result<(String, Empty)> { |
||||
Err(Error::new( |
||||
ErrorKind::Unsupported, |
||||
"HTTP client is not available. Enable the feature 'http-client'", |
||||
)) |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
#[cfg(not(feature = "http-client"))] |
||||
mod dummy; |
||||
#[cfg(feature = "http-client")] |
||||
mod simple; |
||||
|
||||
#[cfg(not(feature = "http-client"))] |
||||
pub use dummy::Client; |
||||
#[cfg(feature = "http-client")] |
||||
pub use simple::Client; |
@ -0,0 +1,90 @@ |
||||
use oxhttp::model::{Body, HeaderName, Method, Request}; |
||||
use std::io::{Error, ErrorKind, Result}; |
||||
use std::time::Duration; |
||||
|
||||
pub struct Client { |
||||
client: oxhttp::Client, |
||||
} |
||||
|
||||
impl Client { |
||||
pub fn new(timeout: Option<Duration>, redirection_limit: usize) -> Self { |
||||
let mut client = oxhttp::Client::new() |
||||
.with_redirection_limit(redirection_limit) |
||||
.with_user_agent(concat!("Oxigraph/", env!("CARGO_PKG_VERSION"))) |
||||
.unwrap(); |
||||
if let Some(timeout) = timeout { |
||||
client = client.with_global_timeout(timeout); |
||||
} |
||||
Self { client } |
||||
} |
||||
|
||||
pub fn get(&self, url: &str, accept: &'static str) -> Result<(String, Body)> { |
||||
let request = Request::builder(Method::GET, url.parse().map_err(invalid_input_error)?) |
||||
.with_header(HeaderName::ACCEPT, accept) |
||||
.map_err(invalid_input_error)? |
||||
.build(); |
||||
let response = self.client.request(request)?; |
||||
let status = response.status(); |
||||
if !status.is_successful() { |
||||
return Err(Error::new( |
||||
ErrorKind::Other, |
||||
format!( |
||||
"Error {} returned by {} with payload:\n{}", |
||||
status, |
||||
url, |
||||
response.into_body().to_string()? |
||||
), |
||||
)); |
||||
} |
||||
let content_type = response |
||||
.header(&HeaderName::CONTENT_TYPE) |
||||
.ok_or_else(|| invalid_data_error(format!("No Content-Type returned by {url}")))? |
||||
.to_str() |
||||
.map_err(invalid_data_error)? |
||||
.to_owned(); |
||||
Ok((content_type, response.into_body())) |
||||
} |
||||
|
||||
pub fn post( |
||||
&self, |
||||
url: &str, |
||||
payload: Vec<u8>, |
||||
content_type: &'static str, |
||||
accept: &'static str, |
||||
) -> Result<(String, Body)> { |
||||
let request = Request::builder(Method::POST, url.parse().map_err(invalid_input_error)?) |
||||
.with_header(HeaderName::ACCEPT, accept) |
||||
.map_err(invalid_input_error)? |
||||
.with_header(HeaderName::CONTENT_TYPE, content_type) |
||||
.map_err(invalid_input_error)? |
||||
.with_body(payload); |
||||
let response = self.client.request(request)?; |
||||
let status = response.status(); |
||||
if !status.is_successful() { |
||||
return Err(Error::new( |
||||
ErrorKind::Other, |
||||
format!( |
||||
"Error {} returned by {} with payload:\n{}", |
||||
status, |
||||
url, |
||||
response.into_body().to_string()? |
||||
), |
||||
)); |
||||
} |
||||
let content_type = response |
||||
.header(&HeaderName::CONTENT_TYPE) |
||||
.ok_or_else(|| invalid_data_error(format!("No Content-Type returned by {url}")))? |
||||
.to_str() |
||||
.map_err(invalid_data_error)? |
||||
.to_owned(); |
||||
Ok((content_type, response.into_body())) |
||||
} |
||||
} |
||||
|
||||
fn invalid_data_error(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Error { |
||||
Error::new(ErrorKind::InvalidData, error) |
||||
} |
||||
|
||||
fn invalid_input_error(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Error { |
||||
Error::new(ErrorKind::InvalidInput, error) |
||||
} |
@ -0,0 +1,328 @@ |
||||
//! [SPARQL](https://www.w3.org/TR/sparql11-overview/) implementation.
|
||||
//!
|
||||
//! Stores execute SPARQL. See [`Store`](crate::store::Store::query()) for an example.
|
||||
|
||||
mod algebra; |
||||
mod dataset; |
||||
mod error; |
||||
mod eval; |
||||
mod http; |
||||
mod model; |
||||
pub mod results; |
||||
mod service; |
||||
mod update; |
||||
|
||||
use crate::model::{NamedNode, Term}; |
||||
pub use crate::sparql::algebra::{Query, QueryDataset, Update}; |
||||
use crate::sparql::dataset::DatasetView; |
||||
pub use crate::sparql::error::EvaluationError; |
||||
use crate::sparql::eval::{EvalNodeWithStats, SimpleEvaluator, Timer}; |
||||
pub use crate::sparql::model::{QueryResults, QuerySolution, QuerySolutionIter, QueryTripleIter}; |
||||
pub use crate::sparql::service::ServiceHandler; |
||||
use crate::sparql::service::{EmptyServiceHandler, ErrorConversionServiceHandler}; |
||||
pub(crate) use crate::sparql::update::evaluate_update; |
||||
use crate::storage::StorageReader; |
||||
use json_event_parser::{JsonEvent, ToWriteJsonWriter}; |
||||
pub use oxrdf::{Variable, VariableNameParseError}; |
||||
use oxsdatatypes::{DayTimeDuration, Float}; |
||||
pub use spargebra::SparqlSyntaxError; |
||||
use sparopt::algebra::GraphPattern; |
||||
use sparopt::Optimizer; |
||||
use std::collections::HashMap; |
||||
use std::rc::Rc; |
||||
use std::sync::Arc; |
||||
use std::time::Duration; |
||||
use std::{fmt, io}; |
||||
|
||||
#[allow(clippy::needless_pass_by_value)] |
||||
pub(crate) fn evaluate_query( |
||||
reader: StorageReader, |
||||
query: impl TryInto<Query, Error = impl Into<EvaluationError>>, |
||||
options: QueryOptions, |
||||
run_stats: bool, |
||||
) -> Result<(Result<QueryResults, EvaluationError>, QueryExplanation), EvaluationError> { |
||||
let query = query.try_into().map_err(Into::into)?; |
||||
let dataset = DatasetView::new(reader, &query.dataset); |
||||
let start_planning = Timer::now(); |
||||
let (results, plan_node_with_stats, planning_duration) = match query.inner { |
||||
spargebra::Query::Select { |
||||
pattern, base_iri, .. |
||||
} => { |
||||
let mut pattern = GraphPattern::from(&pattern); |
||||
if !options.without_optimizations { |
||||
pattern = Optimizer::optimize_graph_pattern(pattern); |
||||
} |
||||
let planning_duration = start_planning.elapsed(); |
||||
let (results, explanation) = SimpleEvaluator::new( |
||||
Rc::new(dataset), |
||||
base_iri.map(Rc::new), |
||||
options.service_handler(), |
||||
Arc::new(options.custom_functions), |
||||
run_stats, |
||||
) |
||||
.evaluate_select(&pattern); |
||||
(Ok(results), explanation, planning_duration) |
||||
} |
||||
spargebra::Query::Ask { |
||||
pattern, base_iri, .. |
||||
} => { |
||||
let mut pattern = GraphPattern::from(&pattern); |
||||
if !options.without_optimizations { |
||||
pattern = Optimizer::optimize_graph_pattern(GraphPattern::Reduced { |
||||
inner: Box::new(pattern), |
||||
}); |
||||
} |
||||
let planning_duration = start_planning.elapsed(); |
||||
let (results, explanation) = SimpleEvaluator::new( |
||||
Rc::new(dataset), |
||||
base_iri.map(Rc::new), |
||||
options.service_handler(), |
||||
Arc::new(options.custom_functions), |
||||
run_stats, |
||||
) |
||||
.evaluate_ask(&pattern); |
||||
(results, explanation, planning_duration) |
||||
} |
||||
spargebra::Query::Construct { |
||||
template, |
||||
pattern, |
||||
base_iri, |
||||
.. |
||||
} => { |
||||
let mut pattern = GraphPattern::from(&pattern); |
||||
if !options.without_optimizations { |
||||
pattern = Optimizer::optimize_graph_pattern(GraphPattern::Reduced { |
||||
inner: Box::new(pattern), |
||||
}); |
||||
} |
||||
let planning_duration = start_planning.elapsed(); |
||||
let (results, explanation) = SimpleEvaluator::new( |
||||
Rc::new(dataset), |
||||
base_iri.map(Rc::new), |
||||
options.service_handler(), |
||||
Arc::new(options.custom_functions), |
||||
run_stats, |
||||
) |
||||
.evaluate_construct(&pattern, &template); |
||||
(Ok(results), explanation, planning_duration) |
||||
} |
||||
spargebra::Query::Describe { |
||||
pattern, base_iri, .. |
||||
} => { |
||||
let mut pattern = GraphPattern::from(&pattern); |
||||
if !options.without_optimizations { |
||||
pattern = Optimizer::optimize_graph_pattern(GraphPattern::Reduced { |
||||
inner: Box::new(pattern), |
||||
}); |
||||
} |
||||
let planning_duration = start_planning.elapsed(); |
||||
let (results, explanation) = SimpleEvaluator::new( |
||||
Rc::new(dataset), |
||||
base_iri.map(Rc::new), |
||||
options.service_handler(), |
||||
Arc::new(options.custom_functions), |
||||
run_stats, |
||||
) |
||||
.evaluate_describe(&pattern); |
||||
(Ok(results), explanation, planning_duration) |
||||
} |
||||
}; |
||||
let explanation = QueryExplanation { |
||||
inner: plan_node_with_stats, |
||||
with_stats: run_stats, |
||||
parsing_duration: query.parsing_duration, |
||||
planning_duration, |
||||
}; |
||||
Ok((results, explanation)) |
||||
} |
||||
|
||||
/// Options for SPARQL query evaluation.
|
||||
///
|
||||
///
|
||||
/// If the `"http-client"` optional feature is enabled,
|
||||
/// a simple HTTP 1.1 client is used to execute [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/) SERVICE calls.
|
||||
///
|
||||
/// Usage example disabling the federated query support:
|
||||
/// ```
|
||||
/// use oxigraph::sparql::QueryOptions;
|
||||
/// use oxigraph::store::Store;
|
||||
///
|
||||
/// let store = Store::new()?;
|
||||
/// store.query_opt(
|
||||
/// "SELECT * WHERE { SERVICE <https://query.wikidata.org/sparql> {} }",
|
||||
/// QueryOptions::default().without_service_handler(),
|
||||
/// )?;
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
#[derive(Clone, Default)] |
||||
pub struct QueryOptions { |
||||
service_handler: Option<Arc<dyn ServiceHandler<Error = EvaluationError>>>, |
||||
custom_functions: CustomFunctionRegistry, |
||||
http_timeout: Option<Duration>, |
||||
http_redirection_limit: usize, |
||||
without_optimizations: bool, |
||||
} |
||||
|
||||
pub(crate) type CustomFunctionRegistry = |
||||
HashMap<NamedNode, Arc<dyn (Fn(&[Term]) -> Option<Term>) + Send + Sync>>; |
||||
|
||||
impl QueryOptions { |
||||
/// Use a given [`ServiceHandler`] to execute [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/) SERVICE calls.
|
||||
#[inline] |
||||
#[must_use] |
||||
pub fn with_service_handler(mut self, service_handler: impl ServiceHandler + 'static) -> Self { |
||||
self.service_handler = Some(Arc::new(ErrorConversionServiceHandler::wrap( |
||||
service_handler, |
||||
))); |
||||
self |
||||
} |
||||
|
||||
/// Disables the `SERVICE` calls
|
||||
#[inline] |
||||
#[must_use] |
||||
pub fn without_service_handler(mut self) -> Self { |
||||
self.service_handler = Some(Arc::new(EmptyServiceHandler)); |
||||
self |
||||
} |
||||
|
||||
/// Sets a timeout for HTTP requests done during SPARQL evaluation.
|
||||
#[cfg(feature = "http-client")] |
||||
#[inline] |
||||
#[must_use] |
||||
pub fn with_http_timeout(mut self, timeout: Duration) -> Self { |
||||
self.http_timeout = Some(timeout); |
||||
self |
||||
} |
||||
|
||||
/// Sets an upper bound of the number of HTTP redirection followed per HTTP request done during SPARQL evaluation.
|
||||
///
|
||||
/// By default this value is `0`.
|
||||
#[cfg(feature = "http-client")] |
||||
#[inline] |
||||
#[must_use] |
||||
pub fn with_http_redirection_limit(mut self, redirection_limit: usize) -> Self { |
||||
self.http_redirection_limit = redirection_limit; |
||||
self |
||||
} |
||||
|
||||
/// Adds a custom SPARQL evaluation function.
|
||||
///
|
||||
/// Example with a function serializing terms to N-Triples:
|
||||
/// ```
|
||||
/// use oxigraph::model::*;
|
||||
/// use oxigraph::sparql::{QueryOptions, QueryResults};
|
||||
/// use oxigraph::store::Store;
|
||||
///
|
||||
/// let store = Store::new()?;
|
||||
///
|
||||
/// if let QueryResults::Solutions(mut solutions) = store.query_opt(
|
||||
/// "SELECT (<http://www.w3.org/ns/formats/N-Triples>(1) AS ?nt) WHERE {}",
|
||||
/// QueryOptions::default().with_custom_function(
|
||||
/// NamedNode::new("http://www.w3.org/ns/formats/N-Triples")?,
|
||||
/// |args| args.get(0).map(|t| Literal::from(t.to_string()).into()),
|
||||
/// ),
|
||||
/// )? {
|
||||
/// assert_eq!(
|
||||
/// solutions.next().unwrap()?.get("nt"),
|
||||
/// Some(&Literal::from("\"1\"^^<http://www.w3.org/2001/XMLSchema#integer>").into())
|
||||
/// );
|
||||
/// }
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
#[inline] |
||||
#[must_use] |
||||
pub fn with_custom_function( |
||||
mut self, |
||||
name: NamedNode, |
||||
evaluator: impl Fn(&[Term]) -> Option<Term> + Send + Sync + 'static, |
||||
) -> Self { |
||||
self.custom_functions.insert(name, Arc::new(evaluator)); |
||||
self |
||||
} |
||||
|
||||
fn service_handler(&self) -> Arc<dyn ServiceHandler<Error = EvaluationError>> { |
||||
self.service_handler.clone().unwrap_or_else(|| { |
||||
if cfg!(feature = "http-client") { |
||||
Arc::new(service::SimpleServiceHandler::new( |
||||
self.http_timeout, |
||||
self.http_redirection_limit, |
||||
)) |
||||
} else { |
||||
Arc::new(EmptyServiceHandler) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
#[doc(hidden)] |
||||
#[inline] |
||||
#[must_use] |
||||
pub fn without_optimizations(mut self) -> Self { |
||||
self.without_optimizations = true; |
||||
self |
||||
} |
||||
} |
||||
|
||||
/// Options for SPARQL update evaluation.
|
||||
#[derive(Clone, Default)] |
||||
pub struct UpdateOptions { |
||||
query_options: QueryOptions, |
||||
} |
||||
|
||||
impl From<QueryOptions> for UpdateOptions { |
||||
#[inline] |
||||
fn from(query_options: QueryOptions) -> Self { |
||||
Self { query_options } |
||||
} |
||||
} |
||||
|
||||
/// The explanation of a query.
|
||||
#[derive(Clone)] |
||||
pub struct QueryExplanation { |
||||
inner: Rc<EvalNodeWithStats>, |
||||
with_stats: bool, |
||||
parsing_duration: Option<DayTimeDuration>, |
||||
planning_duration: Option<DayTimeDuration>, |
||||
} |
||||
|
||||
impl QueryExplanation { |
||||
/// Writes the explanation as JSON.
|
||||
pub fn write_in_json(&self, write: impl io::Write) -> io::Result<()> { |
||||
let mut writer = ToWriteJsonWriter::new(write); |
||||
writer.write_event(JsonEvent::StartObject)?; |
||||
if let Some(parsing_duration) = self.parsing_duration { |
||||
writer.write_event(JsonEvent::ObjectKey("parsing duration in seconds".into()))?; |
||||
writer.write_event(JsonEvent::Number( |
||||
parsing_duration.as_seconds().to_string().into(), |
||||
))?; |
||||
} |
||||
if let Some(planning_duration) = self.planning_duration { |
||||
writer.write_event(JsonEvent::ObjectKey("planning duration in seconds".into()))?; |
||||
writer.write_event(JsonEvent::Number( |
||||
planning_duration.as_seconds().to_string().into(), |
||||
))?; |
||||
} |
||||
writer.write_event(JsonEvent::ObjectKey("plan".into()))?; |
||||
self.inner.json_node(&mut writer, self.with_stats)?; |
||||
writer.write_event(JsonEvent::EndObject) |
||||
} |
||||
} |
||||
|
||||
impl fmt::Debug for QueryExplanation { |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
let mut obj = f.debug_struct("QueryExplanation"); |
||||
if let Some(parsing_duration) = self.parsing_duration { |
||||
obj.field( |
||||
"parsing duration in seconds", |
||||
&f32::from(Float::from(parsing_duration.as_seconds())), |
||||
); |
||||
} |
||||
if let Some(planning_duration) = self.planning_duration { |
||||
obj.field( |
||||
"planning duration in seconds", |
||||
&f32::from(Float::from(planning_duration.as_seconds())), |
||||
); |
||||
} |
||||
obj.field("tree", &self.inner); |
||||
obj.finish_non_exhaustive() |
||||
} |
||||
} |
@ -0,0 +1,371 @@ |
||||
use crate::io::{RdfFormat, RdfSerializer}; |
||||
use crate::model::*; |
||||
use crate::sparql::error::EvaluationError; |
||||
use crate::sparql::results::{ |
||||
FromReadQueryResultsReader, FromReadSolutionsReader, QueryResultsFormat, |
||||
QueryResultsParseError, QueryResultsParser, QueryResultsSerializer, |
||||
}; |
||||
pub use sparesults::QuerySolution; |
||||
use std::io::{Read, Write}; |
||||
use std::sync::Arc; |
||||
|
||||
/// Results of a [SPARQL query](https://www.w3.org/TR/sparql11-query/).
|
||||
pub enum QueryResults { |
||||
/// Results of a [SELECT](https://www.w3.org/TR/sparql11-query/#select) query.
|
||||
Solutions(QuerySolutionIter), |
||||
/// Result of a [ASK](https://www.w3.org/TR/sparql11-query/#ask) query.
|
||||
Boolean(bool), |
||||
/// Results of a [CONSTRUCT](https://www.w3.org/TR/sparql11-query/#construct) or [DESCRIBE](https://www.w3.org/TR/sparql11-query/#describe) query.
|
||||
Graph(QueryTripleIter), |
||||
} |
||||
|
||||
impl QueryResults { |
||||
/// Reads a SPARQL query results serialization.
|
||||
pub fn read( |
||||
read: impl Read + 'static, |
||||
format: QueryResultsFormat, |
||||
) -> Result<Self, QueryResultsParseError> { |
||||
Ok(QueryResultsParser::from_format(format) |
||||
.parse_read(read)? |
||||
.into()) |
||||
} |
||||
|
||||
/// Writes the query results (solutions or boolean).
|
||||
///
|
||||
/// This method fails if it is called on the `Graph` results.
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::store::Store;
|
||||
/// use oxigraph::model::*;
|
||||
/// use oxigraph::sparql::results::QueryResultsFormat;
|
||||
///
|
||||
/// let store = Store::new()?;
|
||||
/// let ex = NamedNodeRef::new("http://example.com")?;
|
||||
/// store.insert(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?;
|
||||
///
|
||||
/// let results = store.query("SELECT ?s WHERE { ?s ?p ?o }")?;
|
||||
/// assert_eq!(
|
||||
/// results.write(Vec::new(), QueryResultsFormat::Json)?,
|
||||
/// r#"{"head":{"vars":["s"]},"results":{"bindings":[{"s":{"type":"uri","value":"http://example.com"}}]}}"#.as_bytes()
|
||||
/// );
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
pub fn write<W: Write>( |
||||
self, |
||||
write: W, |
||||
format: QueryResultsFormat, |
||||
) -> Result<W, EvaluationError> { |
||||
let serializer = QueryResultsSerializer::from_format(format); |
||||
match self { |
||||
Self::Boolean(value) => serializer.serialize_boolean_to_write(write, value), |
||||
Self::Solutions(solutions) => { |
||||
let mut writer = serializer |
||||
.serialize_solutions_to_write(write, solutions.variables().to_vec()) |
||||
.map_err(EvaluationError::ResultsSerialization)?; |
||||
for solution in solutions { |
||||
writer |
||||
.write(&solution?) |
||||
.map_err(EvaluationError::ResultsSerialization)?; |
||||
} |
||||
writer.finish() |
||||
} |
||||
Self::Graph(triples) => { |
||||
let s = VariableRef::new_unchecked("subject"); |
||||
let p = VariableRef::new_unchecked("predicate"); |
||||
let o = VariableRef::new_unchecked("object"); |
||||
let mut writer = serializer |
||||
.serialize_solutions_to_write( |
||||
write, |
||||
vec![s.into_owned(), p.into_owned(), o.into_owned()], |
||||
) |
||||
.map_err(EvaluationError::ResultsSerialization)?; |
||||
for triple in triples { |
||||
let triple = triple?; |
||||
writer |
||||
.write([ |
||||
(s, &triple.subject.into()), |
||||
(p, &triple.predicate.into()), |
||||
(o, &triple.object), |
||||
]) |
||||
.map_err(EvaluationError::ResultsSerialization)?; |
||||
} |
||||
writer.finish() |
||||
} |
||||
} |
||||
.map_err(EvaluationError::ResultsSerialization) |
||||
} |
||||
|
||||
/// Writes the graph query results.
|
||||
///
|
||||
/// This method fails if it is called on the `Solution` or `Boolean` results.
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::io::RdfFormat;
|
||||
/// use oxigraph::model::*;
|
||||
/// use oxigraph::store::Store;
|
||||
///
|
||||
/// let graph = "<http://example.com> <http://example.com> <http://example.com> .\n";
|
||||
///
|
||||
/// let store = Store::new()?;
|
||||
/// store.load_graph(
|
||||
/// graph.as_bytes(),
|
||||
/// RdfFormat::NTriples,
|
||||
/// GraphName::DefaultGraph,
|
||||
/// None,
|
||||
/// )?;
|
||||
///
|
||||
/// let results = store.query("CONSTRUCT WHERE { ?s ?p ?o }")?;
|
||||
/// assert_eq!(
|
||||
/// results.write_graph(Vec::new(), RdfFormat::NTriples)?,
|
||||
/// graph.as_bytes()
|
||||
/// );
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
pub fn write_graph<W: Write>( |
||||
self, |
||||
write: W, |
||||
format: impl Into<RdfFormat>, |
||||
) -> Result<W, EvaluationError> { |
||||
if let Self::Graph(triples) = self { |
||||
let mut writer = RdfSerializer::from_format(format.into()).serialize_to_write(write); |
||||
for triple in triples { |
||||
writer |
||||
.write_triple(&triple?) |
||||
.map_err(EvaluationError::ResultsSerialization)?; |
||||
} |
||||
writer |
||||
.finish() |
||||
.map_err(EvaluationError::ResultsSerialization) |
||||
} else { |
||||
Err(EvaluationError::NotAGraph) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl From<QuerySolutionIter> for QueryResults { |
||||
#[inline] |
||||
fn from(value: QuerySolutionIter) -> Self { |
||||
Self::Solutions(value) |
||||
} |
||||
} |
||||
|
||||
impl<R: Read + 'static> From<FromReadQueryResultsReader<R>> for QueryResults { |
||||
fn from(reader: FromReadQueryResultsReader<R>) -> Self { |
||||
match reader { |
||||
FromReadQueryResultsReader::Solutions(s) => Self::Solutions(s.into()), |
||||
FromReadQueryResultsReader::Boolean(v) => Self::Boolean(v), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// An iterator over [`QuerySolution`]s.
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::sparql::QueryResults;
|
||||
/// use oxigraph::store::Store;
|
||||
///
|
||||
/// let store = Store::new()?;
|
||||
/// if let QueryResults::Solutions(solutions) = store.query("SELECT ?s WHERE { ?s ?p ?o }")? {
|
||||
/// for solution in solutions {
|
||||
/// println!("{:?}", solution?.get("s"));
|
||||
/// }
|
||||
/// }
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
pub struct QuerySolutionIter { |
||||
variables: Arc<[Variable]>, |
||||
iter: Box<dyn Iterator<Item = Result<QuerySolution, EvaluationError>>>, |
||||
} |
||||
|
||||
impl QuerySolutionIter { |
||||
/// Construct a new iterator of solution from an ordered list of solution variables and an iterator of solution tuples
|
||||
/// (each tuple using the same ordering as the variable list such that tuple element 0 is the value for the variable 0...)
|
||||
pub fn new( |
||||
variables: Arc<[Variable]>, |
||||
iter: impl Iterator<Item = Result<Vec<Option<Term>>, EvaluationError>> + 'static, |
||||
) -> Self { |
||||
Self { |
||||
variables: Arc::clone(&variables), |
||||
iter: Box::new( |
||||
iter.map(move |t| t.map(|values| (Arc::clone(&variables), values).into())), |
||||
), |
||||
} |
||||
} |
||||
|
||||
/// The variables used in the solutions.
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::sparql::{QueryResults, Variable};
|
||||
/// use oxigraph::store::Store;
|
||||
///
|
||||
/// let store = Store::new()?;
|
||||
/// if let QueryResults::Solutions(solutions) = store.query("SELECT ?s ?o WHERE { ?s ?p ?o }")? {
|
||||
/// assert_eq!(
|
||||
/// solutions.variables(),
|
||||
/// &[Variable::new("s")?, Variable::new("o")?]
|
||||
/// );
|
||||
/// }
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
#[inline] |
||||
pub fn variables(&self) -> &[Variable] { |
||||
&self.variables |
||||
} |
||||
} |
||||
|
||||
impl<R: Read + 'static> From<FromReadSolutionsReader<R>> for QuerySolutionIter { |
||||
fn from(reader: FromReadSolutionsReader<R>) -> Self { |
||||
Self { |
||||
variables: reader.variables().into(), |
||||
iter: Box::new(reader.map(|t| t.map_err(EvaluationError::from))), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Iterator for QuerySolutionIter { |
||||
type Item = Result<QuerySolution, EvaluationError>; |
||||
|
||||
#[inline] |
||||
fn next(&mut self) -> Option<Self::Item> { |
||||
self.iter.next() |
||||
} |
||||
|
||||
#[inline] |
||||
fn size_hint(&self) -> (usize, Option<usize>) { |
||||
self.iter.size_hint() |
||||
} |
||||
} |
||||
|
||||
/// An iterator over the triples that compose a graph solution.
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::sparql::QueryResults;
|
||||
/// use oxigraph::store::Store;
|
||||
///
|
||||
/// let store = Store::new()?;
|
||||
/// if let QueryResults::Graph(triples) = store.query("CONSTRUCT WHERE { ?s ?p ?o }")? {
|
||||
/// for triple in triples {
|
||||
/// println!("{}", triple?);
|
||||
/// }
|
||||
/// }
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
pub struct QueryTripleIter { |
||||
pub(crate) iter: Box<dyn Iterator<Item = Result<Triple, EvaluationError>>>, |
||||
} |
||||
|
||||
impl Iterator for QueryTripleIter { |
||||
type Item = Result<Triple, EvaluationError>; |
||||
|
||||
#[inline] |
||||
fn next(&mut self) -> Option<Self::Item> { |
||||
self.iter.next() |
||||
} |
||||
|
||||
#[inline] |
||||
fn size_hint(&self) -> (usize, Option<usize>) { |
||||
self.iter.size_hint() |
||||
} |
||||
|
||||
#[inline] |
||||
fn fold<Acc, G>(self, init: Acc, g: G) -> Acc |
||||
where |
||||
G: FnMut(Acc, Self::Item) -> Acc, |
||||
{ |
||||
self.iter.fold(init, g) |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
#[allow(clippy::panic_in_result_fn)] |
||||
mod tests { |
||||
use super::*; |
||||
use std::io::Cursor; |
||||
|
||||
#[test] |
||||
fn test_serialization_roundtrip() -> Result<(), EvaluationError> { |
||||
use std::str; |
||||
|
||||
for format in [ |
||||
QueryResultsFormat::Json, |
||||
QueryResultsFormat::Xml, |
||||
QueryResultsFormat::Tsv, |
||||
] { |
||||
let results = vec![ |
||||
QueryResults::Boolean(true), |
||||
QueryResults::Boolean(false), |
||||
QueryResults::Solutions(QuerySolutionIter::new( |
||||
[ |
||||
Variable::new_unchecked("foo"), |
||||
Variable::new_unchecked("bar"), |
||||
] |
||||
.as_ref() |
||||
.into(), |
||||
Box::new( |
||||
vec![ |
||||
Ok(vec![None, None]), |
||||
Ok(vec![ |
||||
Some(NamedNode::new_unchecked("http://example.com").into()), |
||||
None, |
||||
]), |
||||
Ok(vec![ |
||||
None, |
||||
Some(NamedNode::new_unchecked("http://example.com").into()), |
||||
]), |
||||
Ok(vec![ |
||||
Some(BlankNode::new_unchecked("foo").into()), |
||||
Some(BlankNode::new_unchecked("bar").into()), |
||||
]), |
||||
Ok(vec![Some(Literal::new_simple_literal("foo").into()), None]), |
||||
Ok(vec![ |
||||
Some( |
||||
Literal::new_language_tagged_literal_unchecked("foo", "fr") |
||||
.into(), |
||||
), |
||||
None, |
||||
]), |
||||
Ok(vec![ |
||||
Some(Literal::from(1).into()), |
||||
Some(Literal::from(true).into()), |
||||
]), |
||||
Ok(vec![ |
||||
Some(Literal::from(1.33).into()), |
||||
Some(Literal::from(false).into()), |
||||
]), |
||||
Ok(vec![ |
||||
Some( |
||||
Triple::new( |
||||
NamedNode::new_unchecked("http://example.com/s"), |
||||
NamedNode::new_unchecked("http://example.com/p"), |
||||
Triple::new( |
||||
NamedNode::new_unchecked("http://example.com/os"), |
||||
NamedNode::new_unchecked("http://example.com/op"), |
||||
NamedNode::new_unchecked("http://example.com/oo"), |
||||
), |
||||
) |
||||
.into(), |
||||
), |
||||
None, |
||||
]), |
||||
] |
||||
.into_iter(), |
||||
), |
||||
)), |
||||
]; |
||||
|
||||
for ex in results { |
||||
let mut buffer = Vec::new(); |
||||
ex.write(&mut buffer, format)?; |
||||
let ex2 = QueryResults::read(Cursor::new(buffer.clone()), format)?; |
||||
let mut buffer2 = Vec::new(); |
||||
ex2.write(&mut buffer2, format)?; |
||||
assert_eq!( |
||||
str::from_utf8(&buffer).unwrap(), |
||||
str::from_utf8(&buffer2).unwrap() |
||||
); |
||||
} |
||||
} |
||||
|
||||
Ok(()) |
||||
} |
||||
} |
@ -0,0 +1,44 @@ |
||||
//! Utilities to read and write RDF results formats using [sparesults](https://crates.io/crates/sparesults).
|
||||
//!
|
||||
//! It supports [SPARQL Query Results XML Format (Second Edition)](https://www.w3.org/TR/rdf-sparql-XMLres/), [SPARQL 1.1 Query Results JSON Format](https://www.w3.org/TR/sparql11-results-json/) and [SPARQL 1.1 Query Results CSV and TSV Formats](https://www.w3.org/TR/sparql11-results-csv-tsv/).
|
||||
//!
|
||||
//! Usage example converting a JSON result file into a TSV result file:
|
||||
//!
|
||||
//! ```
|
||||
//! use oxigraph::sparql::results::{QueryResultsFormat, QueryResultsParser, FromReadQueryResultsReader, QueryResultsSerializer};
|
||||
//! use std::io::Result;
|
||||
//!
|
||||
//! fn convert_json_to_tsv(json_file: &[u8]) -> Result<Vec<u8>> {
|
||||
//! let json_parser = QueryResultsParser::from_format(QueryResultsFormat::Json);
|
||||
//! let tsv_serializer = QueryResultsSerializer::from_format(QueryResultsFormat::Tsv);
|
||||
//! // We start to read the JSON file and see which kind of results it is
|
||||
//! match json_parser.parse_read(json_file)? {
|
||||
//! FromReadQueryResultsReader::Boolean(value) => {
|
||||
//! // it's a boolean result, we copy it in TSV to the output buffer
|
||||
//! tsv_serializer.serialize_boolean_to_write(Vec::new(), value)
|
||||
//! }
|
||||
//! FromReadQueryResultsReader::Solutions(solutions_reader) => {
|
||||
//! // it's a set of solutions, we create a writer and we write to it while reading in streaming from the JSON file
|
||||
//! let mut serialize_solutions_to_write = tsv_serializer.serialize_solutions_to_write(Vec::new(), solutions_reader.variables().to_vec())?;
|
||||
//! for solution in solutions_reader {
|
||||
//! serialize_solutions_to_write.write(&solution?)?;
|
||||
//! }
|
||||
//! serialize_solutions_to_write.finish()
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! // Let's test with a boolean
|
||||
//! assert_eq!(
|
||||
//! convert_json_to_tsv(br#"{"boolean":true}"#.as_slice()).unwrap(),
|
||||
//! b"true"
|
||||
//! );
|
||||
//!
|
||||
//! // And with a set of solutions
|
||||
//! assert_eq!(
|
||||
//! convert_json_to_tsv(br#"{"head":{"vars":["foo","bar"]},"results":{"bindings":[{"foo":{"type":"literal","value":"test"}}]}}"#.as_slice()).unwrap(),
|
||||
//! b"?foo\t?bar\n\"test\"\t\n"
|
||||
//! );
|
||||
//! ```
|
||||
|
||||
pub use sparesults::*; |
@ -0,0 +1,124 @@ |
||||
use crate::model::NamedNode; |
||||
use crate::sparql::algebra::Query; |
||||
use crate::sparql::error::EvaluationError; |
||||
use crate::sparql::http::Client; |
||||
use crate::sparql::model::QueryResults; |
||||
use crate::sparql::results::QueryResultsFormat; |
||||
use std::error::Error; |
||||
use std::time::Duration; |
||||
|
||||
/// Handler for [SPARQL 1.1 Federated Query](https://www.w3.org/TR/sparql11-federated-query/) SERVICE.
|
||||
///
|
||||
/// Should be given to [`QueryOptions`](super::QueryOptions::with_service_handler())
|
||||
/// before evaluating a SPARQL query that uses SERVICE calls.
|
||||
///
|
||||
/// ```
|
||||
/// use oxigraph::model::*;
|
||||
/// use oxigraph::sparql::{EvaluationError, Query, QueryOptions, QueryResults, ServiceHandler};
|
||||
/// use oxigraph::store::Store;
|
||||
///
|
||||
/// struct TestServiceHandler {
|
||||
/// store: Store,
|
||||
/// }
|
||||
///
|
||||
/// impl ServiceHandler for TestServiceHandler {
|
||||
/// type Error = EvaluationError;
|
||||
///
|
||||
/// fn handle(
|
||||
/// &self,
|
||||
/// service_name: NamedNode,
|
||||
/// query: Query,
|
||||
/// ) -> Result<QueryResults, Self::Error> {
|
||||
/// if service_name == "http://example.com/service" {
|
||||
/// self.store.query(query)
|
||||
/// } else {
|
||||
/// panic!()
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let store = Store::new()?;
|
||||
/// let service = TestServiceHandler {
|
||||
/// store: Store::new()?,
|
||||
/// };
|
||||
/// let ex = NamedNodeRef::new("http://example.com")?;
|
||||
/// service
|
||||
/// .store
|
||||
/// .insert(QuadRef::new(ex, ex, ex, GraphNameRef::DefaultGraph))?;
|
||||
///
|
||||
/// if let QueryResults::Solutions(mut solutions) = store.query_opt(
|
||||
/// "SELECT ?s WHERE { SERVICE <http://example.com/service> { ?s ?p ?o } }",
|
||||
/// QueryOptions::default().with_service_handler(service),
|
||||
/// )? {
|
||||
/// assert_eq!(solutions.next().unwrap()?.get("s"), Some(&ex.into()));
|
||||
/// }
|
||||
/// # Result::<_,Box<dyn std::error::Error>>::Ok(())
|
||||
/// ```
|
||||
pub trait ServiceHandler: Send + Sync { |
||||
/// The service evaluation error.
|
||||
type Error: Error + Send + Sync + 'static; |
||||
|
||||
/// Evaluates a [`Query`] against a given service identified by a [`NamedNode`].
|
||||
fn handle(&self, service_name: NamedNode, query: Query) -> Result<QueryResults, Self::Error>; |
||||
} |
||||
|
||||
pub struct EmptyServiceHandler; |
||||
|
||||
impl ServiceHandler for EmptyServiceHandler { |
||||
type Error = EvaluationError; |
||||
|
||||
fn handle(&self, name: NamedNode, _: Query) -> Result<QueryResults, Self::Error> { |
||||
Err(EvaluationError::UnsupportedService(name)) |
||||
} |
||||
} |
||||
|
||||
pub struct ErrorConversionServiceHandler<S: ServiceHandler> { |
||||
handler: S, |
||||
} |
||||
|
||||
impl<S: ServiceHandler> ErrorConversionServiceHandler<S> { |
||||
pub fn wrap(handler: S) -> Self { |
||||
Self { handler } |
||||
} |
||||
} |
||||
|
||||
impl<S: ServiceHandler> ServiceHandler for ErrorConversionServiceHandler<S> { |
||||
type Error = EvaluationError; |
||||
|
||||
fn handle(&self, service_name: NamedNode, query: Query) -> Result<QueryResults, Self::Error> { |
||||
self.handler |
||||
.handle(service_name, query) |
||||
.map_err(|e| EvaluationError::Service(Box::new(e))) |
||||
} |
||||
} |
||||
|
||||
pub struct SimpleServiceHandler { |
||||
client: Client, |
||||
} |
||||
|
||||
impl SimpleServiceHandler { |
||||
pub fn new(http_timeout: Option<Duration>, http_redirection_limit: usize) -> Self { |
||||
Self { |
||||
client: Client::new(http_timeout, http_redirection_limit), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl ServiceHandler for SimpleServiceHandler { |
||||
type Error = EvaluationError; |
||||
|
||||
fn handle(&self, service_name: NamedNode, query: Query) -> Result<QueryResults, Self::Error> { |
||||
let (content_type, body) = self |
||||
.client |
||||
.post( |
||||
service_name.as_str(), |
||||
query.to_string().into_bytes(), |
||||
"application/sparql-query", |
||||
"application/sparql-results+json, application/sparql-results+xml", |
||||
) |
||||
.map_err(|e| EvaluationError::Service(Box::new(e)))?; |
||||
let format = QueryResultsFormat::from_media_type(&content_type) |
||||
.ok_or_else(|| EvaluationError::UnsupportedContentType(content_type))?; |
||||
Ok(QueryResults::read(body, format)?) |
||||
} |
||||
} |
@ -0,0 +1,565 @@ |
||||
use crate::io::{RdfFormat, RdfParser}; |
||||
use crate::model::{GraphName as OxGraphName, GraphNameRef, Quad as OxQuad}; |
||||
use crate::sparql::algebra::QueryDataset; |
||||
use crate::sparql::dataset::DatasetView; |
||||
use crate::sparql::eval::{EncodedTuple, SimpleEvaluator}; |
||||
use crate::sparql::http::Client; |
||||
use crate::sparql::{EvaluationError, Update, UpdateOptions}; |
||||
use crate::storage::numeric_encoder::{Decoder, EncodedTerm}; |
||||
use crate::storage::StorageWriter; |
||||
use oxiri::Iri; |
||||
use spargebra::algebra::{GraphPattern, GraphTarget}; |
||||
use spargebra::term::{ |
||||
BlankNode, GraphName, GraphNamePattern, GroundQuad, GroundQuadPattern, GroundSubject, |
||||
GroundTerm, GroundTermPattern, GroundTriple, GroundTriplePattern, NamedNode, NamedNodePattern, |
||||
Quad, QuadPattern, Subject, Term, TermPattern, Triple, TriplePattern, Variable, |
||||
}; |
||||
use spargebra::GraphUpdateOperation; |
||||
use sparopt::Optimizer; |
||||
use std::collections::HashMap; |
||||
use std::io; |
||||
use std::rc::Rc; |
||||
use std::sync::Arc; |
||||
|
||||
pub fn evaluate_update<'a, 'b: 'a>( |
||||
transaction: &'a mut StorageWriter<'b>, |
||||
update: &Update, |
||||
options: &UpdateOptions, |
||||
) -> Result<(), EvaluationError> { |
||||
SimpleUpdateEvaluator { |
||||
transaction, |
||||
base_iri: update.inner.base_iri.clone().map(Rc::new), |
||||
options: options.clone(), |
||||
client: Client::new( |
||||
options.query_options.http_timeout, |
||||
options.query_options.http_redirection_limit, |
||||
), |
||||
} |
||||
.eval_all(&update.inner.operations, &update.using_datasets) |
||||
} |
||||
|
||||
struct SimpleUpdateEvaluator<'a, 'b> { |
||||
transaction: &'a mut StorageWriter<'b>, |
||||
base_iri: Option<Rc<Iri<String>>>, |
||||
options: UpdateOptions, |
||||
client: Client, |
||||
} |
||||
|
||||
impl<'a, 'b: 'a> SimpleUpdateEvaluator<'a, 'b> { |
||||
fn eval_all( |
||||
&mut self, |
||||
updates: &[GraphUpdateOperation], |
||||
using_datasets: &[Option<QueryDataset>], |
||||
) -> Result<(), EvaluationError> { |
||||
for (update, using_dataset) in updates.iter().zip(using_datasets) { |
||||
self.eval(update, using_dataset)?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
fn eval( |
||||
&mut self, |
||||
update: &GraphUpdateOperation, |
||||
using_dataset: &Option<QueryDataset>, |
||||
) -> Result<(), EvaluationError> { |
||||
match update { |
||||
GraphUpdateOperation::InsertData { data } => self.eval_insert_data(data), |
||||
GraphUpdateOperation::DeleteData { data } => self.eval_delete_data(data), |
||||
GraphUpdateOperation::DeleteInsert { |
||||
delete, |
||||
insert, |
||||
pattern, |
||||
.. |
||||
} => self.eval_delete_insert( |
||||
delete, |
||||
insert, |
||||
using_dataset.as_ref().unwrap_or(&QueryDataset::new()), |
||||
pattern, |
||||
), |
||||
GraphUpdateOperation::Load { |
||||
silent, |
||||
source, |
||||
destination, |
||||
} => { |
||||
if let Err(error) = self.eval_load(source, destination) { |
||||
if *silent { |
||||
Ok(()) |
||||
} else { |
||||
Err(error) |
||||
} |
||||
} else { |
||||
Ok(()) |
||||
} |
||||
} |
||||
GraphUpdateOperation::Clear { graph, silent } => self.eval_clear(graph, *silent), |
||||
GraphUpdateOperation::Create { graph, silent } => self.eval_create(graph, *silent), |
||||
GraphUpdateOperation::Drop { graph, silent } => self.eval_drop(graph, *silent), |
||||
} |
||||
} |
||||
|
||||
fn eval_insert_data(&mut self, data: &[Quad]) -> Result<(), EvaluationError> { |
||||
let mut bnodes = HashMap::new(); |
||||
for quad in data { |
||||
let quad = Self::convert_quad(quad, &mut bnodes); |
||||
self.transaction.insert(quad.as_ref())?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
fn eval_delete_data(&mut self, data: &[GroundQuad]) -> Result<(), EvaluationError> { |
||||
for quad in data { |
||||
let quad = Self::convert_ground_quad(quad); |
||||
self.transaction.remove(quad.as_ref())?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
fn eval_delete_insert( |
||||
&mut self, |
||||
delete: &[GroundQuadPattern], |
||||
insert: &[QuadPattern], |
||||
using: &QueryDataset, |
||||
algebra: &GraphPattern, |
||||
) -> Result<(), EvaluationError> { |
||||
let dataset = Rc::new(DatasetView::new(self.transaction.reader(), using)); |
||||
let mut pattern = sparopt::algebra::GraphPattern::from(algebra); |
||||
if !self.options.query_options.without_optimizations { |
||||
pattern = Optimizer::optimize_graph_pattern(sparopt::algebra::GraphPattern::Reduced { |
||||
inner: Box::new(pattern), |
||||
}); |
||||
} |
||||
let evaluator = SimpleEvaluator::new( |
||||
Rc::clone(&dataset), |
||||
self.base_iri.clone(), |
||||
self.options.query_options.service_handler(), |
||||
Arc::new(self.options.query_options.custom_functions.clone()), |
||||
false, |
||||
); |
||||
let mut variables = Vec::new(); |
||||
let mut bnodes = HashMap::new(); |
||||
let (eval, _) = evaluator.graph_pattern_evaluator(&pattern, &mut variables); |
||||
let tuples = |
||||
eval(EncodedTuple::with_capacity(variables.len())).collect::<Result<Vec<_>, _>>()?; // TODO: would be much better to stream
|
||||
for tuple in tuples { |
||||
for quad in delete { |
||||
if let Some(quad) = |
||||
Self::convert_ground_quad_pattern(quad, &variables, &tuple, &dataset)? |
||||
{ |
||||
self.transaction.remove(quad.as_ref())?; |
||||
} |
||||
} |
||||
for quad in insert { |
||||
if let Some(quad) = |
||||
Self::convert_quad_pattern(quad, &variables, &tuple, &dataset, &mut bnodes)? |
||||
{ |
||||
self.transaction.insert(quad.as_ref())?; |
||||
} |
||||
} |
||||
bnodes.clear(); |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
fn eval_load(&mut self, from: &NamedNode, to: &GraphName) -> Result<(), EvaluationError> { |
||||
let (content_type, body) = self |
||||
.client |
||||
.get( |
||||
from.as_str(), |
||||
"application/n-triples, text/turtle, application/rdf+xml", |
||||
) |
||||
.map_err(|e| EvaluationError::Service(Box::new(e)))?; |
||||
let format = RdfFormat::from_media_type(&content_type) |
||||
.ok_or_else(|| EvaluationError::UnsupportedContentType(content_type))?; |
||||
let to_graph_name = match to { |
||||
GraphName::NamedNode(graph_name) => graph_name.into(), |
||||
GraphName::DefaultGraph => GraphNameRef::DefaultGraph, |
||||
}; |
||||
let mut parser = RdfParser::from_format(format) |
||||
.rename_blank_nodes() |
||||
.without_named_graphs() |
||||
.with_default_graph(to_graph_name); |
||||
parser = parser.with_base_iri(from.as_str()).map_err(|e| { |
||||
EvaluationError::Service(Box::new(io::Error::new( |
||||
io::ErrorKind::InvalidInput, |
||||
format!("Invalid URL: {from}: {e}"), |
||||
))) |
||||
})?; |
||||
for q in parser.parse_read(body) { |
||||
self.transaction.insert(q?.as_ref())?; |
||||
} |
||||
Ok(()) |
||||
} |
||||
|
||||
fn eval_create(&mut self, graph_name: &NamedNode, silent: bool) -> Result<(), EvaluationError> { |
||||
if self.transaction.insert_named_graph(graph_name.into())? || silent { |
||||
Ok(()) |
||||
} else { |
||||
Err(EvaluationError::GraphAlreadyExists(graph_name.clone())) |
||||
} |
||||
} |
||||
|
||||
fn eval_clear(&mut self, graph: &GraphTarget, silent: bool) -> Result<(), EvaluationError> { |
||||
match graph { |
||||
GraphTarget::NamedNode(graph_name) => { |
||||
if self |
||||
.transaction |
||||
.reader() |
||||
.contains_named_graph(&graph_name.as_ref().into())? |
||||
{ |
||||
Ok(self.transaction.clear_graph(graph_name.into())?) |
||||
} else if silent { |
||||
Ok(()) |
||||
} else { |
||||
Err(EvaluationError::GraphDoesNotExist(graph_name.clone())) |
||||
} |
||||
} |
||||
GraphTarget::DefaultGraph => { |
||||
self.transaction.clear_graph(GraphNameRef::DefaultGraph)?; |
||||
Ok(()) |
||||
} |
||||
GraphTarget::NamedGraphs => Ok(self.transaction.clear_all_named_graphs()?), |
||||
GraphTarget::AllGraphs => Ok(self.transaction.clear_all_graphs()?), |
||||
} |
||||
} |
||||
|
||||
fn eval_drop(&mut self, graph: &GraphTarget, silent: bool) -> Result<(), EvaluationError> { |
||||
match graph { |
||||
GraphTarget::NamedNode(graph_name) => { |
||||
if self.transaction.remove_named_graph(graph_name.into())? || silent { |
||||
Ok(()) |
||||
} else { |
||||
Err(EvaluationError::GraphDoesNotExist(graph_name.clone())) |
||||
} |
||||
} |
||||
GraphTarget::DefaultGraph => { |
||||
Ok(self.transaction.clear_graph(GraphNameRef::DefaultGraph)?) |
||||
} |
||||
GraphTarget::NamedGraphs => Ok(self.transaction.remove_all_named_graphs()?), |
||||
GraphTarget::AllGraphs => Ok(self.transaction.clear()?), |
||||
} |
||||
} |
||||
|
||||
fn convert_quad(quad: &Quad, bnodes: &mut HashMap<BlankNode, BlankNode>) -> OxQuad { |
||||
OxQuad { |
||||
subject: match &quad.subject { |
||||
Subject::NamedNode(subject) => subject.clone().into(), |
||||
Subject::BlankNode(subject) => Self::convert_blank_node(subject, bnodes).into(), |
||||
Subject::Triple(subject) => Self::convert_triple(subject, bnodes).into(), |
||||
}, |
||||
predicate: quad.predicate.clone(), |
||||
object: match &quad.object { |
||||
Term::NamedNode(object) => object.clone().into(), |
||||
Term::BlankNode(object) => Self::convert_blank_node(object, bnodes).into(), |
||||
Term::Literal(object) => object.clone().into(), |
||||
Term::Triple(subject) => Self::convert_triple(subject, bnodes).into(), |
||||
}, |
||||
graph_name: match &quad.graph_name { |
||||
GraphName::NamedNode(graph_name) => graph_name.clone().into(), |
||||
GraphName::DefaultGraph => OxGraphName::DefaultGraph, |
||||
}, |
||||
} |
||||
} |
||||
|
||||
fn convert_triple(triple: &Triple, bnodes: &mut HashMap<BlankNode, BlankNode>) -> Triple { |
||||
Triple { |
||||
subject: match &triple.subject { |
||||
Subject::NamedNode(subject) => subject.clone().into(), |
||||
Subject::BlankNode(subject) => Self::convert_blank_node(subject, bnodes).into(), |
||||
Subject::Triple(subject) => Self::convert_triple(subject, bnodes).into(), |
||||
}, |
||||
predicate: triple.predicate.clone(), |
||||
object: match &triple.object { |
||||
Term::NamedNode(object) => object.clone().into(), |
||||
Term::BlankNode(object) => Self::convert_blank_node(object, bnodes).into(), |
||||
Term::Literal(object) => object.clone().into(), |
||||
Term::Triple(subject) => Self::convert_triple(subject, bnodes).into(), |
||||
}, |
||||
} |
||||
} |
||||
|
||||
fn convert_blank_node( |
||||
node: &BlankNode, |
||||
bnodes: &mut HashMap<BlankNode, BlankNode>, |
||||
) -> BlankNode { |
||||
bnodes.entry(node.clone()).or_default().clone() |
||||
} |
||||
|
||||
fn convert_ground_quad(quad: &GroundQuad) -> OxQuad { |
||||
OxQuad { |
||||
subject: match &quad.subject { |
||||
GroundSubject::NamedNode(subject) => subject.clone().into(), |
||||
GroundSubject::Triple(subject) => Self::convert_ground_triple(subject).into(), |
||||
}, |
||||
predicate: quad.predicate.clone(), |
||||
object: match &quad.object { |
||||
GroundTerm::NamedNode(object) => object.clone().into(), |
||||
GroundTerm::Literal(object) => object.clone().into(), |
||||
GroundTerm::Triple(subject) => Self::convert_ground_triple(subject).into(), |
||||
}, |
||||
graph_name: match &quad.graph_name { |
||||
GraphName::NamedNode(graph_name) => graph_name.clone().into(), |
||||
GraphName::DefaultGraph => OxGraphName::DefaultGraph, |
||||
}, |
||||
} |
||||
} |
||||
|
||||
fn convert_ground_triple(triple: &GroundTriple) -> Triple { |
||||
Triple { |
||||
subject: match &triple.subject { |
||||
GroundSubject::NamedNode(subject) => subject.clone().into(), |
||||
GroundSubject::Triple(subject) => Self::convert_ground_triple(subject).into(), |
||||
}, |
||||
predicate: triple.predicate.clone(), |
||||
object: match &triple.object { |
||||
GroundTerm::NamedNode(object) => object.clone().into(), |
||||
GroundTerm::Literal(object) => object.clone().into(), |
||||
GroundTerm::Triple(subject) => Self::convert_ground_triple(subject).into(), |
||||
}, |
||||
} |
||||
} |
||||
|
||||
fn convert_quad_pattern( |
||||
quad: &QuadPattern, |
||||
variables: &[Variable], |
||||
values: &EncodedTuple, |
||||
dataset: &DatasetView, |
||||
bnodes: &mut HashMap<BlankNode, BlankNode>, |
||||
) -> Result<Option<OxQuad>, EvaluationError> { |
||||
Ok(Some(OxQuad { |
||||
subject: match Self::convert_term_or_var( |
||||
&quad.subject, |
||||
variables, |
||||
values, |
||||
dataset, |
||||
bnodes, |
||||
)? { |
||||
Some(Term::NamedNode(node)) => node.into(), |
||||
Some(Term::BlankNode(node)) => node.into(), |
||||
Some(Term::Triple(triple)) => triple.into(), |
||||
Some(Term::Literal(_)) | None => return Ok(None), |
||||
}, |
||||
predicate: if let Some(predicate) = |
||||
Self::convert_named_node_or_var(&quad.predicate, variables, values, dataset)? |
||||
{ |
||||
predicate |
||||
} else { |
||||
return Ok(None); |
||||
}, |
||||
object: if let Some(object) = |
||||
Self::convert_term_or_var(&quad.object, variables, values, dataset, bnodes)? |
||||
{ |
||||
object |
||||
} else { |
||||
return Ok(None); |
||||
}, |
||||
graph_name: if let Some(graph_name) = |
||||
Self::convert_graph_name_or_var(&quad.graph_name, variables, values, dataset)? |
||||
{ |
||||
graph_name |
||||
} else { |
||||
return Ok(None); |
||||
}, |
||||
})) |
||||
} |
||||
|
||||
fn convert_term_or_var( |
||||
term: &TermPattern, |
||||
variables: &[Variable], |
||||
values: &EncodedTuple, |
||||
dataset: &DatasetView, |
||||
bnodes: &mut HashMap<BlankNode, BlankNode>, |
||||
) -> Result<Option<Term>, EvaluationError> { |
||||
Ok(match term { |
||||
TermPattern::NamedNode(term) => Some(term.clone().into()), |
||||
TermPattern::BlankNode(bnode) => Some(Self::convert_blank_node(bnode, bnodes).into()), |
||||
TermPattern::Literal(term) => Some(term.clone().into()), |
||||
TermPattern::Triple(triple) => { |
||||
Self::convert_triple_pattern(triple, variables, values, dataset, bnodes)? |
||||
.map(Into::into) |
||||
} |
||||
TermPattern::Variable(v) => Self::lookup_variable(v, variables, values) |
||||
.map(|node| dataset.decode_term(&node)) |
||||
.transpose()?, |
||||
}) |
||||
} |
||||
|
||||
fn convert_named_node_or_var( |
||||
term: &NamedNodePattern, |
||||
variables: &[Variable], |
||||
values: &EncodedTuple, |
||||
dataset: &DatasetView, |
||||
) -> Result<Option<NamedNode>, EvaluationError> { |
||||
Ok(match term { |
||||
NamedNodePattern::NamedNode(term) => Some(term.clone()), |
||||
NamedNodePattern::Variable(v) => Self::lookup_variable(v, variables, values) |
||||
.map(|node| dataset.decode_named_node(&node)) |
||||
.transpose()?, |
||||
}) |
||||
} |
||||
|
||||
fn convert_graph_name_or_var( |
||||
term: &GraphNamePattern, |
||||
variables: &[Variable], |
||||
values: &EncodedTuple, |
||||
dataset: &DatasetView, |
||||
) -> Result<Option<OxGraphName>, EvaluationError> { |
||||
match term { |
||||
GraphNamePattern::NamedNode(term) => Ok(Some(term.clone().into())), |
||||
GraphNamePattern::DefaultGraph => Ok(Some(OxGraphName::DefaultGraph)), |
||||
GraphNamePattern::Variable(v) => Self::lookup_variable(v, variables, values) |
||||
.map(|node| { |
||||
Ok(if node == EncodedTerm::DefaultGraph { |
||||
OxGraphName::DefaultGraph |
||||
} else { |
||||
dataset.decode_named_node(&node)?.into() |
||||
}) |
||||
}) |
||||
.transpose(), |
||||
} |
||||
} |
||||
|
||||
fn convert_triple_pattern( |
||||
triple: &TriplePattern, |
||||
variables: &[Variable], |
||||
values: &EncodedTuple, |
||||
dataset: &DatasetView, |
||||
bnodes: &mut HashMap<BlankNode, BlankNode>, |
||||
) -> Result<Option<Triple>, EvaluationError> { |
||||
Ok(Some(Triple { |
||||
subject: match Self::convert_term_or_var( |
||||
&triple.subject, |
||||
variables, |
||||
values, |
||||
dataset, |
||||
bnodes, |
||||
)? { |
||||
Some(Term::NamedNode(node)) => node.into(), |
||||
Some(Term::BlankNode(node)) => node.into(), |
||||
Some(Term::Triple(triple)) => triple.into(), |
||||
Some(Term::Literal(_)) | None => return Ok(None), |
||||
}, |
||||
predicate: if let Some(predicate) = |
||||
Self::convert_named_node_or_var(&triple.predicate, variables, values, dataset)? |
||||
{ |
||||
predicate |
||||
} else { |
||||
return Ok(None); |
||||
}, |
||||
object: if let Some(object) = |
||||
Self::convert_term_or_var(&triple.object, variables, values, dataset, bnodes)? |
||||
{ |
||||
object |
||||
} else { |
||||
return Ok(None); |
||||
}, |
||||
})) |
||||
} |
||||
|
||||
fn convert_ground_quad_pattern( |
||||
quad: &GroundQuadPattern, |
||||
variables: &[Variable], |
||||
values: &EncodedTuple, |
||||
dataset: &DatasetView, |
||||
) -> Result<Option<OxQuad>, EvaluationError> { |
||||
Ok(Some(OxQuad { |
||||
subject: match Self::convert_ground_term_or_var( |
||||
&quad.subject, |
||||
variables, |
||||
values, |
||||
dataset, |
||||
)? { |
||||
Some(Term::NamedNode(node)) => node.into(), |
||||
Some(Term::BlankNode(node)) => node.into(), |
||||
Some(Term::Triple(triple)) => triple.into(), |
||||
Some(Term::Literal(_)) | None => return Ok(None), |
||||
}, |
||||
predicate: if let Some(predicate) = |
||||
Self::convert_named_node_or_var(&quad.predicate, variables, values, dataset)? |
||||
{ |
||||
predicate |
||||
} else { |
||||
return Ok(None); |
||||
}, |
||||
object: if let Some(object) = |
||||
Self::convert_ground_term_or_var(&quad.object, variables, values, dataset)? |
||||
{ |
||||
object |
||||
} else { |
||||
return Ok(None); |
||||
}, |
||||
graph_name: if let Some(graph_name) = |
||||
Self::convert_graph_name_or_var(&quad.graph_name, variables, values, dataset)? |
||||
{ |
||||
graph_name |
||||
} else { |
||||
return Ok(None); |
||||
}, |
||||
})) |
||||
} |
||||
|
||||
fn convert_ground_term_or_var( |
||||
term: &GroundTermPattern, |
||||
variables: &[Variable], |
||||
values: &EncodedTuple, |
||||
dataset: &DatasetView, |
||||
) -> Result<Option<Term>, EvaluationError> { |
||||
Ok(match term { |
||||
GroundTermPattern::NamedNode(term) => Some(term.clone().into()), |
||||
GroundTermPattern::Literal(term) => Some(term.clone().into()), |
||||
GroundTermPattern::Triple(triple) => { |
||||
Self::convert_ground_triple_pattern(triple, variables, values, dataset)? |
||||
.map(Into::into) |
||||
} |
||||
GroundTermPattern::Variable(v) => Self::lookup_variable(v, variables, values) |
||||
.map(|node| dataset.decode_term(&node)) |
||||
.transpose()?, |
||||
}) |
||||
} |
||||
|
||||
fn convert_ground_triple_pattern( |
||||
triple: &GroundTriplePattern, |
||||
variables: &[Variable], |
||||
values: &EncodedTuple, |
||||
dataset: &DatasetView, |
||||
) -> Result<Option<Triple>, EvaluationError> { |
||||
Ok(Some(Triple { |
||||
subject: match Self::convert_ground_term_or_var( |
||||
&triple.subject, |
||||
variables, |
||||
values, |
||||
dataset, |
||||
)? { |
||||
Some(Term::NamedNode(node)) => node.into(), |
||||
Some(Term::BlankNode(node)) => node.into(), |
||||
Some(Term::Triple(triple)) => triple.into(), |
||||
Some(Term::Literal(_)) | None => return Ok(None), |
||||
}, |
||||
predicate: if let Some(predicate) = |
||||
Self::convert_named_node_or_var(&triple.predicate, variables, values, dataset)? |
||||
{ |
||||
predicate |
||||
} else { |
||||
return Ok(None); |
||||
}, |
||||
object: if let Some(object) = |
||||
Self::convert_ground_term_or_var(&triple.object, variables, values, dataset)? |
||||
{ |
||||
object |
||||
} else { |
||||
return Ok(None); |
||||
}, |
||||
})) |
||||
} |
||||
|
||||
fn lookup_variable( |
||||
v: &Variable, |
||||
variables: &[Variable], |
||||
values: &EncodedTuple, |
||||
) -> Option<EncodedTerm> { |
||||
variables |
||||
.iter() |
||||
.position(|v2| v == v2) |
||||
.and_then(|i| values.get(i)) |
||||
.cloned() |
||||
} |
||||
} |
@ -0,0 +1,310 @@ |
||||
//! TODO: This storage is dramatically naive.
|
||||
|
||||
use crate::storage::StorageError; |
||||
use crate::store::CorruptionError; |
||||
use std::cell::RefCell; |
||||
use std::collections::{BTreeMap, HashMap}; |
||||
use std::error::Error; |
||||
use std::mem::transmute; |
||||
use std::rc::{Rc, Weak}; |
||||
use std::sync::{Arc, RwLock, RwLockWriteGuard}; |
||||
|
||||
pub struct ColumnFamilyDefinition { |
||||
pub name: &'static str, |
||||
pub use_iter: bool, |
||||
pub min_prefix_size: usize, |
||||
pub unordered_writes: bool, |
||||
} |
||||
|
||||
#[derive(Clone)] |
||||
pub struct Db(Arc<RwLock<HashMap<ColumnFamily, BTreeMap<Vec<u8>, Vec<u8>>>>>); |
||||
|
||||
impl Db { |
||||
#[allow(clippy::unnecessary_wraps)] |
||||
pub fn new(column_families: Vec<ColumnFamilyDefinition>) -> Result<Self, StorageError> { |
||||
let mut trees = HashMap::new(); |
||||
for cf in column_families { |
||||
trees.insert(ColumnFamily(cf.name), BTreeMap::default()); |
||||
} |
||||
trees.entry(ColumnFamily("default")).or_default(); // We make sure that "default" key exists.
|
||||
Ok(Self(Arc::new(RwLock::new(trees)))) |
||||
} |
||||
|
||||
#[allow(clippy::unwrap_in_result)] |
||||
pub fn column_family(&self, name: &'static str) -> Result<ColumnFamily, StorageError> { |
||||
let column_family = ColumnFamily(name); |
||||
if self.0.read().unwrap().contains_key(&column_family) { |
||||
Ok(column_family) |
||||
} else { |
||||
Err(CorruptionError::from_missing_column_family_name(name).into()) |
||||
} |
||||
} |
||||
|
||||
#[must_use] |
||||
pub fn snapshot(&self) -> Reader { |
||||
Reader(InnerReader::Simple(Arc::clone(&self.0))) |
||||
} |
||||
|
||||
#[allow(clippy::unwrap_in_result)] |
||||
pub fn transaction<'a, 'b: 'a, T, E: Error + 'static + From<StorageError>>( |
||||
&'b self, |
||||
f: impl Fn(Transaction<'a>) -> Result<T, E>, |
||||
) -> Result<T, E> { |
||||
f(Transaction(Rc::new(RefCell::new(self.0.write().unwrap())))) |
||||
} |
||||
} |
||||
|
||||
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] |
||||
pub struct ColumnFamily(&'static str); |
||||
|
||||
pub struct Reader(InnerReader); |
||||
|
||||
enum InnerReader { |
||||
Simple(Arc<RwLock<HashMap<ColumnFamily, BTreeMap<Vec<u8>, Vec<u8>>>>>), |
||||
Transaction( |
||||
Weak<RefCell<RwLockWriteGuard<'static, HashMap<ColumnFamily, BTreeMap<Vec<u8>, Vec<u8>>>>>>, |
||||
), |
||||
} |
||||
|
||||
impl Reader { |
||||
#[allow(clippy::unwrap_in_result)] |
||||
pub fn get( |
||||
&self, |
||||
column_family: &ColumnFamily, |
||||
key: &[u8], |
||||
) -> Result<Option<Vec<u8>>, StorageError> { |
||||
match &self.0 { |
||||
InnerReader::Simple(reader) => Ok(reader |
||||
.read() |
||||
.unwrap() |
||||
.get(column_family) |
||||
.and_then(|cf| cf.get(key).cloned())), |
||||
InnerReader::Transaction(reader) => { |
||||
if let Some(reader) = reader.upgrade() { |
||||
Ok((*reader) |
||||
.borrow() |
||||
.get(column_family) |
||||
.and_then(|cf| cf.get(key).cloned())) |
||||
} else { |
||||
Err(StorageError::Other( |
||||
"The transaction is already ended".into(), |
||||
)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[allow(clippy::unwrap_in_result)] |
||||
pub fn contains_key( |
||||
&self, |
||||
column_family: &ColumnFamily, |
||||
key: &[u8], |
||||
) -> Result<bool, StorageError> { |
||||
match &self.0 { |
||||
InnerReader::Simple(reader) => Ok(reader |
||||
.read() |
||||
.unwrap() |
||||
.get(column_family) |
||||
.map_or(false, |cf| cf.contains_key(key))), |
||||
InnerReader::Transaction(reader) => { |
||||
if let Some(reader) = reader.upgrade() { |
||||
Ok((*reader) |
||||
.borrow() |
||||
.get(column_family) |
||||
.map_or(false, |cf| cf.contains_key(key))) |
||||
} else { |
||||
Err(StorageError::Other( |
||||
"The transaction is already ended".into(), |
||||
)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[allow(clippy::iter_not_returning_iterator)] |
||||
pub fn iter(&self, column_family: &ColumnFamily) -> Result<Iter, StorageError> { |
||||
self.scan_prefix(column_family, &[]) |
||||
} |
||||
|
||||
#[allow(clippy::unwrap_in_result)] |
||||
pub fn scan_prefix( |
||||
&self, |
||||
column_family: &ColumnFamily, |
||||
prefix: &[u8], |
||||
) -> Result<Iter, StorageError> { |
||||
let data: Vec<_> = match &self.0 { |
||||
InnerReader::Simple(reader) => { |
||||
let trees = reader.read().unwrap(); |
||||
let Some(tree) = trees.get(column_family) else { |
||||
return Ok(Iter { |
||||
iter: Vec::new().into_iter(), |
||||
current: None, |
||||
}); |
||||
}; |
||||
if prefix.is_empty() { |
||||
tree.iter().map(|(k, v)| (k.clone(), v.clone())).collect() |
||||
} else { |
||||
tree.range(prefix.to_vec()..) |
||||
.take_while(|(k, _)| k.starts_with(prefix)) |
||||
.map(|(k, v)| (k.clone(), v.clone())) |
||||
.collect() |
||||
} |
||||
} |
||||
InnerReader::Transaction(reader) => { |
||||
let Some(reader) = reader.upgrade() else { |
||||
return Err(StorageError::Other( |
||||
"The transaction is already ended".into(), |
||||
)); |
||||
}; |
||||
let trees = (*reader).borrow(); |
||||
let Some(tree) = trees.get(column_family) else { |
||||
return Ok(Iter { |
||||
iter: Vec::new().into_iter(), |
||||
current: None, |
||||
}); |
||||
}; |
||||
if prefix.is_empty() { |
||||
tree.iter().map(|(k, v)| (k.clone(), v.clone())).collect() |
||||
} else { |
||||
tree.range(prefix.to_vec()..) |
||||
.take_while(|(k, _)| k.starts_with(prefix)) |
||||
.map(|(k, v)| (k.clone(), v.clone())) |
||||
.collect() |
||||
} |
||||
} |
||||
}; |
||||
let mut iter = data.into_iter(); |
||||
let current = iter.next(); |
||||
Ok(Iter { iter, current }) |
||||
} |
||||
|
||||
#[allow(clippy::unwrap_in_result)] |
||||
pub fn len(&self, column_family: &ColumnFamily) -> Result<usize, StorageError> { |
||||
match &self.0 { |
||||
InnerReader::Simple(reader) => Ok(reader |
||||
.read() |
||||
.unwrap() |
||||
.get(column_family) |
||||
.map_or(0, BTreeMap::len)), |
||||
InnerReader::Transaction(reader) => { |
||||
if let Some(reader) = reader.upgrade() { |
||||
Ok((*reader) |
||||
.borrow() |
||||
.get(column_family) |
||||
.map_or(0, BTreeMap::len)) |
||||
} else { |
||||
Err(StorageError::Other( |
||||
"The transaction is already ended".into(), |
||||
)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[allow(clippy::unwrap_in_result)] |
||||
pub fn is_empty(&self, column_family: &ColumnFamily) -> Result<bool, StorageError> { |
||||
match &self.0 { |
||||
InnerReader::Simple(reader) => Ok(reader |
||||
.read() |
||||
.unwrap() |
||||
.get(column_family) |
||||
.map_or(true, BTreeMap::is_empty)), |
||||
InnerReader::Transaction(reader) => { |
||||
if let Some(reader) = reader.upgrade() { |
||||
Ok((*reader) |
||||
.borrow() |
||||
.get(column_family) |
||||
.map_or(true, BTreeMap::is_empty)) |
||||
} else { |
||||
Err(StorageError::Other( |
||||
"The transaction is already ended".into(), |
||||
)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub struct Transaction<'a>( |
||||
Rc<RefCell<RwLockWriteGuard<'a, HashMap<ColumnFamily, BTreeMap<Vec<u8>, Vec<u8>>>>>>, |
||||
); |
||||
|
||||
impl Transaction<'_> { |
||||
#[allow(unsafe_code, clippy::useless_transmute)] |
||||
pub fn reader(&self) -> Reader { |
||||
// SAFETY: This transmute is safe because we take a weak reference and the only Rc reference used is guarded by the lifetime.
|
||||
Reader(InnerReader::Transaction(Rc::downgrade(unsafe { |
||||
transmute(&self.0) |
||||
}))) |
||||
} |
||||
|
||||
#[allow(clippy::unnecessary_wraps)] |
||||
pub fn contains_key_for_update( |
||||
&self, |
||||
column_family: &ColumnFamily, |
||||
key: &[u8], |
||||
) -> Result<bool, StorageError> { |
||||
Ok((*self.0) |
||||
.borrow() |
||||
.get(column_family) |
||||
.map_or(false, |cf| cf.contains_key(key))) |
||||
} |
||||
|
||||
#[allow(clippy::unnecessary_wraps, clippy::unwrap_in_result)] |
||||
pub fn insert( |
||||
&mut self, |
||||
column_family: &ColumnFamily, |
||||
key: &[u8], |
||||
value: &[u8], |
||||
) -> Result<(), StorageError> { |
||||
self.0 |
||||
.borrow_mut() |
||||
.get_mut(column_family) |
||||
.unwrap() |
||||
.insert(key.into(), value.into()); |
||||
Ok(()) |
||||
} |
||||
|
||||
pub fn insert_empty( |
||||
&mut self, |
||||
column_family: &ColumnFamily, |
||||
key: &[u8], |
||||
) -> Result<(), StorageError> { |
||||
self.insert(column_family, key, &[]) |
||||
} |
||||
|
||||
#[allow(clippy::unnecessary_wraps, clippy::unwrap_in_result)] |
||||
pub fn remove(&mut self, column_family: &ColumnFamily, key: &[u8]) -> Result<(), StorageError> { |
||||
self.0 |
||||
.borrow_mut() |
||||
.get_mut(column_family) |
||||
.unwrap() |
||||
.remove(key); |
||||
Ok(()) |
||||
} |
||||
} |
||||
|
||||
pub struct Iter { |
||||
iter: std::vec::IntoIter<(Vec<u8>, Vec<u8>)>, |
||||
current: Option<(Vec<u8>, Vec<u8>)>, |
||||
} |
||||
|
||||
impl Iter { |
||||
pub fn key(&self) -> Option<&[u8]> { |
||||
Some(&self.current.as_ref()?.0) |
||||
} |
||||
|
||||
#[allow(dead_code)] |
||||
pub fn value(&self) -> Option<&[u8]> { |
||||
Some(&self.current.as_ref()?.1) |
||||
} |
||||
|
||||
pub fn next(&mut self) { |
||||
self.current = self.iter.next(); |
||||
} |
||||
|
||||
#[allow(clippy::unnecessary_wraps, clippy::unused_self)] |
||||
pub fn status(&self) -> Result<(), StorageError> { |
||||
Ok(()) |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
//! A storage backend
|
||||
//! RocksDB is available, if not in memory
|
||||
|
||||
#[cfg(any(target_family = "wasm"))] |
||||
pub use fallback::{ColumnFamily, ColumnFamilyDefinition, Db, Iter, Reader, Transaction}; |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
pub use oxi_rocksdb::{ColumnFamily, ColumnFamilyDefinition, Db, Iter, Reader, Transaction}; |
||||
|
||||
#[cfg(any(target_family = "wasm"))] |
||||
mod fallback; |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
mod oxi_rocksdb; |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,742 @@ |
||||
use crate::storage::error::{CorruptionError, StorageError}; |
||||
use crate::storage::numeric_encoder::{EncodedQuad, EncodedTerm, EncodedTriple, StrHash}; |
||||
use crate::storage::small_string::SmallString; |
||||
use oxsdatatypes::*; |
||||
use std::io::Read; |
||||
use std::mem::size_of; |
||||
|
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
pub const LATEST_STORAGE_VERSION: u64 = 1; |
||||
pub const WRITTEN_TERM_MAX_SIZE: usize = size_of::<u8>() + 2 * size_of::<StrHash>(); |
||||
|
||||
// Encoded term type blocks
|
||||
// 1-7: usual named nodes (except prefixes c.f. later)
|
||||
// 8-15: blank nodes
|
||||
// 16-47: literals
|
||||
// 48-55: triples
|
||||
// 56-64: future use
|
||||
// 64-127: default named node prefixes
|
||||
// 128-255: custom named node prefixes
|
||||
const TYPE_NAMED_NODE_ID: u8 = 1; |
||||
const TYPE_NUMERICAL_BLANK_NODE_ID: u8 = 8; |
||||
const TYPE_SMALL_BLANK_NODE_ID: u8 = 9; |
||||
const TYPE_BIG_BLANK_NODE_ID: u8 = 10; |
||||
const TYPE_SMALL_STRING_LITERAL: u8 = 16; |
||||
const TYPE_BIG_STRING_LITERAL: u8 = 17; |
||||
const TYPE_SMALL_SMALL_LANG_STRING_LITERAL: u8 = 20; |
||||
const TYPE_SMALL_BIG_LANG_STRING_LITERAL: u8 = 21; |
||||
const TYPE_BIG_SMALL_LANG_STRING_LITERAL: u8 = 22; |
||||
const TYPE_BIG_BIG_LANG_STRING_LITERAL: u8 = 23; |
||||
const TYPE_SMALL_TYPED_LITERAL: u8 = 24; |
||||
const TYPE_BIG_TYPED_LITERAL: u8 = 25; |
||||
const TYPE_BOOLEAN_LITERAL_TRUE: u8 = 28; |
||||
const TYPE_BOOLEAN_LITERAL_FALSE: u8 = 29; |
||||
const TYPE_FLOAT_LITERAL: u8 = 30; |
||||
const TYPE_DOUBLE_LITERAL: u8 = 31; |
||||
const TYPE_INTEGER_LITERAL: u8 = 32; |
||||
const TYPE_DECIMAL_LITERAL: u8 = 33; |
||||
const TYPE_DATE_TIME_LITERAL: u8 = 34; |
||||
const TYPE_TIME_LITERAL: u8 = 35; |
||||
const TYPE_DATE_LITERAL: u8 = 36; |
||||
const TYPE_G_YEAR_MONTH_LITERAL: u8 = 37; |
||||
const TYPE_G_YEAR_LITERAL: u8 = 38; |
||||
const TYPE_G_MONTH_DAY_LITERAL: u8 = 39; |
||||
const TYPE_G_DAY_LITERAL: u8 = 40; |
||||
const TYPE_G_MONTH_LITERAL: u8 = 41; |
||||
const TYPE_DURATION_LITERAL: u8 = 42; |
||||
const TYPE_YEAR_MONTH_DURATION_LITERAL: u8 = 43; |
||||
const TYPE_DAY_TIME_DURATION_LITERAL: u8 = 44; |
||||
const TYPE_TRIPLE: u8 = 48; |
||||
|
||||
#[derive(Clone, Copy)] |
||||
pub enum QuadEncoding { |
||||
Spog, |
||||
Posg, |
||||
Ospg, |
||||
Gspo, |
||||
Gpos, |
||||
Gosp, |
||||
Dspo, |
||||
Dpos, |
||||
Dosp, |
||||
} |
||||
|
||||
impl QuadEncoding { |
||||
pub fn decode(self, mut buffer: &[u8]) -> Result<EncodedQuad, StorageError> { |
||||
match self { |
||||
Self::Spog => buffer.read_spog_quad(), |
||||
Self::Posg => buffer.read_posg_quad(), |
||||
Self::Ospg => buffer.read_ospg_quad(), |
||||
Self::Gspo => buffer.read_gspo_quad(), |
||||
Self::Gpos => buffer.read_gpos_quad(), |
||||
Self::Gosp => buffer.read_gosp_quad(), |
||||
Self::Dspo => buffer.read_dspo_quad(), |
||||
Self::Dpos => buffer.read_dpos_quad(), |
||||
Self::Dosp => buffer.read_dosp_quad(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub fn decode_term(mut buffer: &[u8]) -> Result<EncodedTerm, StorageError> { |
||||
buffer.read_term() |
||||
} |
||||
|
||||
pub trait TermReader { |
||||
fn read_term(&mut self) -> Result<EncodedTerm, StorageError>; |
||||
|
||||
fn read_spog_quad(&mut self) -> Result<EncodedQuad, StorageError> { |
||||
let subject = self.read_term()?; |
||||
let predicate = self.read_term()?; |
||||
let object = self.read_term()?; |
||||
let graph_name = self.read_term()?; |
||||
Ok(EncodedQuad { |
||||
subject, |
||||
predicate, |
||||
object, |
||||
graph_name, |
||||
}) |
||||
} |
||||
|
||||
fn read_posg_quad(&mut self) -> Result<EncodedQuad, StorageError> { |
||||
let predicate = self.read_term()?; |
||||
let object = self.read_term()?; |
||||
let subject = self.read_term()?; |
||||
let graph_name = self.read_term()?; |
||||
Ok(EncodedQuad { |
||||
subject, |
||||
predicate, |
||||
object, |
||||
graph_name, |
||||
}) |
||||
} |
||||
|
||||
fn read_ospg_quad(&mut self) -> Result<EncodedQuad, StorageError> { |
||||
let object = self.read_term()?; |
||||
let subject = self.read_term()?; |
||||
let predicate = self.read_term()?; |
||||
let graph_name = self.read_term()?; |
||||
Ok(EncodedQuad { |
||||
subject, |
||||
predicate, |
||||
object, |
||||
graph_name, |
||||
}) |
||||
} |
||||
|
||||
fn read_gspo_quad(&mut self) -> Result<EncodedQuad, StorageError> { |
||||
let graph_name = self.read_term()?; |
||||
let subject = self.read_term()?; |
||||
let predicate = self.read_term()?; |
||||
let object = self.read_term()?; |
||||
Ok(EncodedQuad { |
||||
subject, |
||||
predicate, |
||||
object, |
||||
graph_name, |
||||
}) |
||||
} |
||||
|
||||
fn read_gpos_quad(&mut self) -> Result<EncodedQuad, StorageError> { |
||||
let graph_name = self.read_term()?; |
||||
let predicate = self.read_term()?; |
||||
let object = self.read_term()?; |
||||
let subject = self.read_term()?; |
||||
Ok(EncodedQuad { |
||||
subject, |
||||
predicate, |
||||
object, |
||||
graph_name, |
||||
}) |
||||
} |
||||
|
||||
fn read_gosp_quad(&mut self) -> Result<EncodedQuad, StorageError> { |
||||
let graph_name = self.read_term()?; |
||||
let object = self.read_term()?; |
||||
let subject = self.read_term()?; |
||||
let predicate = self.read_term()?; |
||||
Ok(EncodedQuad { |
||||
subject, |
||||
predicate, |
||||
object, |
||||
graph_name, |
||||
}) |
||||
} |
||||
|
||||
fn read_dspo_quad(&mut self) -> Result<EncodedQuad, StorageError> { |
||||
let subject = self.read_term()?; |
||||
let predicate = self.read_term()?; |
||||
let object = self.read_term()?; |
||||
Ok(EncodedQuad { |
||||
subject, |
||||
predicate, |
||||
object, |
||||
graph_name: EncodedTerm::DefaultGraph, |
||||
}) |
||||
} |
||||
|
||||
fn read_dpos_quad(&mut self) -> Result<EncodedQuad, StorageError> { |
||||
let predicate = self.read_term()?; |
||||
let object = self.read_term()?; |
||||
let subject = self.read_term()?; |
||||
Ok(EncodedQuad { |
||||
subject, |
||||
predicate, |
||||
object, |
||||
graph_name: EncodedTerm::DefaultGraph, |
||||
}) |
||||
} |
||||
|
||||
fn read_dosp_quad(&mut self) -> Result<EncodedQuad, StorageError> { |
||||
let object = self.read_term()?; |
||||
let subject = self.read_term()?; |
||||
let predicate = self.read_term()?; |
||||
Ok(EncodedQuad { |
||||
subject, |
||||
predicate, |
||||
object, |
||||
graph_name: EncodedTerm::DefaultGraph, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
impl<R: Read> TermReader for R { |
||||
fn read_term(&mut self) -> Result<EncodedTerm, StorageError> { |
||||
let mut type_buffer = [0]; |
||||
self.read_exact(&mut type_buffer)?; |
||||
match type_buffer[0] { |
||||
TYPE_NAMED_NODE_ID => { |
||||
let mut buffer = [0; 16]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(EncodedTerm::NamedNode { |
||||
iri_id: StrHash::from_be_bytes(buffer), |
||||
}) |
||||
} |
||||
TYPE_NUMERICAL_BLANK_NODE_ID => { |
||||
let mut buffer = [0; 16]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(EncodedTerm::NumericalBlankNode { |
||||
id: u128::from_be_bytes(buffer), |
||||
}) |
||||
} |
||||
TYPE_SMALL_BLANK_NODE_ID => { |
||||
let mut buffer = [0; 16]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(EncodedTerm::SmallBlankNode( |
||||
SmallString::from_be_bytes(buffer).map_err(CorruptionError::new)?, |
||||
)) |
||||
} |
||||
TYPE_BIG_BLANK_NODE_ID => { |
||||
let mut buffer = [0; 16]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(EncodedTerm::BigBlankNode { |
||||
id_id: StrHash::from_be_bytes(buffer), |
||||
}) |
||||
} |
||||
TYPE_SMALL_SMALL_LANG_STRING_LITERAL => { |
||||
let mut language_buffer = [0; 16]; |
||||
self.read_exact(&mut language_buffer)?; |
||||
let mut value_buffer = [0; 16]; |
||||
self.read_exact(&mut value_buffer)?; |
||||
Ok(EncodedTerm::SmallSmallLangStringLiteral { |
||||
value: SmallString::from_be_bytes(value_buffer) |
||||
.map_err(CorruptionError::new)?, |
||||
language: SmallString::from_be_bytes(language_buffer) |
||||
.map_err(CorruptionError::new)?, |
||||
}) |
||||
} |
||||
TYPE_SMALL_BIG_LANG_STRING_LITERAL => { |
||||
let mut language_buffer = [0; 16]; |
||||
self.read_exact(&mut language_buffer)?; |
||||
let mut value_buffer = [0; 16]; |
||||
self.read_exact(&mut value_buffer)?; |
||||
Ok(EncodedTerm::SmallBigLangStringLiteral { |
||||
value: SmallString::from_be_bytes(value_buffer) |
||||
.map_err(CorruptionError::new)?, |
||||
language_id: StrHash::from_be_bytes(language_buffer), |
||||
}) |
||||
} |
||||
TYPE_BIG_SMALL_LANG_STRING_LITERAL => { |
||||
let mut language_buffer = [0; 16]; |
||||
self.read_exact(&mut language_buffer)?; |
||||
let mut value_buffer = [0; 16]; |
||||
self.read_exact(&mut value_buffer)?; |
||||
Ok(EncodedTerm::BigSmallLangStringLiteral { |
||||
value_id: StrHash::from_be_bytes(value_buffer), |
||||
language: SmallString::from_be_bytes(language_buffer) |
||||
.map_err(CorruptionError::new)?, |
||||
}) |
||||
} |
||||
TYPE_BIG_BIG_LANG_STRING_LITERAL => { |
||||
let mut language_buffer = [0; 16]; |
||||
self.read_exact(&mut language_buffer)?; |
||||
let mut value_buffer = [0; 16]; |
||||
self.read_exact(&mut value_buffer)?; |
||||
Ok(EncodedTerm::BigBigLangStringLiteral { |
||||
value_id: StrHash::from_be_bytes(value_buffer), |
||||
language_id: StrHash::from_be_bytes(language_buffer), |
||||
}) |
||||
} |
||||
TYPE_SMALL_TYPED_LITERAL => { |
||||
let mut datatype_buffer = [0; 16]; |
||||
self.read_exact(&mut datatype_buffer)?; |
||||
let mut value_buffer = [0; 16]; |
||||
self.read_exact(&mut value_buffer)?; |
||||
Ok(EncodedTerm::SmallTypedLiteral { |
||||
datatype_id: StrHash::from_be_bytes(datatype_buffer), |
||||
value: SmallString::from_be_bytes(value_buffer) |
||||
.map_err(CorruptionError::new)?, |
||||
}) |
||||
} |
||||
TYPE_BIG_TYPED_LITERAL => { |
||||
let mut datatype_buffer = [0; 16]; |
||||
self.read_exact(&mut datatype_buffer)?; |
||||
let mut value_buffer = [0; 16]; |
||||
self.read_exact(&mut value_buffer)?; |
||||
Ok(EncodedTerm::BigTypedLiteral { |
||||
datatype_id: StrHash::from_be_bytes(datatype_buffer), |
||||
value_id: StrHash::from_be_bytes(value_buffer), |
||||
}) |
||||
} |
||||
TYPE_SMALL_STRING_LITERAL => { |
||||
let mut buffer = [0; 16]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(EncodedTerm::SmallStringLiteral( |
||||
SmallString::from_be_bytes(buffer).map_err(CorruptionError::new)?, |
||||
)) |
||||
} |
||||
TYPE_BIG_STRING_LITERAL => { |
||||
let mut buffer = [0; 16]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(EncodedTerm::BigStringLiteral { |
||||
value_id: StrHash::from_be_bytes(buffer), |
||||
}) |
||||
} |
||||
TYPE_BOOLEAN_LITERAL_TRUE => Ok(true.into()), |
||||
TYPE_BOOLEAN_LITERAL_FALSE => Ok(false.into()), |
||||
TYPE_FLOAT_LITERAL => { |
||||
let mut buffer = [0; 4]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(Float::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_DOUBLE_LITERAL => { |
||||
let mut buffer = [0; 8]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(Double::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_INTEGER_LITERAL => { |
||||
let mut buffer = [0; 8]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(Integer::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_DECIMAL_LITERAL => { |
||||
let mut buffer = [0; 16]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(Decimal::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_DATE_TIME_LITERAL => { |
||||
let mut buffer = [0; 18]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(DateTime::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_TIME_LITERAL => { |
||||
let mut buffer = [0; 18]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(Time::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_DATE_LITERAL => { |
||||
let mut buffer = [0; 18]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(Date::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_G_YEAR_MONTH_LITERAL => { |
||||
let mut buffer = [0; 18]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(GYearMonth::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_G_YEAR_LITERAL => { |
||||
let mut buffer = [0; 18]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(GYear::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_G_MONTH_DAY_LITERAL => { |
||||
let mut buffer = [0; 18]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(GMonthDay::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_G_DAY_LITERAL => { |
||||
let mut buffer = [0; 18]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(GDay::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_G_MONTH_LITERAL => { |
||||
let mut buffer = [0; 18]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(GMonth::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_DURATION_LITERAL => { |
||||
let mut buffer = [0; 24]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(Duration::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_YEAR_MONTH_DURATION_LITERAL => { |
||||
let mut buffer = [0; 8]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(YearMonthDuration::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_DAY_TIME_DURATION_LITERAL => { |
||||
let mut buffer = [0; 16]; |
||||
self.read_exact(&mut buffer)?; |
||||
Ok(DayTimeDuration::from_be_bytes(buffer).into()) |
||||
} |
||||
TYPE_TRIPLE => Ok(EncodedTriple { |
||||
subject: self.read_term()?, |
||||
predicate: self.read_term()?, |
||||
object: self.read_term()?, |
||||
} |
||||
.into()), |
||||
_ => Err(CorruptionError::msg("the term buffer has an invalid type id").into()), |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub fn write_spog_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) { |
||||
write_term(sink, &quad.subject); |
||||
write_term(sink, &quad.predicate); |
||||
write_term(sink, &quad.object); |
||||
write_term(sink, &quad.graph_name); |
||||
} |
||||
|
||||
pub fn write_posg_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) { |
||||
write_term(sink, &quad.predicate); |
||||
write_term(sink, &quad.object); |
||||
write_term(sink, &quad.subject); |
||||
write_term(sink, &quad.graph_name); |
||||
} |
||||
|
||||
pub fn write_ospg_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) { |
||||
write_term(sink, &quad.object); |
||||
write_term(sink, &quad.subject); |
||||
write_term(sink, &quad.predicate); |
||||
write_term(sink, &quad.graph_name); |
||||
} |
||||
|
||||
pub fn write_gspo_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) { |
||||
write_term(sink, &quad.graph_name); |
||||
write_term(sink, &quad.subject); |
||||
write_term(sink, &quad.predicate); |
||||
write_term(sink, &quad.object); |
||||
} |
||||
|
||||
pub fn write_gpos_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) { |
||||
write_term(sink, &quad.graph_name); |
||||
write_term(sink, &quad.predicate); |
||||
write_term(sink, &quad.object); |
||||
write_term(sink, &quad.subject); |
||||
} |
||||
|
||||
pub fn write_gosp_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) { |
||||
write_term(sink, &quad.graph_name); |
||||
write_term(sink, &quad.object); |
||||
write_term(sink, &quad.subject); |
||||
write_term(sink, &quad.predicate); |
||||
} |
||||
|
||||
pub fn write_spo_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) { |
||||
write_term(sink, &quad.subject); |
||||
write_term(sink, &quad.predicate); |
||||
write_term(sink, &quad.object); |
||||
} |
||||
|
||||
pub fn write_pos_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) { |
||||
write_term(sink, &quad.predicate); |
||||
write_term(sink, &quad.object); |
||||
write_term(sink, &quad.subject); |
||||
} |
||||
|
||||
pub fn write_osp_quad(sink: &mut Vec<u8>, quad: &EncodedQuad) { |
||||
write_term(sink, &quad.object); |
||||
write_term(sink, &quad.subject); |
||||
write_term(sink, &quad.predicate); |
||||
} |
||||
|
||||
pub fn encode_term(t: &EncodedTerm) -> Vec<u8> { |
||||
let mut vec = Vec::with_capacity(WRITTEN_TERM_MAX_SIZE); |
||||
write_term(&mut vec, t); |
||||
vec |
||||
} |
||||
|
||||
pub fn encode_term_pair(t1: &EncodedTerm, t2: &EncodedTerm) -> Vec<u8> { |
||||
let mut vec = Vec::with_capacity(2 * WRITTEN_TERM_MAX_SIZE); |
||||
write_term(&mut vec, t1); |
||||
write_term(&mut vec, t2); |
||||
vec |
||||
} |
||||
|
||||
pub fn encode_term_triple(t1: &EncodedTerm, t2: &EncodedTerm, t3: &EncodedTerm) -> Vec<u8> { |
||||
let mut vec = Vec::with_capacity(3 * WRITTEN_TERM_MAX_SIZE); |
||||
write_term(&mut vec, t1); |
||||
write_term(&mut vec, t2); |
||||
write_term(&mut vec, t3); |
||||
vec |
||||
} |
||||
|
||||
pub fn encode_term_quad( |
||||
t1: &EncodedTerm, |
||||
t2: &EncodedTerm, |
||||
t3: &EncodedTerm, |
||||
t4: &EncodedTerm, |
||||
) -> Vec<u8> { |
||||
let mut vec = Vec::with_capacity(4 * WRITTEN_TERM_MAX_SIZE); |
||||
write_term(&mut vec, t1); |
||||
write_term(&mut vec, t2); |
||||
write_term(&mut vec, t3); |
||||
write_term(&mut vec, t4); |
||||
vec |
||||
} |
||||
|
||||
pub fn write_term(sink: &mut Vec<u8>, term: &EncodedTerm) { |
||||
match term { |
||||
EncodedTerm::DefaultGraph => (), |
||||
EncodedTerm::NamedNode { iri_id } => { |
||||
sink.push(TYPE_NAMED_NODE_ID); |
||||
sink.extend_from_slice(&iri_id.to_be_bytes()); |
||||
} |
||||
EncodedTerm::NumericalBlankNode { id } => { |
||||
sink.push(TYPE_NUMERICAL_BLANK_NODE_ID); |
||||
sink.extend_from_slice(&id.to_be_bytes()) |
||||
} |
||||
EncodedTerm::SmallBlankNode(id) => { |
||||
sink.push(TYPE_SMALL_BLANK_NODE_ID); |
||||
sink.extend_from_slice(&id.to_be_bytes()) |
||||
} |
||||
EncodedTerm::BigBlankNode { id_id } => { |
||||
sink.push(TYPE_BIG_BLANK_NODE_ID); |
||||
sink.extend_from_slice(&id_id.to_be_bytes()); |
||||
} |
||||
EncodedTerm::SmallStringLiteral(value) => { |
||||
sink.push(TYPE_SMALL_STRING_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::BigStringLiteral { value_id } => { |
||||
sink.push(TYPE_BIG_STRING_LITERAL); |
||||
sink.extend_from_slice(&value_id.to_be_bytes()); |
||||
} |
||||
EncodedTerm::SmallSmallLangStringLiteral { value, language } => { |
||||
sink.push(TYPE_SMALL_SMALL_LANG_STRING_LITERAL); |
||||
sink.extend_from_slice(&language.to_be_bytes()); |
||||
sink.extend_from_slice(&value.to_be_bytes()); |
||||
} |
||||
EncodedTerm::SmallBigLangStringLiteral { value, language_id } => { |
||||
sink.push(TYPE_SMALL_BIG_LANG_STRING_LITERAL); |
||||
sink.extend_from_slice(&language_id.to_be_bytes()); |
||||
sink.extend_from_slice(&value.to_be_bytes()); |
||||
} |
||||
EncodedTerm::BigSmallLangStringLiteral { value_id, language } => { |
||||
sink.push(TYPE_BIG_SMALL_LANG_STRING_LITERAL); |
||||
sink.extend_from_slice(&language.to_be_bytes()); |
||||
sink.extend_from_slice(&value_id.to_be_bytes()); |
||||
} |
||||
EncodedTerm::BigBigLangStringLiteral { |
||||
value_id, |
||||
language_id, |
||||
} => { |
||||
sink.push(TYPE_BIG_BIG_LANG_STRING_LITERAL); |
||||
sink.extend_from_slice(&language_id.to_be_bytes()); |
||||
sink.extend_from_slice(&value_id.to_be_bytes()); |
||||
} |
||||
EncodedTerm::SmallTypedLiteral { value, datatype_id } => { |
||||
sink.push(TYPE_SMALL_TYPED_LITERAL); |
||||
sink.extend_from_slice(&datatype_id.to_be_bytes()); |
||||
sink.extend_from_slice(&value.to_be_bytes()); |
||||
} |
||||
EncodedTerm::BigTypedLiteral { |
||||
value_id, |
||||
datatype_id, |
||||
} => { |
||||
sink.push(TYPE_BIG_TYPED_LITERAL); |
||||
sink.extend_from_slice(&datatype_id.to_be_bytes()); |
||||
sink.extend_from_slice(&value_id.to_be_bytes()); |
||||
} |
||||
EncodedTerm::BooleanLiteral(value) => sink.push(if bool::from(*value) { |
||||
TYPE_BOOLEAN_LITERAL_TRUE |
||||
} else { |
||||
TYPE_BOOLEAN_LITERAL_FALSE |
||||
}), |
||||
EncodedTerm::FloatLiteral(value) => { |
||||
sink.push(TYPE_FLOAT_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::DoubleLiteral(value) => { |
||||
sink.push(TYPE_DOUBLE_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::IntegerLiteral(value) => { |
||||
sink.push(TYPE_INTEGER_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::DecimalLiteral(value) => { |
||||
sink.push(TYPE_DECIMAL_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::DateTimeLiteral(value) => { |
||||
sink.push(TYPE_DATE_TIME_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::TimeLiteral(value) => { |
||||
sink.push(TYPE_TIME_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::DurationLiteral(value) => { |
||||
sink.push(TYPE_DURATION_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::DateLiteral(value) => { |
||||
sink.push(TYPE_DATE_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::GYearMonthLiteral(value) => { |
||||
sink.push(TYPE_G_YEAR_MONTH_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::GYearLiteral(value) => { |
||||
sink.push(TYPE_G_YEAR_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::GMonthDayLiteral(value) => { |
||||
sink.push(TYPE_G_MONTH_DAY_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::GDayLiteral(value) => { |
||||
sink.push(TYPE_G_DAY_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::GMonthLiteral(value) => { |
||||
sink.push(TYPE_G_MONTH_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::YearMonthDurationLiteral(value) => { |
||||
sink.push(TYPE_YEAR_MONTH_DURATION_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::DayTimeDurationLiteral(value) => { |
||||
sink.push(TYPE_DAY_TIME_DURATION_LITERAL); |
||||
sink.extend_from_slice(&value.to_be_bytes()) |
||||
} |
||||
EncodedTerm::Triple(value) => { |
||||
sink.push(TYPE_TRIPLE); |
||||
write_term(sink, &value.subject); |
||||
write_term(sink, &value.predicate); |
||||
write_term(sink, &value.object); |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[cfg(test)] |
||||
#[allow(clippy::panic_in_result_fn)] |
||||
mod tests { |
||||
use super::*; |
||||
use crate::model::TermRef; |
||||
use crate::storage::numeric_encoder::*; |
||||
use std::cell::RefCell; |
||||
use std::collections::HashMap; |
||||
|
||||
#[derive(Default)] |
||||
struct MemoryStrStore { |
||||
id2str: RefCell<HashMap<StrHash, String>>, |
||||
} |
||||
|
||||
impl StrLookup for MemoryStrStore { |
||||
fn get_str(&self, key: &StrHash) -> Result<Option<String>, StorageError> { |
||||
Ok(self.id2str.borrow().get(key).cloned()) |
||||
} |
||||
} |
||||
|
||||
impl MemoryStrStore { |
||||
fn insert_term(&self, term: TermRef<'_>, encoded: &EncodedTerm) { |
||||
insert_term(term, encoded, &mut |h, v| { |
||||
self.insert_str(h, v); |
||||
Ok(()) |
||||
}) |
||||
.unwrap(); |
||||
} |
||||
|
||||
fn insert_str(&self, key: &StrHash, value: &str) { |
||||
self.id2str |
||||
.borrow_mut() |
||||
.entry(*key) |
||||
.or_insert_with(|| value.to_owned()); |
||||
} |
||||
} |
||||
|
||||
#[test] |
||||
fn test_encoding() { |
||||
use crate::model::vocab::xsd; |
||||
use crate::model::*; |
||||
|
||||
let store = MemoryStrStore::default(); |
||||
let terms: Vec<Term> = vec![ |
||||
NamedNode::new_unchecked("http://foo.com").into(), |
||||
NamedNode::new_unchecked("http://bar.com").into(), |
||||
NamedNode::new_unchecked("http://foo.com").into(), |
||||
BlankNode::default().into(), |
||||
BlankNode::new_unchecked("bnode").into(), |
||||
BlankNode::new_unchecked("foo-bnode-thisisaverylargeblanknode").into(), |
||||
Literal::new_simple_literal("literal").into(), |
||||
BlankNode::new_unchecked("foo-literal-thisisaverylargestringliteral").into(), |
||||
Literal::from(true).into(), |
||||
Literal::from(1.2).into(), |
||||
Literal::from(1).into(), |
||||
Literal::from("foo-string").into(), |
||||
Literal::new_language_tagged_literal_unchecked("foo-fr", "fr").into(), |
||||
Literal::new_language_tagged_literal_unchecked( |
||||
"foo-fr-literal-thisisaverylargelanguagetaggedstringliteral", |
||||
"fr", |
||||
) |
||||
.into(), |
||||
Literal::new_language_tagged_literal_unchecked( |
||||
"foo-big", |
||||
"fr-FR-Latn-x-foo-bar-baz-bat-aaaa-bbbb-cccc", |
||||
) |
||||
.into(), |
||||
Literal::new_language_tagged_literal_unchecked( |
||||
"foo-big-literal-thisisaverylargelanguagetaggedstringliteral", |
||||
"fr-FR-Latn-x-foo-bar-baz-bat-aaaa-bbbb-cccc", |
||||
) |
||||
.into(), |
||||
Literal::new_typed_literal("-1.32", xsd::DECIMAL).into(), |
||||
Literal::new_typed_literal("2020-01-01T01:01:01Z", xsd::DATE_TIME).into(), |
||||
Literal::new_typed_literal("2020-01-01", xsd::DATE).into(), |
||||
Literal::new_typed_literal("01:01:01Z", xsd::TIME).into(), |
||||
Literal::new_typed_literal("2020-01", xsd::G_YEAR_MONTH).into(), |
||||
Literal::new_typed_literal("2020", xsd::G_YEAR).into(), |
||||
Literal::new_typed_literal("--01-01", xsd::G_MONTH_DAY).into(), |
||||
Literal::new_typed_literal("--01", xsd::G_MONTH).into(), |
||||
Literal::new_typed_literal("---01", xsd::G_DAY).into(), |
||||
Literal::new_typed_literal("PT1S", xsd::DURATION).into(), |
||||
Literal::new_typed_literal("PT1S", xsd::DAY_TIME_DURATION).into(), |
||||
Literal::new_typed_literal("P1Y", xsd::YEAR_MONTH_DURATION).into(), |
||||
Literal::new_typed_literal("-foo", NamedNode::new_unchecked("http://foo.com")).into(), |
||||
Literal::new_typed_literal( |
||||
"-foo-thisisaverybigtypedliteralwiththefoodatatype", |
||||
NamedNode::new_unchecked("http://foo.com"), |
||||
) |
||||
.into(), |
||||
Triple::new( |
||||
NamedNode::new_unchecked("http://foo.com"), |
||||
NamedNode::new_unchecked("http://bar.com"), |
||||
Literal::from(true), |
||||
) |
||||
.into(), |
||||
]; |
||||
for term in terms { |
||||
let encoded = term.as_ref().into(); |
||||
store.insert_term(term.as_ref(), &encoded); |
||||
assert_eq!(encoded, term.as_ref().into()); |
||||
assert_eq!(term, store.decode_term(&encoded).unwrap()); |
||||
|
||||
let mut buffer = Vec::new(); |
||||
write_term(&mut buffer, &encoded); |
||||
assert_eq!(encoded, buffer.as_slice().read_term().unwrap()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,139 @@ |
||||
use crate::io::{RdfFormat, RdfParseError}; |
||||
use crate::storage::numeric_encoder::EncodedTerm; |
||||
use oxiri::IriParseError; |
||||
use oxrdf::TermRef; |
||||
use std::error::Error; |
||||
use std::io; |
||||
|
||||
/// An error related to storage operations (reads, writes...).
|
||||
#[derive(Debug, thiserror::Error)] |
||||
#[non_exhaustive] |
||||
pub enum StorageError { |
||||
/// Error from the OS I/O layer.
|
||||
#[error(transparent)] |
||||
Io(#[from] io::Error), |
||||
/// Error related to data corruption.
|
||||
#[error(transparent)] |
||||
Corruption(#[from] CorruptionError), |
||||
#[doc(hidden)] |
||||
#[error("{0}")] |
||||
Other(#[source] Box<dyn Error + Send + Sync + 'static>), |
||||
} |
||||
|
||||
impl From<StorageError> for io::Error { |
||||
#[inline] |
||||
fn from(error: StorageError) -> Self { |
||||
match error { |
||||
StorageError::Io(error) => error, |
||||
StorageError::Corruption(error) => error.into(), |
||||
StorageError::Other(error) => Self::new(io::ErrorKind::Other, error), |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// An error return if some content in the database is corrupted.
|
||||
#[derive(Debug, thiserror::Error)] |
||||
#[error(transparent)] |
||||
pub struct CorruptionError(#[from] CorruptionErrorKind); |
||||
|
||||
/// An error return if some content in the database is corrupted.
|
||||
#[derive(Debug, thiserror::Error)] |
||||
enum CorruptionErrorKind { |
||||
#[error("{0}")] |
||||
Msg(String), |
||||
#[error("{0}")] |
||||
Other(#[source] Box<dyn Error + Send + Sync + 'static>), |
||||
} |
||||
|
||||
impl CorruptionError { |
||||
/// Builds an error from a printable error message.
|
||||
#[inline] |
||||
pub(crate) fn new(error: impl Into<Box<dyn Error + Send + Sync + 'static>>) -> Self { |
||||
Self(CorruptionErrorKind::Other(error.into())) |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn from_encoded_term(encoded: &EncodedTerm, term: &TermRef<'_>) -> Self { |
||||
// TODO: eventually use a dedicated error enum value
|
||||
Self::msg(format!("Invalid term encoding {encoded:?} for {term}")) |
||||
} |
||||
|
||||
#[inline] |
||||
pub(crate) fn from_missing_column_family_name(name: &'static str) -> Self { |
||||
// TODO: eventually use a dedicated error enum value
|
||||
Self::msg(format!("Column family {name} does not exist")) |
||||
} |
||||
|
||||
/// Builds an error from a printable error message.
|
||||
#[inline] |
||||
pub(crate) fn msg(msg: impl Into<String>) -> Self { |
||||
Self(CorruptionErrorKind::Msg(msg.into())) |
||||
} |
||||
} |
||||
|
||||
impl From<CorruptionError> for io::Error { |
||||
#[inline] |
||||
fn from(error: CorruptionError) -> Self { |
||||
Self::new(io::ErrorKind::InvalidData, error) |
||||
} |
||||
} |
||||
|
||||
/// An error raised while loading a file into a [`Store`](crate::store::Store).
|
||||
#[derive(Debug, thiserror::Error)] |
||||
pub enum LoaderError { |
||||
/// An error raised while reading the file.
|
||||
#[error(transparent)] |
||||
Parsing(#[from] RdfParseError), |
||||
/// An error raised during the insertion in the store.
|
||||
#[error(transparent)] |
||||
Storage(#[from] StorageError), |
||||
/// The base IRI is invalid.
|
||||
#[error("Invalid base IRI '{iri}': {error}")] |
||||
InvalidBaseIri { |
||||
/// The IRI itself.
|
||||
iri: String, |
||||
/// The parsing error.
|
||||
#[source] |
||||
error: IriParseError, |
||||
}, |
||||
} |
||||
|
||||
impl From<LoaderError> for io::Error { |
||||
#[inline] |
||||
fn from(error: LoaderError) -> Self { |
||||
match error { |
||||
LoaderError::Storage(error) => error.into(), |
||||
LoaderError::Parsing(error) => error.into(), |
||||
LoaderError::InvalidBaseIri { .. } => { |
||||
Self::new(io::ErrorKind::InvalidInput, error.to_string()) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// An error raised while writing a file from a [`Store`](crate::store::Store).
|
||||
#[derive(Debug, thiserror::Error)] |
||||
pub enum SerializerError { |
||||
/// An error raised while writing the content.
|
||||
#[error(transparent)] |
||||
Io(#[from] io::Error), |
||||
/// An error raised during the lookup in the store.
|
||||
#[error(transparent)] |
||||
Storage(#[from] StorageError), |
||||
/// A format compatible with [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset) is required.
|
||||
#[error("A RDF format supporting datasets was expected, {0} found")] |
||||
DatasetFormatExpected(RdfFormat), |
||||
} |
||||
|
||||
impl From<SerializerError> for io::Error { |
||||
#[inline] |
||||
fn from(error: SerializerError) -> Self { |
||||
match error { |
||||
SerializerError::Storage(error) => error.into(), |
||||
SerializerError::Io(error) => error, |
||||
SerializerError::DatasetFormatExpected(_) => { |
||||
Self::new(io::ErrorKind::InvalidInput, error.to_string()) |
||||
} |
||||
} |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,177 @@ |
||||
use std::borrow::Borrow; |
||||
use std::cmp::Ordering; |
||||
use std::hash::{Hash, Hasher}; |
||||
use std::ops::Deref; |
||||
use std::str::{FromStr, Utf8Error}; |
||||
use std::{fmt, str}; |
||||
|
||||
/// A small inline string
|
||||
#[derive(Clone, Copy, Default)] |
||||
#[repr(transparent)] |
||||
pub struct SmallString { |
||||
inner: [u8; 16], |
||||
} |
||||
|
||||
impl SmallString { |
||||
#[inline] |
||||
pub const fn new() -> Self { |
||||
Self { inner: [0; 16] } |
||||
} |
||||
|
||||
#[inline] |
||||
pub fn from_utf8(bytes: &[u8]) -> Result<Self, BadSmallStringError> { |
||||
Self::from_str(str::from_utf8(bytes).map_err(BadSmallStringError::BadUtf8)?) |
||||
} |
||||
|
||||
#[inline] |
||||
pub fn from_be_bytes(bytes: [u8; 16]) -> Result<Self, BadSmallStringError> { |
||||
// We check that it is valid UTF-8
|
||||
str::from_utf8(&bytes.as_ref()[..bytes[15].into()]) |
||||
.map_err(BadSmallStringError::BadUtf8)?; |
||||
Ok(Self { inner: bytes }) |
||||
} |
||||
|
||||
#[inline] |
||||
pub fn len(&self) -> usize { |
||||
self.inner[15].into() |
||||
} |
||||
|
||||
#[inline] |
||||
pub fn is_empty(&self) -> bool { |
||||
self.len() == 0 |
||||
} |
||||
|
||||
#[inline] |
||||
#[allow(unsafe_code)] |
||||
pub fn as_str(&self) -> &str { |
||||
// SAFETY: safe because we ensured it in constructors
|
||||
unsafe { str::from_utf8_unchecked(self.as_bytes()) } |
||||
} |
||||
|
||||
#[inline] |
||||
pub fn as_bytes(&self) -> &[u8] { |
||||
&self.inner[..self.len()] |
||||
} |
||||
|
||||
#[inline] |
||||
pub fn to_be_bytes(self) -> [u8; 16] { |
||||
self.inner |
||||
} |
||||
} |
||||
|
||||
impl Deref for SmallString { |
||||
type Target = str; |
||||
|
||||
#[inline] |
||||
fn deref(&self) -> &Self::Target { |
||||
self.as_str() |
||||
} |
||||
} |
||||
|
||||
impl AsRef<str> for SmallString { |
||||
#[inline] |
||||
fn as_ref(&self) -> &str { |
||||
self.as_str() |
||||
} |
||||
} |
||||
|
||||
impl Borrow<str> for SmallString { |
||||
#[inline] |
||||
fn borrow(&self) -> &str { |
||||
self.as_str() |
||||
} |
||||
} |
||||
|
||||
impl fmt::Debug for SmallString { |
||||
#[inline] |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.as_str().fmt(f) |
||||
} |
||||
} |
||||
|
||||
impl fmt::Display for SmallString { |
||||
#[inline] |
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
||||
self.as_str().fmt(f) |
||||
} |
||||
} |
||||
|
||||
impl PartialEq for SmallString { |
||||
#[inline] |
||||
fn eq(&self, other: &Self) -> bool { |
||||
self.as_str() == other.as_str() |
||||
} |
||||
} |
||||
|
||||
impl Eq for SmallString {} |
||||
|
||||
impl PartialOrd for SmallString { |
||||
#[inline] |
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
||||
Some(self.cmp(other)) |
||||
} |
||||
} |
||||
|
||||
impl Ord for SmallString { |
||||
#[inline] |
||||
fn cmp(&self, other: &Self) -> Ordering { |
||||
self.as_str().cmp(other.as_str()) |
||||
} |
||||
} |
||||
|
||||
impl Hash for SmallString { |
||||
#[inline] |
||||
fn hash<H: Hasher>(&self, state: &mut H) { |
||||
self.as_str().hash(state) |
||||
} |
||||
} |
||||
|
||||
impl From<SmallString> for String { |
||||
#[inline] |
||||
fn from(value: SmallString) -> Self { |
||||
value.as_str().into() |
||||
} |
||||
} |
||||
|
||||
impl<'a> From<&'a SmallString> for &'a str { |
||||
#[inline] |
||||
fn from(value: &'a SmallString) -> Self { |
||||
value.as_str() |
||||
} |
||||
} |
||||
|
||||
impl FromStr for SmallString { |
||||
type Err = BadSmallStringError; |
||||
|
||||
#[inline] |
||||
fn from_str(value: &str) -> Result<Self, Self::Err> { |
||||
if value.len() <= 15 { |
||||
let mut inner = [0; 16]; |
||||
inner[..value.len()].copy_from_slice(value.as_bytes()); |
||||
inner[15] = value |
||||
.len() |
||||
.try_into() |
||||
.map_err(|_| Self::Err::TooLong(value.len()))?; |
||||
Ok(Self { inner }) |
||||
} else { |
||||
Err(Self::Err::TooLong(value.len())) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl<'a> TryFrom<&'a str> for SmallString { |
||||
type Error = BadSmallStringError; |
||||
|
||||
#[inline] |
||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> { |
||||
Self::from_str(value) |
||||
} |
||||
} |
||||
|
||||
#[derive(Debug, Clone, Copy, thiserror::Error)] |
||||
pub enum BadSmallStringError { |
||||
#[error("small strings could only contain at most 15 characters, found {0}")] |
||||
TooLong(usize), |
||||
#[error(transparent)] |
||||
BadUtf8(#[from] Utf8Error), |
||||
} |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@ -0,0 +1 @@ |
||||
MANIFEST-000004 |
@ -0,0 +1 @@ |
||||
f08a4c5b-0479-408c-80d3-7d4b10d7c7aa |
Binary file not shown.
@ -0,0 +1,964 @@ |
||||
# This is a RocksDB option file. |
||||
# |
||||
# For detailed file format spec, please refer to the example file |
||||
# in examples/rocksdb_option_file_example.ini |
||||
# |
||||
|
||||
[Version] |
||||
rocksdb_version=6.7.3 |
||||
options_file_version=1.1 |
||||
|
||||
[DBOptions] |
||||
write_dbid_to_manifest=false |
||||
avoid_unnecessary_blocking_io=false |
||||
two_write_queues=false |
||||
allow_ingest_behind=false |
||||
writable_file_max_buffer_size=1048576 |
||||
avoid_flush_during_shutdown=false |
||||
avoid_flush_during_recovery=false |
||||
info_log_level=INFO_LEVEL |
||||
access_hint_on_compaction_start=NORMAL |
||||
allow_concurrent_memtable_write=true |
||||
enable_pipelined_write=false |
||||
stats_dump_period_sec=600 |
||||
stats_persist_period_sec=600 |
||||
strict_bytes_per_sync=false |
||||
WAL_ttl_seconds=0 |
||||
WAL_size_limit_MB=0 |
||||
max_subcompactions=1 |
||||
dump_malloc_stats=false |
||||
db_log_dir= |
||||
wal_recovery_mode=kPointInTimeRecovery |
||||
log_file_time_to_roll=0 |
||||
enable_write_thread_adaptive_yield=true |
||||
recycle_log_file_num=0 |
||||
table_cache_numshardbits=6 |
||||
atomic_flush=false |
||||
preserve_deletes=false |
||||
stats_history_buffer_size=1048576 |
||||
max_open_files=-1 |
||||
max_file_opening_threads=16 |
||||
delete_obsolete_files_period_micros=21600000000 |
||||
max_background_flushes=-1 |
||||
write_thread_slow_yield_usec=3 |
||||
base_background_compactions=-1 |
||||
manual_wal_flush=false |
||||
wal_dir=tests/rockdb_bc_data |
||||
max_background_compactions=-1 |
||||
bytes_per_sync=0 |
||||
max_background_jobs=2 |
||||
use_fsync=false |
||||
unordered_write=false |
||||
fail_if_options_file_error=false |
||||
random_access_max_buffer_size=1048576 |
||||
compaction_readahead_size=0 |
||||
wal_bytes_per_sync=0 |
||||
new_table_reader_for_compaction_inputs=false |
||||
skip_stats_update_on_db_open=false |
||||
persist_stats_to_disk=false |
||||
skip_log_error_on_recovery=false |
||||
log_readahead_size=0 |
||||
is_fd_close_on_exec=true |
||||
use_adaptive_mutex=false |
||||
error_if_exists=false |
||||
write_thread_max_yield_usec=100 |
||||
enable_thread_tracking=false |
||||
db_write_buffer_size=0 |
||||
create_missing_column_families=true |
||||
paranoid_checks=true |
||||
create_if_missing=true |
||||
max_manifest_file_size=1073741824 |
||||
allow_2pc=false |
||||
max_total_wal_size=0 |
||||
use_direct_io_for_flush_and_compaction=false |
||||
manifest_preallocation_size=4194304 |
||||
use_direct_reads=false |
||||
delayed_write_rate=16777216 |
||||
allow_fallocate=true |
||||
max_write_batch_group_size_bytes=1048576 |
||||
keep_log_file_num=1000 |
||||
allow_mmap_reads=false |
||||
max_log_file_size=0 |
||||
allow_mmap_writes=false |
||||
advise_random_on_open=true |
||||
|
||||
|
||||
[CFOptions "default"] |
||||
sample_for_compression=0 |
||||
compaction_pri=kMinOverlappingRatio |
||||
merge_operator=nullptr |
||||
compaction_filter_factory=nullptr |
||||
memtable_factory=SkipListFactory |
||||
memtable_insert_with_hint_prefix_extractor=nullptr |
||||
comparator=leveldb.BytewiseComparator |
||||
target_file_size_base=67108864 |
||||
max_sequential_skip_in_iterations=8 |
||||
compaction_style=kCompactionStyleLevel |
||||
max_bytes_for_level_base=268435456 |
||||
bloom_locality=0 |
||||
write_buffer_size=67108864 |
||||
compression_per_level= |
||||
memtable_huge_page_size=0 |
||||
max_successive_merges=0 |
||||
arena_block_size=8388608 |
||||
memtable_whole_key_filtering=false |
||||
target_file_size_multiplier=1 |
||||
max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 |
||||
num_levels=7 |
||||
min_write_buffer_number_to_merge=1 |
||||
max_write_buffer_number_to_maintain=0 |
||||
max_write_buffer_number=2 |
||||
compression=kSnappyCompression |
||||
level0_stop_writes_trigger=36 |
||||
level0_slowdown_writes_trigger=20 |
||||
compaction_filter=nullptr |
||||
level0_file_num_compaction_trigger=4 |
||||
max_compaction_bytes=1677721600 |
||||
compaction_options_universal={stop_style=kCompactionStopStyleTotalSize;compression_size_percent=-1;allow_trivial_move=false;max_merge_width=4294967295;max_size_amplification_percent=200;min_merge_width=2;size_ratio=1;} |
||||
memtable_prefix_bloom_size_ratio=0.000000 |
||||
max_write_buffer_size_to_maintain=0 |
||||
hard_pending_compaction_bytes_limit=274877906944 |
||||
ttl=2592000 |
||||
table_factory=BlockBasedTable |
||||
soft_pending_compaction_bytes_limit=68719476736 |
||||
prefix_extractor=nullptr |
||||
bottommost_compression=kDisableCompressionOption |
||||
force_consistency_checks=false |
||||
paranoid_file_checks=false |
||||
compaction_options_fifo={allow_compaction=false;max_table_files_size=1073741824;} |
||||
max_bytes_for_level_multiplier=10.000000 |
||||
optimize_filters_for_hits=false |
||||
level_compaction_dynamic_level_bytes=false |
||||
inplace_update_num_locks=10000 |
||||
inplace_update_support=false |
||||
periodic_compaction_seconds=0 |
||||
disable_auto_compactions=false |
||||
report_bg_io_stats=false |
||||
|
||||
[TableOptions/BlockBasedTable "default"] |
||||
pin_top_level_index_and_filter=true |
||||
enable_index_compression=true |
||||
read_amp_bytes_per_bit=8589934592 |
||||
format_version=2 |
||||
block_align=false |
||||
metadata_block_size=4096 |
||||
block_size_deviation=10 |
||||
partition_filters=false |
||||
block_size=4096 |
||||
index_block_restart_interval=1 |
||||
no_block_cache=false |
||||
checksum=kCRC32c |
||||
whole_key_filtering=true |
||||
index_shortening=kShortenSeparators |
||||
data_block_index_type=kDataBlockBinarySearch |
||||
index_type=kBinarySearch |
||||
verify_compression=false |
||||
filter_policy=nullptr |
||||
data_block_hash_table_util_ratio=0.750000 |
||||
pin_l0_filter_and_index_blocks_in_cache=false |
||||
block_restart_interval=16 |
||||
cache_index_and_filter_blocks_with_high_priority=true |
||||
cache_index_and_filter_blocks=false |
||||
hash_index_allow_collision=true |
||||
flush_block_policy_factory=FlushBlockBySizePolicyFactory |
||||
|
||||
|
||||
[CFOptions "id2str"] |
||||
sample_for_compression=0 |
||||
compaction_pri=kMinOverlappingRatio |
||||
merge_operator=nullptr |
||||
compaction_filter_factory=nullptr |
||||
memtable_factory=SkipListFactory |
||||
memtable_insert_with_hint_prefix_extractor=nullptr |
||||
comparator=leveldb.BytewiseComparator |
||||
target_file_size_base=67108864 |
||||
max_sequential_skip_in_iterations=8 |
||||
compaction_style=kCompactionStyleLevel |
||||
max_bytes_for_level_base=268435456 |
||||
bloom_locality=0 |
||||
write_buffer_size=67108864 |
||||
compression_per_level= |
||||
memtable_huge_page_size=0 |
||||
max_successive_merges=0 |
||||
arena_block_size=8388608 |
||||
memtable_whole_key_filtering=false |
||||
target_file_size_multiplier=1 |
||||
max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 |
||||
num_levels=7 |
||||
min_write_buffer_number_to_merge=1 |
||||
max_write_buffer_number_to_maintain=0 |
||||
max_write_buffer_number=2 |
||||
compression=kSnappyCompression |
||||
level0_stop_writes_trigger=36 |
||||
level0_slowdown_writes_trigger=20 |
||||
compaction_filter=nullptr |
||||
level0_file_num_compaction_trigger=4 |
||||
max_compaction_bytes=1677721600 |
||||
compaction_options_universal={stop_style=kCompactionStopStyleTotalSize;compression_size_percent=-1;allow_trivial_move=false;max_merge_width=4294967295;max_size_amplification_percent=200;min_merge_width=2;size_ratio=1;} |
||||
memtable_prefix_bloom_size_ratio=0.000000 |
||||
max_write_buffer_size_to_maintain=0 |
||||
hard_pending_compaction_bytes_limit=274877906944 |
||||
ttl=2592000 |
||||
table_factory=BlockBasedTable |
||||
soft_pending_compaction_bytes_limit=68719476736 |
||||
prefix_extractor=nullptr |
||||
bottommost_compression=kDisableCompressionOption |
||||
force_consistency_checks=false |
||||
paranoid_file_checks=false |
||||
compaction_options_fifo={allow_compaction=false;max_table_files_size=1073741824;} |
||||
max_bytes_for_level_multiplier=10.000000 |
||||
optimize_filters_for_hits=false |
||||
level_compaction_dynamic_level_bytes=false |
||||
inplace_update_num_locks=10000 |
||||
inplace_update_support=false |
||||
periodic_compaction_seconds=0 |
||||
disable_auto_compactions=false |
||||
report_bg_io_stats=false |
||||
|
||||
[TableOptions/BlockBasedTable "id2str"] |
||||
pin_top_level_index_and_filter=true |
||||
enable_index_compression=true |
||||
read_amp_bytes_per_bit=8589934592 |
||||
format_version=2 |
||||
block_align=false |
||||
metadata_block_size=4096 |
||||
block_size_deviation=10 |
||||
partition_filters=false |
||||
block_size=4096 |
||||
index_block_restart_interval=1 |
||||
no_block_cache=false |
||||
checksum=kCRC32c |
||||
whole_key_filtering=true |
||||
index_shortening=kShortenSeparators |
||||
data_block_index_type=kDataBlockBinarySearch |
||||
index_type=kBinarySearch |
||||
verify_compression=false |
||||
filter_policy=nullptr |
||||
data_block_hash_table_util_ratio=0.750000 |
||||
pin_l0_filter_and_index_blocks_in_cache=false |
||||
block_restart_interval=16 |
||||
cache_index_and_filter_blocks_with_high_priority=true |
||||
cache_index_and_filter_blocks=false |
||||
hash_index_allow_collision=true |
||||
flush_block_policy_factory=FlushBlockBySizePolicyFactory |
||||
|
||||
|
||||
[CFOptions "spog"] |
||||
sample_for_compression=0 |
||||
compaction_pri=kMinOverlappingRatio |
||||
merge_operator=nullptr |
||||
compaction_filter_factory=nullptr |
||||
memtable_factory=SkipListFactory |
||||
memtable_insert_with_hint_prefix_extractor=nullptr |
||||
comparator=leveldb.BytewiseComparator |
||||
target_file_size_base=67108864 |
||||
max_sequential_skip_in_iterations=8 |
||||
compaction_style=kCompactionStyleLevel |
||||
max_bytes_for_level_base=268435456 |
||||
bloom_locality=0 |
||||
write_buffer_size=67108864 |
||||
compression_per_level= |
||||
memtable_huge_page_size=0 |
||||
max_successive_merges=0 |
||||
arena_block_size=8388608 |
||||
memtable_whole_key_filtering=false |
||||
target_file_size_multiplier=1 |
||||
max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 |
||||
num_levels=7 |
||||
min_write_buffer_number_to_merge=1 |
||||
max_write_buffer_number_to_maintain=0 |
||||
max_write_buffer_number=2 |
||||
compression=kSnappyCompression |
||||
level0_stop_writes_trigger=36 |
||||
level0_slowdown_writes_trigger=20 |
||||
compaction_filter=nullptr |
||||
level0_file_num_compaction_trigger=4 |
||||
max_compaction_bytes=1677721600 |
||||
compaction_options_universal={stop_style=kCompactionStopStyleTotalSize;compression_size_percent=-1;allow_trivial_move=false;max_merge_width=4294967295;max_size_amplification_percent=200;min_merge_width=2;size_ratio=1;} |
||||
memtable_prefix_bloom_size_ratio=0.000000 |
||||
max_write_buffer_size_to_maintain=0 |
||||
hard_pending_compaction_bytes_limit=274877906944 |
||||
ttl=2592000 |
||||
table_factory=BlockBasedTable |
||||
soft_pending_compaction_bytes_limit=68719476736 |
||||
prefix_extractor=nullptr |
||||
bottommost_compression=kDisableCompressionOption |
||||
force_consistency_checks=false |
||||
paranoid_file_checks=false |
||||
compaction_options_fifo={allow_compaction=false;max_table_files_size=1073741824;} |
||||
max_bytes_for_level_multiplier=10.000000 |
||||
optimize_filters_for_hits=false |
||||
level_compaction_dynamic_level_bytes=false |
||||
inplace_update_num_locks=10000 |
||||
inplace_update_support=false |
||||
periodic_compaction_seconds=0 |
||||
disable_auto_compactions=false |
||||
report_bg_io_stats=false |
||||
|
||||
[TableOptions/BlockBasedTable "spog"] |
||||
pin_top_level_index_and_filter=true |
||||
enable_index_compression=true |
||||
read_amp_bytes_per_bit=8589934592 |
||||
format_version=2 |
||||
block_align=false |
||||
metadata_block_size=4096 |
||||
block_size_deviation=10 |
||||
partition_filters=false |
||||
block_size=4096 |
||||
index_block_restart_interval=1 |
||||
no_block_cache=false |
||||
checksum=kCRC32c |
||||
whole_key_filtering=true |
||||
index_shortening=kShortenSeparators |
||||
data_block_index_type=kDataBlockBinarySearch |
||||
index_type=kBinarySearch |
||||
verify_compression=false |
||||
filter_policy=nullptr |
||||
data_block_hash_table_util_ratio=0.750000 |
||||
pin_l0_filter_and_index_blocks_in_cache=false |
||||
block_restart_interval=16 |
||||
cache_index_and_filter_blocks_with_high_priority=true |
||||
cache_index_and_filter_blocks=false |
||||
hash_index_allow_collision=true |
||||
flush_block_policy_factory=FlushBlockBySizePolicyFactory |
||||
|
||||
|
||||
[CFOptions "posg"] |
||||
sample_for_compression=0 |
||||
compaction_pri=kMinOverlappingRatio |
||||
merge_operator=nullptr |
||||
compaction_filter_factory=nullptr |
||||
memtable_factory=SkipListFactory |
||||
memtable_insert_with_hint_prefix_extractor=nullptr |
||||
comparator=leveldb.BytewiseComparator |
||||
target_file_size_base=67108864 |
||||
max_sequential_skip_in_iterations=8 |
||||
compaction_style=kCompactionStyleLevel |
||||
max_bytes_for_level_base=268435456 |
||||
bloom_locality=0 |
||||
write_buffer_size=67108864 |
||||
compression_per_level= |
||||
memtable_huge_page_size=0 |
||||
max_successive_merges=0 |
||||
arena_block_size=8388608 |
||||
memtable_whole_key_filtering=false |
||||
target_file_size_multiplier=1 |
||||
max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 |
||||
num_levels=7 |
||||
min_write_buffer_number_to_merge=1 |
||||
max_write_buffer_number_to_maintain=0 |
||||
max_write_buffer_number=2 |
||||
compression=kSnappyCompression |
||||
level0_stop_writes_trigger=36 |
||||
level0_slowdown_writes_trigger=20 |
||||
compaction_filter=nullptr |
||||
level0_file_num_compaction_trigger=4 |
||||
max_compaction_bytes=1677721600 |
||||
compaction_options_universal={stop_style=kCompactionStopStyleTotalSize;compression_size_percent=-1;allow_trivial_move=false;max_merge_width=4294967295;max_size_amplification_percent=200;min_merge_width=2;size_ratio=1;} |
||||
memtable_prefix_bloom_size_ratio=0.000000 |
||||
max_write_buffer_size_to_maintain=0 |
||||
hard_pending_compaction_bytes_limit=274877906944 |
||||
ttl=2592000 |
||||
table_factory=BlockBasedTable |
||||
soft_pending_compaction_bytes_limit=68719476736 |
||||
prefix_extractor=nullptr |
||||
bottommost_compression=kDisableCompressionOption |
||||
force_consistency_checks=false |
||||
paranoid_file_checks=false |
||||
compaction_options_fifo={allow_compaction=false;max_table_files_size=1073741824;} |
||||
max_bytes_for_level_multiplier=10.000000 |
||||
optimize_filters_for_hits=false |
||||
level_compaction_dynamic_level_bytes=false |
||||
inplace_update_num_locks=10000 |
||||
inplace_update_support=false |
||||
periodic_compaction_seconds=0 |
||||
disable_auto_compactions=false |
||||
report_bg_io_stats=false |
||||
|
||||
[TableOptions/BlockBasedTable "posg"] |
||||
pin_top_level_index_and_filter=true |
||||
enable_index_compression=true |
||||
read_amp_bytes_per_bit=8589934592 |
||||
format_version=2 |
||||
block_align=false |
||||
metadata_block_size=4096 |
||||
block_size_deviation=10 |
||||
partition_filters=false |
||||
block_size=4096 |
||||
index_block_restart_interval=1 |
||||
no_block_cache=false |
||||
checksum=kCRC32c |
||||
whole_key_filtering=true |
||||
index_shortening=kShortenSeparators |
||||
data_block_index_type=kDataBlockBinarySearch |
||||
index_type=kBinarySearch |
||||
verify_compression=false |
||||
filter_policy=nullptr |
||||
data_block_hash_table_util_ratio=0.750000 |
||||
pin_l0_filter_and_index_blocks_in_cache=false |
||||
block_restart_interval=16 |
||||
cache_index_and_filter_blocks_with_high_priority=true |
||||
cache_index_and_filter_blocks=false |
||||
hash_index_allow_collision=true |
||||
flush_block_policy_factory=FlushBlockBySizePolicyFactory |
||||
|
||||
|
||||
[CFOptions "ospg"] |
||||
sample_for_compression=0 |
||||
compaction_pri=kMinOverlappingRatio |
||||
merge_operator=nullptr |
||||
compaction_filter_factory=nullptr |
||||
memtable_factory=SkipListFactory |
||||
memtable_insert_with_hint_prefix_extractor=nullptr |
||||
comparator=leveldb.BytewiseComparator |
||||
target_file_size_base=67108864 |
||||
max_sequential_skip_in_iterations=8 |
||||
compaction_style=kCompactionStyleLevel |
||||
max_bytes_for_level_base=268435456 |
||||
bloom_locality=0 |
||||
write_buffer_size=67108864 |
||||
compression_per_level= |
||||
memtable_huge_page_size=0 |
||||
max_successive_merges=0 |
||||
arena_block_size=8388608 |
||||
memtable_whole_key_filtering=false |
||||
target_file_size_multiplier=1 |
||||
max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 |
||||
num_levels=7 |
||||
min_write_buffer_number_to_merge=1 |
||||
max_write_buffer_number_to_maintain=0 |
||||
max_write_buffer_number=2 |
||||
compression=kSnappyCompression |
||||
level0_stop_writes_trigger=36 |
||||
level0_slowdown_writes_trigger=20 |
||||
compaction_filter=nullptr |
||||
level0_file_num_compaction_trigger=4 |
||||
max_compaction_bytes=1677721600 |
||||
compaction_options_universal={stop_style=kCompactionStopStyleTotalSize;compression_size_percent=-1;allow_trivial_move=false;max_merge_width=4294967295;max_size_amplification_percent=200;min_merge_width=2;size_ratio=1;} |
||||
memtable_prefix_bloom_size_ratio=0.000000 |
||||
max_write_buffer_size_to_maintain=0 |
||||
hard_pending_compaction_bytes_limit=274877906944 |
||||
ttl=2592000 |
||||
table_factory=BlockBasedTable |
||||
soft_pending_compaction_bytes_limit=68719476736 |
||||
prefix_extractor=nullptr |
||||
bottommost_compression=kDisableCompressionOption |
||||
force_consistency_checks=false |
||||
paranoid_file_checks=false |
||||
compaction_options_fifo={allow_compaction=false;max_table_files_size=1073741824;} |
||||
max_bytes_for_level_multiplier=10.000000 |
||||
optimize_filters_for_hits=false |
||||
level_compaction_dynamic_level_bytes=false |
||||
inplace_update_num_locks=10000 |
||||
inplace_update_support=false |
||||
periodic_compaction_seconds=0 |
||||
disable_auto_compactions=false |
||||
report_bg_io_stats=false |
||||
|
||||
[TableOptions/BlockBasedTable "ospg"] |
||||
pin_top_level_index_and_filter=true |
||||
enable_index_compression=true |
||||
read_amp_bytes_per_bit=8589934592 |
||||
format_version=2 |
||||
block_align=false |
||||
metadata_block_size=4096 |
||||
block_size_deviation=10 |
||||
partition_filters=false |
||||
block_size=4096 |
||||
index_block_restart_interval=1 |
||||
no_block_cache=false |
||||
checksum=kCRC32c |
||||
whole_key_filtering=true |
||||
index_shortening=kShortenSeparators |
||||
data_block_index_type=kDataBlockBinarySearch |
||||
index_type=kBinarySearch |
||||
verify_compression=false |
||||
filter_policy=nullptr |
||||
data_block_hash_table_util_ratio=0.750000 |
||||
pin_l0_filter_and_index_blocks_in_cache=false |
||||
block_restart_interval=16 |
||||
cache_index_and_filter_blocks_with_high_priority=true |
||||
cache_index_and_filter_blocks=false |
||||
hash_index_allow_collision=true |
||||
flush_block_policy_factory=FlushBlockBySizePolicyFactory |
||||
|
||||
|
||||
[CFOptions "gspo"] |
||||
sample_for_compression=0 |
||||
compaction_pri=kMinOverlappingRatio |
||||
merge_operator=nullptr |
||||
compaction_filter_factory=nullptr |
||||
memtable_factory=SkipListFactory |
||||
memtable_insert_with_hint_prefix_extractor=nullptr |
||||
comparator=leveldb.BytewiseComparator |
||||
target_file_size_base=67108864 |
||||
max_sequential_skip_in_iterations=8 |
||||
compaction_style=kCompactionStyleLevel |
||||
max_bytes_for_level_base=268435456 |
||||
bloom_locality=0 |
||||
write_buffer_size=67108864 |
||||
compression_per_level= |
||||
memtable_huge_page_size=0 |
||||
max_successive_merges=0 |
||||
arena_block_size=8388608 |
||||
memtable_whole_key_filtering=false |
||||
target_file_size_multiplier=1 |
||||
max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 |
||||
num_levels=7 |
||||
min_write_buffer_number_to_merge=1 |
||||
max_write_buffer_number_to_maintain=0 |
||||
max_write_buffer_number=2 |
||||
compression=kSnappyCompression |
||||
level0_stop_writes_trigger=36 |
||||
level0_slowdown_writes_trigger=20 |
||||
compaction_filter=nullptr |
||||
level0_file_num_compaction_trigger=4 |
||||
max_compaction_bytes=1677721600 |
||||
compaction_options_universal={stop_style=kCompactionStopStyleTotalSize;compression_size_percent=-1;allow_trivial_move=false;max_merge_width=4294967295;max_size_amplification_percent=200;min_merge_width=2;size_ratio=1;} |
||||
memtable_prefix_bloom_size_ratio=0.000000 |
||||
max_write_buffer_size_to_maintain=0 |
||||
hard_pending_compaction_bytes_limit=274877906944 |
||||
ttl=2592000 |
||||
table_factory=BlockBasedTable |
||||
soft_pending_compaction_bytes_limit=68719476736 |
||||
prefix_extractor=nullptr |
||||
bottommost_compression=kDisableCompressionOption |
||||
force_consistency_checks=false |
||||
paranoid_file_checks=false |
||||
compaction_options_fifo={allow_compaction=false;max_table_files_size=1073741824;} |
||||
max_bytes_for_level_multiplier=10.000000 |
||||
optimize_filters_for_hits=false |
||||
level_compaction_dynamic_level_bytes=false |
||||
inplace_update_num_locks=10000 |
||||
inplace_update_support=false |
||||
periodic_compaction_seconds=0 |
||||
disable_auto_compactions=false |
||||
report_bg_io_stats=false |
||||
|
||||
[TableOptions/BlockBasedTable "gspo"] |
||||
pin_top_level_index_and_filter=true |
||||
enable_index_compression=true |
||||
read_amp_bytes_per_bit=8589934592 |
||||
format_version=2 |
||||
block_align=false |
||||
metadata_block_size=4096 |
||||
block_size_deviation=10 |
||||
partition_filters=false |
||||
block_size=4096 |
||||
index_block_restart_interval=1 |
||||
no_block_cache=false |
||||
checksum=kCRC32c |
||||
whole_key_filtering=true |
||||
index_shortening=kShortenSeparators |
||||
data_block_index_type=kDataBlockBinarySearch |
||||
index_type=kBinarySearch |
||||
verify_compression=false |
||||
filter_policy=nullptr |
||||
data_block_hash_table_util_ratio=0.750000 |
||||
pin_l0_filter_and_index_blocks_in_cache=false |
||||
block_restart_interval=16 |
||||
cache_index_and_filter_blocks_with_high_priority=true |
||||
cache_index_and_filter_blocks=false |
||||
hash_index_allow_collision=true |
||||
flush_block_policy_factory=FlushBlockBySizePolicyFactory |
||||
|
||||
|
||||
[CFOptions "gpos"] |
||||
sample_for_compression=0 |
||||
compaction_pri=kMinOverlappingRatio |
||||
merge_operator=nullptr |
||||
compaction_filter_factory=nullptr |
||||
memtable_factory=SkipListFactory |
||||
memtable_insert_with_hint_prefix_extractor=nullptr |
||||
comparator=leveldb.BytewiseComparator |
||||
target_file_size_base=67108864 |
||||
max_sequential_skip_in_iterations=8 |
||||
compaction_style=kCompactionStyleLevel |
||||
max_bytes_for_level_base=268435456 |
||||
bloom_locality=0 |
||||
write_buffer_size=67108864 |
||||
compression_per_level= |
||||
memtable_huge_page_size=0 |
||||
max_successive_merges=0 |
||||
arena_block_size=8388608 |
||||
memtable_whole_key_filtering=false |
||||
target_file_size_multiplier=1 |
||||
max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 |
||||
num_levels=7 |
||||
min_write_buffer_number_to_merge=1 |
||||
max_write_buffer_number_to_maintain=0 |
||||
max_write_buffer_number=2 |
||||
compression=kSnappyCompression |
||||
level0_stop_writes_trigger=36 |
||||
level0_slowdown_writes_trigger=20 |
||||
compaction_filter=nullptr |
||||
level0_file_num_compaction_trigger=4 |
||||
max_compaction_bytes=1677721600 |
||||
compaction_options_universal={stop_style=kCompactionStopStyleTotalSize;compression_size_percent=-1;allow_trivial_move=false;max_merge_width=4294967295;max_size_amplification_percent=200;min_merge_width=2;size_ratio=1;} |
||||
memtable_prefix_bloom_size_ratio=0.000000 |
||||
max_write_buffer_size_to_maintain=0 |
||||
hard_pending_compaction_bytes_limit=274877906944 |
||||
ttl=2592000 |
||||
table_factory=BlockBasedTable |
||||
soft_pending_compaction_bytes_limit=68719476736 |
||||
prefix_extractor=nullptr |
||||
bottommost_compression=kDisableCompressionOption |
||||
force_consistency_checks=false |
||||
paranoid_file_checks=false |
||||
compaction_options_fifo={allow_compaction=false;max_table_files_size=1073741824;} |
||||
max_bytes_for_level_multiplier=10.000000 |
||||
optimize_filters_for_hits=false |
||||
level_compaction_dynamic_level_bytes=false |
||||
inplace_update_num_locks=10000 |
||||
inplace_update_support=false |
||||
periodic_compaction_seconds=0 |
||||
disable_auto_compactions=false |
||||
report_bg_io_stats=false |
||||
|
||||
[TableOptions/BlockBasedTable "gpos"] |
||||
pin_top_level_index_and_filter=true |
||||
enable_index_compression=true |
||||
read_amp_bytes_per_bit=8589934592 |
||||
format_version=2 |
||||
block_align=false |
||||
metadata_block_size=4096 |
||||
block_size_deviation=10 |
||||
partition_filters=false |
||||
block_size=4096 |
||||
index_block_restart_interval=1 |
||||
no_block_cache=false |
||||
checksum=kCRC32c |
||||
whole_key_filtering=true |
||||
index_shortening=kShortenSeparators |
||||
data_block_index_type=kDataBlockBinarySearch |
||||
index_type=kBinarySearch |
||||
verify_compression=false |
||||
filter_policy=nullptr |
||||
data_block_hash_table_util_ratio=0.750000 |
||||
pin_l0_filter_and_index_blocks_in_cache=false |
||||
block_restart_interval=16 |
||||
cache_index_and_filter_blocks_with_high_priority=true |
||||
cache_index_and_filter_blocks=false |
||||
hash_index_allow_collision=true |
||||
flush_block_policy_factory=FlushBlockBySizePolicyFactory |
||||
|
||||
|
||||
[CFOptions "gosp"] |
||||
sample_for_compression=0 |
||||
compaction_pri=kMinOverlappingRatio |
||||
merge_operator=nullptr |
||||
compaction_filter_factory=nullptr |
||||
memtable_factory=SkipListFactory |
||||
memtable_insert_with_hint_prefix_extractor=nullptr |
||||
comparator=leveldb.BytewiseComparator |
||||
target_file_size_base=67108864 |
||||
max_sequential_skip_in_iterations=8 |
||||
compaction_style=kCompactionStyleLevel |
||||
max_bytes_for_level_base=268435456 |
||||
bloom_locality=0 |
||||
write_buffer_size=67108864 |
||||
compression_per_level= |
||||
memtable_huge_page_size=0 |
||||
max_successive_merges=0 |
||||
arena_block_size=8388608 |
||||
memtable_whole_key_filtering=false |
||||
target_file_size_multiplier=1 |
||||
max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 |
||||
num_levels=7 |
||||
min_write_buffer_number_to_merge=1 |
||||
max_write_buffer_number_to_maintain=0 |
||||
max_write_buffer_number=2 |
||||
compression=kSnappyCompression |
||||
level0_stop_writes_trigger=36 |
||||
level0_slowdown_writes_trigger=20 |
||||
compaction_filter=nullptr |
||||
level0_file_num_compaction_trigger=4 |
||||
max_compaction_bytes=1677721600 |
||||
compaction_options_universal={stop_style=kCompactionStopStyleTotalSize;compression_size_percent=-1;allow_trivial_move=false;max_merge_width=4294967295;max_size_amplification_percent=200;min_merge_width=2;size_ratio=1;} |
||||
memtable_prefix_bloom_size_ratio=0.000000 |
||||
max_write_buffer_size_to_maintain=0 |
||||
hard_pending_compaction_bytes_limit=274877906944 |
||||
ttl=2592000 |
||||
table_factory=BlockBasedTable |
||||
soft_pending_compaction_bytes_limit=68719476736 |
||||
prefix_extractor=nullptr |
||||
bottommost_compression=kDisableCompressionOption |
||||
force_consistency_checks=false |
||||
paranoid_file_checks=false |
||||
compaction_options_fifo={allow_compaction=false;max_table_files_size=1073741824;} |
||||
max_bytes_for_level_multiplier=10.000000 |
||||
optimize_filters_for_hits=false |
||||
level_compaction_dynamic_level_bytes=false |
||||
inplace_update_num_locks=10000 |
||||
inplace_update_support=false |
||||
periodic_compaction_seconds=0 |
||||
disable_auto_compactions=false |
||||
report_bg_io_stats=false |
||||
|
||||
[TableOptions/BlockBasedTable "gosp"] |
||||
pin_top_level_index_and_filter=true |
||||
enable_index_compression=true |
||||
read_amp_bytes_per_bit=8589934592 |
||||
format_version=2 |
||||
block_align=false |
||||
metadata_block_size=4096 |
||||
block_size_deviation=10 |
||||
partition_filters=false |
||||
block_size=4096 |
||||
index_block_restart_interval=1 |
||||
no_block_cache=false |
||||
checksum=kCRC32c |
||||
whole_key_filtering=true |
||||
index_shortening=kShortenSeparators |
||||
data_block_index_type=kDataBlockBinarySearch |
||||
index_type=kBinarySearch |
||||
verify_compression=false |
||||
filter_policy=nullptr |
||||
data_block_hash_table_util_ratio=0.750000 |
||||
pin_l0_filter_and_index_blocks_in_cache=false |
||||
block_restart_interval=16 |
||||
cache_index_and_filter_blocks_with_high_priority=true |
||||
cache_index_and_filter_blocks=false |
||||
hash_index_allow_collision=true |
||||
flush_block_policy_factory=FlushBlockBySizePolicyFactory |
||||
|
||||
|
||||
[CFOptions "dspo"] |
||||
sample_for_compression=0 |
||||
compaction_pri=kMinOverlappingRatio |
||||
merge_operator=nullptr |
||||
compaction_filter_factory=nullptr |
||||
memtable_factory=SkipListFactory |
||||
memtable_insert_with_hint_prefix_extractor=nullptr |
||||
comparator=leveldb.BytewiseComparator |
||||
target_file_size_base=67108864 |
||||
max_sequential_skip_in_iterations=8 |
||||
compaction_style=kCompactionStyleLevel |
||||
max_bytes_for_level_base=268435456 |
||||
bloom_locality=0 |
||||
write_buffer_size=67108864 |
||||
compression_per_level= |
||||
memtable_huge_page_size=0 |
||||
max_successive_merges=0 |
||||
arena_block_size=8388608 |
||||
memtable_whole_key_filtering=false |
||||
target_file_size_multiplier=1 |
||||
max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 |
||||
num_levels=7 |
||||
min_write_buffer_number_to_merge=1 |
||||
max_write_buffer_number_to_maintain=0 |
||||
max_write_buffer_number=2 |
||||
compression=kSnappyCompression |
||||
level0_stop_writes_trigger=36 |
||||
level0_slowdown_writes_trigger=20 |
||||
compaction_filter=nullptr |
||||
level0_file_num_compaction_trigger=4 |
||||
max_compaction_bytes=1677721600 |
||||
compaction_options_universal={stop_style=kCompactionStopStyleTotalSize;compression_size_percent=-1;allow_trivial_move=false;max_merge_width=4294967295;max_size_amplification_percent=200;min_merge_width=2;size_ratio=1;} |
||||
memtable_prefix_bloom_size_ratio=0.000000 |
||||
max_write_buffer_size_to_maintain=0 |
||||
hard_pending_compaction_bytes_limit=274877906944 |
||||
ttl=2592000 |
||||
table_factory=BlockBasedTable |
||||
soft_pending_compaction_bytes_limit=68719476736 |
||||
prefix_extractor=nullptr |
||||
bottommost_compression=kDisableCompressionOption |
||||
force_consistency_checks=false |
||||
paranoid_file_checks=false |
||||
compaction_options_fifo={allow_compaction=false;max_table_files_size=1073741824;} |
||||
max_bytes_for_level_multiplier=10.000000 |
||||
optimize_filters_for_hits=false |
||||
level_compaction_dynamic_level_bytes=false |
||||
inplace_update_num_locks=10000 |
||||
inplace_update_support=false |
||||
periodic_compaction_seconds=0 |
||||
disable_auto_compactions=false |
||||
report_bg_io_stats=false |
||||
|
||||
[TableOptions/BlockBasedTable "dspo"] |
||||
pin_top_level_index_and_filter=true |
||||
enable_index_compression=true |
||||
read_amp_bytes_per_bit=8589934592 |
||||
format_version=2 |
||||
block_align=false |
||||
metadata_block_size=4096 |
||||
block_size_deviation=10 |
||||
partition_filters=false |
||||
block_size=4096 |
||||
index_block_restart_interval=1 |
||||
no_block_cache=false |
||||
checksum=kCRC32c |
||||
whole_key_filtering=true |
||||
index_shortening=kShortenSeparators |
||||
data_block_index_type=kDataBlockBinarySearch |
||||
index_type=kBinarySearch |
||||
verify_compression=false |
||||
filter_policy=nullptr |
||||
data_block_hash_table_util_ratio=0.750000 |
||||
pin_l0_filter_and_index_blocks_in_cache=false |
||||
block_restart_interval=16 |
||||
cache_index_and_filter_blocks_with_high_priority=true |
||||
cache_index_and_filter_blocks=false |
||||
hash_index_allow_collision=true |
||||
flush_block_policy_factory=FlushBlockBySizePolicyFactory |
||||
|
||||
|
||||
[CFOptions "dpos"] |
||||
sample_for_compression=0 |
||||
compaction_pri=kMinOverlappingRatio |
||||
merge_operator=nullptr |
||||
compaction_filter_factory=nullptr |
||||
memtable_factory=SkipListFactory |
||||
memtable_insert_with_hint_prefix_extractor=nullptr |
||||
comparator=leveldb.BytewiseComparator |
||||
target_file_size_base=67108864 |
||||
max_sequential_skip_in_iterations=8 |
||||
compaction_style=kCompactionStyleLevel |
||||
max_bytes_for_level_base=268435456 |
||||
bloom_locality=0 |
||||
write_buffer_size=67108864 |
||||
compression_per_level= |
||||
memtable_huge_page_size=0 |
||||
max_successive_merges=0 |
||||
arena_block_size=8388608 |
||||
memtable_whole_key_filtering=false |
||||
target_file_size_multiplier=1 |
||||
max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 |
||||
num_levels=7 |
||||
min_write_buffer_number_to_merge=1 |
||||
max_write_buffer_number_to_maintain=0 |
||||
max_write_buffer_number=2 |
||||
compression=kSnappyCompression |
||||
level0_stop_writes_trigger=36 |
||||
level0_slowdown_writes_trigger=20 |
||||
compaction_filter=nullptr |
||||
level0_file_num_compaction_trigger=4 |
||||
max_compaction_bytes=1677721600 |
||||
compaction_options_universal={stop_style=kCompactionStopStyleTotalSize;compression_size_percent=-1;allow_trivial_move=false;max_merge_width=4294967295;max_size_amplification_percent=200;min_merge_width=2;size_ratio=1;} |
||||
memtable_prefix_bloom_size_ratio=0.000000 |
||||
max_write_buffer_size_to_maintain=0 |
||||
hard_pending_compaction_bytes_limit=274877906944 |
||||
ttl=2592000 |
||||
table_factory=BlockBasedTable |
||||
soft_pending_compaction_bytes_limit=68719476736 |
||||
prefix_extractor=nullptr |
||||
bottommost_compression=kDisableCompressionOption |
||||
force_consistency_checks=false |
||||
paranoid_file_checks=false |
||||
compaction_options_fifo={allow_compaction=false;max_table_files_size=1073741824;} |
||||
max_bytes_for_level_multiplier=10.000000 |
||||
optimize_filters_for_hits=false |
||||
level_compaction_dynamic_level_bytes=false |
||||
inplace_update_num_locks=10000 |
||||
inplace_update_support=false |
||||
periodic_compaction_seconds=0 |
||||
disable_auto_compactions=false |
||||
report_bg_io_stats=false |
||||
|
||||
[TableOptions/BlockBasedTable "dpos"] |
||||
pin_top_level_index_and_filter=true |
||||
enable_index_compression=true |
||||
read_amp_bytes_per_bit=8589934592 |
||||
format_version=2 |
||||
block_align=false |
||||
metadata_block_size=4096 |
||||
block_size_deviation=10 |
||||
partition_filters=false |
||||
block_size=4096 |
||||
index_block_restart_interval=1 |
||||
no_block_cache=false |
||||
checksum=kCRC32c |
||||
whole_key_filtering=true |
||||
index_shortening=kShortenSeparators |
||||
data_block_index_type=kDataBlockBinarySearch |
||||
index_type=kBinarySearch |
||||
verify_compression=false |
||||
filter_policy=nullptr |
||||
data_block_hash_table_util_ratio=0.750000 |
||||
pin_l0_filter_and_index_blocks_in_cache=false |
||||
block_restart_interval=16 |
||||
cache_index_and_filter_blocks_with_high_priority=true |
||||
cache_index_and_filter_blocks=false |
||||
hash_index_allow_collision=true |
||||
flush_block_policy_factory=FlushBlockBySizePolicyFactory |
||||
|
||||
|
||||
[CFOptions "dosp"] |
||||
sample_for_compression=0 |
||||
compaction_pri=kMinOverlappingRatio |
||||
merge_operator=nullptr |
||||
compaction_filter_factory=nullptr |
||||
memtable_factory=SkipListFactory |
||||
memtable_insert_with_hint_prefix_extractor=nullptr |
||||
comparator=leveldb.BytewiseComparator |
||||
target_file_size_base=67108864 |
||||
max_sequential_skip_in_iterations=8 |
||||
compaction_style=kCompactionStyleLevel |
||||
max_bytes_for_level_base=268435456 |
||||
bloom_locality=0 |
||||
write_buffer_size=67108864 |
||||
compression_per_level= |
||||
memtable_huge_page_size=0 |
||||
max_successive_merges=0 |
||||
arena_block_size=8388608 |
||||
memtable_whole_key_filtering=false |
||||
target_file_size_multiplier=1 |
||||
max_bytes_for_level_multiplier_additional=1:1:1:1:1:1:1 |
||||
num_levels=7 |
||||
min_write_buffer_number_to_merge=1 |
||||
max_write_buffer_number_to_maintain=0 |
||||
max_write_buffer_number=2 |
||||
compression=kSnappyCompression |
||||
level0_stop_writes_trigger=36 |
||||
level0_slowdown_writes_trigger=20 |
||||
compaction_filter=nullptr |
||||
level0_file_num_compaction_trigger=4 |
||||
max_compaction_bytes=1677721600 |
||||
compaction_options_universal={stop_style=kCompactionStopStyleTotalSize;compression_size_percent=-1;allow_trivial_move=false;max_merge_width=4294967295;max_size_amplification_percent=200;min_merge_width=2;size_ratio=1;} |
||||
memtable_prefix_bloom_size_ratio=0.000000 |
||||
max_write_buffer_size_to_maintain=0 |
||||
hard_pending_compaction_bytes_limit=274877906944 |
||||
ttl=2592000 |
||||
table_factory=BlockBasedTable |
||||
soft_pending_compaction_bytes_limit=68719476736 |
||||
prefix_extractor=nullptr |
||||
bottommost_compression=kDisableCompressionOption |
||||
force_consistency_checks=false |
||||
paranoid_file_checks=false |
||||
compaction_options_fifo={allow_compaction=false;max_table_files_size=1073741824;} |
||||
max_bytes_for_level_multiplier=10.000000 |
||||
optimize_filters_for_hits=false |
||||
level_compaction_dynamic_level_bytes=false |
||||
inplace_update_num_locks=10000 |
||||
inplace_update_support=false |
||||
periodic_compaction_seconds=0 |
||||
disable_auto_compactions=false |
||||
report_bg_io_stats=false |
||||
|
||||
[TableOptions/BlockBasedTable "dosp"] |
||||
pin_top_level_index_and_filter=true |
||||
enable_index_compression=true |
||||
read_amp_bytes_per_bit=8589934592 |
||||
format_version=2 |
||||
block_align=false |
||||
metadata_block_size=4096 |
||||
block_size_deviation=10 |
||||
partition_filters=false |
||||
block_size=4096 |
||||
index_block_restart_interval=1 |
||||
no_block_cache=false |
||||
checksum=kCRC32c |
||||
whole_key_filtering=true |
||||
index_shortening=kShortenSeparators |
||||
data_block_index_type=kDataBlockBinarySearch |
||||
index_type=kBinarySearch |
||||
verify_compression=false |
||||
filter_policy=nullptr |
||||
data_block_hash_table_util_ratio=0.750000 |
||||
pin_l0_filter_and_index_blocks_in_cache=false |
||||
block_restart_interval=16 |
||||
cache_index_and_filter_blocks_with_high_priority=true |
||||
cache_index_and_filter_blocks=false |
||||
hash_index_allow_collision=true |
||||
flush_block_policy_factory=FlushBlockBySizePolicyFactory |
||||
|
@ -0,0 +1,542 @@ |
||||
#![cfg(test)] |
||||
#![allow(clippy::panic_in_result_fn)] |
||||
|
||||
use oxigraph::io::RdfFormat; |
||||
use oxigraph::model::vocab::{rdf, xsd}; |
||||
use oxigraph::model::*; |
||||
use oxigraph::store::Store; |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
use rand::random; |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
use std::env::temp_dir; |
||||
use std::error::Error; |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
use std::fs::{create_dir_all, remove_dir_all, File}; |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
use std::io::Write; |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
use std::iter::empty; |
||||
#[cfg(all(target_os = "linux"))] |
||||
use std::iter::once; |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
use std::path::{Path, PathBuf}; |
||||
#[cfg(all(target_os = "linux"))] |
||||
use std::process::Command; |
||||
|
||||
#[allow(clippy::non_ascii_literal)] |
||||
const DATA: &str = r#" |
||||
@prefix schema: <http://schema.org/> .
|
||||
@prefix wd: <http://www.wikidata.org/entity/> .
|
||||
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
||||
|
||||
wd:Q90 a schema:City ; |
||||
schema:name "Paris"@fr , "la ville lumière"@fr ; |
||||
schema:country wd:Q142 ; |
||||
schema:population 2000000 ; |
||||
schema:startDate "-300"^^xsd:gYear ; |
||||
schema:url "https://www.paris.fr/"^^xsd:anyURI ; |
||||
schema:postalCode "75001" . |
||||
"#; |
||||
|
||||
#[allow(clippy::non_ascii_literal)] |
||||
const GRAPH_DATA: &str = r#" |
||||
@prefix schema: <http://schema.org/> .
|
||||
@prefix wd: <http://www.wikidata.org/entity/> .
|
||||
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
||||
|
||||
GRAPH <http://www.wikidata.org/wiki/Special:EntityData/Q90> {
|
||||
wd:Q90 a schema:City ; |
||||
schema:name "Paris"@fr , "la ville lumière"@fr ; |
||||
schema:country wd:Q142 ; |
||||
schema:population 2000000 ; |
||||
schema:startDate "-300"^^xsd:gYear ; |
||||
schema:url "https://www.paris.fr/"^^xsd:anyURI ; |
||||
schema:postalCode "75001" . |
||||
} |
||||
"#; |
||||
const NUMBER_OF_TRIPLES: usize = 8; |
||||
|
||||
fn quads(graph_name: impl Into<GraphNameRef<'static>>) -> Vec<QuadRef<'static>> { |
||||
let graph_name = graph_name.into(); |
||||
let paris = NamedNodeRef::new_unchecked("http://www.wikidata.org/entity/Q90"); |
||||
let france = NamedNodeRef::new_unchecked("http://www.wikidata.org/entity/Q142"); |
||||
let city = NamedNodeRef::new_unchecked("http://schema.org/City"); |
||||
let name = NamedNodeRef::new_unchecked("http://schema.org/name"); |
||||
let country = NamedNodeRef::new_unchecked("http://schema.org/country"); |
||||
let population = NamedNodeRef::new_unchecked("http://schema.org/population"); |
||||
let start_date = NamedNodeRef::new_unchecked("http://schema.org/startDate"); |
||||
let url = NamedNodeRef::new_unchecked("http://schema.org/url"); |
||||
let postal_code = NamedNodeRef::new_unchecked("http://schema.org/postalCode"); |
||||
vec![ |
||||
QuadRef::new(paris, rdf::TYPE, city, graph_name), |
||||
QuadRef::new( |
||||
paris, |
||||
name, |
||||
LiteralRef::new_language_tagged_literal_unchecked("Paris", "fr"), |
||||
graph_name, |
||||
), |
||||
QuadRef::new( |
||||
paris, |
||||
name, |
||||
LiteralRef::new_language_tagged_literal_unchecked("la ville lumi\u{E8}re", "fr"), |
||||
graph_name, |
||||
), |
||||
QuadRef::new(paris, country, france, graph_name), |
||||
QuadRef::new( |
||||
paris, |
||||
population, |
||||
LiteralRef::new_typed_literal("2000000", xsd::INTEGER), |
||||
graph_name, |
||||
), |
||||
QuadRef::new( |
||||
paris, |
||||
start_date, |
||||
LiteralRef::new_typed_literal("-300", xsd::G_YEAR), |
||||
graph_name, |
||||
), |
||||
QuadRef::new( |
||||
paris, |
||||
url, |
||||
LiteralRef::new_typed_literal("https://www.paris.fr/", xsd::ANY_URI), |
||||
graph_name, |
||||
), |
||||
QuadRef::new( |
||||
paris, |
||||
postal_code, |
||||
LiteralRef::new_simple_literal("75001"), |
||||
graph_name, |
||||
), |
||||
] |
||||
} |
||||
|
||||
#[test] |
||||
fn test_load_graph() -> Result<(), Box<dyn Error>> { |
||||
let store = Store::new()?; |
||||
store.load_from_read(RdfFormat::Turtle, DATA.as_bytes())?; |
||||
for q in quads(GraphNameRef::DefaultGraph) { |
||||
assert!(store.contains(q)?); |
||||
} |
||||
store.validate()?; |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
fn test_bulk_load_graph() -> Result<(), Box<dyn Error>> { |
||||
let store = Store::new()?; |
||||
store |
||||
.bulk_loader() |
||||
.load_from_read(RdfFormat::Turtle, DATA.as_bytes())?; |
||||
for q in quads(GraphNameRef::DefaultGraph) { |
||||
assert!(store.contains(q)?); |
||||
} |
||||
store.validate()?; |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
fn test_bulk_load_graph_lenient() -> Result<(), Box<dyn Error>> { |
||||
let store = Store::new()?; |
||||
store.bulk_loader().on_parse_error(|_| Ok(())).load_from_read( |
||||
RdfFormat::NTriples, |
||||
b"<http://example.com> <http://example.com> <http://example.com##> .\n<http://example.com> <http://example.com> <http://example.com> .".as_slice(), |
||||
)?; |
||||
assert_eq!(store.len()?, 1); |
||||
assert!(store.contains(QuadRef::new( |
||||
NamedNodeRef::new_unchecked("http://example.com"), |
||||
NamedNodeRef::new_unchecked("http://example.com"), |
||||
NamedNodeRef::new_unchecked("http://example.com"), |
||||
GraphNameRef::DefaultGraph |
||||
))?); |
||||
store.validate()?; |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
fn test_bulk_load_empty() -> Result<(), Box<dyn Error>> { |
||||
let store = Store::new()?; |
||||
store.bulk_loader().load_quads(empty::<Quad>())?; |
||||
assert!(store.is_empty()?); |
||||
store.validate()?; |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
fn test_load_dataset() -> Result<(), Box<dyn Error>> { |
||||
let store = Store::new()?; |
||||
store.load_from_read(RdfFormat::TriG, GRAPH_DATA.as_bytes())?; |
||||
for q in quads(NamedNodeRef::new_unchecked( |
||||
"http://www.wikidata.org/wiki/Special:EntityData/Q90", |
||||
)) { |
||||
assert!(store.contains(q)?); |
||||
} |
||||
store.validate()?; |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
fn test_bulk_load_dataset() -> Result<(), Box<dyn Error>> { |
||||
let store = Store::new()?; |
||||
store |
||||
.bulk_loader() |
||||
.load_from_read(RdfFormat::TriG, GRAPH_DATA.as_bytes())?; |
||||
let graph_name = |
||||
NamedNodeRef::new_unchecked("http://www.wikidata.org/wiki/Special:EntityData/Q90"); |
||||
for q in quads(graph_name) { |
||||
assert!(store.contains(q)?); |
||||
} |
||||
assert!(store.contains_named_graph(graph_name)?); |
||||
store.validate()?; |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
fn test_load_graph_generates_new_blank_nodes() -> Result<(), Box<dyn Error>> { |
||||
let store = Store::new()?; |
||||
for _ in 0..2 { |
||||
store.load_from_read( |
||||
RdfFormat::NTriples, |
||||
"_:a <http://example.com/p> <http://example.com/p> .".as_bytes(), |
||||
)?; |
||||
} |
||||
assert_eq!(store.len()?, 2); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
fn test_dump_graph() -> Result<(), Box<dyn Error>> { |
||||
let store = Store::new()?; |
||||
for q in quads(GraphNameRef::DefaultGraph) { |
||||
store.insert(q)?; |
||||
} |
||||
|
||||
let mut buffer = Vec::new(); |
||||
store.dump_graph_to_write(GraphNameRef::DefaultGraph, RdfFormat::NTriples, &mut buffer)?; |
||||
assert_eq!( |
||||
buffer.into_iter().filter(|c| *c == b'\n').count(), |
||||
NUMBER_OF_TRIPLES |
||||
); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
fn test_dump_dataset() -> Result<(), Box<dyn Error>> { |
||||
let store = Store::new()?; |
||||
for q in quads(GraphNameRef::DefaultGraph) { |
||||
store.insert(q)?; |
||||
} |
||||
|
||||
let buffer = store.dump_to_write(RdfFormat::NQuads, Vec::new())?; |
||||
assert_eq!( |
||||
buffer.into_iter().filter(|c| *c == b'\n').count(), |
||||
NUMBER_OF_TRIPLES |
||||
); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
fn test_snapshot_isolation_iterator() -> Result<(), Box<dyn Error>> { |
||||
let quad = QuadRef::new( |
||||
NamedNodeRef::new("http://example.com/s")?, |
||||
NamedNodeRef::new("http://example.com/p")?, |
||||
NamedNodeRef::new("http://example.com/o")?, |
||||
NamedNodeRef::new("http://www.wikidata.org/wiki/Special:EntityData/Q90")?, |
||||
); |
||||
let store = Store::new()?; |
||||
store.insert(quad)?; |
||||
let iter = store.iter(); |
||||
store.remove(quad)?; |
||||
assert_eq!( |
||||
iter.collect::<Result<Vec<_>, _>>()?, |
||||
vec![quad.into_owned()] |
||||
); |
||||
store.validate()?; |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
fn test_bulk_load_on_existing_delete_overrides_the_delete() -> Result<(), Box<dyn Error>> { |
||||
let quad = QuadRef::new( |
||||
NamedNodeRef::new_unchecked("http://example.com/s"), |
||||
NamedNodeRef::new_unchecked("http://example.com/p"), |
||||
NamedNodeRef::new_unchecked("http://example.com/o"), |
||||
NamedNodeRef::new_unchecked("http://www.wikidata.org/wiki/Special:EntityData/Q90"), |
||||
); |
||||
let store = Store::new()?; |
||||
store.remove(quad)?; |
||||
store.bulk_loader().load_quads([quad.into_owned()])?; |
||||
assert_eq!(store.len()?, 1); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
fn test_open_bad_dir() -> Result<(), Box<dyn Error>> { |
||||
let dir = TempDir::default(); |
||||
create_dir_all(&dir.0)?; |
||||
{ |
||||
File::create(dir.0.join("CURRENT"))?.write_all(b"foo")?; |
||||
} |
||||
assert!(Store::open(&dir.0).is_err()); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
#[cfg(all(target_os = "linux"))] |
||||
fn test_bad_stt_open() -> Result<(), Box<dyn Error>> { |
||||
let dir = TempDir::default(); |
||||
let store = Store::open(&dir.0)?; |
||||
remove_dir_all(&dir.0)?; |
||||
store |
||||
.bulk_loader() |
||||
.load_quads(once(Quad::new( |
||||
NamedNode::new_unchecked("http://example.com/s"), |
||||
NamedNode::new_unchecked("http://example.com/p"), |
||||
NamedNode::new_unchecked("http://example.com/o"), |
||||
GraphName::DefaultGraph, |
||||
))) |
||||
.unwrap_err(); |
||||
Ok(()) |
||||
} |
||||
|
||||
// #[test]
|
||||
// #[cfg(all(not(target_family = "wasm")))]
|
||||
// fn test_backup() -> Result<(), Box<dyn Error>> {
|
||||
// let quad = QuadRef::new(
|
||||
// NamedNodeRef::new_unchecked("http://example.com/s"),
|
||||
// NamedNodeRef::new_unchecked("http://example.com/p"),
|
||||
// NamedNodeRef::new_unchecked("http://example.com/o"),
|
||||
// GraphNameRef::DefaultGraph,
|
||||
// );
|
||||
// let store_dir = TempDir::default();
|
||||
// let backup_from_rw_dir = TempDir::default();
|
||||
// let backup_from_ro_dir = TempDir::default();
|
||||
// let backup_from_secondary_dir = TempDir::default();
|
||||
|
||||
// let store = Store::open(&store_dir)?;
|
||||
// store.insert(quad)?;
|
||||
// let secondary_store = Store::open_secondary(&store_dir)?;
|
||||
// store.flush()?;
|
||||
|
||||
// store.backup(&backup_from_rw_dir)?;
|
||||
// secondary_store.backup(&backup_from_secondary_dir)?;
|
||||
// store.remove(quad)?;
|
||||
// assert!(!store.contains(quad)?);
|
||||
|
||||
// let backup_from_rw = Store::open_read_only(&backup_from_rw_dir.0)?;
|
||||
// backup_from_rw.validate()?;
|
||||
// assert!(backup_from_rw.contains(quad)?);
|
||||
// backup_from_rw.backup(&backup_from_ro_dir)?;
|
||||
|
||||
// let backup_from_ro = Store::open_read_only(&backup_from_ro_dir.0)?;
|
||||
// backup_from_ro.validate()?;
|
||||
// assert!(backup_from_ro.contains(quad)?);
|
||||
|
||||
// let backup_from_secondary = Store::open_read_only(&backup_from_secondary_dir.0)?;
|
||||
// backup_from_secondary.validate()?;
|
||||
// assert!(backup_from_secondary.contains(quad)?);
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
#[test] |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
fn test_bad_backup() -> Result<(), Box<dyn Error>> { |
||||
let store_dir = TempDir::default(); |
||||
let backup_dir = TempDir::default(); |
||||
|
||||
create_dir_all(&backup_dir.0)?; |
||||
Store::open(&store_dir)?.backup(&backup_dir.0).unwrap_err(); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
fn test_backup_on_in_memory() -> Result<(), Box<dyn Error>> { |
||||
let backup_dir = TempDir::default(); |
||||
Store::new()?.backup(&backup_dir).unwrap_err(); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
#[cfg(all(target_os = "linux"))] |
||||
fn test_backward_compatibility() -> Result<(), Box<dyn Error>> { |
||||
// We run twice to check if data is properly saved and closed
|
||||
for _ in 0..2 { |
||||
let store = Store::open("tests/rocksdb_bc_data")?; |
||||
for q in quads(GraphNameRef::DefaultGraph) { |
||||
assert!(store.contains(q)?); |
||||
} |
||||
let graph_name = |
||||
NamedNodeRef::new_unchecked("http://www.wikidata.org/wiki/Special:EntityData/Q90"); |
||||
for q in quads(graph_name) { |
||||
assert!(store.contains(q)?); |
||||
} |
||||
assert!(store.contains_named_graph(graph_name)?); |
||||
assert_eq!( |
||||
vec![NamedOrBlankNode::from(graph_name)], |
||||
store.named_graphs().collect::<Result<Vec<_>, _>>()? |
||||
); |
||||
} |
||||
reset_dir("tests/rocksdb_bc_data")?; |
||||
Ok(()) |
||||
} |
||||
|
||||
// #[test]
|
||||
// #[cfg(all(not(target_family = "wasm")))]
|
||||
// fn test_secondary() -> Result<(), Box<dyn Error>> {
|
||||
// let quad = QuadRef::new(
|
||||
// NamedNodeRef::new_unchecked("http://example.com/s"),
|
||||
// NamedNodeRef::new_unchecked("http://example.com/p"),
|
||||
// NamedNodeRef::new_unchecked("http://example.com/o"),
|
||||
// GraphNameRef::DefaultGraph,
|
||||
// );
|
||||
// let primary_dir = TempDir::default();
|
||||
|
||||
// // We open the store
|
||||
// let primary = Store::open(&primary_dir)?;
|
||||
// let secondary = Store::open_secondary(&primary_dir)?;
|
||||
|
||||
// // We insert a quad
|
||||
// primary.insert(quad)?;
|
||||
// primary.flush()?;
|
||||
|
||||
// // It is readable from both stores
|
||||
// for store in &[&primary, &secondary] {
|
||||
// assert!(store.contains(quad)?);
|
||||
// assert_eq!(
|
||||
// store.iter().collect::<Result<Vec<_>, _>>()?,
|
||||
// vec![quad.into_owned()]
|
||||
// );
|
||||
// }
|
||||
|
||||
// // We validate the states
|
||||
// primary.validate()?;
|
||||
// secondary.validate()?;
|
||||
|
||||
// // We close the primary store and remove its content
|
||||
// drop(primary);
|
||||
// remove_dir_all(&primary_dir)?;
|
||||
|
||||
// // We secondary store is still readable
|
||||
// assert!(secondary.contains(quad)?);
|
||||
// secondary.validate()?;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// #[cfg(all(not(target_family = "wasm")))]
|
||||
// fn test_open_secondary_bad_dir() -> Result<(), Box<dyn Error>> {
|
||||
// let primary_dir = TempDir::default();
|
||||
// create_dir_all(&primary_dir.0)?;
|
||||
// {
|
||||
// File::create(primary_dir.0.join("CURRENT"))?.write_all(b"foo")?;
|
||||
// }
|
||||
// assert!(Store::open_secondary(&primary_dir).is_err());
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
#[test] |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
fn test_read_only() -> Result<(), Box<dyn Error>> { |
||||
let s = NamedNodeRef::new_unchecked("http://example.com/s"); |
||||
let p = NamedNodeRef::new_unchecked("http://example.com/p"); |
||||
let first_quad = QuadRef::new( |
||||
s, |
||||
p, |
||||
NamedNodeRef::new_unchecked("http://example.com/o"), |
||||
GraphNameRef::DefaultGraph, |
||||
); |
||||
let second_quad = QuadRef::new( |
||||
s, |
||||
p, |
||||
NamedNodeRef::new_unchecked("http://example.com/o2"), |
||||
GraphNameRef::DefaultGraph, |
||||
); |
||||
let store_dir = TempDir::default(); |
||||
|
||||
// We write to the store and close it
|
||||
{ |
||||
let read_write = Store::open(&store_dir)?; |
||||
read_write.insert(first_quad)?; |
||||
read_write.flush()?; |
||||
} |
||||
|
||||
// We open as read-only
|
||||
let read_only = Store::open_read_only(&store_dir, None)?; |
||||
assert!(read_only.contains(first_quad)?); |
||||
assert_eq!( |
||||
read_only.iter().collect::<Result<Vec<_>, _>>()?, |
||||
vec![first_quad.into_owned()] |
||||
); |
||||
read_only.validate()?; |
||||
|
||||
// We open as read-write again
|
||||
let read_write = Store::open(&store_dir)?; |
||||
read_write.insert(second_quad)?; |
||||
read_write.flush()?; |
||||
read_write.optimize()?; // Makes sure it's well flushed
|
||||
|
||||
// The new quad is in the read-write instance but not the read-only instance
|
||||
assert!(read_write.contains(second_quad)?); |
||||
assert!(!read_only.contains(second_quad)?); |
||||
read_only.validate()?; |
||||
|
||||
Ok(()) |
||||
} |
||||
|
||||
#[test] |
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
fn test_open_read_only_bad_dir() -> Result<(), Box<dyn Error>> { |
||||
let dir = TempDir::default(); |
||||
create_dir_all(&dir.0)?; |
||||
{ |
||||
File::create(dir.0.join("CURRENT"))?.write_all(b"foo")?; |
||||
} |
||||
assert!(Store::open_read_only(&dir, None).is_err()); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[cfg(all(target_os = "linux"))] |
||||
fn reset_dir(dir: &str) -> Result<(), Box<dyn Error>> { |
||||
assert!(Command::new("git") |
||||
.args(["clean", "-fX", dir]) |
||||
.status()? |
||||
.success()); |
||||
assert!(Command::new("git") |
||||
.args(["checkout", "HEAD", "--", dir]) |
||||
.status()? |
||||
.success()); |
||||
Ok(()) |
||||
} |
||||
|
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
struct TempDir(PathBuf); |
||||
|
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
impl Default for TempDir { |
||||
fn default() -> Self { |
||||
Self(temp_dir().join(format!("oxigraph-test-{}", random::<u128>()))) |
||||
} |
||||
} |
||||
|
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
impl AsRef<Path> for TempDir { |
||||
fn as_ref(&self) -> &Path { |
||||
&self.0 |
||||
} |
||||
} |
||||
|
||||
#[cfg(all(not(target_family = "wasm")))] |
||||
impl Drop for TempDir { |
||||
fn drop(&mut self) { |
||||
if self.0.is_dir() { |
||||
remove_dir_all(&self.0).unwrap(); |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue