diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 485ed108..e2bb3f1f 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -15,6 +15,10 @@ edition = "2018" [package.metadata.docs.rs] all-features = true +[features] +default = [] +sophia = ["sophia_api"] + [dependencies] rocksdb = { version = "0.14", optional = true } sled = { version = "0.34", optional = true } @@ -35,6 +39,7 @@ nom = "5" peg = "0.6" siphasher = "0.3" lasso = {version="0.3", features=["multi-threaded"]} +sophia_api = { version = "0.6.2", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] js-sys = "0.3" @@ -43,6 +48,7 @@ getrandom = {version="0.1", features=["wasm-bindgen"]} [dev-dependencies] rayon = "1" criterion = "0.3" +sophia_api = { version = "0.6.2", features = ["test_macro"] } [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/lib/src/lib.rs b/lib/src/lib.rs index ba7c43f1..33835e50 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -118,3 +118,13 @@ pub use crate::store::memory::MemoryStore; pub use crate::store::rocksdb::RocksDbStore; #[cfg(feature = "sled")] pub use crate::store::sled::SledStore; + +#[cfg(feature = "sophia")] +/// Provides implementation of [Sophia] traits for Oxigraph types, +/// if the `sophia` feature is enabled. +/// +/// [Sophia]: https://docs.rs/sophia/latest/sophia/ +mod sophia { + mod model; + mod store; +} diff --git a/lib/src/sophia/model.rs b/lib/src/sophia/model.rs new file mode 100644 index 00000000..69bfdf5c --- /dev/null +++ b/lib/src/sophia/model.rs @@ -0,0 +1,408 @@ +//! This crate provides implementation of [Sophia] traits for the `model` module. +//! [Sophia]: https://docs.rs/sophia/latest/sophia/ + +use crate::model::*; +use sophia_api::term::*; +use std::fmt; + +impl TTerm for BlankNode { + fn kind(&self) -> TermKind { + TermKind::BlankNode + } + + fn value_raw(&self) -> RawValue<'_> { + self.as_str().into() + } + + fn as_dyn(&self) -> &dyn TTerm { + self + } +} + +impl TryCopyTerm for BlankNode { + type Error = SophiaToOxigraphConversionError; + + fn try_copy(other: &T) -> Result + where + T: TTerm + ?Sized, + { + match other.kind() { + TermKind::BlankNode => Ok(BlankNode::new_unchecked(other.value_raw().0)), + _ => Err(SophiaToOxigraphConversionError), + } + } +} + +impl<'a> TTerm for BlankNodeRef<'a> { + fn kind(&self) -> TermKind { + TermKind::BlankNode + } + + fn value_raw(&self) -> RawValue<'_> { + self.as_str().into() + } + + fn as_dyn(&self) -> &dyn TTerm { + self + } +} + +impl TTerm for Literal { + fn kind(&self) -> TermKind { + TermKind::Literal + } + + fn value_raw(&self) -> RawValue<'_> { + Literal::value(self).into() + } + + fn datatype(&self) -> Option> { + Some(SimpleIri::new_unchecked( + Literal::datatype(self).as_str(), + None, + )) + } + + fn language(&self) -> Option<&str> { + Literal::language(self) + } + + fn as_dyn(&self) -> &dyn TTerm { + self + } +} + +impl TryCopyTerm for Literal { + type Error = SophiaToOxigraphConversionError; + + fn try_copy(other: &T) -> Result + where + T: TTerm + ?Sized, + { + match other.kind() { + TermKind::Literal => match other.language() { + Some(tag) => Ok(Literal::new_language_tagged_literal_unchecked( + other.value_raw().0, + tag, + )), + None => Ok(Literal::new_typed_literal( + other.value_raw().0, + other.datatype().unwrap(), + )), + }, + _ => Err(SophiaToOxigraphConversionError), + } + } +} + +impl<'a> TTerm for LiteralRef<'a> { + fn kind(&self) -> TermKind { + TermKind::Literal + } + + fn value_raw(&self) -> RawValue<'_> { + LiteralRef::value(*self).into() + } + + fn datatype(&self) -> Option> { + Some(SimpleIri::new_unchecked( + LiteralRef::datatype(*self).as_str(), + None, + )) + } + + fn language(&self) -> Option<&str> { + LiteralRef::language(*self) + } + + fn as_dyn(&self) -> &dyn TTerm { + self + } +} + +impl TTerm for NamedNode { + fn kind(&self) -> TermKind { + TermKind::Iri + } + + fn value_raw(&self) -> RawValue<'_> { + self.as_str().into() + } + + fn as_dyn(&self) -> &dyn TTerm { + self + } +} + +impl TryCopyTerm for NamedNode { + type Error = SophiaToOxigraphConversionError; + + fn try_copy(other: &T) -> Result + where + T: TTerm + ?Sized, + { + match other.kind() { + TermKind::Iri => Ok(NamedNode::new_unchecked(other.value())), + _ => Err(SophiaToOxigraphConversionError), + } + } +} + +impl<'a> From> for NamedNode { + fn from(other: SimpleIri<'a>) -> Self { + NamedNode::new_unchecked(other.value()) + } +} + +impl<'a> TTerm for NamedNodeRef<'a> { + fn kind(&self) -> TermKind { + TermKind::BlankNode + } + + fn value_raw(&self) -> RawValue<'_> { + self.as_str().into() + } + + fn as_dyn(&self) -> &dyn TTerm { + self + } +} + +impl From for Option { + fn from(other: GraphName) -> Self { + use GraphName::*; + match other { + NamedNode(n) => Some(n.into()), + BlankNode(n) => Some(n.into()), + DefaultGraph => None, + } + } +} + +impl<'a> From> for Option> { + fn from(other: GraphNameRef<'a>) -> Self { + use GraphNameRef::*; + match other { + NamedNode(n) => Some(n.into()), + BlankNode(n) => Some(n.into()), + DefaultGraph => None, + } + } +} + +impl TTerm for NamedOrBlankNode { + fn kind(&self) -> TermKind { + use NamedOrBlankNode::*; + match self { + NamedNode(_) => TermKind::Iri, + BlankNode(_) => TermKind::BlankNode, + } + } + + fn value_raw(&self) -> RawValue<'_> { + use NamedOrBlankNode::*; + match self { + NamedNode(n) => n.value_raw(), + BlankNode(n) => n.value_raw(), + } + } + + fn as_dyn(&self) -> &dyn TTerm { + use NamedOrBlankNode::*; + match self { + NamedNode(n) => n.as_dyn(), + BlankNode(n) => n.as_dyn(), + } + } +} + +impl TryCopyTerm for NamedOrBlankNode { + type Error = SophiaToOxigraphConversionError; + + fn try_copy(other: &T) -> Result + where + T: TTerm + ?Sized, + { + match other.kind() { + TermKind::Iri => Ok(NamedNode::try_copy(other).unwrap().into()), + TermKind::BlankNode => Ok(BlankNode::try_copy(other).unwrap().into()), + _ => Err(SophiaToOxigraphConversionError), + } + } +} + +impl<'a> TTerm for NamedOrBlankNodeRef<'a> { + fn kind(&self) -> TermKind { + use NamedOrBlankNodeRef::*; + match self { + NamedNode(_) => TermKind::Iri, + BlankNode(_) => TermKind::BlankNode, + } + } + + fn value_raw(&self) -> RawValue<'_> { + use NamedOrBlankNodeRef::*; + match self { + NamedNode(n) => n.value_raw(), + BlankNode(n) => n.value_raw(), + } + } + + fn as_dyn(&self) -> &dyn TTerm { + use NamedOrBlankNodeRef::*; + match self { + NamedNode(n) => n.as_dyn(), + BlankNode(n) => n.as_dyn(), + } + } +} + +impl TTerm for Term { + fn kind(&self) -> TermKind { + use Term::*; + match self { + NamedNode(_) => TermKind::Iri, + BlankNode(_) => TermKind::BlankNode, + Literal(_) => TermKind::Literal, + } + } + + fn value_raw(&self) -> RawValue<'_> { + use Term::*; + match self { + NamedNode(n) => n.value_raw(), + BlankNode(n) => n.value_raw(), + Literal(l) => l.value_raw(), + } + } + + fn datatype(&self) -> Option> { + use Term::*; + match self { + Literal(l) => TTerm::datatype(l), + _ => None, + } + } + + fn language(&self) -> Option<&str> { + use Term::*; + match self { + Literal(l) => TTerm::language(l), + _ => None, + } + } + + fn as_dyn(&self) -> &dyn TTerm { + use Term::*; + match self { + NamedNode(n) => n.as_dyn(), + BlankNode(n) => n.as_dyn(), + Literal(l) => l.as_dyn(), + } + } +} + +impl TryCopyTerm for Term { + type Error = SophiaToOxigraphConversionError; + + fn try_copy(other: &T) -> Result + where + T: TTerm + ?Sized, + { + match other.kind() { + TermKind::Iri => Ok(NamedNode::try_copy(other).unwrap().into()), + TermKind::BlankNode => Ok(BlankNode::try_copy(other).unwrap().into()), + TermKind::Literal => Ok(Literal::try_copy(other).unwrap().into()), + _ => Err(SophiaToOxigraphConversionError), + } + } +} + +impl<'a> TTerm for TermRef<'a> { + fn kind(&self) -> TermKind { + use TermRef::*; + match self { + NamedNode(_) => TermKind::Iri, + BlankNode(_) => TermKind::BlankNode, + Literal(_) => TermKind::Literal, + } + } + + fn value_raw(&self) -> RawValue<'_> { + use TermRef::*; + match self { + NamedNode(n) => n.value_raw(), + BlankNode(n) => n.value_raw(), + Literal(l) => l.value_raw(), + } + } + + fn datatype(&self) -> Option> { + use TermRef::*; + match self { + Literal(l) => TTerm::datatype(l), + _ => None, + } + } + + fn language(&self) -> Option<&str> { + use TermRef::*; + match self { + Literal(l) => TTerm::language(l), + _ => None, + } + } + + fn as_dyn(&self) -> &dyn TTerm { + use TermRef::*; + match self { + NamedNode(n) => n.as_dyn(), + BlankNode(n) => n.as_dyn(), + Literal(l) => l.as_dyn(), + } + } +} + +impl From for ([Term; 3], Option) { + fn from(other: Quad) -> Self { + ( + [other.subject.into(), other.predicate.into(), other.object], + other.graph_name.into(), + ) + } +} + +impl<'a> From> for ([TermRef<'a>; 3], Option>) { + fn from(other: QuadRef<'a>) -> Self { + ( + [other.subject.into(), other.predicate.into(), other.object], + other.graph_name.into(), + ) + } +} + +impl From for [Term; 3] { + fn from(other: Triple) -> Self { + [other.subject.into(), other.predicate.into(), other.object] + } +} + +impl<'a> From> for [TermRef<'a>; 3] { + fn from(other: TripleRef<'a>) -> Self { + [other.subject.into(), other.predicate.into(), other.object] + } +} + +/// Error raised when trying to conpy a [Sophia] +/// term as an incompatible Oxigraph term +/// (e.g. a literal into `NamedNode`). +/// +/// [Sophia]: https://docs.rs/sophia/latest/sophia/ +#[derive(Clone, Copy, Debug)] +pub struct SophiaToOxigraphConversionError; +impl fmt::Display for SophiaToOxigraphConversionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} +impl std::error::Error for SophiaToOxigraphConversionError {} diff --git a/lib/src/sophia/store.rs b/lib/src/sophia/store.rs new file mode 100644 index 00000000..e23e86f4 --- /dev/null +++ b/lib/src/sophia/store.rs @@ -0,0 +1,806 @@ +//! This crate provides implementation of [Sophia] traits for the `store` module. +//! [Sophia]: https://docs.rs/sophia/latest/sophia/ +use crate::model::*; +use crate::sparql::{EvaluationError, QueryOptions, QueryResults}; +use crate::store::*; +use sophia_api::dataset::*; +use sophia_api::quad::stream::{QuadSource, StreamResult}; +use sophia_api::quad::streaming_mode::{ByValue, StreamedQuad}; +use sophia_api::term::{TTerm, TermKind, TryCopyTerm}; +use std::collections::HashSet; +use std::convert::Infallible; +use std::hash::Hash; +use std::iter::empty; + +type SophiaQuad = ([Term; 3], Option); +type StreamedSophiaQuad<'a> = StreamedQuad<'a, ByValue>; + +/// Execute a SPARQL query in a store, and return the result as a HashSet, +/// mapping the error (if any) through the given function. +/// +/// # Precondition +/// + the query must be a SELECT query with a single selected variable +/// + it must not produce NULL results +macro_rules! sparql_to_hashset { + ($store: ident, $err_map: ident, $sparql: expr) => { + //sparql_result_as_term_set($store, $sparql).map_err($err_map) + { + (|| -> Result, EvaluationError> { + let q = $store.prepare_query($sparql, QueryOptions::default())?; + let r = q.exec()?; + if let QueryResults::Solutions(solutions) = r { + solutions + .map(|r| r.map(|v| v.get(0).unwrap().clone())) + .collect() + } else { + unreachable!() + } + })() + .map_err($err_map) + } + }; +} + +macro_rules! impl_dataset { + ($store: ident, $error: ty, $quad_map: ident, $err_map: ident) => { + impl Dataset for $store { + type Quad = ByValue; + type Error = $error; + + fn quads(&self) -> DQuadSource<'_, Self> { + Box::new( + self.quads_for_pattern(None, None, None, None) + .map($quad_map), + ) + } + fn quads_with_s<'s, TS>(&'s self, s: &'s TS) -> DQuadSource<'s, Self> + where + TS: TTerm + ?Sized, + { + let mut buf_s = String::new(); + let s = convert_subject(s, &mut buf_s); + if s.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(s, None, None, None).map($quad_map)) + } + } + fn quads_with_p<'s, TP>(&'s self, p: &'s TP) -> DQuadSource<'s, Self> + where + TP: TTerm + ?Sized, + { + let mut buf_p = String::new(); + let p = convert_predicate(p, &mut buf_p); + if p.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(None, p, None, None).map($quad_map)) + } + } + fn quads_with_o<'s, TS>(&'s self, o: &'s TS) -> DQuadSource<'s, Self> + where + TS: TTerm + ?Sized, + { + let mut buf_o = String::new(); + let o = convert_object(o, &mut buf_o); + if o.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(None, None, o, None).map($quad_map)) + } + } + fn quads_with_g<'s, TS>(&'s self, g: Option<&'s TS>) -> DQuadSource<'s, Self> + where + TS: TTerm + ?Sized, + { + let mut buf_g = String::new(); + let g = convert_graph_name(g, &mut buf_g); + if g.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(None, None, None, g).map($quad_map)) + } + } + fn quads_with_sp<'s, TS, TP>(&'s self, s: &'s TS, p: &'s TP) -> DQuadSource<'s, Self> + where + TS: TTerm + ?Sized, + TP: TTerm + ?Sized, + { + let mut buf_s = String::new(); + let s = convert_subject(s, &mut buf_s); + let mut buf_p = String::new(); + let p = convert_predicate(p, &mut buf_p); + if s.is_none() || p.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(s, p, None, None).map($quad_map)) + } + } + fn quads_with_so<'s, TS, TO>(&'s self, s: &'s TS, o: &'s TO) -> DQuadSource<'s, Self> + where + TS: TTerm + ?Sized, + TO: TTerm + ?Sized, + { + let mut buf_s = String::new(); + let s = convert_subject(s, &mut buf_s); + let mut buf_o = String::new(); + let o = convert_object(o, &mut buf_o); + if s.is_none() || o.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(s, None, o, None).map($quad_map)) + } + } + fn quads_with_sg<'s, TS, TG>( + &'s self, + s: &'s TS, + g: Option<&'s TG>, + ) -> DQuadSource<'s, Self> + where + TS: TTerm + ?Sized, + TG: TTerm + ?Sized, + { + let mut buf_s = String::new(); + let s = convert_subject(s, &mut buf_s); + let mut buf_g = String::new(); + let g = convert_graph_name(g, &mut buf_g); + if s.is_none() || g.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(s, None, None, g).map($quad_map)) + } + } + fn quads_with_po<'s, TP, TO>(&'s self, p: &'s TP, o: &'s TO) -> DQuadSource<'s, Self> + where + TP: TTerm + ?Sized, + TO: TTerm + ?Sized, + { + let mut buf_p = String::new(); + let p = convert_predicate(p, &mut buf_p); + let mut buf_o = String::new(); + let o = convert_object(o, &mut buf_o); + if p.is_none() || o.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(None, p, o, None).map($quad_map)) + } + } + fn quads_with_pg<'s, TP, TG>( + &'s self, + p: &'s TP, + g: Option<&'s TG>, + ) -> DQuadSource<'s, Self> + where + TP: TTerm + ?Sized, + TG: TTerm + ?Sized, + { + let mut buf_p = String::new(); + let p = convert_predicate(p, &mut buf_p); + let mut buf_g = String::new(); + let g = convert_graph_name(g, &mut buf_g); + if p.is_none() || g.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(None, p, None, g).map($quad_map)) + } + } + fn quads_with_og<'s, TO, TG>( + &'s self, + o: &'s TO, + g: Option<&'s TG>, + ) -> DQuadSource<'s, Self> + where + TO: TTerm + ?Sized, + TG: TTerm + ?Sized, + { + let mut buf_o = String::new(); + let o = convert_object(o, &mut buf_o); + let mut buf_g = String::new(); + let g = convert_graph_name(g, &mut buf_g); + if o.is_none() || g.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(None, None, o, g).map($quad_map)) + } + } + fn quads_with_spo<'s, TS, TP, TO>( + &'s self, + s: &'s TS, + p: &'s TP, + o: &'s TO, + ) -> DQuadSource<'s, Self> + where + TS: TTerm + ?Sized, + TP: TTerm + ?Sized, + TO: TTerm + ?Sized, + { + let mut buf_s = String::new(); + let s = convert_subject(s, &mut buf_s); + let mut buf_p = String::new(); + let p = convert_predicate(p, &mut buf_p); + let mut buf_o = String::new(); + let o = convert_object(o, &mut buf_o); + if s.is_none() || p.is_none() || o.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(s, p, o, None).map($quad_map)) + } + } + fn quads_with_spg<'s, TS, TP, TG>( + &'s self, + s: &'s TS, + p: &'s TP, + g: Option<&'s TG>, + ) -> DQuadSource<'s, Self> + where + TS: TTerm + ?Sized, + TP: TTerm + ?Sized, + TG: TTerm + ?Sized, + { + let mut buf_s = String::new(); + let s = convert_subject(s, &mut buf_s); + let mut buf_p = String::new(); + let p = convert_predicate(p, &mut buf_p); + let mut buf_g = String::new(); + let g = convert_graph_name(g, &mut buf_g); + if s.is_none() || p.is_none() || g.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(s, p, None, g).map($quad_map)) + } + } + fn quads_with_sog<'s, TS, TO, TG>( + &'s self, + s: &'s TS, + o: &'s TO, + g: Option<&'s TG>, + ) -> DQuadSource<'s, Self> + where + TS: TTerm + ?Sized, + TO: TTerm + ?Sized, + TG: TTerm + ?Sized, + { + let mut buf_s = String::new(); + let s = convert_subject(s, &mut buf_s); + let mut buf_o = String::new(); + let o = convert_object(o, &mut buf_o); + let mut buf_g = String::new(); + let g = convert_graph_name(g, &mut buf_g); + if s.is_none() || o.is_none() || g.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(s, None, o, g).map($quad_map)) + } + } + fn quads_with_pog<'s, TP, TO, TG>( + &'s self, + p: &'s TP, + o: &'s TO, + g: Option<&'s TG>, + ) -> DQuadSource<'s, Self> + where + TP: TTerm + ?Sized, + TO: TTerm + ?Sized, + TG: TTerm + ?Sized, + { + let mut buf_p = String::new(); + let p = convert_predicate(p, &mut buf_p); + let mut buf_o = String::new(); + let o = convert_object(o, &mut buf_o); + let mut buf_g = String::new(); + let g = convert_graph_name(g, &mut buf_g); + if p.is_none() || o.is_none() || g.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(None, p, o, g).map($quad_map)) + } + } + fn quads_with_spog<'s, TS, TP, TO, TG>( + &'s self, + s: &'s TS, + p: &'s TP, + o: &'s TO, + g: Option<&'s TG>, + ) -> DQuadSource<'s, Self> + where + TS: TTerm + ?Sized, + TP: TTerm + ?Sized, + TO: TTerm + ?Sized, + TG: TTerm + ?Sized, + { + let mut buf_s = String::new(); + let s = convert_subject(s, &mut buf_s); + let mut buf_p = String::new(); + let p = convert_predicate(p, &mut buf_p); + let mut buf_o = String::new(); + let o = convert_object(o, &mut buf_o); + let mut buf_g = String::new(); + let g = convert_graph_name(g, &mut buf_g); + if s.is_none() || p.is_none() || o.is_none() || g.is_none() { + Box::new(empty()) + } else { + Box::new(self.quads_for_pattern(s, p, o, g).map($quad_map)) + } + } + fn subjects(&self) -> DResultTermSet + where + DTerm: Clone + Eq + Hash, + { + sparql_to_hashset!( + self, + $err_map, + "SELECT DISTINCT ?s {{?s ?p ?o} UNION { GRAPH ?g {?s ?p ?o}}}" + ) + } + fn predicates(&self) -> DResultTermSet + where + DTerm: Clone + Eq + Hash, + { + sparql_to_hashset!( + self, + $err_map, + "SELECT DISTINCT ?p {{?s ?p ?o} UNION { GRAPH ?g {?s ?p ?o}}}" + ) + } + fn objects(&self) -> DResultTermSet + where + DTerm: Clone + Eq + Hash, + { + sparql_to_hashset!( + self, + $err_map, + "SELECT DISTINCT ?o {{?s ?p ?o} UNION { GRAPH ?g {?s ?p ?o}}}" + ) + } + fn graph_names(&self) -> DResultTermSet + where + DTerm: Clone + Eq + Hash, + { + sparql_to_hashset!(self, $err_map, "SELECT DISTINCT ?g {GRAPH ?g {?s ?p ?o}}") + } + fn iris(&self) -> DResultTermSet + where + DTerm: Clone + Eq + Hash, + { + sparql_to_hashset!( + self, + $err_map, + "SELECT DISTINCT ?iri { + {?iri ?p ?o} UNION + {?s ?iri ?o} UNION + {?s ?p ?iri} UNION + {GRAPH ?iri {?s ?p ?o}} UNION + {GRAPH ?s {?iri ?p ?o}} UNION + {GRAPH ?g {?s ?iri ?o}} UNION + {GRAPH ?g {?s ?p ?iri}} + FILTER isIRI(?iri) + }" + ) + } + fn bnodes(&self) -> DResultTermSet + where + DTerm: Clone + Eq + Hash, + { + sparql_to_hashset!( + self, + $err_map, + "SELECT DISTINCT ?bn { + {?bn ?p ?o} UNION + {?s ?p ?bn} UNION + {GRAPH ?bn {?s ?p ?o}} UNION + {GRAPH ?s {?bn ?p ?o}} UNION + {GRAPH ?g {?s ?p ?bn}} + FILTER isBlank(?bn) + }" + ) + } + fn literals(&self) -> DResultTermSet + where + DTerm: Clone + Eq + Hash, + { + sparql_to_hashset!( + self, + $err_map, + "SELECT DISTINCT ?lit { + {?s ?p ?lit} UNION + { GRAPH ?g {?s ?p ?lit}} + FILTER isLiteral(?lit) + }" + ) + } + fn variables(&self) -> DResultTermSet + where + DTerm: Clone + Eq + Hash, + { + Ok(std::collections::HashSet::new()) + } + } + }; +} + +mod mem { + use super::*; + + impl_dataset!( + MemoryStore, + Infallible, + infallible_quad_map, + infallible_err_map + ); + + impl MutableDataset for MemoryStore { + type MutationError = Infallible; + fn insert( + &mut self, + s: &TS, + p: &TP, + o: &TO, + g: Option<&TG>, + ) -> MDResult + where + TS: TTerm + ?Sized, + TP: TTerm + ?Sized, + TO: TTerm + ?Sized, + TG: TTerm + ?Sized, + { + let quad = match convert_quad(s, p, o, g) { + Some(quad) => quad, + None => return Ok(false), + }; + MemoryStore::insert(self, quad); + Ok(true) + } + + fn remove( + &mut self, + s: &TS, + p: &TP, + o: &TO, + g: Option<&TG>, + ) -> MDResult + where + TS: TTerm + ?Sized, + TP: TTerm + ?Sized, + TO: TTerm + ?Sized, + TG: TTerm + ?Sized, + { + let mut buf_s = String::new(); + let mut buf_p = String::new(); + let mut buf_o = String::new(); + let mut buf_g = String::new(); + let quadref = + match convert_quadref(s, p, o, g, &mut buf_s, &mut buf_p, &mut buf_o, &mut buf_g) { + Some(quad) => quad, + None => return Ok(false), + }; + MemoryStore::remove(self, quadref); + Ok(true) + } + } + + impl CollectibleDataset for MemoryStore { + fn from_quad_source( + quads: QS, + ) -> StreamResult { + let mut d = MemoryStore::new(); + d.insert_all(quads)?; + Ok(d) + } + } + + #[cfg(test)] + sophia_api::test_dataset_impl!(test, MemoryStore, false, false); +} + +#[cfg(feature = "sled")] +mod sled { + use super::*; + + impl_dataset!(SledStore, std::io::Error, io_quad_map, io_err_map); + + impl MutableDataset for SledStore { + type MutationError = std::io::Error; + fn insert( + &mut self, + s: &TS, + p: &TP, + o: &TO, + g: Option<&TG>, + ) -> MDResult + where + TS: TTerm + ?Sized, + TP: TTerm + ?Sized, + TO: TTerm + ?Sized, + TG: TTerm + ?Sized, + { + let mut buf_s = String::new(); + let mut buf_p = String::new(); + let mut buf_o = String::new(); + let mut buf_g = String::new(); + let quadref = + match convert_quadref(s, p, o, g, &mut buf_s, &mut buf_p, &mut buf_o, &mut buf_g) { + Some(quad) => quad, + None => return Ok(false), + }; + SledStore::insert(self, quadref).map(|_| true) + } + + fn remove( + &mut self, + s: &TS, + p: &TP, + o: &TO, + g: Option<&TG>, + ) -> MDResult + where + TS: TTerm + ?Sized, + TP: TTerm + ?Sized, + TO: TTerm + ?Sized, + TG: TTerm + ?Sized, + { + let mut buf_s = String::new(); + let mut buf_p = String::new(); + let mut buf_o = String::new(); + let mut buf_g = String::new(); + let quadref = + match convert_quadref(s, p, o, g, &mut buf_s, &mut buf_p, &mut buf_o, &mut buf_g) { + Some(quad) => quad, + None => return Ok(false), + }; + SledStore::remove(self, quadref).map(|_| true) + } + } + + impl CollectibleDataset for SledStore { + fn from_quad_source( + quads: QS, + ) -> StreamResult { + let mut d = + SledStore::new().map_err(sophia_api::quad::stream::StreamError::SinkError)?; + d.insert_all(quads)?; + Ok(d) + } + } + + #[cfg(test)] + sophia_api::test_dataset_impl!(test, SledStore, false, false); +} + +#[cfg(feature = "rocksdb")] +mod rocksdb { + use super::*; + impl_dataset!(RocksDbStore, std::io::Error, io_quad_map, io_err_map); + + impl MutableDataset for RocksDbStore { + type MutationError = std::io::Error; + fn insert( + &mut self, + s: &TS, + p: &TP, + o: &TO, + g: Option<&TG>, + ) -> MDResult + where + TS: TTerm + ?Sized, + TP: TTerm + ?Sized, + TO: TTerm + ?Sized, + TG: TTerm + ?Sized, + { + let mut buf_s = String::new(); + let mut buf_p = String::new(); + let mut buf_o = String::new(); + let mut buf_g = String::new(); + let quadref = + match convert_quadref(s, p, o, g, &mut buf_s, &mut buf_p, &mut buf_o, &mut buf_g) { + Some(quad) => quad, + None => return Ok(false), + }; + RocksDbStore::insert(self, quadref).map(|_| true) + } + + fn remove( + &mut self, + s: &TS, + p: &TP, + o: &TO, + g: Option<&TG>, + ) -> MDResult + where + TS: TTerm + ?Sized, + TP: TTerm + ?Sized, + TO: TTerm + ?Sized, + TG: TTerm + ?Sized, + { + let mut buf_s = String::new(); + let mut buf_p = String::new(); + let mut buf_o = String::new(); + let mut buf_g = String::new(); + let quadref = + match convert_quadref(s, p, o, g, &mut buf_s, &mut buf_p, &mut buf_o, &mut buf_g) { + Some(quad) => quad, + None => return Ok(false), + }; + RocksDbStore::remove(self, quadref).map(|_| true) + } + } +} + +// helper functions + +fn infallible_quad_map<'a>(q: Quad) -> Result, Infallible> { + let q: SophiaQuad = q.into(); + Ok(StreamedQuad::by_value(q)) +} + +fn infallible_err_map(_: EvaluationError) -> Infallible { + panic!("Unexpected error") +} + +#[cfg(any(feature = "rocksdb", feature = "sled"))] +fn io_quad_map<'a>( + res: Result, +) -> Result, std::io::Error> { + res.map(|q| { + let q: SophiaQuad = q.into(); + StreamedQuad::by_value(q) + }) +} + +#[cfg(any(feature = "rocksdb", feature = "sled"))] +fn io_err_map(err: EvaluationError) -> std::io::Error { + match err { + EvaluationError::Io(err) => err, + _ => panic!("Unexpected error"), + } +} + +fn convert_subject<'a, T>(term: &'a T, buffer: &'a mut String) -> Option> +where + T: TTerm + ?Sized + 'a, +{ + match term.kind() { + TermKind::Iri => Some(convert_iri(term, buffer).into()), + TermKind::BlankNode => Some(BlankNodeRef::new_unchecked(term.value_raw().0).into()), + _ => None, + } +} + +fn convert_predicate<'a, T>(term: &'a T, buffer: &'a mut String) -> Option> +where + T: TTerm + ?Sized + 'a, +{ + match term.kind() { + TermKind::Iri => Some(convert_iri(term, buffer)), + _ => None, + } +} + +fn convert_object<'a, T>(term: &'a T, buffer: &'a mut String) -> Option> +where + T: TTerm + ?Sized + 'a, +{ + match term.kind() { + TermKind::Iri => Some(convert_iri(term, buffer).into()), + TermKind::BlankNode => Some(BlankNodeRef::new_unchecked(term.value_raw().0).into()), + TermKind::Literal => { + let value = term.value_raw().0; + let lit = match term.language() { + Some(tag) => LiteralRef::new_language_tagged_literal_unchecked(value, tag), + None => { + let (ns, suffix) = term.datatype().unwrap().destruct(); + let datatype = convert_iri_raw(ns, suffix, buffer); + LiteralRef::new_typed_literal(value, datatype) + } + }; + Some(lit.into()) + } + _ => None, + } +} + +fn convert_graph_name<'a, T>( + graph_name: Option<&'a T>, + buffer: &'a mut String, +) -> Option> +where + T: TTerm + ?Sized + 'a, +{ + match graph_name { + None => Some(GraphNameRef::DefaultGraph), + Some(term) => match term.kind() { + TermKind::Iri => Some(convert_iri(term, buffer).into()), + TermKind::BlankNode => Some(BlankNodeRef::new_unchecked(term.value_raw().0).into()), + _ => None, + }, + } +} + +fn convert_iri<'a, T>(term: &'a T, buffer: &'a mut String) -> NamedNodeRef<'a> +where + T: TTerm + ?Sized + 'a, +{ + debug_assert_eq!(term.kind(), TermKind::Iri); + let raw = term.value_raw(); + convert_iri_raw(raw.0, raw.1, buffer) +} + +fn convert_iri_raw<'a>( + ns: &'a str, + suffix: Option<&'a str>, + buffer: &'a mut String, +) -> NamedNodeRef<'a> { + let iri: &'a str = match suffix { + Some(suffix) => { + buffer.clear(); + buffer.push_str(ns); + buffer.push_str(suffix); + buffer + } + None => ns, + }; + NamedNodeRef::new_unchecked(iri) +} + +fn convert_quad(s: &TS, p: &TP, o: &TO, g: Option<&TG>) -> Option +where + TS: TTerm + ?Sized, + TP: TTerm + ?Sized, + TO: TTerm + ?Sized, + TG: TTerm + ?Sized, +{ + let s = match NamedOrBlankNode::try_copy(s) { + Ok(s) => s, + Err(_) => return None, + }; + let p = match NamedNode::try_copy(p) { + Ok(p) => p, + Err(_) => return None, + }; + let o = match Term::try_copy(o) { + Ok(o) => o, + Err(_) => return None, + }; + let g = match g { + None => GraphName::DefaultGraph, + Some(g) => match NamedOrBlankNode::try_copy(g) { + Ok(g) => g.into(), + Err(_) => return None, + }, + }; + Some(Quad::new(s, p, o, g)) +} + +fn convert_quadref<'a, TS, TP, TO, TG>( + s: &'a TS, + p: &'a TP, + o: &'a TO, + g: Option<&'a TG>, + buf_s: &'a mut String, + buf_p: &'a mut String, + buf_o: &'a mut String, + buf_g: &'a mut String, +) -> Option> +where + TS: TTerm + ?Sized, + TP: TTerm + ?Sized, + TO: TTerm + ?Sized, + TG: TTerm + ?Sized, +{ + let s = match convert_subject(s, buf_s) { + Some(s) => s, + None => return None, + }; + let p = match convert_predicate(p, buf_p) { + Some(p) => p, + None => return None, + }; + let o = match convert_object(o, buf_o) { + Some(o) => o, + None => return None, + }; + let g = match convert_graph_name(g, buf_g) { + Some(g) => g, + None => return None, + }; + Some(QuadRef::new(s, p, o, g)) +}